Compare commits

...

273 commits

Author SHA1 Message Date
Dirk Hohndel
3bd7be809a mobile: fix dive detail scrolling
When using the current version of Subsurface-mobile, you cannot scroll
the dive details (i.e. you cannot see the bottom of the dive
information, depending on the size of your screen), nor can you scroll
the notes editor.

I'm not sure how I didn't stumble across this earlier, but a git bisect
appears to pinpoint commit a39f0e2891 ("Mobile: Fix QML Warnings.")
which is quite old.

Partially reverting this seems sufficient to get scrolling for the dive
details and dive notes edit working again.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-10-03 15:40:59 +13:00
Michael Keller
b392052c37 CICD: Fix Windows Build.
Fix missing define introduced in #4343.

Signed-off-by: Michael Keller <github@ike.ch>
2024-10-03 09:04:42 +13:00
Michael Keller
c72a26afe1 Remove misleading comment about smtk2ssrf build.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-30 22:22:39 +13:00
Salvador Cuñat
f81cf77886 Make libraw support build optional
Build in by default if libraw is found, but make it optional for builds
were it's not needed, like smtk2ssrf or subsurface-downloader.

Signed-off-by: Salvador Cuñat <salvador.cunat@gmail.com>
2024-09-30 22:22:39 +13:00
dependabot[bot]
821f3fc551 build(deps): bump com.github.mik3y:usb-serial-for-android
Bumps [com.github.mik3y:usb-serial-for-android](https://github.com/mik3y/usb-serial-for-android) from v3.4.3 to v3.8.0.
- [Release notes](https://github.com/mik3y/usb-serial-for-android/releases)
- [Changelog](https://github.com/mik3y/usb-serial-for-android/blob/master/CHANGELOG.txt)
- [Commits](https://github.com/mik3y/usb-serial-for-android/compare/v3.4.3...v3.8.0)

---
updated-dependencies:
- dependency-name: com.github.mik3y:usb-serial-for-android
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-29 09:23:36 +13:00
dependabot[bot]
b1493c3540 build(deps): bump com.android.support:support-v4 in /android-mobile
Bumps com.android.support:support-v4 from 25.3.1 to 28.0.0.

---
updated-dependencies:
- dependency-name: com.android.support:support-v4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-29 08:36:07 +13:00
Michael Keller
8a64d1f4b9 Cleanup: Fix Warnings in MacOS build.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-28 23:50:55 +12:00
Michael Keller
784eddc166 Android: Update the docker Image Version Used to Build.
Update to the latest version (5.15.3).

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-28 23:25:02 +12:00
dependabot[bot]
5613014124 build(deps): bump DamianReeves/write-file-action from 1.2 to 1.3
Bumps [DamianReeves/write-file-action](https://github.com/damianreeves/write-file-action) from 1.2 to 1.3.
- [Release notes](https://github.com/damianreeves/write-file-action/releases)
- [Commits](https://github.com/damianreeves/write-file-action/compare/v1.2...v1.3)

---
updated-dependencies:
- dependency-name: DamianReeves/write-file-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 09:50:34 +12:00
dependabot[bot]
b5e5e4c212 build(deps): bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 09:49:23 +12:00
dependabot[bot]
968599c7d9 build(deps): bump insightsengineering/pip-action from 2.0.0 to 2.0.1
Bumps [insightsengineering/pip-action](https://github.com/insightsengineering/pip-action) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/insightsengineering/pip-action/releases)
- [Commits](https://github.com/insightsengineering/pip-action/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: insightsengineering/pip-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 09:48:46 +12:00
Michael Keller
6418d3acbc Update Version in docker Tag, Clean up Dependencies.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-27 09:47:40 +12:00
dependabot[bot]
9873c86192 build(deps): bump ubuntu in /scripts/docker/android-build-container
Bumps ubuntu from 22.04 to 24.04.

---
updated-dependencies:
- dependency-name: ubuntu
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 09:47:40 +12:00
dependabot[bot]
b806c5371f build(deps): bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 07:10:25 +12:00
Michael Keller
89b2b3bf70 CICD: Add Dependency Scanning / Updates by Dependabot.
Add scanning and updates of dependencies with Dependabot where this is
possible.

While this isn't currently available for the majority of our code that
is in C / C++, there are some things 'around the edges' where we can
offload some of the dependency management:
- GitHub actions
- docker images
- the gradle build for android

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-26 19:41:11 +12:00
Michael Keller
716b350af2 Add libraw to the Remaining debian Based CICD Workflows.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-21 11:03:53 +12:00
Michael Keller
478e444cd9 Fix problems from rebase, clean up debian packaging definition.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
28cd093d37 build-system: Fedora surprisingly uses CamelCase for LibRaw
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
40c22f0fe9 deal with typo in older versions of libraw
longtitude instead of longitude prior to libraw 0.20.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
ffa49cfd34 build-system: add libraw to smtk2ssrf if main build uses libraw
Otherwise the link step will fail.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
435d5f5436 build-system: add libraw to more builds
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
8e48a323e7 build-system: add libraw to Ubuntu build
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
6537192e1d build-system: add libraw to Fedora/copr build
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
3d552b84d1 build-system: add libraw to OBS daily build
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
236203bf06 build-system: add libraw to macOS build
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
09f59211ed build-system: de-clutter output of get-version.sh
In certain situations git merge-base would report errors if the branch
you are working in is older than the latest CICD builds. This simply
hides those pointless errors.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
c8ef53c43f build-system: show if libraw was found
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Dirk Hohndel
68f8ca5fd0 build-system: allow empty option for pkg_config_library macro
Especially when adding new optional dependencies, you might want to have
neither REQUIRED nor QUIET there - this way we can see in the CICD where the
library is found and tested against.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-21 11:03:53 +12:00
Michael Keller
c866d2bead Add a package dependency on libraw20 to the debian package manifest.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-21 11:03:53 +12:00
Michał Sawicz
b92475d9c9 snap: add libraw dependency
Signed-off-by: Michał Sawicz <michal@sawicz.net>
2024-09-21 11:03:53 +12:00
Berthold Stoeger
d9f8570728 ubuntu/debian: add libraw-dev to build dependencies
I have not idea if this is enough to make parsing of raw files
work for the Ubuntu/Debian package.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-21 11:03:53 +12:00
Berthold Stoeger
13d1188c41 media: load metadata and thumbnails of raw pictures using libraw
The distinguished photographer shoots raw images. There is a
comprehensive library that can extract metadata and thumbnails
from these images. Let's use it if available.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-21 11:03:53 +12:00
Michael Keller
02638d7c3e CICD: Use the Updated 3.2.0 docker Image for Windows Builds.
This updates MXE to a current version and includes libraw.

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-18 21:22:27 +12:00
Richard Fuchs
a6a15f9d3a core: add missing types to return values
Fixes build failing on Debian Buster (gcc 8.3.0) with:

/build/subsurface-beta-202409160411/./core/units.h: In function 'duration_t operator""_sec(long long unsigned int)':
/build/subsurface-beta-202409160411/./core/units.h:149:48: error: could not convert '{((int32_t)sec)}' from '<brace-enclosed initializer list>' to 'duration_t'
  return { .seconds = static_cast<int32_t>(sec) };

Signed-off-by: Richard Fuchs <dfx@dfx.at>
2024-09-16 22:02:51 +02:00
Michael Keller
cb6766c1d4 CICD: Add libraw to the MXE build container.
In support of #3954.

Also update MXE to the latest version.

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-15 18:46:36 +12:00
Michael Keller
a2f5be13e3 Update libdivecomputer to latest on 'Subsurface-DS9'.
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-15 18:41:20 +12:00
Berthold Stoeger
1a3bc9bf71 units: add comment concerning unit literals
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
dd5def35f5 units: replace SURFACE_PRESSURE by 1_atm
Moreover, convert diveplan::surface_pressure from int to
pressure_t.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
ae81b42fe2 core: introduce a few user-defined literals for unit types
Thise makes initialization of unit types more palatable.

For example:

    surface.time = sample.time - duration_t { .seconds = 20 };
=>  surface.time = sample.time - 20_sec;

    delta_depth.mm = feet_to_mm(1.0); // 1ft
=>  delta_depth = 1_ft;

    get_cylinderid_at_time(..., { .seconds = 20 * 60 + 1 }));
=>  get_cylinderid_at_time(..., 20_min + 1_sec));

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
f09601bc93 core: remove to_feet() function
It wasn't used anywhere.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
77b12bbccf core: add cast_int<> function
We had a pattern where doubles were converted to long with
lrint() and then down-cast to a narrower int type.

Because this is unwieldy, introduce a function encapsulating
this.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
110e64bc66 general: simplify a few unit manipulations
Now that we have defined addition and subtraction on unit
classes, let's use them in a few examples.

Yes, some of these are a bit pointless, because they are
of the kind
        a.mbar - b.mbar => (a-b).mbar

However, these probably should be further simplified
by storing the result in a unit type.

This commit is mostly a proof-of-concept.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
729cc16fc5 core: add addition / subtraction to unit types
When adding / subtracting unit objects it is completely
irrelevant with respect to which unit the data is stored.
Why should the user know this?

Therefore add addition / subtraction functions.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
12ca172a9e core: add CRTP base class to unit types
The goal here is to add general addition and scalar multiplication
functions to the unit types.

Thereto, we need a CRTP
(https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
base class.

However, this breaks compound initialization, so we have to use
named initializers:
	weight_t { 2000 } -> weight_t { .grams = 2000 }
The good thing is that this is exactly how these classes were
supposed to be used: make the unit explicit!

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 10:23:07 +02:00
Berthold Stoeger
696ba61eef planner: move gaschange_before / gaschange_after into loop
Declaring everything at the begin of the function is a K&R
disease, that makes code very hard to follow.

Remove the last assignment to gaschange_after since that is
a noop (found by Coverity).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 07:54:50 +02:00
Berthold Stoeger
6f91a73a05 planner: add move assignment constructor and operator to diveplan
Makes coverity happy and is a good idea.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 07:54:50 +02:00
Berthold Stoeger
db531bbd05 tests: shut up coverity warnings
It wants us to test for end of container when finding elements.
That is of course reasonable in "production" code, but a bit
pointless in the testing code. Oh well.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 07:54:50 +02:00
Berthold Stoeger
80abde2a61 Small updates to CODINGSTYLE.md reflecting our switch to C++
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:32 +12:00
Berthold Stoeger
1287880be0 planner: return decotable from plan()
The old return code was not used by any caller.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:04 +12:00
Berthold Stoeger
22b232661a planner: remove cloneDivePlan()
This was a trivial one-liner and is not needed.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:04 +12:00
Berthold Stoeger
3e006e678a planner: don't use fixed size deco stop table
This was quite ominous: a 60-element fixed size table was
passed as argument to plan(). But there was no check for 60
anywhere? Use a dynamic vector instead.

The whole thing is weird, as the depth of the decostop table
doesn't seem to be used.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:04 +12:00
Berthold Stoeger
0745c50e58 planner: C++-ify a bit more
Use constructor and member functions.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:04 +12:00
Berthold Stoeger
74c8bd34a0 planner: remove unnecessary clearing of dive plan
I don't get the point of these calls to dp.clear().

The plan is overwritten immediately afterwards anyway.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:04 +12:00
Berthold Stoeger
8704a8b6f9 planner: turn diveplan into a C++ structure
No more memory management woes.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-11 00:13:04 +12:00
Michael Keller
6c8f158569 Profile: Allow Editing of Initial Gas.
Allow the initial gas of the dive to be edited through the context menu
in the dive profile, by right-clicking into the profile at the very
start of the dive.
Of course this will likely completely invalidate the decompression
calculation of any actually logged dives, but this is no different to
the addition and modification of gas changes during the dive that is
already possible.
Proposed by @harrydevil in #4291.

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-11 00:12:24 +12:00
Dirk Hohndel
7d215deaa1 macOS: switch to Qt 5.15.15 build with QtWebKit
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-09 15:15:38 -07:00
Dirk Hohndel
5700379560 macOS: small improvements for resign script
Still, mostly useful for me, but this correctly deals with relative path
names for the working directory (and gives a usage message).

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-09 15:15:38 -07:00
Dirk Hohndel
a5effbe0a6 macOS: reorder build of dependencies
libcurl needs openssl.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2024-09-09 15:15:38 -07:00
Michael Keller
96fdaf660b Fix a compiler warning.
Signed-off-by: Michael Keller <mikeller@042.ch>
2024-09-09 12:59:13 +12:00
Michael Keller
515f593a56 Planner: Start with Correct Dive Mode.
Fix a bug introduced in #4245, causing an incorrect dive mode to be
selected when starting the dive planner from a CCR dive.

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-09 12:53:13 +12:00
Berthold Stoeger
f585726283 planner: don't pass reference to asynchronous lambda
A reference to a unique_ptr<> was captured by a lambda used
to calculate variations in the background.

This is of course disastrous, because if the caller runs
first it will delete the object. It's a wonder that this
didn't crash regularly!?

The problem is that capturing unique_ptr<>s in lambdas
works, but makes the lambda non-copyable. Sadly, the
QtConcurrent::run() function wants to copy the lambda.

For now, do this by a release/reaquire pair. This is
not exception safe. However, sine Qt doesn't support
exceptions, we can live with that.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-04 14:44:46 +02:00
Michael Keller
ee25e8a1db Refactoring: Improve event_loop.
Improve the event loop architecture by making it set the divecomputer in
the constructor - using the same loop for multiple dive computers is not
intended to work.
Also change `next()` in `divemode_loop` to `at()` to make the name more
aligned with its function.

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-03 21:24:40 +02:00
Michael Keller
33bb39f1ca Planner: Fix Warning from Coverity.
Fix an interger overflow warning when parsing setpoints.

@bstoeger: In the end it turned out that this parser was only used in
one place in the planner UI, and it was simplest to switch this to
using `QVariant.toFloat()` in the model itself, which is consistent how
the rest of the input values is parsed and validated.

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-04 07:04:35 +12:00
Berthold Stoeger
cc55c442a3 core: fix undo of dive merging
When merging two dives, if a divesite is chosen that doesn't
have a GPS location, but another divesite has a GPS location,
then the GPS location of the former is set to that of the
latter.

However, that was done outside of the undo system, so that
it is not undone and the frontend is not made aware of the
change.

Fix this. To simplify things, move the code from the undo
machinery to the core.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-03 18:20:48 +12:00
Berthold Stoeger
27a89b0232 core: remove dive_site member from merge_result
All callers were just using that member to set the dive_site
in the resulting dive. We might just do that in the called
function [merge_dives()].

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-03 18:20:48 +12:00
Michael Keller
5b46d8cc33 Profile: Refactor use of .
Signed-off-by: Michael Keller <github@ike.ch>
2024-09-03 18:19:44 +12:00
Michael Keller
db516b6d4e Profile: Fix the Initial Gasmix.
Fix the initial gasmix that is shown in the tank bar of the profile.

Also add a meaningful gas name for gases with negative values for
percentages.

@bstoeger: This is a side effect of the `event_loop` functionality
introduced as part of #4198. In the case of an `event_loop("gasmix")`
this does not take into account the edge case where there is no
gaschange event at the very beginning of the dive, and the first gasmix
is implicitly used as the starting gasmix. This happens for planned and
manually added dives, but also for some dive computers.
We are using the
same kind of loop in a number of other places in `core/profile.cpp`,
`core/dive.cpp`, `core/gaspressures.cpp`, and `profile-widget/tankitem.cpp`,
and I am wondering if we should be converting these to use
`gasmix_loop` instead to avoid being bit by this special case?

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-03 18:19:44 +12:00
Berthold Stoeger
38fe08e5e1 planner: use std::move() to store planner notes
Avoids a copy and makes coverity happy (rightfully so).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-02 08:16:29 +02:00
Linus Torvalds
aa3a93a466 Fix location in result of dive merging
This is the minimal fix to actually include the resulting location in
the merged dive.  I'm not sure what the background was to setting the
dive location only in the "merge_result" variable, and not in the actual
result dive itself.

Berthold says that the whole site handling may be broken:

 "From a quick glance, the code in dive_table::merge_dives looks
  fundamentally broken, because it may overwrite site->location outside
  of the undo system. I.e. this will not be undone."

but the "this will not be undone" is about the site location setting,
and is separate and independent of the dive->dive_set setting.

Presumably we would need to make a copy of the site for the undo
functionality.  That will be for somebody else to worry about, this at
least fixes the resulting location in the dive itsels.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2024-08-28 10:35:26 +02:00
Linus Torvalds
da6c753502 Fix sample times in dive merging
Commit c27314d60 ("core: replace add_sample() by append_sample()") broke
the dive computer interleaving when merging two dives: the sample
merging (done by "merge_samples()") no longer took the offset between
the two merged dives into account, and instead just blindly copied the
samples from the second dive computer with no time offset.

The end result was a completely broken profile.

This adds back the sample offset.  It also takes the offset not from the
difference in time of the two dives, but the difference in time of the
dive computers.  That way we're not mixing up different times from
different sources that aren't necessarily in sync (the time *difference*
is hopefully the same, but still..).

The dive merging still messes up the dive location. That's some other bug.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2024-08-28 10:35:26 +02:00
Linus Torvalds
914cdb102b Fix Suunto FIT file import dive duration mis-calculation
The Suunto FIT files end up creating a sample every second, but a lot of
those samples with no depth information, marked as "depth.mm" being negative.

That doesn't end up being a problem for subsurface, _except_ that it
really confuses our "dc_fixup_duration()" logic, and the dive duration
ends up being completely nonsensical (generally roughly by a factor of
five: every tenth sample has a depth, and we only count samples that
"begin or end under water" as being relevant for the dive duration, so
two out of the ten samples will count towards the dive time).

Saving the dive will then not save these invalid depths, so saving and
reloading the dive ends up fixing the dive duration calculation.

The fix is trivial - we just ignore samples with negative depth in
dc_fixup_duration().

The FIT file parser should probably be taught to not even bother sending
empty samples to subsurface, but that's a separate cleanup.  This fixes
the actual bad behavior.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2024-08-26 21:01:32 -07:00
Tim D. Hammer
069f8a5d18 correct re-plan menu label in documentation
The (presumably) older label "Re-plan dive" in the Log menu was replaced with "Edit dive in planner",
but the documentation section "Modifying an existing dive plan" was not updated.

Signed-off-by: Tim D. Hammer <tdhammer@linux.com>
2024-08-26 12:37:29 +12:00
Richard Fuchs
3c3856f9e8 core: add missing #include <tuple>
Fixes gcc complaining about

/build/subsurface-beta-202408210659/core/divecomputer.cpp: In function 'bool operator<(const event&, const event&)':
/build/subsurface-beta-202408210659/core/divecomputer.cpp:290:21: error: 'tie' is not a member of 'std'
  290 |         return std::tie(ev1.time.seconds, ev1.name) <
      |                     ^~~
/build/subsurface-beta-202408210659/core/divecomputer.cpp:13:1: note: 'std::tie' is defined in header '<tuple>'; did you forget to '#include <tuple>'?
   12 | #include <stdlib.h>
  +++ |+#include <tuple>
   13 |
/build/subsurface-beta-202408210659/core/divecomputer.cpp:291:21: error: 'tie' is not a member of 'std'
  291 |                std::tie(ev2.time.seconds, ev2.name);
      |                     ^~~
/build/subsurface-beta-202408210659/core/divecomputer.cpp:291:21: note: 'std::tie' is defined in header '<tuple>'; did you forget to '#include <tuple>'?

Signed-off-by: Richard Fuchs <dfx@dfx.at>
2024-08-26 12:37:07 +12:00
Michael Keller
14b9074f40 Fix layering violations in DivePlannerPointsModel.
Signed-off-by: Michael Keller <mikeller@042.ch>
2024-08-26 12:36:31 +12:00
Michael Keller
2d8e343221 Planner: Improve Gas Handling in CCR Mode.
This has become a bit of a catch-all overhaul of a large portion of the
planner - I started out wanting to improve the CCR mode, but then as I
started pulling all the other threads that needed addressing started to
come with it.

Improve how the gas selection is handled when planning dives in CCR
mode, by making the type (OC / CCR) of segments dependent on the gas use
type that was set for the selected gas.
Add a preference to allow the user to chose to use OC gases as diluent,
in a similar fashion to the original implementation.
Hide gases that cannot be used in the currently selected dive mode in
all drop downs.
Include usage type in gas names if this is needed.
Hide columns and disable elements in the 'Dive planner points' table if
they can they can not be edited in the curently selected dive mode.
Visually identify gases and usage types that are not appropriate for the
currently selected dive mode.
Move the 'Dive mode' selection to the top of the planner view, to
accommodate the fact that this is a property of the dive and not a
planner setting.
Show a warning instead of the dive plan if the plan contains gases that
are not usable in the selected dive mode.
Fix the data entry for the setpoint in the 'Dive planner points' table.
Fix problems with enabling / disabling planner settings when switching
between dive modes.
Refactor some names to make them more appropriate for their current
usage.

One point that is still open is to hide gas usage graphs in the planner
profile if the gas isn't used for OC, as there is no way to meaningfully
interpolate such usage.

Signed-off-by: Michael Keller <github@ike.ch>
2024-08-26 12:36:31 +12:00
Berthold Stoeger
7106c4d5f0 desktop: reinstate WSInfoModel constructor
In 1af00703b3 the constructor of
WSInfoModel was removed, not realizing that it contains a crucial
call to setHeaderDataStrings().

Fixes #4294

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-23 09:03:24 +02:00
Michał Sawicz
8b435c9d8f snap: use a local /etc/gitconfig
Fixes: #3465

Signed-off-by: Michał Sawicz <michal@sawicz.net>
2024-08-21 14:06:57 +12:00
Richard Fuchs
a6ce6e56d3 core: add missing #include <tuple>
Fixes gcc complaining about

/build/subsurface-beta-202408190452/core/event.cpp: In member function 'bool event::operator==(const event&) const':
/build/subsurface-beta-202408190452/core/event.cpp:64:21: error: 'tie' is not a member of 'std'
   64 |         return std::tie(time.seconds, type, flags, value, name) ==
      |                     ^~~
/build/subsurface-beta-202408190452/core/event.cpp:6:1: note: 'std::tie' is defined in header '<tuple>'; did you forget to '#include <tuple>'?
    5 | #include "subsurface-string.h"
  +++ |+#include <tuple>
    6 |
/build/subsurface-beta-202408190452/core/event.cpp:65:21: error: 'tie' is not a member of 'std'
   65 |                std::tie(b.time.seconds, b.type, b.flags, b.value, b.name);
      |                     ^~~
/build/subsurface-beta-202408190452/core/event.cpp:65:21: note: 'std::tie' is defined in header '<tuple>'; did you forget to '#include <tuple>'?

Signed-off-by: Richard Fuchs <dfx@dfx.at>
2024-08-19 22:47:59 +02:00
Berthold Stoeger
26c594382e core: fix deletion of events in fixup_dc_events()
If there were more than one redundant event in the 60 sec range,
the event would be deleted multiple time, leading to a crash.

Only mark for deletion once.

Moreover, don't consider events that were already marked for
deletion, because that would mean that redundant events all 59 secs
would lead to all events (but the first one) deleted.

Finally, optimize the loop by stopping once the 60 sec limit
is reached.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-19 13:14:47 +02:00
Berthold Stoeger
0dc47882cb code hygiene: use std::swap instead of temporary variable
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-19 13:14:47 +02:00
Berthold Stoeger
bdfd37c95b core: fix sorting of events
Fix an embarrassing bug: the less than operator for events
was wrong.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-19 13:14:47 +02:00
Berthold Stoeger
01705a6449 undo: exit early if no event in RemoveEvent()
To avoid dereferencing a null pointer.

Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-18 16:26:31 +02:00
Berthold Stoeger
d295ca1d17 code cleanup: use std::move() to potentially void copies
Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-18 16:26:31 +02:00
Berthold Stoeger
2d5094a48b profile: add move constructor and assignment operator to plot_info
To make Coverity happy.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
92abfb4b90 ostctools: avoid string copy
Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
1578c7be99 uemis downloader: use move instead of copy to return string
Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
f78662acce uemis downloader: close reqtxt_file in case of error
Found by Coverity. Should switch to proper C++ type, though
no priority for now.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
a3340298b6 liquivision import: move notes string
Avoids one copy. Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
ef9ae5f6d6 seac import: report database error
Also free the error message. Annoying C interface - found by
Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
e305546046 undo: fix RemoveEvent if we can't find the event
We check for the event, but then access it anyway even if it
doesn't exist. Should not happen, but let's be safe.

Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
5c6ca2c1ce undo: don't use moved-from string
Found by Coverity.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
1dade48aa6 filter: use std::move() to pass around std::string
Suggested by Coverity. Seems like a good idea.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
455bc8f40c core: add copy constructors/assignment operators to device_data_t
To make Coverity happy.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
6d08903917 uemis: close reqtxt file in error case
Found by Coverity. Should switch to C++ "RAII" type, but no
priority for now.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
2bbc95e8f1 planner: check nextdp for null
As correctly noted by Coverity, we check nextdp for null
and later dereference it.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
c91884223f pressures: do floating point division when interpolating
Coverity correctly complains about an integer division followed
by an assignment to double. Hard to say if intended - but let's
do a floating point division instead.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
ad2ccc8888 core: add move constructor/copy assignment to weight and cylinder
Make Coverity happy (shrug).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
1fc5a294a6 uemis import: disable seemingly dead code
This is probably related to another commented out piece of code.
Disable until someone complains.

Fixes a (good) Coverity warning.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
df568fbb5d suunto import: give and free error message
There was a memory leak in the error case of sqlite3_exec():
The error message was not freed (and also not displayed).

Display and free it. Is there a reasonable C++ version of this
library?

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:37:00 +12:00
Berthold Stoeger
152e6966c9 fix copy/paste of dive-site
The copy/pasting of dive-sites was fundamentally broken in at least two
ways:

1) The dive-site pointer in struct dive was simply overwritten, which
   breaks internal consistency. Also, no dive-site changed signals where
   sent.

2) The copied dive-site was stored as a pointer in a struct dive. Thus,
   the user could copy a dive, then delete the dive-site and paste.
   This would lead to a dangling pointer and ultimately crash the
   application.

Fix this by storing the UUID of the dive-site, not a pointer.
To do that, don't store a copy of the dive, but collect all
the data in a `dive_paste_data` structure.
If the dive site has been deleted on paste, do nothing.
Send the appropriate signals on pasting.

The mobile version had an additional bug: It kept a pointer to the
dive to be copied, which might become stale by undo.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-17 23:34:16 +12:00
Michael Keller
48b4308a7d Windows: Fix smtk-import Build.
Fix a copy/paste error introduced in #4273.

Signed-off-by: Michael Keller <github@ike.ch>
2024-08-17 23:33:58 +12:00
gregbenson314
a75c9c3872 Update equipment.c Add D7 232 Bar Tank
D7 232 Bar tank added in line 342.

D7 232 Bar are common in Europe, not just D7 300 Bar.

Signed-off-by: gregbenson314 <99766165+gregbenson314@users.noreply.github.com>
2024-08-15 18:09:32 +12:00
Michael Keller
a905a78178 Update core/units.h
Signed-off-by: Michael Keller <github@ike.ch>
2024-08-13 19:28:30 +02:00
Michael Keller
94ed723015 Update core/units.h
Signed-off-by: Michael Keller <github@ike.ch>
2024-08-13 19:28:30 +02:00
Michael Keller
67f38ce3ce Update core/units.h
Signed-off-by: Michael Keller <github@ike.ch>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
80b5f6bfcd core: move add_cylinder() to struct cylinder_table
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
22a1120b30 core: move gasname() to struct gasmix
Also, turn it to use std::string instead of writing into a
global(!) buffer. This was not reentrant.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
9c726d8d6f core: move gas_volume() to cylinder_t
Feels natural in a C++ code base.

The commit is somewhat complex, because it also changes the
return type to volume_t. The units system really needs some
work. :(

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
4cb3db2548 core: move remove_weightsystem() to weightsystem_table
Feel natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b5a4e7eb0b core: move set_weightsystem() to weightsystem_table
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
650fda3221 core: move add_to_weightsystem_table() to weightsystem_table
Feels natural in a C++ code base.

In analogy to other tables, this creates a struct that derives
from std::vector<>. This is generally frowned upon, but it works
and is the pragmatic thing for now. If someone wants to "fix" that,
they may just do it.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
498302dcc6 core: remove add_empty_cylinder()
This one-liner wasn't really doing anything and there was only
one user of the return value.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
9bb2255ba8 core: move get_or_create_cylinder() to struct dive
Other cylinder-creation functions were already there.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
777e7f32a5 core: replace same_weightsystem() by operator==()
The important point is that this now takes a reference that
avoid string copying. The old code used C-strings and therefore
copy-semantics were OK.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
fb3a157462 core: move time_during_dive_with_offset() to struct dive
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
28814829e0 core: move best_o2() and best_he() to struct dive
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6e349793d1 core: move startup function declarations to subsurfacestartup.h
These were declared in dive.h, which makes no sense.

This should have been chnaged a long time ago.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
76d672210d core: move get_dive_location()/_country() to struct dive
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
f1f082d86a core: move get_dive_dc() to struct dive
Feels natural in a C++ code base.

This removes a nullptr-check so some care has to be taken.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
731052c776 core: remove accessor get_dive_site_for_dive()
This function does nothing at all, callers may just access
dive::dive_site directly.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
718523e01d core: move get_dive_salinity() to struct dive
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
0aa4efb3d9 core: move divesite_has_gps_information() to struct dive_site
Seems logical in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
c812dd140b core: move get_surface_pressure() to struct dive
Feel natural in a C++ code base.

Remove the second parameter, because all callers where passing
`true` anyway.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6e29c00f35 core: move number_of_divecomputers to struct dive
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e90251b0cf core: move dive_[has|get]_gps_location() to struct dive
Feel natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
286d8fe21c core: move get_max_mod() and get_max_mnd() to struct dive
Feels natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
d36fd79527 core: move total_weight() into struct dive
Feels natural in a C++ code base.

Change the function to return a weight_t. Sadly, use of the
units.h types is very inconsistent and many parts of the code
use int or double instead. So let's try to make this consistent.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
df1affc25b core: move clear_dive() to struct dive
Feels natural in a C++ code base.

Moreover, remove the fulltext-unregistration, as this is a
layering violation.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
79bf79ad7f core: move clone_delete_divecomputer() to struct dive_table
Since this calls force_fixup_dive() it needs access to other dives
in the dive_table.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
576d3a3bc6 core: move has_dive() function into struct divelist
Seems natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
60c7b503cf core: move data file version functions into version.cpp/h
It is unclear why these were located in divelist.cpp/h.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
72bb38601d core: move invalidate_dive_cache() to struct dive
Seems natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
1b593dc56c core: move cylinder related functions to struct dive
Seems natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3aab33ba4c core: move get_dive_gas() to struct dive
It is unclear why this was declared in divelist.h.

Moreover, rename it to get_maximal_gas() to better reflect
what it does.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
4d7291d4a1 core: move merge_dives() functios to struct dive_table
These functions have to access other dives in the list to
calculate CNS, etc, so let's call them from there.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
8ec1f008ab core: move split_dive*() functions to struct dive_table
These functions have to access other dives in the list to
calculate CNS, etc, so let's call them from there.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
a2903b31a7 core: move fixup_dive() to struct dive_table
This accesses the global dive_table, so make this explicit.

Since force_fixup_dive() and default_dive() use fixup_dive(),
also move them to struct dive_table.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
d81ca005ab core: move *_surface_pressure() functions into struct dive
Seems natural in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
4a165980e7 undo: pass dive as unique_ptr to addDive()
Before, a non-owning pointer was passed and the dive moved
away from the dive. Instead, let the caller decide if they
still want to keep a copy of the dive, or give up ownership:

In MainWindow and QMLManager new dives are generated, so
one might just as well give up ownership. In contrast,
the planner works on a copy (originally the infamous
"displayed_dive") and now moves the data manually.

This commit also removes duplicate code, by moving the
"create default dive" code from MainWindow and QMLManager
to struct dive.

Finally, determination of the "time zone offset" is not done
in POSIX, since we want to avoid calls form the core into
Qt.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
bdd5527005 core: move *_to_depth() functions into struct dive
Seems logical in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
bf84d66df2 core: move depth_to_* functions into struct dive
Seems logical in a C++ code base.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3660241993 core: remove get_dive() function
This implicitly accessed the global divelog. Most of the users were
in the test/ folder anyway. Replace by explicit accesses to the
global divelog.dives.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
176f544106 core: move process_import_dives() and related functions to divelog
These functions accessed the global divelog make this explicit.

I'm still not happy about the situation, because these functions
access global state, such as the selection. I think these
should be moved up the call-chain.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b34116e2e2 core: make calculate_cns() member of dive_table
This function implicitely accessed the global divelog. To make
that explicit make it a member of dive_table, such that the
caller must access it via the global variable.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
f3b8e3c4aa core: make register_dive() member of dive_table
This one is for symmetry with unregister_dive(). However, it
makes me unhappy, because it modifies global state, namely the
selection machinery and the fulltext. I think these should be
moved up in the call chain.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
271df8962b core: make get_dive_nr_at_idx() member of dive_table
This function implicitely accessed the global divelog. To make
that explicit make it a member of dive_table, such that the
caller must access it via the global variable.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
cce5f5950c core: make get_surface_interval() member of dive_table
This function implicitely accessed the global divelog. To make
that explicit make it a member of dive_table, such that the
caller must access it via the global variable.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e9a57ac5f5 core: make find_next_visible_dive() member of dive_table
This function implicitely accessed the global divelog. To make
that explicit make it a member of dive_table, such that the
caller must access it via the global variable.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
4afefb1b9b parser: keep divelist sorted
The parser used to append each parsed dive at the end of the log.
At the end the list was sorted. However, the divelist code depends
on the list being sorted. To avoid inconsistent states, add the
dives at the proper position.

Note that the reference data of TestDiveSeabearNewFormat had to
be adapted, because the CNS calculation now gives a different
value. This shouls be investigated.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
124362caa5 parser: move atoi_n to import-divinglog.cpp
That was the only user of this helper function, so move it there.
Moreover, impelement it with the standard function std::from_chars
instead of copying the string.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
541abf7ae4 core: use std::unique_ptr<> to make ownership transfer more clear
The decostate was generated in the main thread and passed down to
a worker thread. To make that explicit, use an std::unique_ptr<>
and std::move().

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
d594cc72f0 core: remove copy_qstring() function
No more users of that (yippie!).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
ccdd92aeb7 preferences: use std::string in struct preferences
This is a messy commit, because the "qPref" system relies
heavily on QString, which means lots of conversions between
the two worlds. Ultimately, I plan to base the preferences
system on std::string and only convert to QString when
pushing through Qt's property system or when writing into
Qt's settings.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
82fc9de40b core: remove structured_list.h
No more users of this, since we switched to C++ containers.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
5805b14734 core: use std::string for hashfile_name
One more free() removed.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
ec92cff92c import: use std::string for location in cobalt-import
One more strdup()/free() pair removed.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6c7942ac1c core: remove add_to_string() function
Last user removed in 160f06db8d.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6252d22adf desktop: use std::string to format subtitles
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
d05e289507 core: use std::string in error_callback
No naked free().

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
71f3189a31 core: remove C-versions of (v)format_string()
Only users of the std::string versions are left.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
0c7c96402c core: remove get_first_converted_string_c() in load-git.cpp
No more users of C-strings.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
7452aa22c2 download: replace progress_bar_text by std::string
No fixed buffers. Sadly, the thing is still a global variable.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
91968ac579 core: remove filterconstraint C boilerplate code
Since all code can now directly access C++ structures these
accessor functions were not necessary.

Split out the table from the filterconstraint source file
and include it directly into the divelog.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
2bdcdab391 core: include trip table directly in divelog
Having this as a pointer is an artifact from the C/C++ split.
The triptable header is small enough so that we can
include it directly

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
5af9d28291 core: include divesite table directly in divelog
Having this as a pointer is an artifact from the C/C++ split.
The divesitetable header is small enough so that we can
include it directly.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
7792f54a73 core: move functions into trip-structure
Not strictly necessary, but a "natural" thing to do in a classical
C++ code base.

Move the tiny trip-table into its own source file, since it also
has its own header.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
67d0f69516 core: remove table.h
No more users of this.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b95ac3f79c core: turn C dive-table into an owning table
This is a humongous commit, because it touches all parts of the
code. It removes the last user of our horrible TABLE macros, which
simulate std::vector<> in a very clumsy way.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
f00c30ad4a fix mrege to unique: a5f291fa12
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
a1e6df46d9 core: move functions into struct dive
Nothing against free-standing functions, but in the case
of dc_watertemp(), dc_airtemp(), endtime() and totaltime(),
it seems natural to move this into the dive class and avoid
polution of the global name space.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b9df26066e core: introduce register_dive() function
There was a weird asymmetry, where the undo-commands would
register the fulltext index of the dive, but the core would
unregister the fulltext index in the "unregister_dive()"
function.

To make this more logical, create a "register_dive()" function
in core that does registers the fulltext index.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
aa60e5d21d core: return unique_ptr<> from merge-dive functions
Try to remove plain owning pointers.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
1e09ec77d7 core: make clone_* functions return a unique_ptr<>
Don't use plain pointers for owning pointers.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
0ef497c3c9 core: make split_dive() and related functions return unique_ptrs
This prepares for turning the dive table into a list of owning
pointers.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
c9d4ce0c15 fulltext: replace plain pointer by std::unique_ptr<>
This was a plain pointer owing to C compatibility.

Replacing it by a unique_ptr<> allows us to make it
'self-desctruct' in the constructor. However, we do this
with a special twist: the data is _not_ copied when copying
the dive, since the copied dive is not registered in the fulltext
system. Hackish, but it should(!) work.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
5c7cfb1057 core: replace list of dives in trip by std::vector<>
The dive_table will be converted into a table of owning pointers.
Since the trip has only non-owning pointers to dives, turn
its dive_table into an std::vector<dive *>.

Add a helper functions to add/remove items in a sorted list.
These could be used elsewhere.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
71518fa77e core: make get_trip_by_uniq_id a member of trip_table
Don't access the global trip_table in an attempt to cut down
on implicit accesses of global variables.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
eacad89531 core: turn trip-table into our own sorted_owning_table
Since the sorted_owning_table depends on the fact that
different elements never compare as equal, make the
comparison function safer in that respect. If all failes,
compare the pointers.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
1cebafb08f core: remove utf8_string() function
That was used to parse C-style strings. It was fully replaced
the the std::string version utf8_string_std().

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3ee41328f9 core: turn dive-trip location and notes into std::string
Simpler memory management.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
2fd226964c core: remove device-fingerprint C access code
No need to have this code, as all callers are now C++.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
73f2605ab1 core: remove device C access code
This was used from C, so there was lots of access code, which is
not necessary.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
9e3e0a5a05 core: turn picture-table into std::vector<>
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3cb04d230b core: turn struct dive string data into std::string
Much easier memory management!

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
2b3d2f1020 core: add default initialization to sruct deco_state
Don't memset() to clear deco_state, use assignment of
default constructed object (or better yet: just default
construct).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
f18acf6fb9 core: port tag-list to C++
Also adds a new test, which tests merging of two tag-lists.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
640ecb345b core: convert weightsystem_t and weightsystem_table to C++
As for cylinders, this had to be done simultaneously,

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
28520da655 core: convert cylinder_t and cylinder_table to C++
This had to be done simultaneously, because the table macros
do not work properly with C++ objects.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
284582d2e8 core: turn divecomputer list into std::vector<>
Since struct divecomputer is now fully C++ (i.e. cleans up
after itself), we can simply turn the list of divecomputers
into an std::vector<>. This makes the code quite a bit simpler,
because the first divecomputer was actually a subobject.

Yes, this makes the common case of a single divecomputer a
little bit less efficient, but it really shouldn't matter.
If it does, we can still write a special std::vector<>-
like container that keeps the first element inline.

This change makes pointers-to-divecomputers not stable.
So always access the divecomputer via its index. As
far as I can tell, most of the code already does this.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e237f29fb2 core: fold event-related functions into event class
Not strictly necessary, but more idiomatic C++ and less
polution of the global namespace. This one is so trivial
that there seems to be no reason not to do it.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
c27314d603 core: replace add_sample() by append_sample()
add_sample() was used in only one place, and the return value was
always ignored. It took a time parameter, suggesting that a sample
could be added anywhere, but in reality the sample was added at
the end of the list. It used prepare_sample() that copies data
from the previous sample, just to overwrite it with the newly
added sample.

All in all very weird. Simplify the function: just append the
passed in sample and name it accordingly.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
af6201f89c core: simplify default initialization of struct sample
Since the units got default constructors, we don't have to
manually initialize them.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
27dbdd35c6 core: turn event-list of divecomputer into std::vector<>
This is a rather long commit, because it refactors lots of the event
code from pointer to value semantics: pointers to entries in an
std::vector<> are not stable, so better use indexes.

To step through the event-list at diven time stamps, add *_loop classes,
which encapsulate state that had to be manually handled before by
the caller. I'm not happy about the interface, but it tries to
mirror the one we had before.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
8ddc960fa0 core: remove update_event_name
Since the name of an event is not incorporated into the even
structure anymore, we don't need these shenanigans. Just assign
the event name.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
f120fecccb core: use std::vector<> to store divecomputer samples
This is a hairy one, because the sample code is rather tricky.

There was a pattern of looping through pairs of adjacent samples,
for interpolation purposes. Add an range adapter to generalize
such loops.

Removes the finish_sample() function: The code would call
prepare_sample() to start parsing of samples and then
finish_sample() to actuall add it. I.e. a kind of commit().

Since, with one exception, all users of prepare_sample()
called finish_sample() in all code paths, we might just add
the sample in the first place. The exception was sample_end()
in parse.cpp. This brings a small change: samples are now
added, even if they could only be parsed partially. I doubt
that this makes any difference, since it will only happen
for broken divelogs anyway.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
bc761344d4 core: convert dive computer extra data to C++
Use std::string and std::vector. Much simpler code.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b9a2eff3c9 core: turn string data in struct divecomputer into std::string
Simplifies memory management.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
cc39f709ce core: add constructor/destructor pairs to dive and divecomputer
This allows us to use non-C member variables. Convert a number
of pointers to unique_ptr<>s.

Code in uemis-downloader.cpp had to be refactored, because
it mixed owning and non-owning pointers. Mad.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
bfb54aa581 core: remove typedefs in pref.h
Some compilers whine when using typedefs with non-C structs with
default initializers. Not yet the case here, but in the future
probably will. So remove them now. No point in C++ anyway.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
eac11683a9 cleanup: remove enum typedef in color.h
Just call the enum that way and be done with it.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
37be879e17 cleanup: remove typedef in qtserialbluetooth.cpp
This was very odd: A typedef to the same name as the structure
was named. Huh?

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
058485e374 core: remove typedefs in units.h
They make no sense under C++ and seem to produce tons of warnings
on some compilers (Apple).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
65dcb98e41 core: remove typedefs in equipment.h
Wuth C++ the distinction between "typedef" and regular "struct"
makes no sense anymore. Remove the typedefs, it's just confusing.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
d242198c99 divelog: turn owning-pointers into unique_ptr<>s
Since everything is C++ now, we can use unique_ptr<>s. This makes
the code significantly shorter, because we can now use the default
move constructor and assignment operators.

This has a semantic change when std::move()-ing the divelog:
now not the contents of the tables are moved, but the pointers.
That is, the moved-from object now has no more tables and
must not be used anymore. This made it necessary to replace
std::move()s by std::swap()s. In that regard, the old code was
in principle broken: it used moved-from objects, which may work
but usually doesn't.

This commit adds a myriad of .get() function calls where the code
expects a C-style pointer. The plan is to remove virtually all of
them, when we move free-standing functions into the class it acts
on. Or, replace C-style pointers by references where we don't support
NULL.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6e352d5281 core: move freestanding functions into divelog class
There were only two of them, from the time C-code had to access
the divelog: clear_divelog() and delete_single_dive().

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
90d5bab4e9 cleanup: pass location_t as value to divesite functions
These were passed as pointers, which makes no sense.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
858a0aecba import: initialize DiveSiteImportModel in constructor
The old code would construct and then initialize the object
in a separate function, which added lots of complication.

Just initialize the thing in the constructor, store a
reference, not a pointer to the table. And do a few other
code cleanups. The result is distinctly more pleasing.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
512eada468 core: move get_same_dive_site() into dive_site_table class
This was the only dive_site_table function that accessed
to global divelog, which is odd. Make it consistent with
the others.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
4ac2486a23 core: move constructLocationTags from divesite.cpp to taxonomy.cpp
After all it doesn't access any dive_site structure.

Moreover, rename it, since we use mostly snake_case in core.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
7d7766be9a core: move get_distance() from divesite.cpp to units.cpp
This gives the distance between to location_t objects. It is
unclear why this was in divesite.cpp.

Moreover pass by value, not raw pointer.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
2de6f69c19 core: move dive-site functions into class
In analogy to the previous commit for dive-site-table.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
76c52c87a3 core: move dive-site-table functions into class
There were a number of free standing functions acting on a
dive-site-table. Make them member functions. This allows
for shorter names. Use the get_idx() function of the base
class, which returns a size_t instead of an int (since that
is what the standard, somewhat unfortunately, uses).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6b835710bc map: use value semantics for MapLocation
This makes memory management more simple, as not explicit deletion
is necessary.

A rather large commit, because changing QVector<> to std::vector<>
is propagated up the call chain.

Adds a new range_contains() helper function for collection
types such as std::vector<>. I didn't want to call it
contains(), since we already have a contains function
for strings and let's keep argument overloading simple.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e39dea3d68 core: replace divesite_table_t by a vector of std::unique_ptr<>s
This is a long commit, because it introduces a new abstraction:
a general std::vector<> of std::unique_ptrs<>.

Moreover, it replaces a number of pointers by C++ references,
when the callee does not suppoert null objects.

This simplifies memory management and makes ownership more
explicit. It is a proof-of-concept and a test-bed for
the other core data structrures.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
411188728d core: return pressures structure from fill_pressures()
Instead of taking an out-parameter. That's more idiomatic C++.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
ead58cd039 core: remove membufferpp
This the C++ version of membuffer. Since everything is C++, it can
just be made the default.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
db4b972897 core: replace same_location by operator==()
And operator!=() in the negative case.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
2df30a4144 core: remove ssrf.h include file
It didn't contain anything.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
4d183637d0 cleanup: remove unnecessary Q_UNUSED macros
Also remove the UNUSED() macro, as there were no users left.

The macro was silly anyway - there were many falso positives.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
5960fb7340 core: remove get_event_mutable()
We can now return mutable/imutable depending on const-ness of
the parameter, owing to parameter overloading.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b8c7b173c6 core: make event name an std::string
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
408b31b6ce core: default initialize units-type objects to 0
Makes the code much nicer to read.

Default initialize cylinder_t to the empty cylinder.

This produces lots of warnings, because most structure are now
not PODs anymore and shouldn't be erased using memset().

These memset()s will be removed one-by-one and replaced by
proper constructors.

The whole ordeal made it necessary to add a constructor to
struct event. To simplify things the whole optimization of
the variable-size event names was removed. In upcoming commits
this will be replaced by std::string anyway.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b82fdd1d20 general: remove (void) function parameter declarations
To my understanding, declaring empty parameter lists using "(void)"
is an artifact from the bad old K&R times, when functions were
declared without(!) parameters. Which in hindsight was an absolute
recipe for disaster. So for backwards compatibility, functions
without parameters had to be declared using "(void)" as "()"
could also mean "any function".

That was 40 years ago. Meanwhile, C++ introduced references,
which made it a necessity to declare the function parameters.
So "(void)" is redundant and inconsistent in C++ code and
just makes no sense.

Remove it.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b56dd13add build: remove extern "C" linkage
No more C source files, no more necessity to use C-linkage.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
03b910ee7f core: remove __cplusplus ifdefs
Since all source files are now C++, this is redundant.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
9065bf8622 core: convert picture.c to C++
The last C-file in core. Yippie.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
7d3977481a core: convert divesite strings to std::string
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3916125786 core: convert trip.c to C++
Necessary so that we can continue porting the divesite code to C++.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
177246b419 core: fold divesite-helper.cpp into divesite.cpp
The divesite-helper.cpp only existed because C-string manipulation
was too tedious. Now that divesite.cpp is C++ anyway, the split
is not necessary anymore.

Moreover, return an std::string, since this is a core-function.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
801b5d50b2 core: replace dive_site::dives by an std::vector<>
Since this is now in C++, we don't have to use our crazy
TABLE_* macros.

This contains a logic change: the dives associated to a
dive site are now unsorted.

The old code was subtly buggy: dives were added in a sorted
manner, but when the dive was edited the list was not
resorted. Very unlikely that this leads to a serious
problem, still not good.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3f8b4604be core: convert taxonomy.c to C++
Since the taxonomy is now a real C++ struct with constructor
and destructor, dive_site has to be converted to C++ as well.

A bit hairy for now, but will ultimately be distinctly simpler.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3c1401785b core: use C++ structures for tanksystem info
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
1af00703b3 core: use C++ structures for weightsystem info
Use std::vector<> instead of fixed size array.
Doesn't do any logic change, even though the back-translation
logic is ominous.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
c5f96d877d core: convert equipment.c to C++
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
19148d30e7 core: convert event.c to C++
No code changes.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
48f7828d10 profile: C++-ify plot_info
Use more C++ style memory management for plot_info: Use std::vector
for array data. Return the plot_info instead of filling an output
parameter. Add a constructor/destructor pair so that the caller
isn't bothered with memory management.

The bulk of the commit is replacement of pointers with references,
which is kind of gratuitous. But I started and then went on...

Default initializiation of gas_pressures made it necessary to convert
gas.c to c++, though with minimal changes to the code.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
aaab5157d4 core: convert save-profiledata to C++
Leave the code as is for now. Just replace membuffer by its
C++ version.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e537904965 core: convert gaspressures.c to C++
Replace "poor man's" linked list implementation by std::vector<>.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
3395c61bc8 core: convert gas-model.c to C++
A nice one - nothing to do. Introduce an std::clamp(), just
because we can...

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
7c2b580bfa core: convert ftdi.c to C++
Replace malloc/free of one structure by C++ idioms and add a
destructor to the struct. Otherwise, don't touch the code.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e29a0c1b29 import: free dc_descriptor in new device_data_t destructor
It seems that smartrak was the only part of the code that cared
about freeing the dc_descriptor. Make that a general feature
of the new device_data_t destructor, which we could implement
now that things are in C++.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
01306224ff import: turn C-string in device_data_t into std::strings
It was never clear what was a pointer to a static string from
libdivecomputer and what was allocated.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
0915c1ce43 cleanup: don't allocate device_data_t structure
These can all just be local objects.

Also, don't overwrite them with 0. We later want to convert the
string to std::string, where this would be very sketchy.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b74703b61d core: convert ostctools as C++
Replace some of the memory management by C++ idioms.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
fe0bb905f3 core: convert pref.c and units.c to C++
Convert both files simultanously, because the SI_UNITS define works
either under C or under C++.

This was painful, because initialization of struct-members has to
be done in order of definition in C++. And it was completely out
of order. However, as long as not all is C++, we can't use
default initialization directly in the struct definition. :(

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6cda13a9fe core: replace core/timer.c by std::chrono
No point in reimplementing the wheel.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
1daa4f0584 core: C++-ify statistics.c
The old code was wild: For the yearly statistics it would allocate
one entry per dive in the log. Of course, it would also leak
C-style strings.

Convert the whole thing to somewhat idiomatic C++.

Somewhat wasted work, because I'd like to convert the whole thing
to the new statistics code. But let's finish the conversion to C++
first.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
84219641de core: remove MIN() and MAX() macros
All have been converted to std::min() and std::max().

Moreover, this was Windows only and since we cross-compile, it
is not even clear if this is needed.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
edc0c69943 core: convert divecomputer.c to C++
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
a244e71256 core: replace MIN() by type-sage std::min() in divelist.cpp
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
7b79bc954b core: convert divelist.c to C++
Fortunately, not much to do in this file.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
a6815661d5 uemis: replace a defines by numeric constants
Always good to use the type system of the language instead of
text substitution.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
52fb77da69 uemis: replace UEMIS_CHECK_* defines by enum
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
018884dfde uemis: turn UEMIS_MEM_* defines into enum
One value (UEMIS_MEM_CRITICAL) wasn't ever used. Remove it.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
6d8a904981 uemis: replace divespot_mapping by hash-map
This was the umpteenth inefficient reinvention of a trivial
map. Replace by a hash-map (std::unordered_map). Might just
as well use a balanced binary tree or a sorted array. In the
end, it probably doesn't matter at all.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
f3a36b62ac uemis: unglobalize mindiveid
uemis_get_divenr() returns maxdiveid and passes mindiveid as a
global variable.

Make this more reasonable by returning a min, max pair.

The way mindiveid is an unsigned int and then reinterpreted as
int is very sketchy. This commit attempts to not change that
behavior.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
5554acb2c5 uemis: unglobalize next_table_index
This is only initialized and used in one loop. Very mysterious
why this should be a global variable.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
ad33d498c7 uemis: unglobalize reqtxt_file
This global variable is used in two function independently of
each other. I don't see how there should be transport of this
value from one function to the other. Ominous.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
9e7b98024e uemis: unglobalize response buffer
uemis_get_answer() would put the raw response into a global variable.
This could be anywhere in the call stack and thus you never knew
when the existing buffer was removed under your feet.

Instead, return the buffer explicitly from uemis_get_answer().

I'm nit perfectly happy about the new interface: an error is
indicated by an empty buffer, which is awkward to test for.
If an empty buffer turns out to be a valid response, this
should be replaced by an std::optional<> or std::expected<>.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
58b3583b3b uemis: replace C-strings by std::string and std::string_view
The string code of uemis-downloader.cpp was broken in more ways
than can be listed here. Notably, it brazenly refused to free any
memory allocated for the parameters buffer.

Using std::string and std::string_view should plug all those
memory holes. That made it necessary to do some major refactoring.

This was done blind and therefore will break.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
16e19b550b uemis-downloader: use std::string for constructing strings
This extends work started by Richard Fuchs <dfx@dfx.at>

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
ad3be20c9f uemis-downloader: make newmax an integer variable
newmax was an integer variable kept as a string. Very ominous.

Moreover, memory management seems to be broken:

1) The string is never freed.

2) The string is passed as value from do_uemis_import() to
   get_matching_dives(), which passes it as reference to
   process_raw_buffer(), which may reallocate it, which means
   that do_uemis_import() now possesses a pointer to a free()d
   string.

Simplify all that by making newmax an integer variable and
passing it as a reference from do_uemis_import() to
get_matching_dives().

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
bf92407a4a uemis downloader: use report_info() for debugging
There were a number of fprintf()s that escaped the conversion
to report_info(), because they used "debugfile" instead of
"stderr" as target. However, debugfile was just #defined to
be stderr, so we might just use report_info() for consistency.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b28d6cf0fc core: convert uemis.c to C++
The uemis code is wild. It simply doesn't deallocate memory
and uses global variables. To get this under control, create
a "struct uemis" and make the functions exported by "uemis.h"
members of "struct uemis". Thus, we don't have to carry around
a parameter for the state of the importing process.

Turn a linked list of "helper" structures (one per imported dive)
into a std::unordered_map, to fix leaking of the helper structures.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
cdc87618da uemis: pass dive pointer as dive pointer, not void pointer
This was very obscure: the function that parses into a struct
dive was passed a void-pointer instead of a struct dive-pointer.

Why? Just pass the correct type.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
21f68387ae core: C++-ify SHA1 interface
All callers of the SHA1 code are C++. Might just as well use
a C++ like interface.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
729356e0b1 cleanup: fix typo in comment
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
742193a5e3 core: convert divesite.c to C++
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
b24f37fb4f core: replace SHA1() function by SHA1_uint32()
The SHA1() helper function was only used when calculating a
SHA1 hash and taking the first four bytes of it as uint32.

Make that explicit by renaming the function into SHA1_uint32()
and directly returning an uint32_t.

Note that the usage in cochran.cpp is sketchy: it generates
a four-byte hash out of two-byte data. Why!?

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
0b817e468a core: convert version.c to C++
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
e3f6496f59 core: convert strtod.c to C++
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
092035d883 core: simplify custom strtod() interface
The strtod_flags() function allowed for fine control of how to
parse strings. However, only two different modes were actually
used: ascii mode ("C" locale) and permissive mode (accept ","
and "." as decimal separator).

The former had already its own function name (ascii_strtod).
Make the latter a separatge function as well (permissive_strtod)
and remove all the flags rigmarole.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
Berthold Stoeger
628e2fe933 parser: remove int_or_float union & other small float-parsing cleanups
Owing to bitrot, this union only contained a float and therefore
is pointless. Let's remove it.

That makes the function name "integer_or_float()" non-sensical.
Call it "parse_float()" instead.

Moreover, change the output-arguments of "parse_float()" from
pointers to references, as null-pointers are not supported.

Finally, remove the "errno" check after "ascii_strtod()". As far as
I can tell, errno is not set in "ascii_strtod()" and using a global
variable for error-reporting it is an incredibly silly interface
anyway.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-08-13 19:28:30 +02:00
416 changed files with 15285 additions and 18354 deletions

View file

@ -10,7 +10,7 @@ ColumnLimit: 0
ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 8 ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 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 IndentFunctionDeclarationAfterType: false #personal taste, good for long methods
IndentWidth: 8 IndentWidth: 8
MaxEmptyLinesToKeep: 2 MaxEmptyLinesToKeep: 2

19
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: "github-actions"
# Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.)
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "gradle"
directory: "/android-mobile"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directories:
- "/scripts/docker/android-build-container"
- "/scripts/docker/mxe-build-container"
schedule:
interval: "weekly"

View file

@ -12,7 +12,7 @@ jobs:
android-build-container: android-build-container:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -18,7 +18,7 @@ jobs:
KEYSTORE_FILE: ${{ github.workspace }}/../subsurface.keystore KEYSTORE_FILE: ${{ github.workspace }}/../subsurface.keystore
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: docker://subsurface/android-build:5.15.2 image: docker://subsurface/android-build:5.15.3
steps: steps:
- name: checkout sources - name: checkout sources

View file

@ -46,7 +46,7 @@ jobs:
qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \ qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \
qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \ qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \
qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools \ 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. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View file

@ -25,7 +25,7 @@ jobs:
qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \ qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \
qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \ qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \
qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools \ 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 - name: checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4

View file

@ -30,7 +30,7 @@ jobs:
qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \ qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \
qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \ qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \
qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools \ 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 mdbtools-dev
git config --global user.email "ci@subsurface-divelog.org" git config --global user.email "ci@subsurface-divelog.org"
@ -95,6 +95,5 @@ jobs:
echo "--------------------------------------------------------------" echo "--------------------------------------------------------------"
echo "building smtk2ssrf" echo "building smtk2ssrf"
# build smtk2ssrf (needs the artefacts generated by the subsurface build
cd .. cd ..
bash -e -x subsurface/scripts/smtk2ssrf-build.sh -y bash -e -x subsurface/scripts/smtk2ssrf-build.sh -y

View file

@ -32,7 +32,7 @@ jobs:
qt6-qtlocation-devel qt6-qtsvg-devel \ qt6-qtlocation-devel qt6-qtsvg-devel \
qt6-qttools-devel redhat-rpm-config \ qt6-qttools-devel redhat-rpm-config \
libxkbcommon-devel qt6-qt5compat-devel \ 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 xorg-x11-server-Xvfb
- name: checkout sources - name: checkout sources

View file

@ -55,7 +55,7 @@ jobs:
echo "key=$( git merge-base origin/master $GITHUB_SHA )" >> $GITHUB_OUTPUT echo "key=$( git merge-base origin/master $GITHUB_SHA )" >> $GITHUB_OUTPUT
- name: CCache - name: CCache
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
key: ccache-${{ runner.os }}-${{ steps.setup-ccache.outputs.key }} key: ccache-${{ runner.os }}-${{ steps.setup-ccache.outputs.key }}
restore-keys: | restore-keys: |

View file

@ -35,7 +35,7 @@ jobs:
qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \ qml-module-qtquick2 qt5-qmake qtchooser qtconnectivity5-dev \
qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \ qtdeclarative5-dev qtdeclarative5-private-dev qtlocation5-dev \
qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools \ 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 mdbtools-dev curl
git config --global user.email "ci@subsurface-divelog.org" git config --global user.email "ci@subsurface-divelog.org"

View file

@ -24,7 +24,7 @@ jobs:
submodules: recursive submodules: recursive
- name: setup Homebrew - 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 - name: checkout Qt resources
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -45,7 +45,7 @@ jobs:
CANONICALVERSION: ${{ steps.version_number.outputs.version }} CANONICALVERSION: ${{ steps.version_number.outputs.version }}
run: | run: |
cd ${GITHUB_WORKSPACE}/.. 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 QT_QPA_PLATFORM_PLUGIN_PATH=$QT_ROOT/plugins
export PATH=$QT_ROOT/bin:$PATH export PATH=$QT_ROOT/bin:$PATH
export CMAKE_PREFIX_PATH=$QT_ROOT/lib/cmake export CMAKE_PREFIX_PATH=$QT_ROOT/lib/cmake

View file

@ -14,12 +14,12 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
- name: Install Python dependencies - name: Install Python dependencies
uses: insightsengineering/pip-action@v2.0.0 uses: insightsengineering/pip-action@v2.0.1
with: with:
requirements: .github/workflows/scripts/requirements.txt requirements: .github/workflows/scripts/requirements.txt
@ -28,7 +28,7 @@ jobs:
sudo snap install review-tools --edge sudo snap install review-tools --edge
- name: Set up Launchpad credentials - name: Set up Launchpad credentials
uses: DamianReeves/write-file-action@v1.2 uses: DamianReeves/write-file-action@v1.3
with: with:
path: lp_credentials path: lp_credentials
contents: ${{ secrets.LAUNCHPAD_CREDENTIALS }} contents: ${{ secrets.LAUNCHPAD_CREDENTIALS }}

View file

@ -12,8 +12,8 @@ jobs:
windows-mxe: windows-mxe:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
VERSION: ${{ '3.1.0' }} # 'official' images should have a dot-zero version VERSION: ${{ '3.2.0' }} # 'official' images should have a dot-zero version
mxe_sha: 'c0bfefc57a00fdf6cb5278263e21a478e47b0bf5' mxe_sha: '974808c2ecb02866764d236fe533ae57ba342e7a'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -16,7 +16,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: docker://subsurface/mxe-build:3.1.0 image: docker://subsurface/mxe-build:3.2.0
steps: steps:
- name: checkout sources - name: checkout sources

3
.gitignore vendored
View file

@ -17,6 +17,9 @@ Documentation/docbook-xsl.css
Documentation/user-manual*.html Documentation/user-manual*.html
Documentation/user-manual*.pdf Documentation/user-manual*.pdf
Documentation/user-manual*.text Documentation/user-manual*.text
Documentation/mobile-manual*.html
Documentation/mobile-manual*.pdf
Documentation/mobile-manual*.text
Documentation/mobile-images/mobile-images Documentation/mobile-images/mobile-images
packaging/windows/subsurface.nsi packaging/windows/subsurface.nsi
packaging/macos/Info.plist packaging/macos/Info.plist

View file

@ -8,6 +8,7 @@ desktop: fix gas switches in UDDF exports
core: allow of up to 6 O2 sensors (and corresponding voting logic) core: allow of up to 6 O2 sensors (and corresponding voting logic)
desktop: add divemode as a possible dive list column desktop: add divemode as a possible dive list column
profile-widget: Now zomed in profiles can be panned with horizontal scroll. 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 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: 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 equipment: include unused cylinders in merged dive if the preference is enabled

View file

@ -52,6 +52,7 @@ option(NO_USERMANUAL "don't include a viewer for the user manual" OFF)
#Options regarding enabling parts of subsurface #Options regarding enabling parts of subsurface
option(BTSUPPORT "enable support for QtBluetooth" ON) option(BTSUPPORT "enable support for QtBluetooth" ON)
option(FTDISUPPORT "enable support for libftdi based serial" OFF) 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 # Options regarding What should we build on subsurface
option(MAKE_TESTS "Make the tests" ON) option(MAKE_TESTS "Make the tests" ON)
@ -166,6 +167,7 @@ if(NOT ANDROID)
endif() endif()
pkg_config_library(LIBUSB libusb-1.0 QUIET) pkg_config_library(LIBUSB libusb-1.0 QUIET)
pkg_config_library(LIBMTP libmtp QUIET) pkg_config_library(LIBMTP libmtp QUIET)
pkg_config_library(LIBRAW libraw )
endif() endif()
include_directories(. include_directories(.
@ -295,6 +297,7 @@ elseif (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DownloaderExecutable")
set(SUBSURFACE_TARGET subsurface-downloader) set(SUBSURFACE_TARGET subsurface-downloader)
endif() endif()
set(BTSUPPORT ON) set(BTSUPPORT ON)
set(LIBRAW_SUPPORT OFF)
add_definitions(-DSUBSURFACE_DOWNLOADER) add_definitions(-DSUBSURFACE_DOWNLOADER)
message(STATUS "building the embedded Subsurface-downloader app") message(STATUS "building the embedded Subsurface-downloader app")
endif() endif()
@ -358,6 +361,14 @@ if(BTSUPPORT)
add_definitions(-DBLE_SUPPORT) add_definitions(-DBLE_SUPPORT)
endif() endif()
if (LIBRAW_SUPPORT)
if(LIBRAW_FOUND)
add_definitions(-DLIBRAW_SUPPORT)
endif()
else()
message(STATUS "building without built-in libraw support")
endif()
if(ANDROID) if(ANDROID)
# when building for Android, the toolchain file requires all cmake modules # when building for Android, the toolchain file requires all cmake modules
# to be inside the CMAKE_FIND_ROOT_PATH - which prevents cmake from finding # to be inside the CMAKE_FIND_ROOT_PATH - which prevents cmake from finding

View file

@ -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 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. 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 * all indentation is tabs (set to 8 char) with the exception of
continuation lines that are aligned with tabs and then spaces 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 * 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 for variable names and PascalCase
``` ```
ClassInCPlusPlus ClassInQt
``` ```
for names of classes and other types 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 #define frobulate(x) (x)+frob
``` ```
Since C++ is strongly typed, avoid macros where possible.
For constants use `constexpr`:
```
static constexpr int frob = 17;
```
and for functions use templated inline functions such as
```
template<typename T>
static bool less_than(T x, T y)
{
return x < y;
}
```
* there is a strong preference for lower case file names; sometimes conventions * there is a strong preference for lower case file names; sometimes conventions
or outside requirements make camelCase filenames the better (or only) choice, or outside requirements make camelCase filenames the better (or only) choice,
but absent such an outside reason all file names should be lower case 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 ## Coding conventions
* variable declarations * variable declarations
In C code we really like them to be at the beginning of a code block, In C++ the lifetime of a variable often coincides with the
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
lifetime of a resource (e.g. file) and therefore the variable is defined 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 * The `*`, `&` and `&&` declarators are grouped with the name, not the type
(classical C-style) as in `char *string` instead of `char* string`. This (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; 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. Use `auto` sparingly and only in cases where code readability improves.
Two classical examples are: Two classical examples are:
- Iterators, whose type names often are verbose: - 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 is not only distinctly shorter than
``` ```
QMap<qint64, gpsTracker>::iterator it = m_trackers.find(when); std::map<qint64, gpsTracker>::iterator it = m_trackers.find(when);
``` ```
it will also continue working if a different data structure is chosen. it will also continue working if a different data structure is chosen.
- If the type is given in the same line anyway. Thus, - If the type is given in the same line anyway. Thus,
``` ```
auto service = qobject_cast<QLowEnergyService*>(sender()); auto service = std::make_unique<QLowEnergyService*>(sender());
``` ```
is easier to read than and conveys the same information as is easier to read than and conveys the same information as
``` ```
QLowEnergyService *service = qobject_cast<QLowEnergyService*>(sender()); std::unique_ptr<QLowEnergyService> service = std::make_unique<QLowEnergyService>(sender());
``` ```
- If the variable is a container that is only assigned to a local variable to
be able to use it in a range-based `for` loop * containers
```
const auto serviceUuids = device.serviceUuids(); The standard library (STL) containers are robust, but their usage may
for (QBluetoothUuid id: serviceUuids) { appear verbose. Therefore, we have a few convenience functions in the
``` `core/ranges.h` header.
The variable has also to be const to avoid that Qt containers will do a For example, to loop with an index variable, use
deep copy when the range bases `for` loop will call the `begin()` method ```
internally. for (auto [idx, v]: container) {
...
}
```
* text strings * 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 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 context for translations accessed in core code. To use it from C, include
from C++ helper functions. To use it from C, include the `"core/gettext.h"` the `"core/gettext.h"` header and invoke the `translate()` macro:
header and invoke the `translate()` macro:
``` ```
#include "core/gettext.h" #include "core/gettext.h"
@ -280,18 +320,85 @@ other editors that implement this coding style, please add them here.
* string manipulation * string manipulation
* user interface - user interface
In UI part of the code use of `QString` methods is preferred, see this pretty In UI part of the code use of `QString` methods is preferred, see this pretty
good guide in [`QString` documentation][1] good guide in [`QString` documentation][1]
* core components - core components
In the core part of the code, C-string should be used. In the core part of the code, std::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 * memory management in core
to help with this. Documentation and usage examples can be found in
[core/membuffer.h][2] 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 ## Sample Settings
@ -401,7 +508,6 @@ close to our coding standards.
filetype plugin indent on filetype plugin indent on
filetype detect filetype detect
set cindent tabstop=8 shiftwidth=8 cinoptions=l1,:0,(0,g0 set cindent tabstop=8 shiftwidth=8 cinoptions=l1,:0,(0,g0
" TODO: extern "C" gets indented
" And some sane defaults, optional, but quite nice " And some sane defaults, optional, but quite nice
set nocompatible set nocompatible

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -3825,24 +3825,18 @@ user interface. It is explicitly used under the following conditions:
=== The _Subsurface_ dive planner screen === The _Subsurface_ dive planner screen
Like the _Subsurface_ dive log, the planner screen is divided into several sections (see image below). The *setup* Like the _Subsurface_ dive log, the planner screen is divided into several sections (see image below):
parameters for a dive are entered into the sections on the left hand and bottom side of the screen. - 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.
They are: Available Gases, Rates, Planning, Gas Options and Notes. - 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 - 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.
manipulated directly by dragging and clicking as explained below. This feature makes the - 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.
_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"]
image::images/PlannerWindow1.png["FIGURE: Dive planner startup window",align="center"]
=== Open circuit dives === 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) ** Open Circuit (the default)
** CCR ** CCR
** pSCR ** 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. 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 ** 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. 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). 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: - 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 * 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 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. 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. 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 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 table. Segments entered into the _Dive planner points_ table automatically appear in the *Dive
Profile* diagram. 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 ==== 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 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. 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 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 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 Below is the same dive plan as above, but with a safety stop and reduced gradient factors for
a larger safety margin. 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 ==== 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, 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 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 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 (e.g. helium and oxygen content) and - in the case of planning a CCR dive, the intended use as diluent or OC bailout gas.
indicates a valid setpoint for oxygen partial pressure and that the segment 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_.
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 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 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 decompression is computed in OC mode and the planner only considers gas
changes in OC mode. 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 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). 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, 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. 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 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 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* *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 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. 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 ==== 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 === Planning CCR dives
To plan a dive using a closed circuit rebreather, select the _CCR_ option in the dropdown 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 *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 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 *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 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 use the default setpoint value. Then, different setpoints can be specified for dive segments
in the _Dive planner points_ table. A zero setpoint in the _Dive planner points_ table.
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.
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 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 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 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. 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, 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. so gas consumptions of 0 liters are the norm.
==== Planning for CCR bailout ==== Planning for CCR bailout
[icon="images/CCR_b1.jpg"] image::images/CCR_b1.png["FIGURE: Planning a CCR dive: closed circuit deco",align="center"]
[NOTE]
It is often necessary to plan for a worst-case bailout event in order to ensure sufficient bailout gas to reach the 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 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); (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.
image::images/CCR_b2.png["FIGURE: Planning a CCR dive: open circuit bailout",align="center"]
[icon="images/CCR_b2.jpg"] In the _Available gases_
[NOTE] table, change the _Use_ of the gas used for the last segment to _OC-gas_. This signifies bailout.
In the _Dive planner points The appropriate pO~2~ and cylinder pressure
table_, change the _Dive mode_ of this 1-minute segment to _OC_. This signifies bailout. In this case there is bailout to graphs are shown in the dive profile:
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.
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 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. overlapping icons.
The volumes of gases required for bailout can be found at the bottom of the *Dive plan details* panel. 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 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. 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, 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. allowing changes to be made and saved as usual.
In addition, there is the option "Save new". This keeps the original In addition, there is the option "Save new". This keeps the original

View file

@ -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-qtbase-devel qt5-qtconnectivity-devel qt5-qtdeclarative-devel \
qt5-qtlocation-devel qt5-qtscript-devel qt5-qtsvg-devel \ qt5-qtlocation-devel qt5-qtscript-devel qt5-qtsvg-devel \
qt5-qttools-devel qt5-qtwebkit-devel redhat-rpm-config \ 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-qtbase-devel libQt5WebKit5-devel libqt5-qtsvg-devel \
libqt5-qtscript-devel libqt5-qtdeclarative-devel \ libqt5-qtscript-devel libqt5-qtdeclarative-devel \
libqt5-qtconnectivity-devel libqt5-qtlocation-devel libcurl-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 On Debian Bookworm this seems to work
@ -183,7 +182,7 @@ sudo apt install \
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 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 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 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 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 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 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 Note that you'll need to increase the swap space as the default of 100MB

View file

@ -45,28 +45,28 @@ SOURCES += subsurface-mobile-main.cpp \
core/fulltext.cpp \ core/fulltext.cpp \
core/subsurfacestartup.cpp \ core/subsurfacestartup.cpp \
core/subsurface-string.cpp \ core/subsurface-string.cpp \
core/pref.c \ core/pref.cpp \
core/profile.cpp \ core/profile.cpp \
core/device.cpp \ core/device.cpp \
core/dive.cpp \ core/dive.cpp \
core/divecomputer.c \ core/divecomputer.cpp \
core/divefilter.cpp \ core/divefilter.cpp \
core/event.c \ core/event.cpp \
core/eventtype.cpp \ core/eventtype.cpp \
core/filterconstraint.cpp \ core/filterconstraint.cpp \
core/filterpreset.cpp \ core/filterpreset.cpp \
core/divelist.c \ core/filterpresettable.cpp \
core/divelist.cpp \
core/divelog.cpp \ core/divelog.cpp \
core/gas-model.c \ core/gas-model.cpp \
core/gaspressures.c \ core/gaspressures.cpp \
core/git-access.cpp \ core/git-access.cpp \
core/globals.cpp \ core/globals.cpp \
core/liquivision.cpp \ core/liquivision.cpp \
core/load-git.cpp \ core/load-git.cpp \
core/parse-xml.cpp \ core/parse-xml.cpp \
core/parse.cpp \ core/parse.cpp \
core/picture.c \ core/picture.cpp \
core/pictureobj.cpp \
core/sample.cpp \ core/sample.cpp \
core/import-suunto.cpp \ core/import-suunto.cpp \
core/import-shearwater.cpp \ core/import-shearwater.cpp \
@ -75,37 +75,38 @@ SOURCES += subsurface-mobile-main.cpp \
core/import-divinglog.cpp \ core/import-divinglog.cpp \
core/import-csv.cpp \ core/import-csv.cpp \
core/save-html.cpp \ core/save-html.cpp \
core/statistics.c \ core/statistics.cpp \
core/worldmap-save.cpp \ core/worldmap-save.cpp \
core/libdivecomputer.cpp \ core/libdivecomputer.cpp \
core/version.c \ core/version.cpp \
core/save-git.cpp \ core/save-git.cpp \
core/datatrak.cpp \ core/datatrak.cpp \
core/ostctools.c \ core/ostctools.cpp \
core/planner.cpp \ core/planner.cpp \
core/save-xml.cpp \ core/save-xml.cpp \
core/cochran.cpp \ core/cochran.cpp \
core/deco.cpp \ core/deco.cpp \
core/divesite.c \ core/divesite.cpp \
core/equipment.c \ core/equipment.cpp \
core/gas.c \ core/gas.cpp \
core/membuffer.cpp \ core/membuffer.cpp \
core/selection.cpp \ core/selection.cpp \
core/sha1.c \ core/sha1.cpp \
core/string-format.cpp \ core/string-format.cpp \
core/strtod.c \ core/strtod.cpp \
core/tag.cpp \ core/tag.cpp \
core/taxonomy.c \ core/taxonomy.cpp \
core/time.cpp \ core/time.cpp \
core/trip.c \ core/trip.cpp \
core/units.c \ core/triptable.cpp \
core/uemis.c \ core/units.cpp \
core/uemis.cpp \
core/btdiscovery.cpp \ core/btdiscovery.cpp \
core/connectionlistmodel.cpp \ core/connectionlistmodel.cpp \
core/qt-ble.cpp \ core/qt-ble.cpp \
core/uploadDiveShare.cpp \ core/uploadDiveShare.cpp \
core/uploadDiveLogsDE.cpp \ core/uploadDiveLogsDE.cpp \
core/save-profiledata.c \ core/save-profiledata.cpp \
core/xmlparams.cpp \ core/xmlparams.cpp \
core/settings/qPref.cpp \ core/settings/qPref.cpp \
core/settings/qPrefCloudStorage.cpp \ core/settings/qPrefCloudStorage.cpp \
@ -207,7 +208,6 @@ HEADERS += \
core/extradata.h \ core/extradata.h \
core/git-access.h \ core/git-access.h \
core/globals.h \ core/globals.h \
core/owning_ptrs.h \
core/pref.h \ core/pref.h \
core/profile.h \ core/profile.h \
core/qthelper.h \ core/qthelper.h \
@ -217,9 +217,9 @@ HEADERS += \
core/units.h \ core/units.h \
core/version.h \ core/version.h \
core/picture.h \ core/picture.h \
core/pictureobj.h \
core/planner.h \ core/planner.h \
core/divesite.h \ core/divesite.h \
core/divesitetable.h \
core/checkcloudconnection.h \ core/checkcloudconnection.h \
core/cochran.h \ core/cochran.h \
core/color.h \ core/color.h \
@ -229,6 +229,7 @@ HEADERS += \
core/divefilter.h \ core/divefilter.h \
core/filterconstraint.h \ core/filterconstraint.h \
core/filterpreset.h \ core/filterpreset.h \
core/filterpresettable.h \
core/divelist.h \ core/divelist.h \
core/divelog.h \ core/divelog.h \
core/divelogexportlogic.h \ core/divelogexportlogic.h \
@ -249,6 +250,8 @@ HEADERS += \
core/subsurfacestartup.h \ core/subsurfacestartup.h \
core/subsurfacesysinfo.h \ core/subsurfacesysinfo.h \
core/taxonomy.h \ core/taxonomy.h \
core/trip.h \
core/triptable.h \
core/uemis.h \ core/uemis.h \
core/webservice.h \ core/webservice.h \
core/windowtitleupdate.h \ core/windowtitleupdate.h \

View file

@ -11,7 +11,7 @@ buildscript {
} }
dependencies { 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 { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.3' implementation 'com.github.mik3y:usb-serial-for-android:v3.8.0'
implementation 'com.android.support:support-v4:25.3.1' implementation 'com.android.support:support-v4:28.0.0'
} }
android { android {

View file

@ -13,6 +13,7 @@
#include "core/divesite.h" #include "core/divesite.h"
#include "core/picture.h" #include "core/picture.h"
#include "core/pref.h" #include "core/pref.h"
#include "core/range.h"
#include "core/sample.h" #include "core/sample.h"
#include "core/selection.h" #include "core/selection.h"
#include "core/taxonomy.h" #include "core/taxonomy.h"
@ -40,12 +41,12 @@ static constexpr int profileScale = 4;
static constexpr int profileWidth = 800 * profileScale; static constexpr int profileWidth = 800 * profileScale;
static constexpr int profileHeight = 600 * 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); QImage image = QImage(QSize(profileWidth, profileHeight), QImage::Format_RGB32);
QPainter paint; QPainter paint;
paint.begin(&image); 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); image.save(filename);
} }
@ -56,17 +57,15 @@ static std::unique_ptr<ProfileScene> getPrintProfile()
void exportProfile(QString filename, bool selected_only, ExportCallback &cb) void exportProfile(QString filename, bool selected_only, ExportCallback &cb)
{ {
struct dive *dive;
int i;
int count = 0; int count = 0;
if (!filename.endsWith(".png", Qt::CaseInsensitive)) if (!filename.endsWith(".png", Qt::CaseInsensitive))
filename = filename.append(".png"); filename = filename.append(".png");
QFileInfo fi(filename); QFileInfo fi(filename);
int todo = selected_only ? amount_selected : divelog.dives->nr; int todo = selected_only ? amount_selected : static_cast<int>(divelog.dives.size());
int done = 0; int done = 0;
auto profile = getPrintProfile(); auto profile = getPrintProfile();
for_each_dive (i, dive) { for (auto &dive: divelog.dives) {
if (cb.canceled()) if (cb.canceled())
return; return;
if (selected_only && !dive->selected) if (selected_only && !dive->selected)
@ -74,7 +73,7 @@ void exportProfile(QString filename, bool selected_only, ExportCallback &cb)
cb.setProgress(done++ * 1000 / todo); cb.setProgress(done++ * 1000 / todo);
QString fn = count ? fi.path() + QDir::separator() + fi.completeBaseName().append(QString("-%1.").arg(count)) + fi.suffix() QString fn = count ? fi.path() + QDir::separator() + fi.completeBaseName().append(QString("-%1.").arg(count)) + fi.suffix()
: filename; : filename;
exportProfile(profile.get(), dive, fn); exportProfile(*profile, *dive, fn);
++count; ++count;
} }
} }
@ -83,14 +82,12 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
{ {
FILE *f; FILE *f;
QDir texdir = QFileInfo(filename).dir(); QDir texdir = QFileInfo(filename).dir();
struct dive *dive;
const struct units *units = get_units(); const struct units *units = get_units();
const char *unit; const char *unit;
const char *ssrf; const char *ssrf;
int i;
bool need_pagebreak = false; bool need_pagebreak = false;
struct membufferpp buf; membuffer buf;
if (plain) { if (plain) {
ssrf = ""; 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"); put_format(&buf, "\n%%%%%%%%%% Begin Dive Data: %%%%%%%%%%\n");
int todo = selected_only ? amount_selected : divelog.dives->nr; int todo = selected_only ? amount_selected : static_cast<int>(divelog.dives.size());
int done = 0; int done = 0;
auto profile = getPrintProfile(); auto profile = getPrintProfile();
for_each_dive (i, dive) { for (auto &dive: divelog.dives) {
if (cb.canceled()) if (cb.canceled())
return; return;
if (selected_only && !dive->selected) if (selected_only && !dive->selected)
continue; continue;
cb.setProgress(done++ * 1000 / todo); 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; struct tm tm;
utc_mkdate(dive->when, &tm); utc_mkdate(dive->when, &tm);
const char *country = NULL; std::string country;
dive_site *site = dive->dive_site; dive_site *site = dive->dive_site;
if (site) if (site)
country = taxonomy_get_country(&site->taxonomy); country = taxonomy_get_country(site->taxonomy);
pressure_t delta_p = {.mbar = 0}; pressure_t delta_p;
QString star = "*"; QString star = "*";
QString viz = star.repeated(dive->visibility); QString viz = star.repeated(dive->visibility);
QString rating = star.repeated(dive->rating); QString rating = star.repeated(dive->rating);
int i;
int qty_cyl;
int qty_weight;
double total_weight;
if (need_pagebreak) { if (need_pagebreak) {
if (plain) if (plain)
put_format(&buf, "\\vfill\\eject\n"); 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\\%shour{%02u}\n", ssrf, tm.tm_hour);
put_format(&buf, "\\def\\%sminute{%02u}\n", ssrf, tm.tm_min); 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\\%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\\%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\\%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"); 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\\%scomputer{%s}\n", ssrf, dive->dcs[0].model.c_str());
put_format(&buf, "\\def\\%scountry{%s}\n", ssrf, country ?: ""); 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, "\\def\\%stime{%u:%02u}\n", ssrf, FRACTION_TUPLE(dive->duration.seconds, 60));
put_format(&buf, "\n%% Dive Profile Details:\n"); 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->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); 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\\%stype{%s}\n", ssrf, tags.c_str());
put_format(&buf, "\\def\\%sviz{%s}\n", ssrf, qPrintable(viz)); put_format(&buf, "\\def\\%sviz{%s}\n", ssrf, qPrintable(viz));
put_format(&buf, "\\def\\%srating{%s}\n", ssrf, qPrintable(rating)); 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\\%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\\%sprofilename{profile%d}\n", ssrf, dive->number);
put_format(&buf, "\\def\\%scomment{%s}\n", ssrf, dive->notes ? dive->notes : ""); put_format(&buf, "\\def\\%scomment{%s}\n", ssrf, dive->notes.c_str());
put_format(&buf, "\\def\\%sbuddy{%s}\n", ssrf, dive->buddy ? dive->buddy : ""); put_format(&buf, "\\def\\%sbuddy{%s}\n", ssrf, dive->buddy.c_str());
put_format(&buf, "\\def\\%sdivemaster{%s}\n", ssrf, dive->diveguide ? dive->diveguide : ""); put_format(&buf, "\\def\\%sdivemaster{%s}\n", ssrf, dive->diveguide.c_str());
put_format(&buf, "\\def\\%ssuit{%s}\n", ssrf, dive->suit ? dive->suit : ""); put_format(&buf, "\\def\\%ssuit{%s}\n", ssrf, dive->suit.c_str());
// Print cylinder data // Print cylinder data
put_format(&buf, "\n%% Gas use information:\n"); put_format(&buf, "\n%% Gas use information:\n");
qty_cyl = 0; int qty_cyl = 0;
for (i = 0; i < dive->cylinders.nr; i++){ for (auto [i, cyl]: enumerated_range(dive->cylinders)) {
const cylinder_t &cyl = *get_cylinder(dive, i); if (dive->is_cylinder_used(i) || (prefs.include_unused_tanks && !cyl.type.description.empty())){
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.c_str());
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, cyl.gasmix.name().c_str());
put_format(&buf, "\\def\\%scyl%cgasname{%s}\n", ssrf, 'a' + i, gasname(cyl.gasmix));
put_format(&buf, "\\def\\%scyl%cmixO2{%.1f\\%%}\n", ssrf, 'a' + i, get_o2(cyl.gasmix)/10.0); 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%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))); 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%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); 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; 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. //Code block prints all weights listed in dive.
put_format(&buf, "\n%% Weighting information:\n"); put_format(&buf, "\n%% Weighting information:\n");
qty_weight = 0; int qty_weight = 0;
total_weight = 0; double total_weight = 0;
for (i = 0; i < dive->weightsystems.nr; i++) { for (auto [i, w]: enumerated_range(dive->weightsystems)) {
weightsystem_t w = dive->weightsystems.weightsystems[i]; put_format(&buf, "\\def\\%sweight%ctype{%s}\n", ssrf, 'a' + i, w.description.c_str());
put_format(&buf, "\\def\\%sweight%ctype{%s}\n", ssrf, 'a' + i, w.description);
put_format(&buf, "\\def\\%sweight%camt{%.3f\\%sweightunit}\n", ssrf, 'a' + i, get_weight_units(w.weight.grams, NULL, &unit), ssrf); put_format(&buf, "\\def\\%sweight%camt{%.3f\\%sweightunit}\n", ssrf, 'a' + i, get_weight_units(w.weight.grams, NULL, &unit), ssrf);
qty_weight += 1; qty_weight += 1;
total_weight += get_weight_units(w.weight.grams, NULL, &unit); 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 // Legacy fields
put_format(&buf, "\\def\\%sspot{}\n", ssrf); put_format(&buf, "\\def\\%sspot{}\n", ssrf);
put_format(&buf, "\\def\\%sentrance{}\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); 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); 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) void export_depths(const char *filename, bool selected_only)
{ {
FILE *f; FILE *f;
struct dive *dive;
depth_t depth;
int i;
const char *unit = NULL; const char *unit = NULL;
struct membufferpp buf; membuffer buf;
for_each_dive (i, dive) { for (auto &dive: divelog.dives) {
if (selected_only && !dive->selected) if (selected_only && !dive->selected)
continue; continue;
FOR_EACH_PICTURE (dive) { for (auto &picture: dive->pictures) {
int n = dive->dc.samples; depth_t depth;
struct sample *s = dive->dc.sample; for (auto &s: dive->dcs[0].samples) {
depth.mm = 0; if ((int32_t)s.time.seconds > picture.offset.seconds)
while (--n >= 0 && (int32_t)s->time.seconds <= picture->offset.seconds) { break;
depth.mm = s->depth.mm; depth = s.depth;
s++;
} }
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); put_format(&buf, "%s\n", unit);
} }
} }
@ -318,28 +304,23 @@ std::vector<const dive_site *> getDiveSitesToExport(bool selectedOnly)
if (selectedOnly && DiveFilter::instance()->diveSiteMode()) { if (selectedOnly && DiveFilter::instance()->diveSiteMode()) {
// Special case in dive site mode: export all selected dive sites, // Special case in dive site mode: export all selected dive sites,
// not the dive sites of selected dives. // not the dive sites of selected dives.
QVector<dive_site *> sites = DiveFilter::instance()->filteredDiveSites(); for (auto ds: DiveFilter::instance()->filteredDiveSites())
res.reserve(sites.size());
for (const dive_site *ds: sites)
res.push_back(ds); res.push_back(ds);
return res; return res;
} }
res.reserve(divelog.sites->nr); res.reserve(divelog.sites.size());
for (int i = 0; i < divelog.sites->nr; i++) { for (const auto &ds: divelog.sites) {
struct dive_site *ds = get_dive_site(i, divelog.sites); if (ds->is_empty())
if (dive_site_is_empty(ds))
continue; continue;
if (selectedOnly && !is_dive_site_selected(ds)) if (selectedOnly && !ds->is_selected())
continue; continue;
res.push_back(ds); res.push_back(ds.get());
} }
#else #else
/* walk the dive site list */ /* walk the dive site list */
int i; for (const auto &ds: divelog.sites)
const struct dive_site *ds; res.push_back(ds.get());
for_each_dive_site (i, ds, divelog.sites)
res.push_back(get_dive_site(i, divelog.sites));
#endif #endif
return res; return res;
} }

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "qt-models/diveimportedmodel.h" #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 diveImportedModel;
DiveImportedModel::connect(&diveImportedModel, &DiveImportedModel::downloadFinished, [] { 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(); auto data = diveImportedModel.thread.data();
data->setVendor(vendor); data->setVendor(QString::fromStdString(vendor));
data->setProduct(product); data->setProduct(QString::fromStdString(product));
data->setBluetoothMode(false); data->setBluetoothMode(false);
if (data->vendor() == "Uemis") { if (data->vendor() == "Uemis") {
QString devname(device); QString devname = QString::fromStdString(device);
int colon = devname.indexOf(QStringLiteral(":\\ (UEMISSDA)")); int colon = devname.indexOf(QStringLiteral(":\\ (UEMISSDA)"));
if (colon >= 0) { if (colon >= 0) {
devname.truncate(colon + 2); devname.truncate(colon + 2);
@ -22,7 +22,7 @@ void cliDownloader(const char *vendor, const char *product, const char *device)
} }
data->setDevName(devname); data->setDevName(devname);
} else { } else {
data->setDevName(device); data->setDevName(QString::fromStdString(device));
} }
// some assumptions - should all be configurable // some assumptions - should all be configurable

View file

@ -1,5 +1,5 @@
MACRO(pkg_config_library LIBNAME pcfile option) MACRO(pkg_config_library LIBNAME pcfile )
pkg_check_modules(${LIBNAME} ${option} ${pcfile}) pkg_check_modules(${LIBNAME} ${ARGN} ${pcfile})
include_directories(${${LIBNAME}_INCLUDE_DIRS}) include_directories(${${LIBNAME}_INCLUDE_DIRS})
link_directories(${${LIBNAME}_LIBRARY_DIRS}) link_directories(${${LIBNAME}_LIBRARY_DIRS})
add_definitions(${${LIBNAME}_CFLAGS_OTHER}) add_definitions(${${LIBNAME}_CFLAGS_OTHER})

View file

@ -13,9 +13,9 @@
namespace Command { namespace Command {
// Dive-list related commands // Dive-list related commands
void addDive(dive *d, bool autogroup, bool newNumber) void addDive(std::unique_ptr<dive> d, bool autogroup, bool newNumber)
{ {
execute(new AddDive(d, autogroup, newNumber)); execute(new AddDive(std::move(d), autogroup, newNumber));
} }
void importDives(struct divelog *log, int flags, const QString &source) void importDives(struct divelog *log, int flags, const QString &source)
@ -134,9 +134,9 @@ void addDiveSite(const QString &name)
execute(new AddDiveSite(name)); execute(new AddDiveSite(name));
} }
void importDiveSites(struct dive_site_table *sites, const QString &source) void importDiveSites(dive_site_table sites, const QString &source)
{ {
execute(new ImportDiveSites(sites, source)); execute(new ImportDiveSites(std::move(sites), source));
} }
void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites) void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites)
@ -267,9 +267,9 @@ int editDiveGuide(const QStringList &newList, bool currentDiveOnly)
return execute_edit(new EditDiveGuide(newList, currentDiveOnly)); 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) void replanDive(dive *d)
@ -294,7 +294,7 @@ int removeWeight(int index, bool currentDiveOnly)
int editWeight(int index, weightsystem_t ws, 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) 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) 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) 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)); 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) void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank)

View file

@ -4,7 +4,7 @@
#include "core/divelog.h" #include "core/divelog.h"
#include "core/equipment.h" #include "core/equipment.h"
#include "core/pictureobj.h" #include "core/picture.h"
#include "core/taxonomy.h" #include "core/taxonomy.h"
#include <QVector> #include <QVector>
#include <QAction> #include <QAction>
@ -13,6 +13,7 @@
struct divecomputer; struct divecomputer;
struct divelog; struct divelog;
struct dive_components; struct dive_components;
struct dive_paste_data;
struct dive_site; struct dive_site;
struct dive_trip; struct dive_trip;
struct event; 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)! // 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 // If newNumber is true, the dive is assigned a new number, depending on the
// insertion position. // insertion position.
void addDive(dive *d, bool autogroup, bool newNumber); void addDive(std::unique_ptr<dive> d, bool autogroup, bool newNumber);
void importDives(struct divelog *log, int flags, const QString &source); // The tables are consumed! void importDives(struct divelog *log, int flags, const QString &source); // The tables are consumed!
void deleteDive(const QVector<struct dive*> &divesToDelete); void deleteDive(const QVector<struct dive*> &divesToDelete);
void shiftTime(const std::vector<dive *> &changedDives, int amount); void shiftTime(const std::vector<dive *> &changedDives, int amount);
@ -68,7 +69,7 @@ void editDiveSiteCountry(dive_site *ds, const QString &value);
void editDiveSiteLocation(dive_site *ds, location_t value); void 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 editDiveSiteTaxonomy(dive_site *ds, taxonomy_data &value); // value is consumed (i.e. will be erased after call)!
void addDiveSite(const QString &name); void addDiveSite(const QString &name);
void importDiveSites(struct dive_site_table *sites, const QString &source); void importDiveSites(dive_site_table sites, const QString &source); // takes ownership of dive site table
void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites); void mergeDiveSites(dive_site *ds, const QVector<dive_site *> &sites);
void purgeUnusedDiveSites(); void purgeUnusedDiveSites();
@ -95,7 +96,7 @@ int editDiveSiteNew(const QString &newName, bool currentDiveOnly);
int editTags(const QStringList &newList, bool currentDiveOnly); int editTags(const QStringList &newList, bool currentDiveOnly);
int editBuddies(const QStringList &newList, bool currentDiveOnly); int editBuddies(const QStringList &newList, bool currentDiveOnly);
int editDiveGuide(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 { enum class EditProfileType {
ADD, ADD,
REMOVE, REMOVE,
@ -132,8 +133,8 @@ void editTripNotes(dive_trip *trip, const QString &s);
void addEventBookmark(struct dive *d, int dcNr, int seconds); void addEventBookmark(struct dive *d, int dcNr, int seconds);
void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode); void addEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode);
void addEventSetpointChange(struct dive *d, int dcNr, int seconds, pressure_t pO2); 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 renameEvent(struct dive *d, int dcNr, int idx, std::string name);
void removeEvent(struct dive *d, int dcNr, struct event *ev); void removeEvent(struct dive *d, int dcNr, int idx);
void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank); void addGasSwitch(struct dive *d, int dcNr, int seconds, int tank);
// 7) Picture (media) commands // 7) Picture (media) commands
@ -144,7 +145,7 @@ struct PictureListForDeletion {
}; };
struct PictureListForAddition { struct PictureListForAddition {
dive *d; dive *d;
std::vector<PictureObj> pics; std::vector<picture> pics;
}; };
void setPictureOffset(dive *d, const QString &filename, offset_t offset); void setPictureOffset(dive *d, const QString &filename, offset_t offset);
void removePictures(const std::vector<PictureListForDeletion> &pictures); void removePictures(const std::vector<PictureListForDeletion> &pictures);

View file

@ -66,7 +66,7 @@ QString diveNumberOrDate(struct dive *d)
QString getListOfDives(const std::vector<struct dive*> &dives) QString getListOfDives(const std::vector<struct dive*> &dives)
{ {
QString listOfDives; QString listOfDives;
if ((int)dives.size() == divelog.dives->nr) if (dives.size() == divelog.dives.size())
return Base::tr("all dives"); return Base::tr("all dives");
int i = 0; int i = 0;
for (dive *d: dives) { for (dive *d: dives) {

View file

@ -7,7 +7,6 @@
#include "core/divesite.h" #include "core/divesite.h"
#include "core/trip.h" #include "core/trip.h"
#include "core/dive.h" #include "core/dive.h"
#include "core/owning_ptrs.h"
#include <QUndoCommand> #include <QUndoCommand>
#include <QCoreApplication> // For Q_DECLARE_TR_FUNCTIONS #include <QCoreApplication> // For Q_DECLARE_TR_FUNCTIONS
@ -106,9 +105,8 @@
// 1) Dive 2 was deleted with the "add dive 2" command, because that was the owner. // 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. // 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 // To take ownership of dives/trips, std::unique_ptr<>s are used.
// are simply derived from std::unique_ptr and therefore use well-established semantics. // Expressed in C-terms: std::unique_ptr<T> is the same as T* with the following
// Expressed in C-terms: std::unique_ptr<T> is exactly the same as T* with the following
// twists: // twists:
// 1) default-initialized to NULL. // 1) default-initialized to NULL.
// 2) if it goes out of scope (local scope or containing object destroyed), it does: // 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. // move-semantics and Qt's containers are incompatible, owing to COW semantics.
// //
// Usage: // Usage:
// OwningDivePtr dPtr; // Initialize to null-state: not owning any dive. // std::unique_ptr<dive> dPtr; // Initialize to null-state: not owning any dive.
// OwningDivePtr dPtr(dive); // Take ownership of dive (which is of type struct dive *). // std::unique_ptr<dive> dPtr(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(). // // 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 = 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. // 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. // dPtr.reset(); // Delete currently owned dive and reset to null.
// dPtr2 = dPtr1; // Fails to compile. // dPtr2 = dPtr1; // Fails to compile.
// dPtr2 = std::move(dPtr1); // dPtr2 takes ownership, dPtr1 is reset to null. // dPtr2 = std::move(dPtr1); // dPtr2 takes ownership, dPtr1 is reset to null.
// OwningDivePtr fun(); // std::unique_ptr<dive> fun();
// dPtr1 = fun(); // Compiles. Simply put: the compiler knows that the result of fun() will // dPtr1 = fun(); // Compiles. Simply put: the compiler knows that the result of fun() will
// // be trashed and therefore can be moved-from. // // be trashed and therefore can be moved-from.
// std::vector<OwningDivePtr> v: // Define an empty vector of owning pointers. // std::vector<std::unique_ptr<dive>> v: // Define an empty vector of owning pointers.
// v.emplace_back(dive); // Take ownership of dive and add at end of vector // 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(). // // 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, // v.clear(v); // Reset the vector to zero length. If the elements weren't release()d,
@ -151,6 +149,12 @@ QVector<T> stdToQt(const std::vector<T> &v)
#endif #endif
} }
template<typename T>
std::vector<T> qtToStd(const QVector<T> &v)
{
return std::vector<T>(v.begin(), v.end());
}
// We put everything in a namespace, so that we can shorten names without polluting the global namespace // We put everything in a namespace, so that we can shorten names without polluting the global namespace
namespace Command { namespace Command {

View file

@ -13,20 +13,19 @@ EditDeviceNickname::EditDeviceNickname(const struct divecomputer *dc, const QStr
if (index == -1) if (index == -1)
return; 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() bool EditDeviceNickname::workToBeDone()
{ {
return get_device(divelog.devices, index) != nullptr; return index >= 0;
} }
void EditDeviceNickname::redo() void EditDeviceNickname::redo()
{ {
device *dev = get_device_mutable(divelog.devices, index); if (index < 0 || static_cast<size_t>(index) >= divelog.devices.size())
if (!dev)
return; return;
std::swap(dev->nickName, nickname); std::swap(divelog.devices[index].nickName, nickname);
emit diveListNotifier.deviceEdited(); emit diveListNotifier.deviceEdited();
} }

View file

@ -1,31 +1,31 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "command_divelist.h" #include "command_divelist.h"
#include "core/divefilter.h"
#include "core/divelist.h" #include "core/divelist.h"
#include "core/divelog.h" #include "core/divelog.h"
#include "core/qthelper.h" #include "core/qthelper.h"
#include "core/selection.h" #include "core/selection.h"
#include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-qt/divelistnotifier.h"
#include "qt-models/filtermodels.h" #include "qt-models/filtermodels.h"
#include "core/divefilter.h" #include "qt-models/divelocationmodel.h"
#include <array>
namespace Command { namespace Command {
// Helper function that takes care to unselect trips that are removed from the backend // Helper function that takes care to unselect trips that are removed from the backend
static void remove_trip_from_backend(dive_trip *trip) static std::unique_ptr<dive_trip> remove_trip_from_backend(dive_trip *trip)
{ {
if (trip->selected) if (trip->selected)
deselect_trip(trip); 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. // 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. // 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 // 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! // set and that the trips are added before they are used!
DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector<OwningTripPtr> &tripsToAdd) DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector<std::unique_ptr<dive_trip>> &tripsToAdd)
{ {
// If the dive was the current dive, reset the current dive. The calling // If the dive was the current dive, reset the current dive. The calling
// command is responsible of finding a new dive. // command is responsible of finding a new dive.
@ -39,18 +39,19 @@ DiveToAdd DiveListBase::removeDive(struct dive *d, std::vector<OwningTripPtr> &t
if (d->dive_site) if (d->dive_site)
diveSiteCountChanged(d->dive_site); diveSiteCountChanged(d->dive_site);
res.site = unregister_dive_from_dive_site(d); res.site = unregister_dive_from_dive_site(d);
if (res.trip && res.trip->dives.nr == 0) { if (res.trip && res.trip->dives.empty()) {
remove_trip_from_backend(res.trip); // Remove trip from backend divelog.trips.sort(); // Removal of dives has changed order of trips! (TODO: remove this)
tripsToAdd.emplace_back(res.trip); // Take ownership of trip 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); size_t idx = divelog.dives.get_idx(d);
if (idx < 0) if (idx == std::string::npos)
qWarning("Deletion of unknown dive!"); qWarning("Deletion of unknown dive!");
DiveFilter::instance()->diveRemoved(d); 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; return res;
} }
@ -67,23 +68,12 @@ void DiveListBase::diveSiteCountChanged(struct dive_site *ds)
dive *DiveListBase::addDive(DiveToAdd &d) dive *DiveListBase::addDive(DiveToAdd &d)
{ {
if (d.trip) if (d.trip)
add_dive_to_trip(d.dive.get(), d.trip); d.trip->add_dive(d.dive.get());
if (d.site) { if (d.site) {
add_dive_to_dive_site(d.dive.get(), d.site); d.site->add_dive(d.dive.get());
diveSiteCountChanged(d.site); diveSiteCountChanged(d.site);
} }
dive *res = d.dive.release(); // Give up ownership of dive return divelog.dives.register_dive(std::move(d.dive)); // Transfer ownership to core and update fulltext index
// 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;
} }
// Some signals are sent in batches per trip. To avoid writing the same loop // Some signals are sent in batches per trip. To avoid writing the same loop
@ -99,7 +89,7 @@ void processByTrip(std::vector<std::pair<dive_trip *, dive *>> &dives, Function
// Sort lexicographically by trip then according to the dive_less_than() function. // Sort lexicographically by trip then according to the dive_less_than() function.
std::sort(dives.begin(), dives.end(), std::sort(dives.begin(), dives.end(),
[](const std::pair<dive_trip *, dive *> &e1, const std::pair<dive_trip *, dive *> &e2) [](const std::pair<dive_trip *, dive *> &e1, const std::pair<dive_trip *, dive *> &e2)
{ return e1.first == e2.first ? dive_less_than(e1.second, e2.second) : e1.first < e2.first; }); { return e1.first == e2.first ? dive_less_than(*e1.second, *e2.second) : e1.first < e2.first; });
// Then, process the dives in batches by trip // Then, process the dives in batches by trip
size_t i, j; // Begin and end of batch size_t i, j; // Begin and end of batch
@ -124,8 +114,8 @@ void processByTrip(std::vector<std::pair<dive_trip *, dive *>> &dives, Function
DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSitesToDelete) DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSitesToDelete)
{ {
std::vector<DiveToAdd> divesToAdd; std::vector<DiveToAdd> divesToAdd;
std::vector<OwningTripPtr> tripsToAdd; std::vector<std::unique_ptr<dive_trip>> tripsToAdd;
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
divesToAdd.reserve(divesAndSitesToDelete.dives.size()); divesToAdd.reserve(divesAndSitesToDelete.dives.size());
sitesToAdd.reserve(divesAndSitesToDelete.sites.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 // 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 // and the recipients assume that the dives are sorted the same way as they are
// in the core list. // 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) for (dive *d: divesAndSitesToDelete.dives)
divesToAdd.push_back(removeDive(d, tripsToAdd)); divesToAdd.push_back(removeDive(d, tripsToAdd));
divesAndSitesToDelete.dives.clear(); divesAndSitesToDelete.dives.clear();
for (dive_site *ds: divesAndSitesToDelete.sites) { for (dive_site *ds: divesAndSitesToDelete.sites) {
int idx = unregister_dive_site(ds); auto res = divelog.sites.pull(ds);
sitesToAdd.emplace_back(ds); sitesToAdd.push_back(std::move(res.ptr));
emit diveListNotifier.diveSiteDeleted(ds, idx); emit diveListNotifier.diveSiteDeleted(ds, res.idx);
} }
divesAndSitesToDelete.sites.clear(); divesAndSitesToDelete.sites.clear();
@ -159,7 +150,7 @@ DivesAndTripsToAdd DiveListBase::removeDives(DivesAndSitesToRemove &divesAndSite
processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) { processByTrip(dives, [&](dive_trip *trip, const QVector<dive *> &divesInTrip) {
// Check if this trip is supposed to be deleted, by checking if it was marked as "add it". // Check if this trip is supposed to be deleted, by checking if it was marked as "add it".
bool deleteTrip = trip && bool deleteTrip = trip &&
std::find_if(tripsToAdd.begin(), tripsToAdd.end(), [trip](const OwningTripPtr &ptr) std::find_if(tripsToAdd.begin(), tripsToAdd.end(), [trip](const std::unique_ptr<dive_trip> &ptr)
{ return ptr.get() == trip; }) != tripsToAdd.end(); { return ptr.get() == trip; }) != tripsToAdd.end();
emit diveListNotifier.divesDeleted(trip, deleteTrip, divesInTrip); emit diveListNotifier.divesDeleted(trip, deleteTrip, divesInTrip);
}); });
@ -188,7 +179,7 @@ DivesAndSitesToRemove DiveListBase::addDives(DivesAndTripsToAdd &toAdd)
// in the core list. // in the core list.
std::sort(toAdd.dives.begin(), toAdd.dives.end(), std::sort(toAdd.dives.begin(), toAdd.dives.end(),
[](const DiveToAdd &d, const DiveToAdd &d2) [](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 // Now, add the dives
// Note: the idiomatic STL-way would be std::transform, but let's use a loop since // 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 // Remember the pointers so that we can later check if a trip was newly added
std::vector<dive_trip *> addedTrips; std::vector<dive_trip *> addedTrips;
addedTrips.reserve(toAdd.trips.size()); addedTrips.reserve(toAdd.trips.size());
for (OwningTripPtr &trip: toAdd.trips) { for (std::unique_ptr<dive_trip> &trip: toAdd.trips) {
addedTrips.push_back(trip.get()); auto [t, idx] = divelog.trips.put(std::move(trip)); // Return ownership to backend
insert_trip(trip.release(), divelog.trips); // Return ownership to backend addedTrips.push_back(t);
} }
toAdd.trips.clear(); toAdd.trips.clear();
// Finally, add any necessary dive sites // Finally, add any necessary dive sites
for (OwningDiveSitePtr &ds: toAdd.sites) { for (std::unique_ptr<dive_site> &ds: toAdd.sites) {
sites.push_back(ds.get()); auto res = divelog.sites.register_site(std::move(ds));
int idx = register_dive_site(ds.release()); // Return ownership to backend sites.push_back(res.ptr);
emit diveListNotifier.diveSiteAdded(sites.back(), idx); emit diveListNotifier.diveSiteAdded(sites.back(), res.idx);
} }
toAdd.sites.clear(); toAdd.sites.clear();
@ -249,7 +240,7 @@ static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber)
continue; continue;
std::swap(d->number, pair.second); std::swap(d->number, pair.second);
dives.push_back(d); dives.push_back(d);
invalidate_dive_cache(d); d->invalidate_cache();
} }
// Send signals. // Send signals.
@ -260,27 +251,25 @@ static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber)
// passed-in structure. This means that calling the function twice on the same // 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 // 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. // core, an owning pointer to the removed trip is returned, otherwise a null pointer.
static OwningTripPtr moveDiveToTrip(DiveToTrip &diveToTrip) static std::unique_ptr<dive_trip> moveDiveToTrip(DiveToTrip &diveToTrip)
{ {
// Firstly, check if we move to the same trip and bail if this is a no-op. // Firstly, check if we move to the same trip and bail if this is a no-op.
if (diveToTrip.trip == diveToTrip.dive->divetrip) if (diveToTrip.trip == diveToTrip.dive->divetrip)
return {}; return {};
// Remove from old trip // Remove from old trip
OwningTripPtr res; std::unique_ptr<dive_trip> res;
// Remove dive from trip - if this is the last dive in the trip, remove the whole trip. // 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); dive_trip *trip = unregister_dive_from_trip(diveToTrip.dive);
if (trip && trip->dives.nr == 0) { if (trip && trip->dives.empty())
remove_trip_from_backend(trip); // Remove trip from backend res = remove_trip_from_backend(trip); // Remove trip from backend
res.reset(trip);
}
// Store old trip and get new trip we should associate this dive with // Store old trip and get new trip we should associate this dive with
std::swap(trip, diveToTrip.trip); std::swap(trip, diveToTrip.trip);
if (trip) if (trip)
add_dive_to_trip(diveToTrip.dive, trip); trip->add_dive(diveToTrip.dive);
invalidate_dive_cache(diveToTrip.dive); // Ensure that dive is written in git_save() diveToTrip.dive->invalidate_cache(); // Ensure that dive is written in git_save()
return res; return res;
} }
@ -298,15 +287,14 @@ static void moveDivesBetweenTrips(DivesToTrip &dives)
createdTrips.reserve(dives.tripsToAdd.size()); createdTrips.reserve(dives.tripsToAdd.size());
// First, bring back the trip(s) // First, bring back the trip(s)
for (OwningTripPtr &trip: dives.tripsToAdd) { for (std::unique_ptr<dive_trip> &trip: dives.tripsToAdd) {
dive_trip *t = trip.release(); // Give up ownership auto [t, idx] = divelog.trips.put(std::move(trip)); // Return ownership to backend
createdTrips.push_back(t); createdTrips.push_back(t);
insert_trip(t, divelog.trips); // Return ownership to backend
} }
dives.tripsToAdd.clear(); dives.tripsToAdd.clear();
for (DiveToTrip &dive: dives.divesToMove) { for (DiveToTrip &dive: dives.divesToMove) {
OwningTripPtr tripToAdd = moveDiveToTrip(dive); std::unique_ptr<dive_trip> tripToAdd = moveDiveToTrip(dive);
// register trips that we'll have to readd // register trips that we'll have to readd
if (tripToAdd) if (tripToAdd)
dives.tripsToAdd.push_back(std::move(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"? 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() && [from](const DiveMoved &entry) { return entry.from == from; }) == divesMoved.end() &&
std::find_if(dives.tripsToAdd.begin(), dives.tripsToAdd.end(), // Is "from" in tripsToAdd? std::find_if(dives.tripsToAdd.begin(), dives.tripsToAdd.end(), // Is "from" in tripsToAdd?
[from](const OwningTripPtr &trip) { return trip.get() == from; }) != dives.tripsToAdd.end(); [from](const std::unique_ptr<dive_trip> &trip) { return trip.get() == from; }) != dives.tripsToAdd.end();
// Check if the to-trip has to be created. For this purpose, we saved an array of trips to be created. // Check if the to-trip has to be created. For this purpose, we saved an array of trips to be created.
bool createTo = false; bool createTo = false;
if (to) { if (to) {
@ -399,43 +387,39 @@ void DiveListBase::redo()
finishWork(); finishWork();
} }
AddDive::AddDive(dive *d, bool autogroup, bool newNumber) AddDive::AddDive(std::unique_ptr<dive> d, bool autogroup, bool newNumber)
{ {
setText(Command::Base::tr("add dive")); 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 = 0_m;
d->maxdepth.mm = 0; d->dcs[0].maxdepth = 0_m;
d->dc.maxdepth.mm = 0; divelog.dives.fixup_dive(*d);
fixup_dive(d);
// this only matters if undoit were called before redoit // this only matters if undoit were called before redoit
currentDive = nullptr; currentDive = nullptr;
// Get an owning pointer to a moved dive. d->selected = false; // If we clone a planned dive, it might have been selected.
OwningDivePtr divePtr(move_dive(d)); // We have to clear the flag, as selections will be managed
divePtr->selected = false; // If we clone a planned dive, it might have been selected. // on dive-addition.
// 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. // If we alloc a new-trip for autogrouping, get an owning pointer to it.
OwningTripPtr allocTrip; std::unique_ptr<dive_trip> allocTrip;
dive_trip *trip = divePtr->divetrip; dive_trip *trip = d->divetrip;
dive_site *site = divePtr->dive_site; 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 // 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! // 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; d->divetrip = nullptr;
divePtr->dive_site = nullptr; d->dive_site = nullptr;
if (!trip && autogroup) { if (!trip && autogroup) {
bool alloc; auto [t, allocated] = get_trip_for_new_dive(divelog, d.get());
trip = get_trip_for_new_dive(divePtr.get(), &alloc); trip = t;
if (alloc) allocTrip = std::move(allocated);
allocTrip.reset(trip);
} }
int idx = dive_table_get_insertion_index(divelog.dives, divePtr.get()); int idx = divelog.dives.get_insertion_index(d.get());
if (newNumber) 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) if (allocTrip)
divesToAdd.trips.push_back(std::move(allocTrip)); divesToAdd.trips.push_back(std::move(allocTrip));
} }
@ -452,7 +436,7 @@ void AddDive::redoit()
currentDive = current_dive; currentDive = current_dive;
divesAndSitesToRemove = addDives(divesToAdd); 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 // Select the newly added dive
setSelection(divesAndSitesToRemove.dives, divesAndSitesToRemove.dives[0], -1); setSelection(divesAndSitesToRemove.dives, divesAndSitesToRemove.dives[0], -1);
@ -462,7 +446,7 @@ void AddDive::undoit()
{ {
// Simply remove the dive that was previously added... // Simply remove the dive that was previously added...
divesToAdd = removeDives(divesAndSitesToRemove); 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 // ...and restore the selection
setSelection(selection, currentDive, -1); setSelection(selection, currentDive, -1);
@ -470,33 +454,28 @@ void AddDive::undoit()
ImportDives::ImportDives(struct divelog *log, int flags, const QString &source) 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 // this only matters if undoit were called before redoit
currentDive = nullptr; currentDive = nullptr;
struct dive_table dives_to_add = empty_dive_table; auto [dives_to_add, dives_to_remove, trips_to_add, sites_to_add, devices_to_add] =
struct dive_table dives_to_remove = empty_dive_table; divelog.process_imported_dives(*log, flags);
struct trip_table trips_to_add = empty_trip_table;
struct dive_site_table sites_to_add = empty_dive_site_table; // Add devices to devicesToAddAndRemove structure
process_imported_dives(log, flags, devicesToAddAndRemove = std::move(devices_to_add);
&dives_to_add, &dives_to_remove, &trips_to_add,
&sites_to_add, &devicesToAddAndRemove);
// Add trips to the divesToAdd.trips structure // Add trips to the divesToAdd.trips structure
divesToAdd.trips.reserve(trips_to_add.nr); divesToAdd.trips.reserve(trips_to_add.size());
for (int i = 0; i < trips_to_add.nr; ++i) for (auto &trip: trips_to_add)
divesToAdd.trips.emplace_back(trips_to_add.trips[i]); divesToAdd.trips.push_back(std::move(trip));
// Add sites to the divesToAdd.sites structure // Add sites to the divesToAdd.sites structure
divesToAdd.sites.reserve(sites_to_add.nr); divesToAdd.sites = std::move(sites_to_add);
for (int i = 0; i < sites_to_add.nr; ++i)
divesToAdd.sites.emplace_back(sites_to_add.dive_sites[i]);
// Add dives to the divesToAdd.dives structure // Add dives to the divesToAdd.dives structure
divesToAdd.dives.reserve(dives_to_add.nr); divesToAdd.dives.reserve(dives_to_add.size());
for (int i = 0; i < dives_to_add.nr; ++i) { for (auto &divePtr: dives_to_add) {
OwningDivePtr divePtr(dives_to_add.dives[i]);
divePtr->selected = false; // See above in AddDive::AddDive() divePtr->selected = false; // See above in AddDive::AddDive()
dive_trip *trip = divePtr->divetrip; dive_trip *trip = divePtr->divetrip;
divePtr->divetrip = nullptr; // See above in AddDive::AddDive() 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 // Add dive to be deleted to the divesToRemove structure
divesAndSitesToRemove.dives.reserve(dives_to_remove.nr); divesAndSitesToRemove.dives = std::move(dives_to_remove);
for (int i = 0; i < dives_to_remove.nr; ++i)
divesAndSitesToRemove.dives.push_back(dives_to_remove.dives[i]);
// When encountering filter presets with equal names, check whether they are // When encountering filter presets with equal names, check whether they are
// the same. If they are, ignore them. // 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; 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; }); [&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; continue;
filterPresetsToAdd.emplace_back(preset.name, preset.data); 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() bool ImportDives::workToBeDone()
@ -551,12 +523,12 @@ void ImportDives::redoit()
divesAndSitesToRemove = std::move(divesAndSitesToRemoveNew); divesAndSitesToRemove = std::move(divesAndSitesToRemoveNew);
// Add devices // Add devices
for (const device &dev: devicesToAddAndRemove.devices) for (const device &dev: devicesToAddAndRemove)
add_to_device_table(divelog.devices, &dev); add_to_device_table(divelog.devices, dev);
// Add new filter presets // Add new filter presets
for (auto &it: filterPresetsToAdd) { 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()); emit diveListNotifier.filterPresetAdded(filterPresetsToRemove.back());
} }
filterPresetsToAdd.clear(); filterPresetsToAdd.clear();
@ -579,15 +551,16 @@ void ImportDives::undoit()
setSelection(selection, currentDive, -1); setSelection(selection, currentDive, -1);
// Remove devices // Remove devices
for (const device &dev: devicesToAddAndRemove.devices) for (const device &dev: devicesToAddAndRemove)
remove_device(divelog.devices, &dev); remove_device(divelog.devices, dev);
// Remove filter presets. Do this in reverse order. // Remove filter presets. Do this in reverse order.
for (auto it = filterPresetsToRemove.rbegin(); it != filterPresetsToRemove.rend(); ++it) { for (auto it = filterPresetsToRemove.rbegin(); it != filterPresetsToRemove.rend(); ++it) {
int index = *it; int index = *it;
std::string oldName = filter_preset_name(index); const filter_preset &preset = divelog.filter_presets[index];
FilterData oldData = filter_preset_get(index); std::string oldName = preset.name;
filter_preset_delete(index); FilterData oldData = preset.data;
divelog.filter_presets.remove(index);
emit diveListNotifier.filterPresetRemoved(index); emit diveListNotifier.filterPresetRemoved(index);
filterPresetsToAdd.emplace_back(oldName, oldData); filterPresetsToAdd.emplace_back(oldName, oldData);
} }
@ -609,7 +582,7 @@ bool DeleteDive::workToBeDone()
void DeleteDive::undoit() void DeleteDive::undoit()
{ {
divesToDelete = addDives(divesToAdd); 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 // Select all re-added dives and make the first one current
dive *currentDive = !divesToDelete.dives.empty() ? divesToDelete.dives[0] : nullptr; dive *currentDive = !divesToDelete.dives.empty() ? divesToDelete.dives[0] : nullptr;
@ -619,13 +592,13 @@ void DeleteDive::undoit()
void DeleteDive::redoit() void DeleteDive::redoit()
{ {
divesToAdd = removeDives(divesToDelete); 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 // Deselect all dives and select dive that was close to the first deleted dive
dive *newCurrent = nullptr; dive *newCurrent = nullptr;
if (!divesToAdd.dives.empty()) { if (!divesToAdd.dives.empty()) {
timestamp_t when = divesToAdd.dives[0].dive->when; 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); select_single_dive(newCurrent);
} }
@ -647,10 +620,10 @@ void ShiftTime::redoit()
} }
// Changing times may have unsorted the dive and trip tables // Changing times may have unsorted the dive and trip tables
sort_dive_table(divelog.dives); divelog.dives.sort();
sort_trip_table(divelog.trips); divelog.trips.sort();
for (dive_trip *trip: trips) for (dive_trip *trip: trips)
sort_dive_table(&trip->dives); // Keep the trip-table in order trip->sort_dives();
// Send signals // Send signals
QVector<dive *> dives = stdToQt<dive *>(diveList); QVector<dive *> dives = stdToQt<dive *>(diveList);
@ -716,7 +689,7 @@ bool TripBase::workToBeDone()
void TripBase::redoit() void TripBase::redoit()
{ {
moveDivesBetweenTrips(divesToMove); 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 // Select the moved dives
std::vector<dive *> dives; std::vector<dive *> dives;
@ -754,11 +727,9 @@ RemoveAutogenTrips::RemoveAutogenTrips()
{ {
setText(Command::Base::tr("remove autogenerated trips")); setText(Command::Base::tr("remove autogenerated trips"));
// TODO: don't touch core-innards directly // TODO: don't touch core-innards directly
int i; for (auto &d: divelog.dives) {
struct dive *dive; if (d->divetrip && d->divetrip->autogen)
for_each_dive(i, dive) { divesToMove.divesToMove.push_back( {d.get(), nullptr} );
if (dive->divetrip && dive->divetrip->autogen)
divesToMove.divesToMove.push_back( {dive, nullptr} );
} }
} }
@ -776,25 +747,22 @@ CreateTrip::CreateTrip(const QVector<dive *> &divesToAddIn)
if (divesToAddIn.isEmpty()) if (divesToAddIn.isEmpty())
return; return;
dive_trip *trip = create_trip_from_dive(divesToAddIn[0]); auto trip = create_trip_from_dive(divesToAddIn[0]);
divesToMove.tripsToAdd.emplace_back(trip);
for (dive *d: divesToAddIn) 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() AutogroupDives::AutogroupDives()
{ {
setText(Command::Base::tr("autogroup dives")); setText(Command::Base::tr("autogroup dives"));
dive_trip *trip; for (auto &entry: get_dives_to_autogroup(divelog.dives)) {
bool alloc;
int from, to;
for(int i = 0; (trip = get_dives_to_autogroup(divelog.dives, i, &from, &to, &alloc)) != NULL; i = to) {
// If this is an allocated trip, take ownership // If this is an allocated trip, take ownership
if (alloc) if (entry.created_trip)
divesToMove.tripsToAdd.emplace_back(trip); divesToMove.tripsToAdd.push_back(std::move(entry.created_trip));
for (int j = from; j < to; ++j) for (auto it = divelog.dives.begin() + entry.from; it != divelog.dives.begin() + entry.to; ++it)
divesToMove.divesToMove.push_back( { get_dive(j), trip } ); divesToMove.divesToMove.push_back( { it->get(), entry.trip } );
} }
} }
@ -802,18 +770,15 @@ MergeTrips::MergeTrips(dive_trip *trip1, dive_trip *trip2)
{ {
if (trip1 == trip2) if (trip1 == trip2)
return; return;
dive_trip *newTrip = combine_trips(trip1, trip2); std::unique_ptr<dive_trip> newTrip = combine_trips(trip1, trip2);
divesToMove.tripsToAdd.emplace_back(newTrip); for (dive *d: trip1->dives)
for (int i = 0; i < trip1->dives.nr; ++i) divesToMove.divesToMove.push_back( { d, newTrip.get() } );
divesToMove.divesToMove.push_back( { trip1->dives.dives[i], newTrip } ); for (dive *d: trip2->dives)
for (int i = 0; i < trip2->dives.nr; ++i) divesToMove.divesToMove.push_back( { d, newTrip.get() } );
divesToMove.divesToMove.push_back( { trip2->dives.dives[i], newTrip } ); divesToMove.tripsToAdd.push_back(std::move(newTrip));
} }
// std::array<dive *, 2> is the same as struct *dive[2], with the fundamental SplitDivesBase::SplitDivesBase(dive *d, std::array<std::unique_ptr<dive>, 2> newDives)
// difference that it can be returned from functions. Thus, this constructor
// can be chained with the result of a function.
SplitDivesBase::SplitDivesBase(dive *d, std::array<dive *, 2> newDives)
{ {
// If either of the new dives is null, simply return. Empty arrays indicate that nothing is to be done. // If either of the new dives is null, simply return. Empty arrays indicate that nothing is to be done.
if (!newDives[0] || !newDives[1]) if (!newDives[0] || !newDives[1])
@ -833,10 +798,10 @@ SplitDivesBase::SplitDivesBase(dive *d, std::array<dive *, 2> newDives)
diveToSplit.dives.push_back(d); diveToSplit.dives.push_back(d);
splitDives.dives.resize(2); 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].trip = d->divetrip;
splitDives.dives[0].site = d->dive_site; 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].trip = d->divetrip;
splitDives.dives[1].site = d->dive_site; splitDives.dives[1].site = d->dive_site;
} }
@ -865,16 +830,13 @@ void SplitDivesBase::undoit()
setSelection(diveToSplit.dives, diveToSplit.dives[0], -1); setSelection(diveToSplit.dives, diveToSplit.dives[0], -1);
} }
static std::array<dive *, 2> doSplitDives(const dive *d, duration_t time) static std::array<std::unique_ptr<dive>, 2> doSplitDives(const dive *d, duration_t time)
{ {
// Split the dive // Split the dive
dive *new1, *new2;
if (time.seconds < 0) if (time.seconds < 0)
split_dive(d, &new1, &new2); return divelog.dives.split_dive(*d);
else else
split_dive_at_time(d, time, &new1, &new2); return divelog.dives.split_dive_at_time(*d, time);
return { new1, new2 };
} }
SplitDives::SplitDives(dive *d, duration_t time) : SplitDivesBase(d, doSplitDives(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")); setText(Command::Base::tr("split dive"));
} }
static std::array<dive *, 2> splitDiveComputer(const dive *d, int dc_num) SplitDiveComputer::SplitDiveComputer(dive *d, int dc_num) :
{ SplitDivesBase(d, divelog.dives.split_divecomputer(*d, 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))
{ {
setText(Command::Base::tr("split dive computer")); setText(Command::Base::tr("split dive computer"));
} }
DiveComputerBase::DiveComputerBase(dive *old_dive, dive *new_dive, int dc_nr_before, int dc_nr_after) : DiveComputerBase::DiveComputerBase(dive *old_dive, std::unique_ptr<dive> new_dive, int dc_nr_before, int dc_nr_after) :
dc_nr_before(dc_nr_before), dc_nr_before(dc_nr_before),
dc_nr_after(dc_nr_after) 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; new_dive->dive_site = nullptr;
diveToAdd.dives.resize(1); 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].trip = old_dive->divetrip;
diveToAdd.dives[0].site = old_dive->dive_site; diveToAdd.dives[0].site = old_dive->dive_site;
} }
@ -950,49 +900,30 @@ void DiveComputerBase::undoit()
} }
MoveDiveComputerToFront::MoveDiveComputerToFront(dive *d, int dc_num) 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")); setText(Command::Base::tr("move dive computer to front"));
} }
DeleteDiveComputer::DeleteDiveComputer(dive *d, int dc_num) 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")); setText(Command::Base::tr("delete dive computer"));
} }
MergeDives::MergeDives(const QVector <dive *> &dives) MergeDives::MergeDives(const QVector <dive *> &dives) : site(nullptr)
{ {
setText(Command::Base::tr("merge dive")); 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. // The caller should have made sure that this doesn't happen.
if (dives.count() < 2) { if (dives.count() < 2) {
qWarning("Merging less than two dives"); qWarning("Merging less than two dives");
return; return;
} }
dive_trip *preferred_trip; auto [d, set_location] = divelog.dives.merge_dives(qtToStd(dives));
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;
// The merged dive gets the number of the first dive with a non-zero number // The merged dive gets the number of the first dive with a non-zero number
for (const dive *dive: dives) { for (const dive *dive: dives) {
@ -1005,9 +936,9 @@ MergeDives::MergeDives(const QVector <dive *> &dives)
// We will only renumber the remaining dives if the joined dives are consecutive. // 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 // Otherwise all bets are off concerning what the user wanted and doing nothing seems
// like the best option. // like the best option.
int idx = get_divenr(dives[0]); size_t idx = divelog.dives.get_idx(dives[0]);
int num = dives.count(); size_t num = dives.count();
if (idx < 0 || idx + num > divelog.dives->nr) { if (idx == std::string::npos) {
// It was the callers responsibility to pass only known dives. // It was the callers responsibility to pass only known dives.
// Something is seriously wrong - give up. // Something is seriously wrong - give up.
qWarning("Merging unknown dives"); qWarning("Merging unknown dives");
@ -1015,7 +946,8 @@ MergeDives::MergeDives(const QVector <dive *> &dives)
} }
// std::equal compares two ranges. The parameters are (begin_range1, end_range1, begin_range2). // 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. // Here, we can compare C-arrays, because QVector guarantees contiguous storage.
if (std::equal(&dives[0], &dives[0] + num, &divelog.dives->dives[idx]) && if (std::equal(&dives[0], &dives[0] + num, divelog.dives.begin() + idx, [](dive *d1,
const std::unique_ptr<dive> &d2) { return d1 == d2.get(); }) &&
dives[0]->number && dives.last()->number && dives[0]->number < dives.last()->number) { 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 // 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. // number of erased dives. This considers that there might be missing numbers.
@ -1031,24 +963,33 @@ MergeDives::MergeDives(const QVector <dive *> &dives)
// consecutive, and the difference will be 1, so the // consecutive, and the difference will be 1, so the
// above example is not supposed to be normal. // above example is not supposed to be normal.
int diff = dives.last()->number - dives[0]->number; int diff = dives.last()->number - dives[0]->number;
divesToRenumber.reserve(divelog.dives->nr - idx - num);
int previousnr = dives[0]->number; int previousnr = dives[0]->number;
for (int i = idx + num; i < divelog.dives->nr; ++i) { for (size_t i = idx + num; i < divelog.dives.size(); ++i) {
int newnr = divelog.dives->dives[i]->number - diff; int newnr = divelog.dives[i]->number - diff;
// Stop renumbering if stuff isn't in order (see also core/divelist.c) // Stop renumbering if stuff isn't in order (see also core/divelist.c)
if (newnr <= previousnr) if (newnr <= previousnr)
break; break;
divesToRenumber.append(QPair<dive *,int>(divelog.dives->dives[i], newnr)); divesToRenumber.append(QPair<dive *,int>(divelog.dives[i].get(), newnr));
previousnr = newnr; previousnr = newnr;
} }
} }
mergedDive.dives.resize(1); mergedDive.dives.resize(1);
mergedDive.dives[0].dive = std::move(d); mergedDive.dives[0].trip = d->divetrip;
mergedDive.dives[0].trip = preferred_trip; mergedDive.dives[0].site = d->dive_site;
mergedDive.dives[0].site = preferred_site;
divesToMerge.dives = std::vector<dive *>(dives.begin(), dives.end()); divesToMerge.dives = std::vector<dive *>(dives.begin(), dives.end());
if (set_location.has_value() && d->dive_site) {
location = *set_location;
site = d->dive_site;
}
// We got our preferred trip and site, so now the references can be deleted from the newly generated dive
d->divetrip = nullptr;
d->dive_site = nullptr;
mergedDive.dives[0].dive = std::move(d);
} }
bool MergeDives::workToBeDone() bool MergeDives::workToBeDone()
@ -1056,11 +997,20 @@ bool MergeDives::workToBeDone()
return !mergedDive.dives.empty(); 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() void MergeDives::redoit()
{ {
renumberDives(divesToRenumber); renumberDives(divesToRenumber);
diveToUnmerge = addDives(mergedDive); diveToUnmerge = addDives(mergedDive);
unmergedDives = removeDives(divesToMerge); unmergedDives = removeDives(divesToMerge);
swapDivesite();
// Select merged dive and make it current // Select merged dive and make it current
setSelection(diveToUnmerge.dives, diveToUnmerge.dives[0], -1); setSelection(diveToUnmerge.dives, diveToUnmerge.dives[0], -1);
@ -1071,6 +1021,7 @@ void MergeDives::undoit()
divesToMerge = addDives(unmergedDives); divesToMerge = addDives(unmergedDives);
mergedDive = removeDives(diveToUnmerge); mergedDive = removeDives(diveToUnmerge);
renumberDives(divesToRenumber); renumberDives(divesToRenumber);
swapDivesite();
// Select unmerged dives and make first one current // Select unmerged dives and make first one current
setSelection(divesToMerge.dives, divesToMerge.dives[0], -1); setSelection(divesToMerge.dives, divesToMerge.dives[0], -1);

View file

@ -15,16 +15,16 @@ namespace Command {
// This helper structure describes a dive that we want to add. // This helper structure describes a dive that we want to add.
struct DiveToAdd { struct DiveToAdd {
OwningDivePtr dive; // Dive to add std::unique_ptr<struct dive> dive; // Dive to add
dive_trip *trip; // Trip the dive belongs to, may be null dive_trip *trip; // Trip the dive belongs to, may be null
dive_site *site; // Site the dive is associated with, 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 // Multiple trips, dives and dive sites that have to be added for a command
struct DivesAndTripsToAdd { struct DivesAndTripsToAdd {
std::vector<DiveToAdd> dives; std::vector<DiveToAdd> dives;
std::vector<OwningTripPtr> trips; std::vector<std::unique_ptr<dive_trip>> trips;
std::vector<OwningDiveSitePtr> sites; std::vector<std::unique_ptr<dive_site>> sites;
}; };
// Dives and sites that have to be removed for a command // Dives and sites that have to be removed for a command
@ -48,7 +48,7 @@ struct DiveToTrip
struct DivesToTrip struct DivesToTrip
{ {
std::vector<DiveToTrip> divesToMove; // If dive_trip is null, remove from trip std::vector<DiveToTrip> divesToMove; // If dive_trip is null, remove from trip
std::vector<OwningTripPtr> tripsToAdd; std::vector<std::unique_ptr<dive_trip>> tripsToAdd;
}; };
// All divelist commands derive from a common base class. It keeps track // All divelist commands derive from a common base class. It keeps track
@ -58,7 +58,7 @@ struct DivesToTrip
class DiveListBase : public Base { class DiveListBase : public Base {
protected: protected:
// These are helper functions to add / remove dive from the C-core structures. // These are helper functions to add / remove dive from the C-core structures.
DiveToAdd removeDive(struct dive *d, std::vector<OwningTripPtr> &tripsToAdd); DiveToAdd removeDive(struct dive *d, std::vector<std::unique_ptr<dive_trip>> &tripsToAdd);
dive *addDive(DiveToAdd &d); dive *addDive(DiveToAdd &d);
DivesAndTripsToAdd removeDives(DivesAndSitesToRemove &divesAndSitesToDelete); DivesAndTripsToAdd removeDives(DivesAndSitesToRemove &divesAndSitesToDelete);
DivesAndSitesToRemove addDives(DivesAndTripsToAdd &toAdd); DivesAndSitesToRemove addDives(DivesAndTripsToAdd &toAdd);
@ -79,7 +79,7 @@ private:
class AddDive : public DiveListBase { class AddDive : public DiveListBase {
public: public:
AddDive(dive *dive, bool autogroup, bool newNumber); AddDive(std::unique_ptr<struct dive> dive, bool autogroup, bool newNumber);
private: private:
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
@ -108,10 +108,10 @@ private:
// For redo and undo // For redo and undo
DivesAndTripsToAdd divesToAdd; DivesAndTripsToAdd divesToAdd;
DivesAndSitesToRemove divesAndSitesToRemove; DivesAndSitesToRemove divesAndSitesToRemove;
struct device_table devicesToAddAndRemove; device_table devicesToAddAndRemove;
// For redo // For redo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
std::vector<std::pair<std::string,FilterData>> std::vector<std::pair<std::string,FilterData>>
filterPresetsToAdd; filterPresetsToAdd;
@ -133,7 +133,7 @@ private:
// For redo // For redo
DivesAndSitesToRemove divesToDelete; DivesAndSitesToRemove divesToDelete;
std::vector<OwningTripPtr> tripsToAdd; std::vector<std::unique_ptr<dive_trip>> tripsToAdd;
DivesAndTripsToAdd divesToAdd; DivesAndTripsToAdd divesToAdd;
}; };
@ -196,7 +196,7 @@ struct MergeTrips : public TripBase {
class SplitDivesBase : public DiveListBase { class SplitDivesBase : public DiveListBase {
protected: protected:
SplitDivesBase(dive *old, std::array<dive *, 2> newDives); SplitDivesBase(dive *old, std::array<std::unique_ptr<dive>, 2> newDives);
private: private:
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
@ -237,7 +237,7 @@ class DiveComputerBase : public DiveListBase {
protected: protected:
// old_dive must be a dive known to the core. // old_dive must be a dive known to the core.
// new_dive must be new dive whose ownership is taken. // new_dive must be new dive whose ownership is taken.
DiveComputerBase(dive *old_dive, dive *new_dive, int dc_nr_before, int dc_nr_after); DiveComputerBase(dive *old_dive, std::unique_ptr<dive> new_dive, int dc_nr_before, int dc_nr_after);
private: private:
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
@ -267,6 +267,7 @@ private:
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
bool workToBeDone() override; bool workToBeDone() override;
void swapDivesite(); // Common code for undo and redo.
// For redo // For redo
// Add one and remove a batch of dives // Add one and remove a batch of dives
@ -284,6 +285,8 @@ private:
// For undo and redo // For undo and redo
QVector<QPair<dive *, int>> divesToRenumber; QVector<QPair<dive *, int>> divesToRenumber;
dive_site *site;
location_t location;
}; };
} // namespace Command } // namespace Command

View file

@ -15,25 +15,24 @@ namespace Command {
// Add a set of dive sites to the core. The dives that were associated with // 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. // that dive site will be restored to that dive site.
static std::vector<dive_site *> addDiveSites(std::vector<OwningDiveSitePtr> &sites) static std::vector<dive_site *> addDiveSites(std::vector<std::unique_ptr<dive_site>> &sites)
{ {
std::vector<dive_site *> res; std::vector<dive_site *> res;
QVector<dive *> changedDives; QVector<dive *> changedDives;
res.reserve(sites.size()); res.reserve(sites.size());
for (OwningDiveSitePtr &ds: sites) { for (std::unique_ptr<dive_site> &ds: sites) {
// Readd the dives that belonged to this site // 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 // TODO: send dive site changed signal
struct dive *d = ds->dives.dives[i];
d->dive_site = ds.get(); d->dive_site = ds.get();
changedDives.push_back(d); changedDives.push_back(d);
} }
// Add dive site to core, but remember a non-owning pointer first. // Add dive site to core, but remember a non-owning pointer first.
res.push_back(ds.get()); auto add_res = divelog.sites.put(std::move(ds)); // Return ownership to backend.
int idx = register_dive_site(ds.release()); // Return ownership to backend. res.push_back(add_res.ptr);
emit diveListNotifier.diveSiteAdded(res.back(), idx); // Inform frontend of new dive site. emit diveListNotifier.diveSiteAdded(res.back(), add_res.idx); // Inform frontend of new dive site.
} }
emit diveListNotifier.divesChanged(changedDives, DiveField::DIVESITE); emit diveListNotifier.divesChanged(changedDives, DiveField::DIVESITE);
@ -47,24 +46,23 @@ static std::vector<dive_site *> addDiveSites(std::vector<OwningDiveSitePtr> &sit
// Remove a set of dive sites. Get owning pointers to them. The dives are set to // 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 // 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. // that the dives can be readded to the site on undo.
static std::vector<OwningDiveSitePtr> removeDiveSites(std::vector<dive_site *> &sites) static std::vector<std::unique_ptr<dive_site>> removeDiveSites(std::vector<dive_site *> &sites)
{ {
std::vector<OwningDiveSitePtr> res; std::vector<std::unique_ptr<dive_site>> res;
QVector<dive *> changedDives; QVector<dive *> changedDives;
res.reserve(sites.size()); res.reserve(sites.size());
for (dive_site *ds: sites) { for (dive_site *ds: sites) {
// Reset the dive_site field of the affected dives // Reset the dive_site field of the affected dives
for (int i = 0; i < ds->dives.nr; ++i) { for (dive *d: ds->dives) {
struct dive *d = ds->dives.dives[i];
d->dive_site = nullptr; d->dive_site = nullptr;
changedDives.push_back(d); changedDives.push_back(d);
} }
// Remove dive site from core and take ownership. // Remove dive site from core and take ownership.
int idx = unregister_dive_site(ds); auto pull_res = divelog.sites.pull(ds);
res.emplace_back(ds); res.push_back(std::move(pull_res.ptr));
emit diveListNotifier.diveSiteDeleted(ds, idx); // Inform frontend of removed dive site. emit diveListNotifier.diveSiteDeleted(ds, pull_res.idx); // Inform frontend of removed dive site.
} }
emit diveListNotifier.divesChanged(changedDives, DiveField::DIVESITE); emit diveListNotifier.divesChanged(changedDives, DiveField::DIVESITE);
@ -77,8 +75,8 @@ static std::vector<OwningDiveSitePtr> removeDiveSites(std::vector<dive_site *> &
AddDiveSite::AddDiveSite(const QString &name) AddDiveSite::AddDiveSite(const QString &name)
{ {
setText(Command::Base::tr("add dive site")); setText(Command::Base::tr("add dive site"));
sitesToAdd.emplace_back(alloc_dive_site()); sitesToAdd.push_back(std::make_unique<dive_site>());
sitesToAdd.back()->name = copy_qstring(name); sitesToAdd.back()->name = name.toStdString();
} }
bool AddDiveSite::workToBeDone() bool AddDiveSite::workToBeDone()
@ -96,25 +94,17 @@ void AddDiveSite::undo()
sitesToAdd = removeDiveSites(sitesToRemove); 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)); setText(Command::Base::tr("import dive sites from %1").arg(source));
for (int i = 0; i < sites->nr; ++i) { for (auto &new_ds: sites) {
struct dive_site *new_ds = sites->dive_sites[i]; // Don't import dive sites that already exist.
// We might want to be smarter here and merge dive site data, etc.
// Don't import dive sites that already exist. Currently we only check for if (divelog.sites.get_same(*new_ds))
// 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);
continue; continue;
} sitesToAdd.push_back(std::move(new_ds));
sitesToAdd.emplace_back(new_ds);
} }
// All site have been consumed
sites->nr = 0;
} }
bool ImportDiveSites::workToBeDone() bool ImportDiveSites::workToBeDone()
@ -155,10 +145,9 @@ void DeleteDiveSites::undo()
PurgeUnusedDiveSites::PurgeUnusedDiveSites() PurgeUnusedDiveSites::PurgeUnusedDiveSites()
{ {
setText(Command::Base::tr("purge unused dive sites")); setText(Command::Base::tr("purge unused dive sites"));
for (int i = 0; i < divelog.sites->nr; ++i) { for (const auto &ds: divelog.sites) {
dive_site *ds = divelog.sites->dive_sites[i]; if (ds->dives.empty())
if (ds->dives.nr == 0) sitesToRemove.push_back(ds.get());
sitesToRemove.push_back(ds);
} }
} }
@ -177,24 +166,15 @@ void PurgeUnusedDiveSites::undo()
sitesToRemove = addDiveSites(sitesToAdd); 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), EditDiveSiteName::EditDiveSiteName(dive_site *dsIn, const QString &name) : ds(dsIn),
value(name) value(name.toStdString())
{ {
setText(Command::Base::tr("Edit dive site name")); setText(Command::Base::tr("Edit dive site name"));
} }
bool EditDiveSiteName::workToBeDone() bool EditDiveSiteName::workToBeDone()
{ {
return value != QString(ds->name); return value != ds->name;
} }
void EditDiveSiteName::redo() void EditDiveSiteName::redo()
@ -210,14 +190,14 @@ void EditDiveSiteName::undo()
} }
EditDiveSiteDescription::EditDiveSiteDescription(dive_site *dsIn, const QString &description) : ds(dsIn), EditDiveSiteDescription::EditDiveSiteDescription(dive_site *dsIn, const QString &description) : ds(dsIn),
value(description) value(description.toStdString())
{ {
setText(Command::Base::tr("Edit dive site description")); setText(Command::Base::tr("Edit dive site description"));
} }
bool EditDiveSiteDescription::workToBeDone() bool EditDiveSiteDescription::workToBeDone()
{ {
return value != QString(ds->description); return value != ds->description;
} }
void EditDiveSiteDescription::redo() void EditDiveSiteDescription::redo()
@ -233,14 +213,14 @@ void EditDiveSiteDescription::undo()
} }
EditDiveSiteNotes::EditDiveSiteNotes(dive_site *dsIn, const QString &notes) : ds(dsIn), EditDiveSiteNotes::EditDiveSiteNotes(dive_site *dsIn, const QString &notes) : ds(dsIn),
value(notes) value(notes.toStdString())
{ {
setText(Command::Base::tr("Edit dive site notes")); setText(Command::Base::tr("Edit dive site notes"));
} }
bool EditDiveSiteNotes::workToBeDone() bool EditDiveSiteNotes::workToBeDone()
{ {
return value != QString(ds->notes); return value != ds->notes;
} }
void EditDiveSiteNotes::redo() void EditDiveSiteNotes::redo()
@ -256,20 +236,20 @@ void EditDiveSiteNotes::undo()
} }
EditDiveSiteCountry::EditDiveSiteCountry(dive_site *dsIn, const QString &country) : ds(dsIn), EditDiveSiteCountry::EditDiveSiteCountry(dive_site *dsIn, const QString &country) : ds(dsIn),
value(country) value(country.toStdString())
{ {
setText(Command::Base::tr("Edit dive site country")); setText(Command::Base::tr("Edit dive site country"));
} }
bool EditDiveSiteCountry::workToBeDone() bool EditDiveSiteCountry::workToBeDone()
{ {
return !same_string(qPrintable(value), taxonomy_get_country(&ds->taxonomy)); return value == taxonomy_get_country(ds->taxonomy);
} }
void EditDiveSiteCountry::redo() void EditDiveSiteCountry::redo()
{ {
QString old = taxonomy_get_country(&ds->taxonomy); std::string old = taxonomy_get_country(ds->taxonomy);
taxonomy_set_country(&ds->taxonomy, qPrintable(value), taxonomy_origin::GEOMANUAL); taxonomy_set_country(ds->taxonomy, value, taxonomy_origin::GEOMANUAL);
value = old; value = old;
emit diveListNotifier.diveSiteChanged(ds, LocationInformationModel::TAXONOMY); // Inform frontend of changed dive site. 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); bool old_ok = has_location(&ds->location);
if (ok != old_ok) if (ok != old_ok)
return true; return true;
return ok && !same_location(&value, &ds->location); return ok && value != ds->location;
} }
void EditDiveSiteLocation::redo() void EditDiveSiteLocation::redo()
@ -310,14 +290,11 @@ void EditDiveSiteLocation::undo()
EditDiveSiteTaxonomy::EditDiveSiteTaxonomy(dive_site *dsIn, taxonomy_data &taxonomy) : ds(dsIn), EditDiveSiteTaxonomy::EditDiveSiteTaxonomy(dive_site *dsIn, taxonomy_data &taxonomy) : ds(dsIn),
value(taxonomy) 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")); setText(Command::Base::tr("Edit dive site taxonomy"));
} }
EditDiveSiteTaxonomy::~EditDiveSiteTaxonomy() EditDiveSiteTaxonomy::~EditDiveSiteTaxonomy()
{ {
free_taxonomy(&value);
} }
bool EditDiveSiteTaxonomy::workToBeDone() bool EditDiveSiteTaxonomy::workToBeDone()
@ -364,10 +341,10 @@ void MergeDiveSites::redo()
// The dives of the above dive sites were reset to no dive sites. // The dives of the above dive sites were reset to no dive sites.
// Add them to the merged-into dive site. Thankfully, we remember // Add them to the merged-into dive site. Thankfully, we remember
// the dives in the sitesToAdd vector. // the dives in the sitesToAdd vector.
for (const OwningDiveSitePtr &site: sitesToAdd) { for (const std::unique_ptr<dive_site> &site: sitesToAdd) {
for (int i = 0; i < site->dives.nr; ++i) { for (dive *d: site->dives) {
add_dive_to_dive_site(site->dives.dives[i], ds); ds->add_dive(d);
divesChanged.push_back(site->dives.dives[i]); divesChanged.push_back(d);
} }
} }
emit diveListNotifier.divesChanged(divesChanged, DiveField::DIVESITE); 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 // Before readding the dive sites, unregister the corresponding dives so that they can be
// readded to their old dive sites. // readded to their old dive sites.
for (const OwningDiveSitePtr &site: sitesToAdd) { for (const std::unique_ptr<dive_site> &site: sitesToAdd) {
for (int i = 0; i < site->dives.nr; ++i) { for (dive *d: site->dives) {
unregister_dive_from_dive_site(site->dives.dives[i]); unregister_dive_from_dive_site(d);
divesChanged.push_back(site->dives.dives[i]); divesChanged.push_back(d);
} }
} }
@ -405,9 +382,9 @@ ApplyGPSFixes::ApplyGPSFixes(const std::vector<DiveAndLocation> &fixes)
siteLocations.push_back({ ds, dl.location }); siteLocations.push_back({ ds, dl.location });
} }
} else { } else {
ds = create_dive_site(qPrintable(dl.name), divelog.sites); ds = divelog.sites.create(dl.name.toStdString());
ds->location = dl.location; 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() dl.d->dive_site = nullptr; // This will be set on redo()
sitesToAdd.emplace_back(ds); sitesToAdd.emplace_back(ds);
} }

View file

@ -31,13 +31,13 @@ private:
std::vector<dive_site *> sitesToRemove; std::vector<dive_site *> sitesToRemove;
// For redo // For redo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
}; };
class ImportDiveSites : public Base { class ImportDiveSites : public Base {
public: public:
// Note: the dive site table is consumed after the call it will be empty. // Note: Takes ownership of dive site table
ImportDiveSites(struct dive_site_table *sites, const QString &source); ImportDiveSites(dive_site_table sites, const QString &source);
private: private:
bool workToBeDone() override; bool workToBeDone() override;
void undo() override; void undo() override;
@ -47,7 +47,7 @@ private:
std::vector<dive_site *> sitesToRemove; std::vector<dive_site *> sitesToRemove;
// For redo // For redo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
}; };
class DeleteDiveSites : public Base { class DeleteDiveSites : public Base {
@ -62,7 +62,7 @@ private:
std::vector<dive_site *> sitesToRemove; std::vector<dive_site *> sitesToRemove;
// For undo // For undo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
}; };
class PurgeUnusedDiveSites : public Base { class PurgeUnusedDiveSites : public Base {
@ -77,7 +77,7 @@ private:
std::vector<dive_site *> sitesToRemove; std::vector<dive_site *> sitesToRemove;
// For undo // For undo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
}; };
class EditDiveSiteName : public Base { class EditDiveSiteName : public Base {
@ -89,7 +89,7 @@ private:
void redo() override; void redo() override;
dive_site *ds; dive_site *ds;
QString value; // Value to be set std::string value; // Value to be set
}; };
class EditDiveSiteDescription : public Base { class EditDiveSiteDescription : public Base {
@ -101,7 +101,7 @@ private:
void redo() override; void redo() override;
dive_site *ds; dive_site *ds;
QString value; // Value to be set std::string value; // Value to be set
}; };
class EditDiveSiteNotes : public Base { class EditDiveSiteNotes : public Base {
@ -113,7 +113,7 @@ private:
void redo() override; void redo() override;
dive_site *ds; dive_site *ds;
QString value; // Value to be set std::string value; // Value to be set
}; };
class EditDiveSiteCountry : public Base { class EditDiveSiteCountry : public Base {
@ -125,7 +125,7 @@ private:
void redo() override; void redo() override;
dive_site *ds; dive_site *ds;
QString value; // Value to be set std::string value; // Value to be set
}; };
class EditDiveSiteLocation : public Base { class EditDiveSiteLocation : public Base {
@ -167,7 +167,7 @@ private:
std::vector<dive_site *> sitesToRemove; std::vector<dive_site *> sitesToRemove;
// For undo // For undo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
}; };
class ApplyGPSFixes : public Base { class ApplyGPSFixes : public Base {
@ -183,7 +183,7 @@ private:
std::vector<dive_site *> sitesToRemove; std::vector<dive_site *> sitesToRemove;
// For redo // For redo
std::vector<OwningDiveSitePtr> sitesToAdd; std::vector<std::unique_ptr<dive_site>> sitesToAdd;
// For redo and undo // For redo and undo
struct SiteAndLocation { struct SiteAndLocation {

File diff suppressed because it is too large Load diff

View file

@ -88,7 +88,7 @@ private:
// Automatically generate getter and setter in the case for string assignments. // 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. // The third parameter is a pointer to a C-style string in the dive structure.
template <DiveField::Flags ID, char *dive::*PTR> template <DiveField::Flags ID, std::string dive::*PTR>
class EditStringSetter : public EditTemplate<QString, ID> { class EditStringSetter : public EditTemplate<QString, ID> {
private: private:
using EditTemplate<QString, ID>::EditTemplate; using EditTemplate<QString, ID>::EditTemplate;
@ -208,7 +208,7 @@ public:
// deriving from it and hooks into undo() and redo() to add / remove the dive site. // deriving from it and hooks into undo() and redo() to add / remove the dive site.
class EditDiveSiteNew : public EditDiveSite { class EditDiveSiteNew : public EditDiveSite {
public: public:
OwningDiveSitePtr diveSiteToAdd; std::unique_ptr<dive_site> diveSiteToAdd;
struct dive_site *diveSiteToRemove; struct dive_site *diveSiteToRemove;
EditDiveSiteNew(const QString &newName, bool currentDiveOnly); EditDiveSiteNew(const QString &newName, bool currentDiveOnly);
void undo() override; void undo() override;
@ -287,34 +287,35 @@ public:
// Fields we have to remember to undo paste // Fields we have to remember to undo paste
struct PasteState { struct PasteState {
dive *d; dive &d;
dive_site *divesite; std::optional<dive_site *> divesite;
QString notes; std::optional<std::string> notes;
QString diveguide; std::optional<std::string> diveguide;
QString buddy; std::optional<std::string> buddy;
QString suit; std::optional<std::string> suit;
int rating; std::optional<int> rating;
int wavesize; std::optional<int> wavesize;
int visibility; std::optional<int> visibility;
int current; std::optional<int> current;
int surge; std::optional<int> surge;
int chill; std::optional<int> chill;
tag_entry *tags; std::optional<tag_list> tags;
struct cylinder_table cylinders; std::optional<cylinder_table> cylinders;
struct weightsystem_table weightsystems; std::optional<weightsystem_table> weightsystems;
int number; std::optional<int> number;
timestamp_t when; std::optional<timestamp_t> when;
PasteState(dive *d, const dive *data, dive_components what); // Read data from dive data for dive d PasteState(dive &d, const dive_paste_data &data, std::vector<dive_site *> &changed_dive_sites);
~PasteState(); ~PasteState();
void swap(dive_components what); // Exchange values here and in dive void swap(); // Exchange values here and in dive
}; };
class PasteDives : public Base { class PasteDives : public Base {
dive_components what; dive_paste_data data;
std::vector<PasteState> dives; std::vector<PasteState> dives;
std::vector<dive_site *> dive_sites_changed;
public: public:
PasteDives(const dive *d, dive_components what); PasteDives(const dive_paste_data &data);
private: private:
void undo() override; void undo() override;
void redo() override; void redo() override;
@ -329,7 +330,7 @@ class ReplanDive : public Base {
depth_t maxdepth, meandepth; depth_t maxdepth, meandepth;
struct cylinder_table cylinders; struct cylinder_table cylinders;
struct divecomputer dc; struct divecomputer dc;
char *notes; std::string notes;
pressure_t surface_pressure; pressure_t surface_pressure;
duration_t duration; duration_t duration;
int salinity; 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 EditDive(dive *oldDive, dive *newDive, dive_site *createDs, dive_site *editDs, location_t dsLocation); // Takes ownership of newDive
private: private:
dive *oldDive; // Dive that is going to be overwritten dive *oldDive; // Dive that is going to be overwritten
OwningDivePtr newDive; // New data std::unique_ptr<dive> newDive; // New data
dive_site *newDiveSite; dive_site *newDiveSite;
int changedFields; int changedFields;
dive_site *siteToRemove; dive_site *siteToRemove;
OwningDiveSitePtr siteToAdd; std::unique_ptr<dive_site> siteToAdd;
dive_site *siteToEdit; dive_site *siteToEdit;
location_t dsLocation; location_t dsLocation;

View file

@ -41,13 +41,12 @@ void EditTripBase::redo()
// ***** Location ***** // ***** Location *****
void EditTripLocation::set(dive_trip *t, const QString &s) const void EditTripLocation::set(dive_trip *t, const QString &s) const
{ {
free(t->location); t->location = s.toStdString();
t->location = copy_qstring(s);
} }
QString EditTripLocation::data(dive_trip *t) const QString EditTripLocation::data(dive_trip *t) const
{ {
return QString(t->location); return QString::fromStdString(t->location);
} }
QString EditTripLocation::fieldName() const QString EditTripLocation::fieldName() const
@ -63,13 +62,12 @@ TripField EditTripLocation::fieldId() const
// ***** Notes ***** // ***** Notes *****
void EditTripNotes::set(dive_trip *t, const QString &s) const void EditTripNotes::set(dive_trip *t, const QString &s) const
{ {
free(t->notes); t->notes = s.toStdString();
t->notes = copy_qstring(s);
} }
QString EditTripNotes::data(dive_trip *t) const QString EditTripNotes::data(dive_trip *t) const
{ {
return QString(t->notes); return QString::fromStdString(t->notes);
} }
QString EditTripNotes::fieldName() const QString EditTripNotes::fieldName() const

View file

@ -30,7 +30,7 @@ protected:
void redo() override; void redo() override;
// Get and set functions to be overriden by sub-classes. // 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 data(struct dive_trip *t) const = 0;
virtual QString fieldName() const = 0; // Name of the field, used to create the undo menu-entry virtual QString fieldName() const = 0; // Name of the field, used to create the undo menu-entry
virtual TripField fieldId() const = 0; virtual TripField fieldId() const = 0;

View file

@ -2,7 +2,8 @@
#include "command_event.h" #include "command_event.h"
#include "core/dive.h" #include "core/dive.h"
#include "core/event.h" #include "core/divelist.h"
#include "core/divelog.h"
#include "core/selection.h" #include "core/selection.h"
#include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-qt/divelistnotifier.h"
#include "core/libdivecomputer.h" #include "core/libdivecomputer.h"
@ -30,13 +31,13 @@ void EventBase::undo()
void EventBase::updateDive() void EventBase::updateDive()
{ {
invalidate_dive_cache(d); d->invalidate_cache();
emit diveListNotifier.eventsChanged(d); emit diveListNotifier.eventsChanged(d);
setSelection({ d }, d, dcNr); setSelection({ d }, d, dcNr);
} }
AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr), AddEventBase::AddEventBase(struct dive *d, int dcNr, struct event ev) : EventBase(d, dcNr),
eventToAdd(ev) ev(std::move(ev))
{ {
} }
@ -47,33 +48,30 @@ bool AddEventBase::workToBeDone()
void AddEventBase::redoit() void AddEventBase::redoit()
{ {
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
eventToRemove = eventToAdd.get(); idx = add_event_to_dc(dc, ev); // return ownership to backend
add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend
} }
void AddEventBase::undoit() void AddEventBase::undoit()
{ {
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
remove_event_from_dc(dc, eventToRemove); ev = remove_event_from_dc(dc, idx);
eventToAdd.reset(eventToRemove); // take ownership of event
eventToRemove = nullptr;
} }
AddEventBookmark::AddEventBookmark(struct dive *d, int dcNr, int seconds) : 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")); setText(Command::Base::tr("Add bookmark"));
} }
AddEventDivemodeSwitch::AddEventDivemodeSwitch(struct dive *d, int dcNr, int seconds, int divemode) : 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]))); 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) : 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) divemode(CCR)
{ {
setText(Command::Base::tr("Add set point change")); // TODO: format pO2 value in bar or psi. 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() void AddEventSetpointChange::undoit()
{ {
AddEventBase::undoit(); AddEventBase::undoit();
std::swap(get_dive_dc(d, dcNr)->divemode, divemode); std::swap(d->get_dc(dcNr)->divemode, divemode);
} }
void AddEventSetpointChange::redoit() void AddEventSetpointChange::redoit()
{ {
AddEventBase::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), RenameEvent::RenameEvent(struct dive *d, int dcNr, int idx, const std::string nameIn) : EventBase(d, dcNr),
eventToAdd(clone_event_rename(ev, name)), idx(idx),
eventToRemove(ev) 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() bool RenameEvent::workToBeDone()
@ -105,45 +103,47 @@ bool RenameEvent::workToBeDone()
void RenameEvent::redoit() void RenameEvent::redoit()
{ {
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
swap_event(dc, eventToRemove, eventToAdd.get()); event *ev = get_event(dc, idx);
event *tmp = eventToRemove; if (ev)
eventToRemove = eventToAdd.release(); std::swap(ev->name, name);
eventToAdd.reset(tmp);
} }
void RenameEvent::undoit() 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(); redoit();
} }
RemoveEvent::RemoveEvent(struct dive *d, int dcNr, struct event *ev) : EventBase(d, dcNr), RemoveEvent::RemoveEvent(struct dive *d, int dcNr, int idxIn) : EventBase(d, dcNr),
eventToRemove(ev), idx(idxIn), cylinder(-1)
cylinder(ev->type == SAMPLE_EVENT_GASCHANGE2 || ev->type == SAMPLE_EVENT_GASCHANGE ?
ev->gas.index : -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() bool RemoveEvent::workToBeDone()
{ {
return true; return idx >= 0;
} }
void RemoveEvent::redoit() void RemoveEvent::redoit()
{ {
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
remove_event_from_dc(dc, eventToRemove); ev = remove_event_from_dc(dc, idx);
eventToAdd.reset(eventToRemove); // take ownership of event
eventToRemove = nullptr;
} }
void RemoveEvent::undoit() void RemoveEvent::undoit()
{ {
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
eventToRemove = eventToAdd.get(); idx = add_event_to_dc(dc, std::move(ev));
add_event_to_dc(dc, eventToAdd.release()); // return ownership to backend
} }
void RemoveEvent::post() const void RemoveEvent::post() const
@ -151,7 +151,7 @@ void RemoveEvent::post() const
if (cylinder < 0) if (cylinder < 0)
return; return;
fixup_dive(d); divelog.dives.fixup_dive(*d);
emit diveListNotifier.cylinderEdited(d, cylinder); emit diveListNotifier.cylinderEdited(d, cylinder);
// TODO: This is silly we send a DURATION change event so that the statistics are recalculated. // 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. // 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 // There shouldn't be more than one gas change per time stamp. Just in case we'll
// support that anyway. // support that anyway.
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
struct event *gasChangeEvent = dc->events;
while ((gasChangeEvent = get_next_event_mutable(gasChangeEvent, "gaschange")) != NULL) { // Note that we remove events in reverse order so that the indexes don't change
if (gasChangeEvent->time.seconds == seconds) { // meaning while removing. This should be an extremely rare case anyway.
eventsToRemove.push_back(gasChangeEvent); for (int idx = static_cast<int>(dc->events.size()) - 1; idx > 0; --idx) {
int idx = gasChangeEvent->gas.index; const event &ev = dc->events[idx];
if (std::find(cylinders.begin(), cylinders.end(), idx) == cylinders.end()) if (ev.time.seconds == seconds && ev.name == "gaschange")
cylinders.push_back(idx); // cylinders might have changed their status eventsToRemove.push_back(idx);
} if (std::find(cylinders.begin(), cylinders.end(), ev.gas.index) == cylinders.end())
gasChangeEvent = gasChangeEvent->next; 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() bool AddGasSwitch::workToBeDone()
@ -186,25 +186,26 @@ bool AddGasSwitch::workToBeDone()
void AddGasSwitch::redoit() void AddGasSwitch::redoit()
{ {
std::vector<OwningEventPtr> newEventsToAdd; std::vector<event> newEventsToAdd;
std::vector<event *> newEventsToRemove; std::vector<int> newEventsToRemove;
newEventsToAdd.reserve(eventsToRemove.size()); newEventsToAdd.reserve(eventsToRemove.size());
newEventsToRemove.reserve(eventsToAdd.size()); newEventsToRemove.reserve(eventsToAdd.size());
struct divecomputer *dc = get_dive_dc(d, dcNr); struct divecomputer *dc = d->get_dc(dcNr);
for (int idx: eventsToRemove)
newEventsToAdd.push_back(remove_event_from_dc(dc, idx));
for (auto &ev: eventsToAdd)
newEventsToRemove.push_back(add_event_to_dc(dc, std::move(ev)));
// Make sure that events are removed in reverse order
std::sort(newEventsToRemove.begin(), newEventsToRemove.end(), std::greater<int>());
for (event *ev: eventsToRemove) {
remove_event_from_dc(dc, ev);
newEventsToAdd.emplace_back(ev); // take ownership of event
}
for (OwningEventPtr &ev: eventsToAdd) {
newEventsToRemove.push_back(ev.get());
add_event_to_dc(dc, ev.release()); // return ownership to backend
}
eventsToAdd = std::move(newEventsToAdd); eventsToAdd = std::move(newEventsToAdd);
eventsToRemove = std::move(newEventsToRemove); eventsToRemove = std::move(newEventsToRemove);
// this means we potentially have a new tank that is being used and needs to be shown // 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) for (int idx: cylinders)
emit diveListNotifier.cylinderEdited(d, idx); emit diveListNotifier.cylinderEdited(d, idx);

View file

@ -6,15 +6,12 @@
#include "command_base.h" #include "command_base.h"
#include "core/divemode.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 // We put everything in a namespace, so that we can shorten names without polluting the global namespace
namespace Command { namespace Command {
// Events are a strange thing: they contain there own description which means // Pointers to events are not stable, so we always store indexes.
// 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
class EventBase : public Base { class EventBase : public Base {
protected: protected:
@ -25,8 +22,7 @@ protected:
virtual void undoit() = 0; virtual void undoit() = 0;
// Note: we store dive and the divecomputer-number instead of a pointer to the divecomputer. // 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 // Pointers to divecomputers are not stable.
// are probably not stable.
struct dive *d; struct dive *d;
int dcNr; int dcNr;
private: private:
@ -35,15 +31,15 @@ private:
class AddEventBase : public EventBase { class AddEventBase : public EventBase {
public: 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: protected:
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
private: private:
bool workToBeDone() override; bool workToBeDone() override;
OwningEventPtr eventToAdd; // for redo struct event ev; // for redo
event *eventToRemove; // for undo int idx; // for undo
}; };
class AddEventBookmark : public AddEventBase { class AddEventBookmark : public AddEventBase {
@ -67,28 +63,28 @@ private:
class RenameEvent : public EventBase { class RenameEvent : public EventBase {
public: 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: private:
bool workToBeDone() override; bool workToBeDone() override;
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
OwningEventPtr eventToAdd; // for undo and redo int idx; // for undo and redo
event *eventToRemove; // for undo and redo std::string name; // for undo and redo
}; };
class RemoveEvent : public EventBase { class RemoveEvent : public EventBase {
public: public:
RemoveEvent(struct dive *d, int dcNr, struct event *ev); RemoveEvent(struct dive *d, int dcNr, int idx);
private: private:
bool workToBeDone() override; bool workToBeDone() override;
void undoit() override; void undoit() override;
void redoit() override; void redoit() override;
void post() const; // Called to fix up dives should a gas-change have happened. void post() const; // Called to fix up dives should a gas-change have happened.
OwningEventPtr eventToAdd; // for undo event ev; // for undo
event *eventToRemove; // for redo int idx; // for redo
int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch. int cylinder; // affected cylinder (if removing gas switch). <0: not a gas switch.
}; };
class AddGasSwitch : public EventBase { class AddGasSwitch : public EventBase {
@ -100,8 +96,8 @@ private:
void redoit() override; void redoit() override;
std::vector<int> cylinders; // cylinders that are modified std::vector<int> cylinders; // cylinders that are modified
std::vector<OwningEventPtr> eventsToAdd; std::vector<event> eventsToAdd;
std::vector<event *> eventsToRemove; std::vector<int> eventsToRemove;
}; };
} // namespace Command } // namespace Command

View file

@ -1,23 +1,26 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "command_filter.h" #include "command_filter.h"
#include "core/divelog.h"
#include "core/filterpreset.h" #include "core/filterpreset.h"
#include "core/filterpresettable.h"
#include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-qt/divelistnotifier.h"
namespace Command { namespace Command {
static int createFilterPreset(const std::string &name, const FilterData &data) 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); emit diveListNotifier.filterPresetAdded(index);
return index; return index;
} }
static std::pair<std::string, FilterData> removeFilterPreset(int index) static std::pair<std::string, FilterData> removeFilterPreset(int index)
{ {
std::string oldName = filter_preset_name(index); const filter_preset &preset = divelog.filter_presets[index];
FilterData oldData = filter_preset_get(index); std::string oldName = preset.name;
filter_preset_delete(index); FilterData oldData = preset.data;
divelog.filter_presets.remove(index);
emit diveListNotifier.filterPresetRemoved(index); emit diveListNotifier.filterPresetRemoved(index);
return { oldName, oldData }; return { oldName, oldData };
} }
@ -46,7 +49,8 @@ void CreateFilterPreset::undo()
RemoveFilterPreset::RemoveFilterPreset(int indexIn) : index(indexIn) 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() bool RemoveFilterPreset::workToBeDone()
@ -68,7 +72,8 @@ void RemoveFilterPreset::undo()
EditFilterPreset::EditFilterPreset(int indexIn, const FilterData &dataIn) : EditFilterPreset::EditFilterPreset(int indexIn, const FilterData &dataIn) :
index(indexIn), data(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() bool EditFilterPreset::workToBeDone()
@ -78,9 +83,8 @@ bool EditFilterPreset::workToBeDone()
void EditFilterPreset::redo() void EditFilterPreset::redo()
{ {
FilterData oldData = filter_preset_get(index); filter_preset &preset = divelog.filter_presets[index];
filter_preset_set(index, data); std::swap(data, preset.data);
data = std::move(oldData);
} }
void EditFilterPreset::undo() void EditFilterPreset::undo()

View file

@ -2,15 +2,16 @@
#include "command_pictures.h" #include "command_pictures.h"
#include "core/errorhelper.h" #include "core/errorhelper.h"
#include "core/range.h"
#include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-qt/divelistnotifier.h"
#include "qt-models/divelocationmodel.h" #include "qt-models/divelocationmodel.h"
namespace Command { 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)); int idx = get_picture_idx(d->pictures, fn.toStdString());
return idx < 0 ? nullptr : &d->pictures.pictures[idx]; return idx < 0 ? nullptr : &d->pictures[idx];
} }
SetPictureOffset::SetPictureOffset(dive *dIn, const QString &filenameIn, offset_t offsetIn) : 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. // Instead of trying to be smart, let's simply resort the picture table.
// If someone complains about speed, do our usual "smart" thing. // 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); emit diveListNotifier.pictureOffsetChanged(d, filename, newOffset);
invalidate_dive_cache(d); d->invalidate_cache();
} }
// Undo and redo do the same thing // Undo and redo do the same thing
@ -55,10 +56,9 @@ static PictureListForDeletion filterPictureListForDeletion(const PictureListForD
PictureListForDeletion res; PictureListForDeletion res;
res.d = p.d; res.d = p.d;
res.filenames.reserve(p.filenames.size()); res.filenames.reserve(p.filenames.size());
for (int i = 0; i < p.d->pictures.nr; ++i) { for (auto &pic: p.d->pictures) {
std::string fn = p.d->pictures.pictures[i].filename; if (range_contains(p.filenames, pic.filename))
if (std::find(p.filenames.begin(), p.filenames.end(), fn) != p.filenames.end()) res.filenames.push_back(pic.filename);
res.filenames.push_back(fn);
} }
return res; return res;
} }
@ -72,18 +72,18 @@ static std::vector<PictureListForAddition> removePictures(std::vector<PictureLis
PictureListForAddition toAdd; PictureListForAddition toAdd;
toAdd.d = list.d; toAdd.d = list.d;
for (const std::string &fn: list.filenames) { for (const std::string &fn: list.filenames) {
int idx = get_picture_idx(&list.d->pictures, fn.c_str()); int idx = get_picture_idx(list.d->pictures, fn);
if (idx < 0) { if (idx < 0) {
report_info("removePictures(): picture disappeared!"); report_info("removePictures(): picture disappeared!");
continue; // Huh? We made sure that this can't happen by filtering out non-existent pictures. continue; // Huh? We made sure that this can't happen by filtering out non-existent pictures.
} }
filenames.push_back(QString::fromStdString(fn)); filenames.push_back(QString::fromStdString(fn));
toAdd.pics.emplace_back(list.d->pictures.pictures[idx]); toAdd.pics.emplace_back(list.d->pictures[idx]);
remove_from_picture_table(&list.d->pictures, idx); list.d->pictures.erase(list.d->pictures.begin() + idx);
} }
if (!toAdd.pics.empty()) if (!toAdd.pics.empty())
res.push_back(toAdd); res.push_back(toAdd);
invalidate_dive_cache(list.d); list.d->invalidate_cache();
emit diveListNotifier.picturesRemoved(list.d, std::move(filenames)); emit diveListNotifier.picturesRemoved(list.d, std::move(filenames));
} }
picturesToRemove.clear(); picturesToRemove.clear();
@ -98,22 +98,22 @@ static std::vector<PictureListForDeletion> addPictures(std::vector<PictureListFo
// happen, as we checked that before. // happen, as we checked that before.
std::vector<PictureListForDeletion> res; std::vector<PictureListForDeletion> res;
for (const PictureListForAddition &list: picturesToAdd) { for (const PictureListForAddition &list: picturesToAdd) {
QVector<PictureObj> picsForSignal; QVector<picture> picsForSignal;
PictureListForDeletion toRemove; PictureListForDeletion toRemove;
toRemove.d = list.d; toRemove.d = list.d;
for (const PictureObj &pic: list.pics) { for (const picture &pic: list.pics) {
int idx = get_picture_idx(&list.d->pictures, pic.filename.c_str()); // This should *not* already exist! int idx = get_picture_idx(list.d->pictures, pic.filename); // This should *not* already exist!
if (idx >= 0) { if (idx >= 0) {
report_info("addPictures(): picture disappeared!"); report_info("addPictures(): picture disappeared!");
continue; // Huh? We made sure that this can't happen by filtering out existing pictures. continue; // Huh? We made sure that this can't happen by filtering out existing pictures.
} }
picsForSignal.push_back(pic); picsForSignal.push_back(pic);
add_picture(&list.d->pictures, pic.toCore()); add_picture(list.d->pictures, pic);
toRemove.filenames.push_back(pic.filename); toRemove.filenames.push_back(pic.filename);
} }
if (!toRemove.filenames.empty()) if (!toRemove.filenames.empty())
res.push_back(toRemove); res.push_back(toRemove);
invalidate_dive_cache(list.d); list.d->invalidate_cache();
emit diveListNotifier.picturesAdded(list.d, std::move(picsForSignal)); emit diveListNotifier.picturesAdded(list.d, std::move(picsForSignal));
} }
picturesToAdd.clear(); picturesToAdd.clear();
@ -164,17 +164,16 @@ AddPictures::AddPictures(const std::vector<PictureListForAddition> &pictures) :
std::sort(p.pics.begin(), p.pics.end()); std::sort(p.pics.begin(), p.pics.end());
// Find a picture with a location // 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()) { if (it != p.pics.end()) {
// There is a dive with a location, we might want to modify the dive accordingly. // There is a dive with a location, we might want to modify the dive accordingly.
struct dive_site *ds = p.d->dive_site; struct dive_site *ds = p.d->dive_site;
if (!ds) { if (!ds) {
// This dive doesn't yet have a dive site -> add a new dive site. // This dive doesn't yet have a dive site -> add a new dive site.
QString name = Command::Base::tr("unnamed dive site"); QString name = Command::Base::tr("unnamed dive site");
dive_site *ds = alloc_dive_site_with_gps(qPrintable(name), &it->location); sitesToAdd.push_back(std::make_unique<dive_site>(qPrintable(name), it->location));
sitesToAdd.emplace_back(ds); sitesToSet.push_back({ p.d, sitesToAdd.back().get() });
sitesToSet.push_back({ p.d, ds }); } else if (!ds->has_gps_location()) {
} else if (!dive_site_has_gps_location(ds)) {
// This dive has a dive site, but without coordinates. Let's add them. // This dive has a dive site, but without coordinates. Let's add them.
sitesToEdit.push_back({ ds, it->location }); 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 unregister_dive_from_dive_site(entry.d); // the dive-site pointer in the dive is now NULL
std::swap(ds, entry.ds); std::swap(ds, entry.ds);
if (ds) if (ds)
add_dive_to_dive_site(entry.d, ds); ds->add_dive(entry.d);
emit diveListNotifier.divesChanged(QVector<dive *>{ entry.d }, DiveField::DIVESITE); emit diveListNotifier.divesChanged(QVector<dive *>{ entry.d }, DiveField::DIVESITE);
} }
@ -218,9 +217,9 @@ void AddPictures::undo()
// Remove dive sites // Remove dive sites
for (dive_site *siteToRemove: sitesToRemove) { for (dive_site *siteToRemove: sitesToRemove) {
int idx = unregister_dive_site(siteToRemove); auto res = divelog.sites.pull(siteToRemove);
sitesToAdd.emplace_back(siteToRemove); sitesToAdd.push_back(std::move(res.ptr));
emit diveListNotifier.diveSiteDeleted(siteToRemove, idx); // Inform frontend of removed dive site. emit diveListNotifier.diveSiteDeleted(siteToRemove, res.idx); // Inform frontend of removed dive site.
} }
sitesToRemove.clear(); sitesToRemove.clear();
} }
@ -228,10 +227,10 @@ void AddPictures::undo()
void AddPictures::redo() void AddPictures::redo()
{ {
// Add dive sites // Add dive sites
for (OwningDiveSitePtr &siteToAdd: sitesToAdd) { for (std::unique_ptr<dive_site> &siteToAdd: sitesToAdd) {
sitesToRemove.push_back(siteToAdd.get()); auto res = divelog.sites.register_site(std::move(siteToAdd)); // Return ownership to backend.
int idx = register_dive_site(siteToAdd.release()); // Return ownership to backend. sitesToRemove.push_back(res.ptr);
emit diveListNotifier.diveSiteAdded(sitesToRemove.back(), idx); // Inform frontend of new dive site. emit diveListNotifier.diveSiteAdded(sitesToRemove.back(), res.idx); // Inform frontend of new dive site.
} }
sitesToAdd.clear(); sitesToAdd.clear();

View file

@ -48,7 +48,7 @@ private:
location_t location; location_t location;
}; };
std::vector<PictureListForAddition> picturesToAdd; // for redo std::vector<PictureListForAddition> picturesToAdd; // for redo
std::vector<OwningDiveSitePtr> sitesToAdd; //for redo std::vector<std::unique_ptr<dive_site>> sitesToAdd; //for redo
std::vector<PictureListForDeletion> picturesToRemove; // for undo std::vector<PictureListForDeletion> picturesToRemove; // for undo
std::vector<dive_site *> sitesToRemove; // for undo std::vector<dive_site *> sitesToRemove; // for undo
std::vector<DiveSiteEntry> sitesToSet; // for redo and undo std::vector<DiveSiteEntry> sitesToSet; // for redo and undo

View file

@ -17,7 +17,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
endif() endif()
if(FTDISUPPORT) if(FTDISUPPORT)
set(SERIAL_FTDI serial_ftdi.c) set(SERIAL_FTDI serial_ftdi.cpp)
endif() endif()
if(BTSUPPORT) if(BTSUPPORT)
@ -61,29 +61,29 @@ set(SUBSURFACE_CORE_LIB_SRCS
devicedetails.h devicedetails.h
dive.cpp dive.cpp
dive.h dive.h
divecomputer.c divecomputer.cpp
divecomputer.h divecomputer.h
dive.h dive.h
divefilter.cpp divefilter.cpp
divefilter.h divefilter.h
divelist.c divelist.cpp
divelist.h divelist.h
divelog.cpp divelog.cpp
divelog.h divelog.h
divelogexportlogic.cpp divelogexportlogic.cpp
divelogexportlogic.h divelogexportlogic.h
divesite-helper.cpp divesite.cpp
divesite.c
divesite.h divesite.h
divesitetable.h
divesitehelpers.cpp divesitehelpers.cpp
divesitehelpers.h divesitehelpers.h
downloadfromdcthread.cpp downloadfromdcthread.cpp
downloadfromdcthread.h downloadfromdcthread.h
event.c event.cpp
event.h event.h
eventtype.cpp eventtype.cpp
eventtype.h eventtype.h
equipment.c equipment.cpp
equipment.h equipment.h
errorhelper.cpp errorhelper.cpp
exif.cpp exif.cpp
@ -95,14 +95,16 @@ set(SUBSURFACE_CORE_LIB_SRCS
filterconstraint.h filterconstraint.h
filterpreset.cpp filterpreset.cpp
filterpreset.h filterpreset.h
filterpresettable.cpp
filterpresettable.h
format.cpp format.cpp
format.h format.h
fulltext.cpp fulltext.cpp
fulltext.h fulltext.h
gas.c gas.cpp
gas.h gas.h
gas-model.c gas-model.cpp
gaspressures.c gaspressures.cpp
gaspressures.h gaspressures.h
gettext.h gettext.h
gettextfromc.cpp gettextfromc.cpp
@ -131,21 +133,18 @@ set(SUBSURFACE_CORE_LIB_SRCS
metadata.h metadata.h
metrics.cpp metrics.cpp
metrics.h metrics.h
ostctools.c ostctools.cpp
owning_ptrs.h
parse-gpx.cpp parse-gpx.cpp
parse-xml.cpp parse-xml.cpp
parse.cpp parse.cpp
parse.h parse.h
picture.c picture.cpp
picture.h picture.h
pictureobj.cpp
pictureobj.h
planner.cpp planner.cpp
planner.h planner.h
plannernotes.cpp plannernotes.cpp
pref.h pref.h
pref.c pref.cpp
profile.cpp profile.cpp
profile.h profile.h
qt-gui.h qt-gui.h
@ -158,18 +157,17 @@ set(SUBSURFACE_CORE_LIB_SRCS
save-git.cpp save-git.cpp
save-html.cpp save-html.cpp
save-html.h save-html.h
save-profiledata.c save-profiledata.cpp
save-xml.cpp save-xml.cpp
selection.cpp selection.cpp
selection.h selection.h
sha1.c sha1.cpp
sha1.h sha1.h
ssrf.h statistics.cpp
statistics.c
statistics.h statistics.h
string-format.h string-format.h
string-format.cpp string-format.cpp
strtod.c strtod.cpp
subsurface-float.h subsurface-float.h
subsurface-string.cpp subsurface-string.cpp
subsurface-string.h subsurface-string.h
@ -179,23 +177,23 @@ set(SUBSURFACE_CORE_LIB_SRCS
subsurfacesysinfo.h subsurfacesysinfo.h
tag.cpp tag.cpp
tag.h tag.h
taxonomy.c taxonomy.cpp
taxonomy.h taxonomy.h
time.cpp time.cpp
timer.c trip.cpp
timer.h
trip.c
trip.h trip.h
triptable.cpp
triptable.h
uemis-downloader.cpp uemis-downloader.cpp
uemis.c uemis.cpp
uemis.h uemis.h
units.h units.h
units.c units.cpp
uploadDiveShare.cpp uploadDiveShare.cpp
uploadDiveShare.h uploadDiveShare.h
uploadDiveLogsDE.cpp uploadDiveLogsDE.cpp
uploadDiveLogsDE.h uploadDiveLogsDE.h
version.c version.cpp
version.h version.h
videoframeextractor.cpp videoframeextractor.cpp
videoframeextractor.h videoframeextractor.h

View file

@ -1,10 +1,10 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
/* implements Android specific functions */ /* implements Android specific functions */
#include "dive.h"
#include "device.h" #include "device.h"
#include "libdivecomputer.h" #include "libdivecomputer.h"
#include "file.h" #include "file.h"
#include "qthelper.h" #include "qthelper.h"
#include "subsurfacestartup.h"
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
#include <dirent.h> #include <dirent.h>
@ -41,33 +41,31 @@ static std::string make_default_filename()
return system_default_path() + "/subsurface.xml"; return system_default_path() + "/subsurface.xml";
} }
extern "C" { using namespace std::string_literals;
std::string system_divelist_default_font = "Roboto"s;
const char android_system_divelist_default_font[] = "Roboto"; double system_divelist_default_font_size = -1.0;
const char *system_divelist_default_font = android_system_divelist_default_font;
double system_divelist_default_font_size = -1;
int get_usb_fd(uint16_t idVendor, uint16_t idProduct); 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 // there are no old default fonts that we would want to ignore
return false; return false;
} }
const char *system_default_directory(void) std::string system_default_directory()
{ {
static const std::string path = system_default_path(); 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(); 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 JNIEXPORT void JNICALL
Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_setUsbDevice(JNIEnv *env, Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_setUsbDevice(JNIEnv *,
jobject obj, jobject,
jobject javaUsbDevice) jobject javaUsbDevice)
{ {
Q_UNUSED (obj)
Q_UNUSED (env)
QAndroidJniObject usbDevice(javaUsbDevice); QAndroidJniObject usbDevice(javaUsbDevice);
if (usbDevice.isValid()) { if (usbDevice.isValid()) {
android_usb_serial_device_descriptor descriptor = getDescriptor(usbDevice); android_usb_serial_device_descriptor descriptor = getDescriptor(usbDevice);
@ -177,12 +173,10 @@ Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_setUsbDevice(JNIEnv *
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_restartDownload(JNIEnv *env, Java_org_subsurfacedivelog_mobile_SubsurfaceMobileActivity_restartDownload(JNIEnv *,
jobject obj, jobject,
jobject javaUsbDevice) jobject javaUsbDevice)
{ {
Q_UNUSED (obj)
Q_UNUSED (env)
QAndroidJniObject usbDevice(javaUsbDevice); QAndroidJniObject usbDevice(javaUsbDevice);
if (usbDevice.isValid()) { if (usbDevice.isValid()) {
android_usb_serial_device_descriptor descriptor = getDescriptor(usbDevice); android_usb_serial_device_descriptor descriptor = getDescriptor(usbDevice);
@ -237,12 +231,12 @@ int subsurface_zip_close(struct zip *zip)
} }
/* win32 console */ /* win32 console */
void subsurface_console_init(void) void subsurface_console_init()
{ {
/* NOP */ /* NOP */
} }
void subsurface_console_exit(void) void subsurface_console_exit()
{ {
/* NOP */ /* NOP */
} }
@ -251,7 +245,6 @@ bool subsurface_user_is_root()
{ {
return false; return false;
} }
}
/* called from QML manager */ /* called from QML manager */
void checkPendingIntents() void checkPendingIntents()

View file

@ -476,23 +476,22 @@ QString extractBluetoothAddress(const QString &address)
return m.captured(0); return m.captured(0);
} }
QString extractBluetoothNameAddress(const QString &address, QString &name) std::pair<QString, QString> extractBluetoothNameAddress(const QString &address)
{ {
// sometimes our device text is of the form "name (address)", sometimes it's just "address" // sometimes our device text is of the form "name (address)", sometimes it's just "address"
// let's simply return the address // let's simply return the address
name = QString();
QString extractedAddress = extractBluetoothAddress(address); QString extractedAddress = extractBluetoothAddress(address);
if (extractedAddress == address.trimmed()) if (extractedAddress == address.trimmed())
return address; return { address, QString() };
QRegularExpression re("^([^()]+)\\(([^)]*\\))$"); QRegularExpression re("^([^()]+)\\(([^)]*\\))$");
QRegularExpressionMatch m = re.match(address); QRegularExpressionMatch m = re.match(address);
if (m.hasMatch()) { if (m.hasMatch()) {
name = m.captured(1).trimmed(); QString name = m.captured(1).trimmed();
return extractedAddress; return { extractedAddress, name };
} }
report_info("can't parse address %s", qPrintable(address)); report_info("can't parse address %s", qPrintable(address));
return QString(); return { QString(), QString() };
} }
void saveBtDeviceInfo(const QString &devaddr, QBluetoothDeviceInfo deviceInfo) void saveBtDeviceInfo(const QString &devaddr, QBluetoothDeviceInfo deviceInfo)

View file

@ -20,7 +20,7 @@ void saveBtDeviceInfo(const QString &devaddr, QBluetoothDeviceInfo deviceInfo);
bool isBluetoothAddress(const QString &address); bool isBluetoothAddress(const QString &address);
bool matchesKnownDiveComputerNames(QString btName); bool matchesKnownDiveComputerNames(QString btName);
QString extractBluetoothAddress(const QString &address); QString extractBluetoothAddress(const QString &address);
QString extractBluetoothNameAddress(const QString &address, QString &name); std::pair<QString, QString> extractBluetoothNameAddress(const QString &address); // returns address/name pair
QBluetoothDeviceInfo getBtDeviceInfo(const QString &devaddr); QBluetoothDeviceInfo getBtDeviceInfo(const QString &devaddr);
class BTDiscovery : public QObject { class BTDiscovery : public QObject {

View file

@ -21,7 +21,6 @@ CheckCloudConnection::CheckCloudConnection(QObject *parent) :
QObject(parent), QObject(parent),
reply(0) reply(0)
{ {
} }
// two free APIs to figure out where we are // two free APIs to figure out where we are
@ -43,7 +42,7 @@ bool CheckCloudConnection::checkServer()
QNetworkRequest request; QNetworkRequest request;
request.setRawHeader("Accept", "text/plain"); request.setRawHeader("Accept", "text/plain");
request.setRawHeader("User-Agent", getUserAgent().toUtf8()); 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); reply = mgr->get(request);
QTimer timer; QTimer timer;
timer.setSingleShot(true); timer.setSingleShot(true);
@ -73,7 +72,7 @@ bool CheckCloudConnection::checkServer()
} }
} }
if (verbose) if (verbose)
report_info("connection test to cloud server %s failed %d %s %d %s", prefs.cloud_base_url, report_info("connection test to cloud server %s failed %d %s %d %s", prefs.cloud_base_url.c_str(),
static_cast<int>(reply->error()), qPrintable(reply->errorString()), static_cast<int>(reply->error()), qPrintable(reply->errorString()),
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),
qPrintable(reply->readAll())); qPrintable(reply->readAll()));
@ -109,19 +108,16 @@ bool CheckCloudConnection::nextServer()
}; };
const char *server = nullptr; const char *server = nullptr;
for (serverTried &item: cloudServers) { for (serverTried &item: cloudServers) {
if (strstr(prefs.cloud_base_url, item.server)) if (contains(prefs.cloud_base_url, item.server))
item.tried = true; item.tried = true;
else if (item.tried == false) else if (item.tried == false)
server = item.server; server = item.server;
} }
if (server) { if (server) {
int s = strlen(server); using namespace std::string_literals;
char *baseurl = (char *)malloc(10 + s); std::string base_url = "https://"s + server + "/"s;
strcpy(baseurl, "https://"); report_info("failed to connect to %s next server to try: %s", prefs.cloud_base_url.c_str(), base_url.c_str());
strncat(baseurl, server, s); prefs.cloud_base_url = std::move(base_url);
strcat(baseurl, "/");
report_info("failed to connect to %s next server to try: %s", prefs.cloud_base_url, baseurl);
prefs.cloud_base_url = baseurl;
git_storage_update_progress(qPrintable(tr("Trying different cloud server..."))); git_storage_update_progress(qPrintable(tr("Trying different cloud server...")));
return true; return true;
} }
@ -192,7 +188,7 @@ void CheckCloudConnection::gotContinent(QNetworkReply *reply)
base_url = "https://" CLOUD_HOST_US "/"; base_url = "https://" CLOUD_HOST_US "/";
else else
base_url = "https://" CLOUD_HOST_EU "/"; base_url = "https://" CLOUD_HOST_EU "/";
if (!same_string(base_url, prefs.cloud_base_url)) { if (base_url != prefs.cloud_base_url) {
if (verbose) if (verbose)
report_info("remember cloud server %s based on IP location in %s", base_url, qPrintable(continentString)); report_info("remember cloud server %s based on IP location in %s", base_url, qPrintable(continentString));
qPrefCloudStorage::instance()->store_cloud_base_url(base_url); qPrefCloudStorage::instance()->store_cloud_base_url(base_url);
@ -200,7 +196,7 @@ void CheckCloudConnection::gotContinent(QNetworkReply *reply)
} }
// helper to be used from C code // helper to be used from C code
extern "C" bool canReachCloudServer(struct git_info *info) bool canReachCloudServer(struct git_info *info)
{ {
if (verbose) if (verbose)
qWarning() << "Cloud storage: checking connection to cloud server" << info->url.c_str(); 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/..." // the cloud_base_url ends with a '/', so we need the text starting at "git/..."
size_t pos = info->url.find("org/git/"); size_t pos = info->url.find("org/git/");
if (pos != std::string::npos) { 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) if (verbose)
report_info("updating remote to: %s", info->url.c_str()); report_info("updating remote to: %s", info->url.c_str());
} }

View file

@ -13,24 +13,43 @@ CloudStorageAuthenticate::CloudStorageAuthenticate(QObject *parent) :
userAgent = getUserAgent(); userAgent = getUserAgent();
} }
#define CLOUDURL QString(prefs.cloud_base_url) static QString cloudUrl()
#define CLOUDBACKENDSTORAGE CLOUDURL + "/storage" {
#define CLOUDBACKENDVERIFY CLOUDURL + "/verify" return QString::fromStdString(prefs.cloud_base_url);
#define CLOUDBACKENDUPDATE CLOUDURL + "/update" }
#define CLOUDBACKENDDELETE CLOUDURL + "/delete-account"
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) QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QString& password,const QString& pin,const QString& newpasswd)
{ {
QString payload(email + QChar(' ') + password); QString payload(email + QChar(' ') + password);
QUrl requestUrl; QUrl requestUrl;
if (pin.isEmpty() && newpasswd.isEmpty()) { if (pin.isEmpty() && newpasswd.isEmpty()) {
requestUrl = QUrl(CLOUDBACKENDSTORAGE); requestUrl = cloudBackendStorage();
} else if (!newpasswd.isEmpty()) { } else if (!newpasswd.isEmpty()) {
requestUrl = QUrl(CLOUDBACKENDUPDATE); requestUrl = cloudBackendUpdate();
payload += QChar(' ') + newpasswd; payload += QChar(' ') + newpasswd;
cloudNewPassword = newpasswd; cloudNewPassword = newpasswd;
} else { } else {
requestUrl = QUrl(CLOUDBACKENDVERIFY); requestUrl = cloudBackendVerify();
payload += QChar(' ') + pin; payload += QChar(' ') + pin;
} }
QNetworkRequest *request = new QNetworkRequest(requestUrl); 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) QNetworkReply* CloudStorageAuthenticate::deleteAccount(const QString& email, const QString& password)
{ {
QString payload(email + QChar(' ') + 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("Accept", "text/xml, text/plain");
request->setRawHeader("User-Agent", userAgent.toUtf8()); request->setRawHeader("User-Agent", userAgent.toUtf8());
request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");

View file

@ -4,7 +4,6 @@
#pragma clang diagnostic ignored "-Wmissing-field-initializers" #pragma clang diagnostic ignored "-Wmissing-field-initializers"
#endif #endif
#include "ssrf.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -440,12 +439,12 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
{ {
const unsigned char *s; const unsigned char *s;
unsigned int offset = 0, profile_period = 1, sample_cnt = 0; 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; //int ascent_rate = 0;
unsigned int ndl = 0; unsigned int ndl = 0;
unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 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; struct sample *sample;
// Initialize stat variables // 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; + log[CMD_START_DEPTH + 1] * 256) / 4;
temp = log[CMD_START_TEMP]; temp = log[CMD_START_TEMP];
psi = log[CMD_START_PSI] + log[CMD_START_PSI + 1] * 256; 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]; profile_period = log[CMD_PROFILE_PERIOD];
break; break;
case TYPE_COMMANDER: 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) { while (offset + config.sample_size < size) {
s = samples + offset; s = samples + offset;
// Start with an empty sample
sample = prepare_sample(dc);
sample->time.seconds = sample_cnt * profile_period;
// Check for event // Check for event
if (s[0] & 0x80) { if (s[0] & 0x80) {
cochran_dive_event(dc, s, sample_cnt * profile_period, &in_deco, &deco_ceiling, &deco_time); 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; continue;
} }
// Start with an empty sample
sample = prepare_sample(dc);
sample->time.seconds = sample_cnt * profile_period;
// Depth is in every sample // Depth is in every sample
depth_sample = (double)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); depth_sample = (double)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1);
depth += depth_sample; depth += depth_sample;
@ -534,9 +531,6 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
case 2: // PSI change case 2: // PSI change
psi -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4; psi -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4;
break; break;
case 1: // SGC rate
sgc_rate -= (double)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2;
break;
case 3: // Temperature case 3: // Temperature
temp = (double)s[1] / 2 + 20; temp = (double)s[1] / 2 + 20;
break; break;
@ -592,8 +586,6 @@ static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
sample->sensor[0] = 0; sample->sensor[0] = 0;
sample->pressure[0].mbar = lrint(psi * PSI / 100); sample->pressure[0].mbar = lrint(psi * PSI / 100);
finish_sample(dc);
offset += config.sample_size; offset += config.sample_size;
sample_cnt++; 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, static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
const unsigned char *in, unsigned size, const unsigned char *in, unsigned size,
struct dive_table *table) struct dive_table &table)
{ {
unsigned char *buf = (unsigned char *)malloc(size); unsigned char *buf = (unsigned char *)malloc(size);
struct dive *dive;
struct divecomputer *dc; struct divecomputer *dc;
struct tm tm = {0}; struct tm tm = {0};
uint32_t csum[5];
double max_depth, avg_depth, min_temp; double max_depth, avg_depth, min_temp;
unsigned int duration = 0, corrupt_dive = 0; 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"); puts("\nSample Data\n");
#endif #endif
dive = alloc_dive(); auto dive = std::make_unique<struct dive>();
dc = &dive->dc; dc = &dive->dcs[0];
unsigned char *log = (buf + 0x4914); 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_GEMINI:
case TYPE_COMMANDER: case TYPE_COMMANDER:
if (config.type == TYPE_GEMINI) { if (config.type == TYPE_GEMINI) {
cylinder_t cyl = empty_cylinder;
dc->model = "Gemini"; dc->model = "Gemini";
dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no 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 cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT] / 256
+ log[CMD_O2_PERCENT + 1]) * 10; + log[CMD_O2_PERCENT + 1]) * 10;
cyl.gasmix.he.permille = 0; cyl.gasmix.he = 0_percent;
add_cylinder(&dive->cylinders, 0, cyl); dive->cylinders.add(0, std::move(cyl));
} else { } else {
dc->model = "Commander"; dc->model = "Commander";
dc->deviceid = array_uint32_le(buf + 0x31e); // serial no dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
for (g = 0; g < 2; g++) { for (g = 0; g < 2; g++) {
cylinder_t cyl = empty_cylinder; cylinder_t cyl = default_cylinder(dive.get());
fill_default_cylinder(dive, &cyl);
cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256 cyl.gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256
+ log[CMD_O2_PERCENT + g * 2 + 1]) * 10; + log[CMD_O2_PERCENT + g * 2 + 1]) * 10;
cyl.gasmix.he.permille = 0; cyl.gasmix.he = 0_percent;
add_cylinder(&dive->cylinders, g, cyl); 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); * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588) * 1000);
dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY]; dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY];
SHA1(log + CMD_NUMBER, 2, (unsigned char *)csum); dc->diveid = SHA1_uint32(log + CMD_NUMBER, 2);
dc->diveid = csum[0];
if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff) if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff)
corrupt_dive = 1; corrupt_dive = 1;
@ -733,15 +720,14 @@ static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
dc->model = "EMC"; dc->model = "EMC";
dc->deviceid = array_uint32_le(buf + 0x31e); // serial no dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
for (g = 0; g < 4; g++) { for (g = 0; g < 4; g++) {
cylinder_t cyl = empty_cylinder; cylinder_t cyl = default_cylinder(dive.get());
fill_default_cylinder(dive, &cyl);
cyl.gasmix.o2.permille = cyl.gasmix.o2.permille =
(log[EMC_O2_PERCENT + g * 2] / 256 (log[EMC_O2_PERCENT + g * 2] / 256
+ log[EMC_O2_PERCENT + g * 2 + 1]) * 10; + log[EMC_O2_PERCENT + g * 2 + 1]) * 10;
cyl.gasmix.he.permille = cyl.gasmix.he.permille =
(log[EMC_HE_PERCENT + g * 2] / 256 (log[EMC_HE_PERCENT + g * 2] / 256
+ log[EMC_HE_PERCENT + g * 2 + 1]) * 10; + 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]; 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); * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588) * 1000);
dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3); dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3);
SHA1(log + EMC_NUMBER, 2, (unsigned char *)csum); dc->diveid = SHA1_uint32(log + EMC_NUMBER, 2);
dc->diveid = csum[0];
if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff) if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff)
corrupt_dive = 1; 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) if (sample_pre_offset < sample_end_offset && sample_end_offset != 0xffffffff)
sample_size = sample_end_offset - sample_pre_offset; 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, + config.logbook_size, sample_size,
&duration, &max_depth, &avg_depth, &min_temp); &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; dc->duration.seconds = duration;
} }
record_dive_to_table(dive, table); table.record_dive(std::move(dive));
free(buf); free(buf);
} }

View file

@ -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_START_IDX VELO_STABLE
#define VELOCITY_COLORS 5 #define VELOCITY_COLORS 5
typedef enum { enum color_index_t {
/* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */
SAC_1, SAC_1,
SAC_2, SAC_2,
@ -145,7 +145,7 @@ typedef enum {
CALC_CEILING_DEEP, CALC_CEILING_DEEP,
TISSUE_PERCENTAGE, TISSUE_PERCENTAGE,
DURATION_LINE DURATION_LINE
} color_index_t; };
QColor getColor(const color_index_t i, bool isGrayscale = false); QColor getColor(const color_index_t i, bool isGrayscale = false);
QColor getSacColor(int sac, int diveSac); QColor getSacColor(int sac, int diveSac);

View file

@ -68,16 +68,14 @@ static QString writeGasDetails(gas g)
bool ConfigureDiveComputer::saveXMLBackup(const QString &fileName, const DeviceDetails &details, device_data_t *data) bool ConfigureDiveComputer::saveXMLBackup(const QString &fileName, const DeviceDetails &details, device_data_t *data)
{ {
QString xml = ""; QString xml = "";
QString vendor = data->vendor;
QString product = data->product;
QXmlStreamWriter writer(&xml); QXmlStreamWriter writer(&xml);
writer.setAutoFormatting(true); writer.setAutoFormatting(true);
writer.writeStartDocument(); writer.writeStartDocument();
writer.writeStartElement("DiveComputerSettingsBackup"); writer.writeStartElement("DiveComputerSettingsBackup");
writer.writeStartElement("DiveComputer"); writer.writeStartElement("DiveComputer");
writer.writeTextElement("Vendor", vendor); writer.writeTextElement("Vendor", QString::fromStdString(data->vendor));
writer.writeTextElement("Product", product); writer.writeTextElement("Product", QString::fromStdString(data->product));
writer.writeEndElement(); writer.writeEndElement();
writer.writeStartElement("Settings"); writer.writeStartElement("Settings");
writer.writeTextElement("CustomText", details.customText); writer.writeTextElement("CustomText", details.customText);

View file

@ -15,11 +15,11 @@
#include "units.h" #include "units.h"
#include "device.h" #include "device.h"
#include "file.h" #include "file.h"
#include "format.h"
#include "divesite.h" #include "divesite.h"
#include "dive.h" #include "dive.h"
#include "divelog.h" #include "divelog.h"
#include "errorhelper.h" #include "errorhelper.h"
#include "ssrf.h"
#include "tag.h" #include "tag.h"
static unsigned int two_bytes_to_int(unsigned char x, unsigned char y) 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 * Returns libdc's equivalent model number (also from g_models) or zero if
* this a manual dive. * 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; dc_descriptor_t *d = NULL;
int i = 0; int i = 0;
while (model != g_models[i].model_num && g_models[i].model_num != 0xEE) while (model != g_models[i].model_num && g_models[i].model_num != 0xEE)
i++; i++;
dev_data->model = copy_string(g_models[i].name); dev_data.model = g_models[i].name;
dev_data->vendor = (const char *)malloc(strlen(g_models[i].name) + 1); dev_data.vendor.clear();
sscanf(g_models[i].name, "%[A-Za-z] ", (char *)dev_data->vendor); for (const char *s = g_models[i].name; isalpha(*s); ++s)
dev_data->product = copy_string(strchr(g_models[i].name, ' ') + 1); 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); d = get_descriptor(g_models[i].type, g_models[i].libdc_num);
if (d) if (d)
dev_data->descriptor = d; dev_data.descriptor = d;
else else
return 0; return 0;
return g_models[i].libdc_num; 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. * 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. * 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) { auto it = std::find_if(tank_info_table.begin(), tank_info_table.end(),
const struct tank_info *ti = &tank_info_table.infos[i]; [size] (const tank_info &info) { return info.ml == size; });
if (ti->ml == size) return it != tank_info_table.end() ? it->name : std::string();
return ti->name;
}
return "";
} }
/* /*
@ -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) static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct divelog *log, char *maxbuf)
{ {
int rc, profile_length, libdc_model; int rc, profile_length, libdc_model;
char *tmp_notes_str = NULL;
unsigned char *tmp_string1 = NULL, unsigned char *tmp_string1 = NULL,
*locality = NULL, *locality = NULL,
*dive_point = NULL, *dive_point = NULL,
*compl_buffer, *compl_buffer,
*membuf = runner; *membuf = runner;
char buffer[1024];
unsigned char tmp_1byte; unsigned char tmp_1byte;
unsigned int tmp_2bytes; unsigned int tmp_2bytes;
unsigned long tmp_4bytes; unsigned long tmp_4bytes;
struct dive_site *ds; std::string tmp_notes_str;
char is_nitrox = 0, is_O2 = 0, is_SCR = 0; char is_nitrox = 0, is_O2 = 0, is_SCR = 0;
device_data_t *devdata = (device_data_t *)calloc(1, sizeof(device_data_t)); device_data_t devdata;
devdata->log = log; devdata.log = log;
/* /*
* Parse byte to byte till next dive entry * 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 * Next, Time in minutes since 00:00
*/ */
read_bytes(2); 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 * 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 * Subsurface only have a location variable, so we have to merge DTrak's
* Locality and Dive points. * Locality and Dive points.
*/ */
snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point); {
ds = get_dive_site_by_name(buffer, log->sites); std::string buffer2 = std::string((char *)locality) + " " + (char *)dive_point;
if (!ds) struct dive_site *ds = log->sites.get_by_name(buffer2);
ds = create_dive_site(buffer, log->sites); if (!ds)
add_dive_to_dive_site(dt_dive, ds); ds = log->sites.create(buffer2);
ds->add_dive(dt_dive);
}
free(locality); free(locality);
locality = NULL; locality = NULL;
free(dive_point); free(dive_point);
@ -241,19 +239,19 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
read_bytes(1); read_bytes(1);
switch (tmp_1byte) { switch (tmp_1byte) {
case 1: case 1:
dt_dive->dc.surface_pressure.mbar = 1013; dt_dive->dcs[0].surface_pressure = 1_atm;
break; break;
case 2: case 2:
dt_dive->dc.surface_pressure.mbar = 932; dt_dive->dcs[0].surface_pressure = 932_mbar;
break; break;
case 3: case 3:
dt_dive->dc.surface_pressure.mbar = 828; dt_dive->dcs[0].surface_pressure = 828_mbar;
break; break;
case 4: case 4:
dt_dive->dc.surface_pressure.mbar = 735; dt_dive->dcs[0].surface_pressure = 735_mbar;
break; break;
default: 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); read_bytes(2);
if (tmp_2bytes != 0x7FFF) 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 * Weather, values table, 0 to 6
* Subsurface don't have this record but we can use tags * Subsurface don't have this record but we can use tags
*/ */
dt_dive->tag_list = NULL; dt_dive->tags.clear();
read_bytes(1); read_bytes(1);
switch (tmp_1byte) { switch (tmp_1byte) {
case 1: 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; break;
case 2: 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; break;
case 3: 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; break;
case 4: 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; break;
case 5: 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; break;
case 6: 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; break;
default: default:
// unknown, do nothing // unknown, do nothing
@ -298,7 +296,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
*/ */
read_bytes(2); read_bytes(2);
if (tmp_2bytes != 0x7FFF) 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 * 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); read_bytes(1);
switch (tmp_1byte) { switch (tmp_1byte) {
case 1: case 1:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit")); dt_dive->suit = "No suit";
break; break;
case 2: case 2:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty")); dt_dive->suit = "Shorty";
break; break;
case 3: case 3:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi")); dt_dive->suit = "Combi";
break; break;
case 4: case 4:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit")); dt_dive->suit = "Wet suit";
break; break;
case 5: case 5:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit")); dt_dive->suit = "Semidry suit";
break; break;
case 6: case 6:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit")); dt_dive->suit = "Dry suit";
break; break;
default: default:
// unknown, do nothing // unknown, do nothing
@ -335,14 +333,14 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
*/ */
read_bytes(2); read_bytes(2);
if (tmp_2bytes != 0x7FFF) { if (tmp_2bytes != 0x7FFF) {
cylinder_t cyl = empty_cylinder; cylinder_t cyl;
cyl.type.size.mliter = tmp_2bytes * 10; cyl.type.size.mliter = tmp_2bytes * 10;
cyl.type.description = cyl_type_by_size(tmp_2bytes * 10); cyl.type.description = cyl_type_by_size(tmp_2bytes * 10);
cyl.start.mbar = 200000; cyl.start = 200_bar;
cyl.gasmix.he.permille = 0; cyl.gasmix.he = 0_percent;
cyl.gasmix.o2.permille = 210; cyl.gasmix.o2 = 21_percent;
cyl.manually_added = true; 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); read_bytes(2);
if (tmp_2bytes != 0x7FFF) 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. * Dive time in minutes.
*/ */
read_bytes(2); read_bytes(2);
if (tmp_2bytes != 0x7FFF) 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 * 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); read_bytes(2);
if (tmp_2bytes != 0x7fff) 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 else
dt_dive->watertemp.mkelvin = 0; dt_dive->watertemp = 0_K;
/* /*
* Air used in bar*100. * Air used in bar*100.
*/ */
read_bytes(2); read_bytes(2);
if (tmp_2bytes != 0x7FFF && dt_dive->cylinders.nr > 0) if (tmp_2bytes != 0x7FFF && dt_dive->cylinders.size() > 0)
get_cylinder(dt_dive, 0)->gas_used.mliter = lrint(get_cylinder(dt_dive, 0)->type.size.mliter * (tmp_2bytes / 100.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 * 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); read_bytes(1);
if (bit_set(tmp_1byte, 2)) 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)) 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)) 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)) 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)) 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)) 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 * Dive Type 2 - Bit table, use tags again
*/ */
read_bytes(1); read_bytes(1);
if (bit_set(tmp_1byte, 0)) { 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; is_nitrox = 1;
} }
if (bit_set(tmp_1byte, 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; 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); read_bytes(1);
if (bit_set(tmp_1byte, 0)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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 * Dive Activity 2 - Bit table, use tags again
*/ */
read_bytes(1); read_bytes(1);
if (bit_set(tmp_1byte, 0)) 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)) 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)) 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)) 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)) 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 * 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); read_bytes(1);
if (tmp_1byte != 0) { if (tmp_1byte != 0) {
read_string(tmp_string1); read_string(tmp_string1);
snprintf(buffer, sizeof(buffer), "%s: %s\n", tmp_notes_str= format_string_std("%s: %s\n",
QT_TRANSLATE_NOOP("gettextFromC", "Other activities"), translate("gettextFromC", "Other activities"),
tmp_string1); tmp_string1);
tmp_notes_str = strdup(buffer);
free(tmp_string1); free(tmp_string1);
} }
@ -464,7 +461,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
read_bytes(1); read_bytes(1);
if (tmp_1byte != 0) { if (tmp_1byte != 0) {
read_string(tmp_string1); read_string(tmp_string1);
dt_dive->buddy = strdup((char *)tmp_string1); dt_dive->buddy = (const char *)tmp_string1;
free(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); read_bytes(1);
if (tmp_1byte != 0) { if (tmp_1byte != 0) {
read_string(tmp_string1); read_string(tmp_string1);
int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s", dt_dive->notes = format_string_std("%s%s:\n%s",
tmp_notes_str ? tmp_notes_str : "", tmp_notes_str.c_str(),
QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"), translate("gettextFromC", "Datatrak/Wlog notes"),
tmp_string1); tmp_string1);
dt_dive->notes = (char *)calloc((len +1), 1);
memcpy(dt_dive->notes, buffer, len);
free(tmp_string1); free(tmp_string1);
} }
free(tmp_notes_str);
/* /*
* Alarms 1 and Alarms2 - Bit tables - Not in Subsurface, we use the profile * 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); libdc_model = dtrak_prepare_data(tmp_1byte, devdata);
if (!libdc_model) if (!libdc_model)
report_error(translate("gettextFromC", "[Warning] Manual dive # %d\n"), dt_dive->number); 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 * 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); compl_buffer = (unsigned char *) calloc(18 + profile_length, 1);
rc = dt_libdc_buffer(membuf, profile_length, libdc_model, compl_buffer); rc = dt_libdc_buffer(membuf, profile_length, libdc_model, compl_buffer);
if (rc == DC_STATUS_SUCCESS) { 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 { } else {
report_error(translate("gettextFromC", "[Error] Out of memory for dive %d. Abort parsing."), dt_dive->number); report_error(translate("gettextFromC", "[Error] Out of memory for dive %d. Abort parsing."), dt_dive->number);
free(compl_buffer); free(compl_buffer);
goto bail; goto bail;
} }
if (is_nitrox && dt_dive->cylinders.nr > 0) if (is_nitrox && dt_dive->cylinders.size() > 0)
get_cylinder(dt_dive, 0)->gasmix.o2.permille = dt_dive->get_cylinder(0)->gasmix.o2.permille =
lrint(membuf[23] & 0x0F ? 20.0 + 2 * (membuf[23] & 0x0F) : 21.0) * 10; lrint(membuf[23] & 0x0F ? 20.0 + 2 * (membuf[23] & 0x0F) : 21.0) * 10;
if (is_O2 && dt_dive->cylinders.nr > 0) if (is_O2 && dt_dive->cylinders.size() > 0)
get_cylinder(dt_dive, 0)->gasmix.o2.permille = membuf[23] * 10; dt_dive->get_cylinder(0)->gasmix.o2.permille = membuf[23] * 10;
free(compl_buffer); free(compl_buffer);
} }
JUMP(membuf, profile_length); 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 * Initialize some dive data not supported by Datatrak/WLog
*/ */
if (!libdc_model) if (!libdc_model)
dt_dive->dc.deviceid = 0; dt_dive->dcs[0].deviceid = 0;
else else
dt_dive->dc.deviceid = 0xffffffff; dt_dive->dcs[0].deviceid = 0xffffffff;
dt_dive->dc.next = NULL; if (!is_SCR && dt_dive->cylinders.size() > 0) {
if (!is_SCR && dt_dive->cylinders.nr > 0) { dt_dive->get_cylinder(0)->end.mbar = dt_dive->get_cylinder(0)->start.mbar -
get_cylinder(dt_dive, 0)->end.mbar = get_cylinder(dt_dive, 0)->start.mbar - ((dt_dive->get_cylinder(0)->gas_used.mliter / dt_dive->get_cylinder(0)->type.size.mliter) * 1000);
((get_cylinder(dt_dive, 0)->gas_used.mliter / get_cylinder(dt_dive, 0)->type.size.mliter) * 1000);
} }
free(devdata);
return (char *)membuf; return (char *)membuf;
bail: bail:
free(locality); free(locality);
free(devdata);
return NULL; 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_viz = offset + 258,
pos_tank_init = offset + 266, pos_tank_init = offset + 266,
pos_suit = offset + 268; 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(); 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); (void)memcpy(wlog_notes_temp, runner + offset, NOTES_LENGTH);
wlog_notes = to_utf8((unsigned char *) wlog_notes_temp); wlog_notes = to_utf8((unsigned char *) wlog_notes_temp);
} }
if (dt_dive->notes && wlog_notes) { if (wlog_notes)
buffer = (char *)calloc (strlen(dt_dive->notes) + strlen(wlog_notes) + 1, 1); dt_dive->notes += wlog_notes;
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);
/* /*
* Weight in Kg * 100 * Weight in Kg * 100
*/ */
tmp = (int) two_bytes_to_int(runner[pos_weight + 1], runner[pos_weight]); tmp = (int) two_bytes_to_int(runner[pos_weight + 1], runner[pos_weight]);
if (tmp != 0x7fff) { if (tmp != 0x7fff) {
weightsystem_t ws = { {tmp * 10}, QT_TRANSLATE_NOOP("gettextFromC", "unknown"), false }; weightsystem_t ws = { {.grams = tmp * 10}, translate("gettextFromC", "unknown"), false };
add_cloned_weightsystem(&dt_dive->weightsystems, ws); 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]); tmp = (int) two_bytes_to_int(runner[pos_tank_init + 1], runner[pos_tank_init]);
if (tmp != 0x7fff) { if (tmp != 0x7fff) {
get_cylinder(dt_dive, 0)->start.mbar = tmp * 10; dt_dive->get_cylinder(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)->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); wlog_suit = to_utf8((unsigned char *) wlog_suit_temp);
} }
if (wlog_suit) if (wlog_suit)
dt_dive->suit = copy_string(wlog_suit); dt_dive->suit = wlog_suit;
free(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(); runner = mem.data();
JUMP(runner, 12); 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)) { while ((i < numdives) && (runner < maxbuf)) {
struct dive *ptdive = alloc_dive(); auto ptdive = std::make_unique<dive>();
runner = dt_dive_parser((unsigned char *)runner, ptdive, log, maxbuf); runner = dt_dive_parser((unsigned char *)runner, ptdive.get(), log, maxbuf);
if (!wl_mem.empty()) if (!wl_mem.empty())
wlog_compl_parser(wl_mem, ptdive, i); wlog_compl_parser(wl_mem, ptdive.get(), i);
if (runner == NULL) { if (runner == NULL) {
report_error("%s", translate("gettextFromC", "Error: no dive")); report_error("%s", translate("gettextFromC", "Error: no dive"));
free(ptdive);
rc = 1; rc = 1;
goto out; goto out;
} else { } else {
record_dive_to_table(ptdive, log->dives); log->dives.record_dive(std::move(ptdive));
} }
i++; i++;
} }
out: out:
sort_dive_table(log->dives); log->dives.sort();
return rc; return rc;
bail: bail:
return 1; return 1;

View file

@ -21,7 +21,6 @@
#include <assert.h> #include <assert.h>
#include "deco.h" #include "deco.h"
#include "ssrf.h"
#include "dive.h" #include "dive.h"
#include "gas.h" #include "gas.h"
#include "subsurface-string.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; 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; int ci = -1;
double ret_tolerance_limit_ambient_pressure = 0.0; double ret_tolerance_limit_ambient_pressure = 0.0;
double gf_high = buehlmann_config.gf_high; double gf_high = buehlmann_config.gf_high;
double gf_low = buehlmann_config.gf_low; 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 lowest_ceiling = 0.0;
double tissue_lowest_ceiling[16]; double tissue_lowest_ceiling[16];
@ -323,7 +322,7 @@ static double calc_surface_phase(double surface_pressure, double he_pressure, do
return 0; return 0;
} }
extern "C" void vpmb_start_gradient(struct deco_state *ds) void vpmb_start_gradient(struct deco_state *ds)
{ {
int ci; 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; int ci;
double n2_b, n2_c; 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; time /= 60.0;
int ci; 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 // 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; int ci;
double gradient; 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 */ /* 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; int ci;
struct gas_pressures pressures;
bool icd = false; 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); gasmix, (double) ccpo2 / 1000.0, divemode);
for (ci = 0; ci < 16; ci++) { 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 #if DECO_CALC_DEBUG
extern "C" void dump_tissues(struct deco_state *ds) void dump_tissues(struct deco_state *ds)
{ {
int ci; int ci;
printf("N2 tissues:"); printf("N2 tissues:");
@ -489,7 +487,7 @@ extern "C" void dump_tissues(struct deco_state *ds)
} }
#endif #endif
extern "C" void clear_vpmb_state(struct deco_state *ds) void clear_vpmb_state(struct deco_state *ds)
{ {
int ci; int ci;
for (ci = 0; ci < 16; 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_he_crushing_pressure[ci] = 0.0;
} }
ds->max_ambient_pressure = 0; ds->max_ambient_pressure = 0;
ds->first_ceiling_pressure.mbar = 0; ds->first_ceiling_pressure = 0_bar;
ds->max_bottom_ceiling_pressure.mbar = 0; 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; int ci;
memset(ds, 0, sizeof(*ds)); *ds = deco_state();
clear_vpmb_state(ds); clear_vpmb_state(ds);
for (ci = 0; ci < 16; ci++) { 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; 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; *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; int depth;
double pressure_delta; double pressure_delta;
@ -553,7 +551,7 @@ extern "C" int deco_allowed_depth(double tissues_tolerance, double surface_press
/* Avoid negative depths */ /* Avoid negative depths */
pressure_delta = tissues_tolerance > surface_pressure ? tissues_tolerance - surface_pressure : 0.0; 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) if (!smooth)
depth = lrint(ceil(depth / DECO_STOPS_MULTIPLIER_MM) * DECO_STOPS_MULTIPLIER_MM); 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; return depth;
} }
extern "C" void set_gf(short gflow, short gfhigh) void set_gf(short gflow, short gfhigh)
{ {
if (gflow != -1) if (gflow != -1)
buehlmann_config.gf_low = (double)gflow / 100.0; 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; 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) if (conservatism < 0)
vpmb_config.conservatism = 0; vpmb_config.conservatism = 0;
@ -582,9 +580,9 @@ extern "C" void set_vpmb_conservatism(short conservatism)
vpmb_config.conservatism = 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_low = buehlmann_config.gf_low;
double gf_high = buehlmann_config.gf_high; double gf_high = buehlmann_config.gf_high;
double gf; double gf;
@ -596,7 +594,7 @@ extern "C" double get_gf(struct deco_state *ds, double ambpressure_bar, const st
return gf; return gf;
} }
extern "C" double regressiona(const struct deco_state *ds) double regressiona(const struct deco_state *ds)
{ {
if (ds->sum1 > 1) { if (ds->sum1 > 1) {
double avxy = ds->sumxy / ds->sum1; double avxy = ds->sumxy / ds->sum1;
@ -609,7 +607,7 @@ extern "C" double regressiona(const struct deco_state *ds)
return 0.0; return 0.0;
} }
extern "C" double regressionb(const struct deco_state *ds) double regressionb(const struct deco_state *ds)
{ {
if (ds->sum1) if (ds->sum1)
return ds->sumy / ds->sum1 - ds->sumx * regressiona(ds) / 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; return 0.0;
} }
extern "C" void reset_regression(struct deco_state *ds) void reset_regression(struct deco_state *ds)
{ {
ds->sum1 = 0; ds->sum1 = 0;
ds->sumxx = ds->sumx = 0L; ds->sumxx = ds->sumx = 0L;
ds->sumy = ds->sumxy = 0.0; 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) if (!ds->plot_depth)
return; return;
@ -632,12 +630,12 @@ extern "C" void update_regression(struct deco_state *ds, const struct dive *dive
ds->sumx += ds->plot_depth; ds->sumx += ds->plot_depth;
ds->sumxx += (long)ds->plot_depth * ds->plot_depth; ds->sumxx += (long)ds->plot_depth * ds->plot_depth;
double n2_gradient, he_gradient, total_gradient; 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]); 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, depth_to_bar(ds->plot_depth, dive), ds->bottom_he_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])) 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]); / (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; double gf = (total_gradient - vpmb_config.other_gases_pressure) / buehlmann_gradient;
ds->sumxy += gf * ds->plot_depth; ds->sumxy += gf * ds->plot_depth;
ds->sumy += gf; ds->sumy += gf;

View file

@ -5,46 +5,43 @@
#include "units.h" #include "units.h"
#include "gas.h" #include "gas.h"
#include "divemode.h" #include "divemode.h"
#include <memory>
#ifdef __cplusplus
extern "C" {
#endif
struct dive; struct dive;
struct divecomputer; struct divecomputer;
struct decostop; struct decostop;
struct deco_state { struct deco_state {
double tissue_n2_sat[16]; double tissue_n2_sat[16] = {};
double tissue_he_sat[16]; double tissue_he_sat[16] = {};
double tolerated_by_tissue[16]; double tolerated_by_tissue[16] = {};
double tissue_inertgas_saturation[16]; double tissue_inertgas_saturation[16] = {};
double buehlmann_inertgas_a[16]; double buehlmann_inertgas_a[16] = {};
double buehlmann_inertgas_b[16]; double buehlmann_inertgas_b[16] = {};
double max_n2_crushing_pressure[16]; double max_n2_crushing_pressure[16] = {};
double max_he_crushing_pressure[16]; double max_he_crushing_pressure[16] = {};
double crushing_onset_tension[16]; // total inert gas tension in the t* moment double crushing_onset_tension[16] = {}; // total inert gas tension in the t* moment
double n2_regen_radius[16]; // rs double n2_regen_radius[16] = {}; // rs
double he_regen_radius[16]; double he_regen_radius[16] = {};
double max_ambient_pressure; // last moment we were descending double max_ambient_pressure = 0.0; // last moment we were descending
double bottom_n2_gradient[16]; double bottom_n2_gradient[16] = {};
double bottom_he_gradient[16]; double bottom_he_gradient[16] = {};
double initial_n2_gradient[16]; double initial_n2_gradient[16] = {};
double initial_he_gradient[16]; double initial_he_gradient[16] = {};
pressure_t first_ceiling_pressure; pressure_t first_ceiling_pressure;
pressure_t max_bottom_ceiling_pressure; pressure_t max_bottom_ceiling_pressure;
int ci_pointing_to_guiding_tissue; int ci_pointing_to_guiding_tissue = 0;
double gf_low_pressure_this_dive; double gf_low_pressure_this_dive = 0.0;
int deco_time; int deco_time = 0;
bool icd_warning; bool icd_warning = false;
int sum1; int sum1 = 0;
long sumx, sumxx; long sumx = 0, sumxx = 0;
double sumy, sumxy; double sumy = 0, sumxy = 0;
int plot_depth; int plot_depth = 0;
}; };
extern const double buehlmann_N2_t_halflife[]; 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 reset_regression(struct deco_state *ds);
extern void update_regression(struct deco_state *ds, const struct dive *dive); extern void update_regression(struct deco_state *ds, const struct dive *dive);
#ifdef __cplusplus
}
// C++ only functions
#include <memory>
struct deco_state_cache { struct deco_state_cache {
// Test if there is cached data // Test if there is cached data
operator bool () { operator bool () {
@ -87,6 +78,4 @@ private:
std::unique_ptr<deco_state> data; std::unique_ptr<deco_state> data;
}; };
#endif
#endif // DECO_H #endif // DECO_H

View file

@ -8,7 +8,7 @@
#include "selection.h" #include "selection.h"
#include "core/settings/qPrefDiveComputer.h" #include "core/settings/qPrefDiveComputer.h"
struct fingerprint_table fingerprint_table; fingerprint_table fingerprints;
static bool same_device(const device &dev1, const device &dev2) 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 bool device::operator<(const device &a) const
{ {
int diff; return std::tie(model, serialNumber) < std::tie(a.model, a.serialNumber);
diff = model.compare(a.model);
if (diff)
return diff < 0;
return serialNumber < 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; return NULL;
const std::vector<device> &dcs = table->devices;
device dev { dc->model, dc->serial }; device dev { dc->model, dc->serial };
auto it = std::lower_bound(dcs.begin(), dcs.end(), dev); auto it = std::lower_bound(table.begin(), table.end(), dev);
return it != dcs.end() && same_device(*it, dev) ? &*it : NULL; 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; return -1;
const struct device *dev = get_device_for_dc(table, dc); const struct device *dev = get_device_for_dc(table, dc);
if (dev) { if (dev) {
auto it = std::lower_bound(table->devices.begin(), table->devices.end(), *dev); auto it = std::lower_bound(table.begin(), table.end(), *dev);
return it - table->devices.begin(); 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); auto it = std::lower_bound(table.begin(), table.end(), dev);
return it != device_table->devices.end() && same_device(*it, *dev); return it != table.end() && same_device(*it, dev);
} }
void device::showchanges(const std::string &n) const void device::showchanges(const std::string &n) const
@ -66,7 +59,7 @@ void device::showchanges(const std::string &n) const
} }
} }
static int addDC(std::vector<device> &dcs, const std::string &m, const std::string &s, const std::string &n) int create_device_node(device_table &dcs, const std::string &m, const std::string &s, const std::string &n)
{ {
if (m.empty() || s.empty()) if (m.empty() || s.empty())
return -1; return -1;
@ -86,220 +79,122 @@ static int addDC(std::vector<device> &dcs, const std::string &m, const std::stri
} }
} }
extern "C" int create_device_node(struct device_table *device_table, const char *model, const char *serial, const char *nickname) int add_to_device_table(device_table &device_table, const struct device &dev)
{ {
return addDC(device_table->devices, model ?: "", serial ?: "", nickname ?: ""); return create_device_node(device_table, dev.model, dev.serialNumber, dev.nickName);
} }
extern "C" int add_to_device_table(struct device_table *device_table, const struct device *dev) int remove_device(device_table &table, const struct device &dev)
{ {
return create_device_node(device_table, dev->model.c_str(), dev->serialNumber.c_str(), dev->nickName.c_str()); auto it = std::lower_bound(table.begin(), table.end(), dev);
} if (it != table.end() && same_device(*it, dev)) {
int idx = it - table.begin();
extern "C" int remove_device(struct device_table *device_table, const struct device *dev) table.erase(it);
{
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);
return idx; return idx;
} else { } else {
return -1; 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; return;
device_table->devices.erase(device_table->devices.begin() + idx); table.erase(table.begin() + idx);
}
extern "C" void clear_device_table(struct device_table *device_table)
{
device_table->devices.clear();
} }
/* Returns whether the given device is used by a selected dive. */ /* 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()) { for (dive *d: getDiveSelection()) {
struct divecomputer *dc; for (auto &dc: d->dcs) {
for_each_dc (d, dc) { if (dc.deviceid == dev.deviceId)
if (dc->deviceid == dev->deviceId)
return true; return true;
} }
} }
return false; 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; 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); const device *existNode = get_device_for_dc(divelog.devices, dc);
if (existNode && !existNode->nickName.empty()) if (existNode && !existNode->nickName.empty())
return existNode->nickName.c_str(); return existNode->nickName;
else else
return dc->model; 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 // managing fingerprint data
bool fingerprint_record::operator<(const fingerprint_record &a) const bool fingerprint_record::operator<(const fingerprint_record &a) const
{ {
if (model == a.model) return std::tie(model, serial) < std::tie(a.model, a.serial);
return serial < a.serial;
return model < a.model;
} }
// annoyingly, the Cressi Edy doesn't support a serial number (it's always 0), but still uses fingerprints // 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 // so we can't bail on the serial number being 0
extern "C" unsigned int get_fingerprint_data(const struct fingerprint_table *table, uint32_t model, uint32_t serial, const unsigned char **fp_out) std::pair<int, const unsigned char *> get_fingerprint_data(const fingerprint_table &table, uint32_t model, uint32_t serial)
{ {
if (model == 0 || fp_out == nullptr) if (model == 0)
return 0; return { 0, nullptr };
struct fingerprint_record fpr = { model, serial }; struct fingerprint_record fpr = { model, serial };
auto it = std::lower_bound(table->fingerprints.begin(), table->fingerprints.end(), fpr); auto it = std::lower_bound(table.begin(), table.end(), fpr);
if (it != table->fingerprints.end() && it->model == model && it->serial == serial) { 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 // 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 // for - so if one is found, we still need to check for equality
if (has_dive(it->fdeviceid, it->fdiveid)) { if (divelog.dives.has_dive(it->fdeviceid, it->fdiveid))
*fp_out = it->raw_data; return { it->fsize, it->raw_data.get() };
return it->fsize;
}
} }
return 0; return { 0, nullptr };
} }
extern "C" void create_fingerprint_node(struct fingerprint_table *table, uint32_t model, uint32_t serial, void create_fingerprint_node(fingerprint_table &table, uint32_t model, uint32_t serial,
const unsigned char *raw_data_in, unsigned int fsize, uint32_t fdeviceid, uint32_t fdiveid) 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 // since raw data can contain \0 we copy this manually, not as string
unsigned char *raw_data = (unsigned char *)malloc(fsize); auto raw_data = std::make_unique<unsigned char []>(fsize);
if (!raw_data) std::copy(raw_data_in, raw_data_in + fsize, raw_data.get());
return;
memcpy(raw_data, raw_data_in, fsize);
struct fingerprint_record fpr = { model, serial, raw_data, fsize, fdeviceid, fdiveid }; struct fingerprint_record fpr = { model, serial, std::move(raw_data), fsize, fdeviceid, fdiveid };
auto it = std::lower_bound(table->fingerprints.begin(), table->fingerprints.end(), fpr); auto it = std::lower_bound(table.begin(), table.end(), fpr);
if (it != table->fingerprints.end() && it->model == model && it->serial == serial) { 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 // 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 // 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 // can update the existing entry; first we free the memory for the stored raw data
free(it->raw_data);
it->fdeviceid = fdeviceid; it->fdeviceid = fdeviceid;
it->fdiveid = fdiveid; it->fdiveid = fdiveid;
it->raw_data = raw_data; it->raw_data = std::move(fpr.raw_data);
it->fsize = fsize; it->fsize = fsize;
} else { } else {
// insert a new one // 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, void create_fingerprint_node_from_hex(fingerprint_table &table, uint32_t model, uint32_t serial,
const char *hex_data, uint32_t fdeviceid, uint32_t fdiveid) 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, create_fingerprint_node(table, model, serial,
(const unsigned char *)raw.constData(), raw.size(), fdeviceid, fdiveid); (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) static char to_hex_digit(unsigned char d)
{ {
return d <= 9 ? d + '0' : d - 10 + 'a'; 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()) std::string res(fsize * 2, ' ');
return std::string(); for (unsigned int i = 0; i < fsize; ++i) {
struct fingerprint_record *fpr = &table->fingerprints[i]; res[2 * i] = to_hex_digit((raw_data[i] >> 4) & 0xf);
std::string res(fpr->fsize * 2, ' '); res[2 * i + 1] = to_hex_digit(raw_data[i] & 0xf);
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);
} }
return res; return res;
} }

View file

@ -3,73 +3,13 @@
#define DEVICE_H #define DEVICE_H
#include <stdint.h> #include <stdint.h>
#include <memory>
#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 <string> #include <string>
#include <vector> #include <vector>
struct divecomputer;
struct dive_table;
struct device { struct device {
bool operator<(const device &a) const; bool operator<(const device &a) const;
void showchanges(const std::string &n) const; void showchanges(const std::string &n) const;
@ -79,28 +19,48 @@ struct device {
uint32_t deviceId; // Always the string hash of the serialNumber uint32_t deviceId; // Always the string hash of the serialNumber
}; };
using device_table = std::vector<device>;
extern int create_device_node(device_table &table, const std::string &model, const std::string &serial, const std::string &nickname);
std::string get_dc_nickname(const struct divecomputer *dc);
extern bool device_used_by_selected_dive(const struct device &dev);
extern const struct device *get_device_for_dc(const device_table &table, const struct divecomputer *dc);
extern int get_or_add_device_for_dc(device_table &table, const struct divecomputer *dc);
extern bool device_exists(const device_table &table, const struct device &dev);
extern int add_to_device_table(device_table &table, const struct device &dev); // returns index
extern int remove_device(device_table &table, const struct device &dev); // returns index or -1 if not found
extern void remove_from_device_table(device_table &table, int idx);
struct fingerprint_record { struct fingerprint_record {
bool operator<(const fingerprint_record &a) const; bool operator<(const fingerprint_record &a) const;
uint32_t model; // model and libdivecomputer serial number to uint32_t model; // model and libdivecomputer serial number to
uint32_t serial; // look up the fingerprint uint32_t serial; // look up the fingerprint
unsigned char *raw_data; // fingerprint data as provided by libdivecomputer std::unique_ptr<unsigned char[]> raw_data; // fingerprint data as provided by libdivecomputer
unsigned int fsize; // size of raw fingerprint data unsigned int fsize; // size of raw fingerprint data
unsigned int fdeviceid; // corresponding deviceid unsigned int fdeviceid; // corresponding deviceid
unsigned int fdiveid; // corresponding diveid unsigned int fdiveid; // corresponding diveid
std::string get_data() const; // As hex-string
}; };
struct device_table { using fingerprint_table = std::vector<fingerprint_record>;
// Keep the dive computers in a vector sorted by (model, serial)
std::vector<device> devices;
};
struct fingerprint_table { // global device table
// Keep the fingerprint records in a vector sorted by (model, serial) - these are uint32_t here extern fingerprint_table fingerprints;
std::vector<fingerprint_record> fingerprints;
};
std::string fp_get_data(struct fingerprint_table *table, unsigned int i); // create fingerprint entry - raw data remains owned by caller
extern void create_fingerprint_node(fingerprint_table &table, uint32_t model, uint32_t serial,
const unsigned char *raw_data, unsigned int fsize, uint32_t fdeviceid, uint32_t fdiveid);
extern void create_fingerprint_node_from_hex(fingerprint_table &table, uint32_t model, uint32_t serial,
const std::string &hex_data, uint32_t fdeviceid, uint32_t fdiveid);
// look up the fingerprint for model/serial - returns the number of bytes in the fingerprint; memory owned by the table
extern std::pair <int, const unsigned char *> get_fingerprint_data(const fingerprint_table &table, uint32_t model, uint32_t serial);
extern int is_default_dive_computer_device(const char *);
typedef void (*device_callback_t)(const char *name, void *userdata);
extern int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport);
#endif
#endif // DEVICE_H #endif // DEVICE_H

File diff suppressed because it is too large Load diff

View file

@ -7,14 +7,14 @@
#include "divemode.h" #include "divemode.h"
#include "divecomputer.h" #include "divecomputer.h"
#include "equipment.h" #include "equipment.h"
#include "picture.h" #include "picture.h" // TODO: remove
#include "tag.h"
#include <stdio.h> #include <array>
#include <stdlib.h> #include <memory>
#include <optional>
#ifdef __cplusplus #include <string>
extern "C" { #include <vector>
#endif
extern int last_xml_version; extern int last_xml_version;
@ -22,44 +22,119 @@ extern const char *divemode_text_ui[];
extern const char *divemode_text[]; extern const char *divemode_text[];
struct dive_site; struct dive_site;
struct dive_site_table;
struct dive_table; struct dive_table;
struct dive_trip; struct dive_trip;
struct full_text_cache; struct full_text_cache;
struct event; struct event;
struct trip_table; struct trip_table;
/* A unique_ptr that will not be copied if the parent class is copied.
* This is used to keep a pointer to the fulltext cache and avoid
* having it copied when the dive is copied, since the new dive is
* not (yet) registered in the fulltext system. Quite hackish.
*/
template<typename T>
struct non_copying_unique_ptr : public std::unique_ptr<T> {
using std::unique_ptr<T>::unique_ptr;
using std::unique_ptr<T>::operator=;
non_copying_unique_ptr(const non_copying_unique_ptr<T> &) { }
void operator=(const non_copying_unique_ptr<T> &) { }
};
struct dive { struct dive {
struct dive_trip *divetrip; struct dive_trip *divetrip = nullptr;
timestamp_t when; timestamp_t when = 0;
struct dive_site *dive_site; struct dive_site *dive_site = nullptr;
char *notes; std::string notes;
char *diveguide, *buddy; std::string diveguide, buddy;
struct cylinder_table cylinders; std::string suit;
struct weightsystem_table weightsystems; cylinder_table cylinders;
char *suit; weightsystem_table weightsystems;
int number; int number = 0;
int rating; int rating = 0;
int wavesize, current, visibility, surge, chill; /* 0 - 5 star ratings */ int wavesize = 0, current = 0, visibility = 0, surge = 0, chill = 0; /* 0 - 5 star ratings */
int sac, otu, cns, maxcns; int sac = 0, otu = 0, cns = 0, maxcns = 0;
/* Calculated based on dive computer data */ /* Calculated based on dive computer data */
temperature_t mintemp, maxtemp, watertemp, airtemp; temperature_t mintemp, maxtemp, watertemp, airtemp;
depth_t maxdepth, meandepth; depth_t maxdepth, meandepth;
pressure_t surface_pressure; pressure_t surface_pressure;
duration_t duration; duration_t duration;
int salinity; // kg per 10000 l int salinity = 0; // kg per 10000 l
int user_salinity; // water density reflecting a user-specified type int user_salinity = 0; // water density reflecting a user-specified type
struct tag_entry *tag_list; tag_list tags;
struct divecomputer dc; std::vector<divecomputer> dcs; // Attn: pointers to divecomputers are not stable!
int id; // unique ID for this dive int id = 0; // unique ID for this dive
struct picture_table pictures; picture_table pictures;
unsigned char git_id[20]; std::array<unsigned char, 20> git_id = {};
bool notrip; /* Don't autogroup this dive to a trip */ bool notrip = false; /* Don't autogroup this dive to a trip */
bool selected; bool selected = false;
bool hidden_by_filter; bool hidden_by_filter = false;
struct full_text_cache *full_text; /* word cache for full text search */ non_copying_unique_ptr<full_text_cache> full_text; /* word cache for full text search */
bool invalid; bool invalid = false;
dive();
~dive();
dive(const dive &);
dive(dive &&);
dive &operator=(const dive &);
void invalidate_cache();
bool cache_is_valid() const;
struct divecomputer *get_dc(int nr);
const struct divecomputer *get_dc(int nr) const;
void clear();
int number_of_computers() const;
void fixup_no_cylinder(); /* to fix cylinders, we need the divelist (to calculate cns) */
timestamp_t endtime() const; /* maximum over divecomputers (with samples) */
duration_t totaltime() const; /* maximum over divecomputers (with samples) */
temperature_t dc_airtemp() const; /* average over divecomputers */
temperature_t dc_watertemp() const; /* average over divecomputers */
pressure_t get_surface_pressure() const;
struct get_maximal_gas_result { int o2_p; int he_p; int o2low_p; };
get_maximal_gas_result get_maximal_gas() const;
bool is_planned() const;
bool is_logged() const;
bool likely_same(const struct dive &b) const;
bool is_cylinder_used(int idx) const;
bool is_cylinder_prot(int idx) const;
int get_cylinder_index(const struct event &ev) const;
bool has_gaschange_event(const struct divecomputer *dc, int idx) const;
struct gasmix get_gasmix_from_event(const struct event &ev) const;
struct gasmix get_gasmix_at_time(const struct divecomputer &dc, duration_t time) const;
cylinder_t *get_cylinder(int idx);
cylinder_t *get_or_create_cylinder(int idx);
const cylinder_t *get_cylinder(int idx) const;
weight_t total_weight() const;
int get_salinity() const;
bool time_during_dive_with_offset(timestamp_t when, timestamp_t offset) const;
std::string get_country() const;
std::string get_location() const;
int depth_to_mbar(int depth) const;
double depth_to_mbarf(int depth) const;
double depth_to_bar(int depth) const;
double depth_to_atm(int depth) const;
int rel_mbar_to_depth(int mbar) const;
int mbar_to_depth(int mbar) const;
pressure_t calculate_surface_pressure() const;
pressure_t un_fixup_surface_pressure() const;
depth_t gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const;
depth_t gas_mnd(struct gasmix mix, depth_t end, int roundto) const;
fraction_t best_o2(depth_t depth, bool in_planner) const;
fraction_t best_he(depth_t depth, bool o2narcotic, fraction_t fo2) const;
bool dive_has_gps_location() const;
location_t get_gps_location() const;
/* Don't call directly, use dive_table::merge_dives()! */
static std::unique_ptr<dive> create_merged_dive(const struct dive &a, const struct dive &b, int offset, bool prefer_downloaded);
}; };
/* For the top-level list: an entry is either a dive or a trip */ /* 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; struct dive_trip *trip;
}; };
extern void invalidate_dive_cache(struct dive *dive); extern void cylinder_renumber(struct dive &dive, int mapping[]);
extern bool dive_cache_is_valid(const struct dive *dive); 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); /* Data stored when copying a dive */
extern void cylinder_renumber(struct dive *dive, int mapping[]); struct dive_paste_data {
extern int same_gasmix_cylinder(const cylinder_t *cyl, int cylid, const struct dive *dive, bool check_unused); std::optional<uint32_t> divesite; // We save the uuid not a pointer, because the
// user might copy and then delete the dive site.
/* when selectively copying dive information, which parts should be copied? */ std::optional<std::string> notes;
struct dive_components { std::optional<std::string> diveguide;
unsigned int divesite : 1; std::optional<std::string> buddy;
unsigned int notes : 1; std::optional<std::string> suit;
unsigned int diveguide : 1; std::optional<int> rating;
unsigned int buddy : 1; std::optional<int> visibility;
unsigned int suit : 1; std::optional<int> wavesize;
unsigned int rating : 1; std::optional<int> current;
unsigned int visibility : 1; std::optional<int> surge;
unsigned int wavesize : 1; std::optional<int> chill;
unsigned int current : 1; std::optional<tag_list> tags;
unsigned int surge : 1; std::optional<cylinder_table> cylinders;
unsigned int chill : 1; std::optional<weightsystem_table> weights;
unsigned int tags : 1; std::optional<int> number;
unsigned int cylinders : 1; std::optional<timestamp_t> when;
unsigned int weights : 1;
unsigned int number : 1;
unsigned int when : 1;
}; };
extern bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx); extern std::unique_ptr<dive> clone_make_first_dc(const struct dive &d, int dc_number);
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 int save_dives(const char *filename); extern int save_dives(const char *filename);
extern int save_dives_logic(const char *filename, bool select_only, bool anonymize); 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 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); extern int save_dive_sites_logic(const char *filename, const struct dive_site *sites[], int nr_sites, bool anonymize);
struct membuffer; 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 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 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 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 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_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 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 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 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 void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration); 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); 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); 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 /* Make pointers to dive and dive_trip "Qt metatypes" so that they can be passed through
* QVariants and through QML. * QVariants and through QML.
*/ */
#include <QObject> #include <QObject>
#include <string>
Q_DECLARE_METATYPE(struct dive *); Q_DECLARE_METATYPE(struct dive *);
extern std::string existing_filename; extern std::string existing_filename;
#endif
#endif // DIVE_H #endif // DIVE_H

View file

@ -1,570 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include "divecomputer.h"
#include "event.h"
#include "extradata.h"
#include "pref.h"
#include "sample.h"
#include "structured_list.h"
#include "subsurface-string.h"
#include <string.h>
#include <stdlib.h>
/*
* Good fake dive profiles are hard.
*
* "depthtime" is the integral of the dive depth over
* time ("area" of the dive profile). We want that
* area to match the average depth (avg_d*max_t).
*
* To do that, we generate a 6-point profile:
*
* (0, 0)
* (t1, max_d)
* (t2, max_d)
* (t3, d)
* (t4, d)
* (max_t, 0)
*
* with the same ascent/descent rates between the
* different depths.
*
* NOTE: avg_d, max_d and max_t are given constants.
* The rest we can/should play around with to get a
* good-looking profile.
*
* That six-point profile gives a total area of:
*
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3)
*
* And the "same ascent/descent rates" requirement
* gives us (time per depth must be same):
*
* t1 / max_d = (t3-t2) / (max_d-d)
* t1 / max_d = (max_t-t4) / d
*
* We also obviously require:
*
* 0 <= t1 <= t2 <= t3 <= t4 <= max_t
*
* Let us call 'd_frac = d / max_d', and we get:
*
* Total area must match average depth-time:
*
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t
* max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t
* max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d
* t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d)
*
* and descent slope must match ascent slopes:
*
* t1 / max_d = (t3-t2) / (max_d*(1-d_frac))
* t1 = (t3-t2)/(1-d_frac)
*
* and
*
* t1 / max_d = (max_t-t4) / (max_d*d_frac)
* t1 = (max_t-t4)/d_frac
*
* In general, we have more free variables than we have constraints,
* but we can aim for certain basics, like a good ascent slope.
*/
static int fill_samples(struct sample *s, int max_d, int avg_d, int max_t, double slope, double d_frac)
{
double t_frac = max_t * (1 - avg_d / (double)max_d);
int t1 = lrint(max_d / slope);
int t4 = lrint(max_t - t1 * d_frac);
int t3 = lrint(t4 - (t_frac - t1) / (1 - d_frac));
int t2 = lrint(t3 - t1 * (1 - d_frac));
if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t)
return 0;
s[1].time.seconds = t1;
s[1].depth.mm = max_d;
s[2].time.seconds = t2;
s[2].depth.mm = max_d;
s[3].time.seconds = t3;
s[3].depth.mm = lrint(max_d * d_frac);
s[4].time.seconds = t4;
s[4].depth.mm = lrint(max_d * d_frac);
return 1;
}
/* we have no average depth; instead of making up a random average depth
* we should assume either a PADI rectangular profile (for short and/or
* shallow dives) or more reasonably a six point profile with a 3 minute
* safety stop at 5m */
static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope)
{
// shallow or short dives are just trapecoids based on the given slope
if (max_d < 10000 || max_t < 600) {
s[1].time.seconds = lrint(max_d / slope);
s[1].depth.mm = max_d;
s[2].time.seconds = max_t - lrint(max_d / slope);
s[2].depth.mm = max_d;
} else {
s[1].time.seconds = lrint(max_d / slope);
s[1].depth.mm = max_d;
s[2].time.seconds = max_t - lrint(max_d / slope) - 180;
s[2].depth.mm = max_d;
s[3].time.seconds = max_t - lrint(5000 / slope) - 180;
s[3].depth.mm = 5000;
s[4].time.seconds = max_t - lrint(5000 / slope);
s[4].depth.mm = 5000;
}
}
void fake_dc(struct divecomputer *dc)
{
alloc_samples(dc, 6);
struct sample *fake = dc->sample;
int i;
dc->samples = 6;
/* The dive has no samples, so create a few fake ones */
int max_t = dc->duration.seconds;
int max_d = dc->maxdepth.mm;
int avg_d = dc->meandepth.mm;
memset(fake, 0, 6 * sizeof(struct sample));
fake[5].time.seconds = max_t;
for (i = 0; i < 6; i++) {
fake[i].bearing.degrees = -1;
fake[i].ndl.seconds = -1;
}
if (!max_t || !max_d) {
dc->samples = 0;
return;
}
/* Set last manually entered time to the total dive length */
dc->last_manual_time = dc->duration;
/*
* We want to fake the profile so that the average
* depth ends up correct. However, in the absence of
* a reasonable average, let's just make something
* up. Note that 'avg_d == max_d' is _not_ a reasonable
* average.
* We explicitly treat avg_d == 0 differently */
if (avg_d == 0) {
/* we try for a sane slope, but bow to the insanity of
* the user supplied data */
fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, (double)prefs.ascratelast6m));
if (fake[3].time.seconds == 0) { // just a 4 point profile
dc->samples = 4;
fake[3].time.seconds = max_t;
}
return;
}
if (avg_d < max_d / 10 || avg_d >= max_d) {
avg_d = (max_d + 10000) / 3;
if (avg_d > max_d)
avg_d = max_d * 2 / 3;
}
if (!avg_d)
avg_d = 1;
/*
* Ok, first we try a basic profile with a specific ascent
* rate (5 meters per minute) and d_frac (1/3).
*/
if (fill_samples(fake, max_d, avg_d, max_t, (double)prefs.ascratelast6m, 0.33))
return;
/*
* Ok, assume that didn't work because we cannot make the
* average come out right because it was a quick deep dive
* followed by a much shallower region
*/
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10))
return;
/*
* Uhhuh. That didn't work. We'd need to find a good combination that
* satisfies our constraints. Currently, we don't, we just give insane
* slopes.
*/
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01))
return;
/* Even that didn't work? Give up, there's something wrong */
}
/* Find the divemode at time 'time' (in seconds) into the dive. Sequentially step through the divemode-change events,
* saving the dive mode for each event. When the events occur AFTER 'time' seconds, the last stored divemode
* is returned. This function is self-tracking, relying on setting the event pointer 'evp' so that, in each iteration
* that calls this function, the search does not have to begin at the first event of the dive */
enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode)
{
const struct event *ev = *evp;
if (dc) {
if (*divemode == UNDEF_COMP_TYPE) {
*divemode = dc->divemode;
ev = get_next_event(dc->events, "modechange");
}
} else {
ev = NULL;
}
while (ev && ev->time.seconds < time) {
*divemode = (enum divemode_t) ev->value;
ev = get_next_event(ev->next, "modechange");
}
*evp = ev;
return *divemode;
}
/* helper function to make it easier to work with our structures
* we don't interpolate here, just use the value from the last sample up to that time */
int get_depth_at_time(const struct divecomputer *dc, unsigned int time)
{
int depth = 0;
if (dc && dc->sample)
for (int i = 0; i < dc->samples; i++) {
if (dc->sample[i].time.seconds > time)
break;
depth = dc->sample[i].depth.mm;
}
return depth;
}
/* The first divecomputer is embedded in the dive structure. Free its data but not
* the structure itself. For all remainding dcs in the list, free data *and* structures. */
void free_dive_dcs(struct divecomputer *dc)
{
free_dc_contents(dc);
STRUCTURED_LIST_FREE(struct divecomputer, dc->next, free_dc);
}
/* make room for num samples; if not enough space is available, the sample
* array is reallocated and the existing samples are copied. */
void alloc_samples(struct divecomputer *dc, int num)
{
if (num > dc->alloc_samples) {
dc->alloc_samples = (num * 3) / 2 + 10;
dc->sample = realloc(dc->sample, dc->alloc_samples * sizeof(struct sample));
if (!dc->sample)
dc->samples = dc->alloc_samples = 0;
}
}
void free_samples(struct divecomputer *dc)
{
if (dc) {
free(dc->sample);
dc->sample = 0;
dc->samples = 0;
dc->alloc_samples = 0;
}
}
struct sample *prepare_sample(struct divecomputer *dc)
{
if (dc) {
int nr = dc->samples;
struct sample *sample;
alloc_samples(dc, nr + 1);
if (!dc->sample)
return NULL;
sample = dc->sample + nr;
memset(sample, 0, sizeof(*sample));
// Copy the sensor numbers - but not the pressure values
// from the previous sample if any.
if (nr) {
for (int idx = 0; idx < MAX_SENSORS; idx++)
sample->sensor[idx] = sample[-1].sensor[idx];
}
// Init some values with -1
sample->bearing.degrees = -1;
sample->ndl.seconds = -1;
return sample;
}
return NULL;
}
void finish_sample(struct divecomputer *dc)
{
dc->samples++;
}
struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc)
{
struct sample *p = prepare_sample(dc);
if (p) {
*p = *sample;
p->time.seconds = time;
finish_sample(dc);
}
return p;
}
/*
* Calculate how long we were actually under water, and the average
* depth while under water.
*
* This ignores any surface time in the middle of the dive.
*/
void fixup_dc_duration(struct divecomputer *dc)
{
int duration, i;
int lasttime, lastdepth, depthtime;
duration = 0;
lasttime = 0;
lastdepth = 0;
depthtime = 0;
for (i = 0; i < dc->samples; i++) {
struct sample *sample = dc->sample + i;
int time = sample->time.seconds;
int depth = sample->depth.mm;
/* We ignore segments at the surface */
if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) {
duration += time - lasttime;
depthtime += (time - lasttime) * (depth + lastdepth) / 2;
}
lastdepth = depth;
lasttime = time;
}
if (duration) {
dc->duration.seconds = duration;
dc->meandepth.mm = (depthtime + duration / 2) / duration;
}
}
/*
* What do the dive computers say the water temperature is?
* (not in the samples, but as dc property for dcs that support that)
*/
unsigned int dc_watertemp(const struct divecomputer *dc)
{
int sum = 0, nr = 0;
do {
if (dc->watertemp.mkelvin) {
sum += dc->watertemp.mkelvin;
nr++;
}
} while ((dc = dc->next) != NULL);
if (!nr)
return 0;
return (sum + nr / 2) / nr;
}
/*
* What do the dive computers say the air temperature is?
*/
unsigned int dc_airtemp(const struct divecomputer *dc)
{
int sum = 0, nr = 0;
do {
if (dc->airtemp.mkelvin) {
sum += dc->airtemp.mkelvin;
nr++;
}
} while ((dc = dc->next) != NULL);
if (!nr)
return 0;
return (sum + nr / 2) / nr;
}
/* copies all events in this dive computer */
void copy_events(const struct divecomputer *s, struct divecomputer *d)
{
const struct event *ev;
struct event **pev;
if (!s || !d)
return;
ev = s->events;
pev = &d->events;
while (ev != NULL) {
struct event *new_ev = clone_event(ev);
*pev = new_ev;
pev = &new_ev->next;
ev = ev->next;
}
*pev = NULL;
}
void copy_samples(const struct divecomputer *s, struct divecomputer *d)
{
/* instead of carefully copying them one by one and calling add_sample
* over and over again, let's just copy the whole blob */
if (!s || !d)
return;
int nr = s->samples;
d->samples = nr;
d->alloc_samples = nr;
// We expect to be able to read the memory in the other end of the pointer
// if its a valid pointer, so don't expect malloc() to return NULL for
// zero-sized malloc, do it ourselves.
d->sample = NULL;
if(!nr)
return;
d->sample = malloc(nr * sizeof(struct sample));
if (d->sample)
memcpy(d->sample, s->sample, nr * sizeof(struct sample));
}
void add_event_to_dc(struct divecomputer *dc, struct event *ev)
{
struct event **p;
p = &dc->events;
/* insert in the sorted list of events */
while (*p && (*p)->time.seconds <= ev->time.seconds)
p = &(*p)->next;
ev->next = *p;
*p = ev;
}
struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name)
{
struct event *ev = create_event(time, type, flags, value, name);
if (!ev)
return NULL;
add_event_to_dc(dc, ev);
return ev;
}
/* Substitutes an event in a divecomputer for another. No reordering is performed! */
void swap_event(struct divecomputer *dc, struct event *from, struct event *to)
{
for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) {
if (*ep == from) {
to->next = from->next;
*ep = to;
from->next = NULL; // For good measure.
break;
}
}
}
/* Remove given event from dive computer. Does *not* free the event. */
void remove_event_from_dc(struct divecomputer *dc, struct event *event)
{
for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) {
if (*ep == event) {
*ep = event->next;
event->next = NULL; // For good measure.
break;
}
}
}
void add_extra_data(struct divecomputer *dc, const char *key, const char *value)
{
struct extra_data **ed = &dc->extra_data;
if (!strcasecmp(key, "Serial")) {
dc->deviceid = calculate_string_hash(value);
dc->serial = strdup(value);
}
if (!strcmp(key, "FW Version")) {
dc->fw_version = strdup(value);
}
while (*ed)
ed = &(*ed)->next;
*ed = malloc(sizeof(struct extra_data));
if (*ed) {
(*ed)->key = strdup(key);
(*ed)->value = strdup(value);
(*ed)->next = NULL;
}
}
/*
* Match two dive computer entries against each other, and
* tell if it's the same dive. Return 0 if "don't know",
* positive for "same dive" and negative for "definitely
* not the same dive"
*/
int match_one_dc(const struct divecomputer *a, const struct divecomputer *b)
{
/* Not same model? Don't know if matching.. */
if (!a->model || !b->model)
return 0;
if (strcasecmp(a->model, b->model))
return 0;
/* Different device ID's? Don't know */
if (a->deviceid != b->deviceid)
return 0;
/* Do we have dive IDs? */
if (!a->diveid || !b->diveid)
return 0;
/*
* If they have different dive ID's on the same
* dive computer, that's a definite "same or not"
*/
return a->diveid == b->diveid && a->when == b->when ? 1 : -1;
}
static void free_extra_data(struct extra_data *ed)
{
free((void *)ed->key);
free((void *)ed->value);
}
void free_dc_contents(struct divecomputer *dc)
{
free(dc->sample);
free((void *)dc->model);
free((void *)dc->serial);
free((void *)dc->fw_version);
free_events(dc->events);
STRUCTURED_LIST_FREE(struct extra_data, dc->extra_data, free_extra_data);
}
void free_dc(struct divecomputer *dc)
{
free_dc_contents(dc);
free(dc);
}
static const char *planner_dc_name = "planned dive";
bool is_dc_planner(const struct divecomputer *dc)
{
return dc && same_string(dc->model, planner_dc_name);
}
void make_planner_dc(struct divecomputer *dc)
{
free((void *)dc->model);
dc->model = strdup(planner_dc_name);
}
const char *manual_dc_name = "manually added dive";
bool is_dc_manually_added_dive(const struct divecomputer *dc)
{
return dc && same_string(dc->model, manual_dc_name);
}
void make_manually_added_dive_dc(struct divecomputer *dc)
{
free((void *)dc->model);
dc->model = strdup(manual_dc_name);
}

400
core/divecomputer.cpp Normal file
View file

@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0
#include "divecomputer.h"
#include "errorhelper.h"
#include "event.h"
#include "extradata.h"
#include "pref.h"
#include "sample.h"
#include "subsurface-string.h"
#include <string.h>
#include <stdlib.h>
#include <tuple>
divecomputer::divecomputer() = default;
divecomputer::~divecomputer() = default;
divecomputer::divecomputer(const divecomputer &) = default;
divecomputer::divecomputer(divecomputer &&) = default;
divecomputer &divecomputer::operator=(const divecomputer &) = default;
/*
* Good fake dive profiles are hard.
*
* "depthtime" is the integral of the dive depth over
* time ("area" of the dive profile). We want that
* area to match the average depth (avg_d*max_t).
*
* To do that, we generate a 6-point profile:
*
* (0, 0)
* (t1, max_d)
* (t2, max_d)
* (t3, d)
* (t4, d)
* (max_t, 0)
*
* with the same ascent/descent rates between the
* different depths.
*
* NOTE: avg_d, max_d and max_t are given constants.
* The rest we can/should play around with to get a
* good-looking profile.
*
* That six-point profile gives a total area of:
*
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3)
*
* And the "same ascent/descent rates" requirement
* gives us (time per depth must be same):
*
* t1 / max_d = (t3-t2) / (max_d-d)
* t1 / max_d = (max_t-t4) / d
*
* We also obviously require:
*
* 0 <= t1 <= t2 <= t3 <= t4 <= max_t
*
* Let us call 'd_frac = d / max_d', and we get:
*
* Total area must match average depth-time:
*
* (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t
* max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t
* max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d
* t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d)
*
* and descent slope must match ascent slopes:
*
* t1 / max_d = (t3-t2) / (max_d*(1-d_frac))
* t1 = (t3-t2)/(1-d_frac)
*
* and
*
* t1 / max_d = (max_t-t4) / (max_d*d_frac)
* t1 = (max_t-t4)/d_frac
*
* In general, we have more free variables than we have constraints,
* but we can aim for certain basics, like a good ascent slope.
*/
static int fill_samples(std::vector<sample> &s, int max_d, int avg_d, int max_t, double slope, double d_frac)
{
double t_frac = max_t * (1 - avg_d / (double)max_d);
int t1 = lrint(max_d / slope);
int t4 = lrint(max_t - t1 * d_frac);
int t3 = lrint(t4 - (t_frac - t1) / (1 - d_frac));
int t2 = lrint(t3 - t1 * (1 - d_frac));
if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t)
return 0;
s[1].time.seconds = t1;
s[1].depth.mm = max_d;
s[2].time.seconds = t2;
s[2].depth.mm = max_d;
s[3].time.seconds = t3;
s[3].depth.mm = lrint(max_d * d_frac);
s[4].time.seconds = t4;
s[4].depth.mm = lrint(max_d * d_frac);
return 1;
}
/* we have no average depth; instead of making up a random average depth
* we should assume either a PADI rectangular profile (for short and/or
* shallow dives) or more reasonably a six point profile with a 3 minute
* safety stop at 5m */
static void fill_samples_no_avg(std::vector<sample> &s, int max_d, int max_t, double slope)
{
// shallow or short dives are just trapecoids based on the given slope
if (max_d < 10000 || max_t < 600) {
s[1].time.seconds = lrint(max_d / slope);
s[1].depth.mm = max_d;
s[2].time.seconds = max_t - lrint(max_d / slope);
s[2].depth.mm = max_d;
} else {
s[1].time.seconds = lrint(max_d / slope);
s[1].depth.mm = max_d;
s[2].time.seconds = max_t - lrint(max_d / slope) - 180;
s[2].depth.mm = max_d;
s[3].time.seconds = max_t - lrint(5000 / slope) - 180;
s[3].depth = 5_m;
s[4].time.seconds = max_t - lrint(5000 / slope);
s[4].depth = 5_m;
}
}
void fake_dc(struct divecomputer *dc)
{
/* The dive has no samples, so create a few fake ones */
int max_t = dc->duration.seconds;
int max_d = dc->maxdepth.mm;
int avg_d = dc->meandepth.mm;
if (!max_t || !max_d) {
dc->samples.clear();
return;
}
std::vector<struct sample> &fake = dc->samples;
fake.resize(6);
fake[5].time.seconds = max_t;
for (int i = 0; i < 6; i++) {
fake[i].bearing.degrees = -1;
fake[i].ndl.seconds = -1;
}
/* Set last manually entered time to the total dive length */
dc->last_manual_time = dc->duration;
/*
* We want to fake the profile so that the average
* depth ends up correct. However, in the absence of
* a reasonable average, let's just make something
* up. Note that 'avg_d == max_d' is _not_ a reasonable
* average.
* We explicitly treat avg_d == 0 differently */
if (avg_d == 0) {
/* we try for a sane slope, but bow to the insanity of
* the user supplied data */
fill_samples_no_avg(fake, max_d, max_t, std::max(2.0 * max_d / max_t, (double)prefs.ascratelast6m));
if (fake[3].time.seconds == 0) { // just a 4 point profile
dc->samples.resize(4);
fake[3].time.seconds = max_t;
}
return;
}
if (avg_d < max_d / 10 || avg_d >= max_d) {
avg_d = (max_d + 10000) / 3;
if (avg_d > max_d)
avg_d = max_d * 2 / 3;
}
if (!avg_d)
avg_d = 1;
/*
* Ok, first we try a basic profile with a specific ascent
* rate (5 meters per minute) and d_frac (1/3).
*/
if (fill_samples(fake, max_d, avg_d, max_t, (double)prefs.ascratelast6m, 0.33))
return;
/*
* Ok, assume that didn't work because we cannot make the
* average come out right because it was a quick deep dive
* followed by a much shallower region
*/
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10))
return;
/*
* Uhhuh. That didn't work. We'd need to find a good combination that
* satisfies our constraints. Currently, we don't, we just give insane
* slopes.
*/
if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01))
return;
/* Even that didn't work? Give up, there's something wrong */
}
divemode_loop::divemode_loop(const struct divecomputer &dc) :
last(dc.divemode), loop("modechange", dc)
{
/* on first invocation, get first event (if any) */
ev = loop.next();
}
divemode_t divemode_loop::at(int time)
{
while (ev && ev->time.seconds <= time) {
last = static_cast<divemode_t>(ev->value);
ev = loop.next();
}
return last;
}
/* helper function to make it easier to work with our structures
* we don't interpolate here, just use the value from the last sample up to that time */
int get_depth_at_time(const struct divecomputer *dc, unsigned int time)
{
int depth = 0;
if (dc) {
for (const auto &sample: dc->samples) {
if (sample.time.seconds > (int)time)
break;
depth = sample.depth.mm;
}
}
return depth;
}
struct sample *prepare_sample(struct divecomputer *dc)
{
if (dc) {
dc->samples.emplace_back();
auto &sample = dc->samples.back();
// Copy the sensor numbers - but not the pressure values
// from the previous sample if any.
if (dc->samples.size() >= 2) {
auto &prev = dc->samples[dc->samples.size() - 2];
for (int idx = 0; idx < MAX_SENSORS; idx++)
sample.sensor[idx] = prev.sensor[idx];
}
// Init some values with -1
sample.bearing.degrees = -1;
sample.ndl.seconds = -1;
return &sample;
}
return NULL;
}
void append_sample(const struct sample &sample, struct divecomputer *dc)
{
dc->samples.push_back(sample);
}
/*
* Calculate how long we were actually under water, and the average
* depth while under water.
*
* This ignores any surface time in the middle of the dive.
*/
void fixup_dc_duration(struct divecomputer &dc)
{
int duration = 0;
int lasttime = 0, lastdepth = 0, depthtime = 0;
for (const auto &sample: dc.samples) {
int time = sample.time.seconds;
int depth = sample.depth.mm;
/* Do we *have* a depth? */
if (depth < 0)
continue;
/* We ignore segments at the surface */
if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) {
duration += time - lasttime;
depthtime += (time - lasttime) * (depth + lastdepth) / 2;
}
lastdepth = depth;
lasttime = time;
}
if (duration) {
dc.duration.seconds = duration;
dc.meandepth.mm = (depthtime + duration / 2) / duration;
}
}
static bool operator<(const event &ev1, const event &ev2)
{
return std::tie(ev1.time.seconds, ev1.name) <
std::tie(ev2.time.seconds, ev2.name);
}
int add_event_to_dc(struct divecomputer *dc, struct event ev)
{
// Do a binary search for insertion point
auto it = std::lower_bound(dc->events.begin(), dc->events.end(), ev);
int idx = it - dc->events.begin();
dc->events.insert(it, ev);
return idx;
}
struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const std::string &name)
{
struct event ev(time, type, flags, value, name);
int idx = add_event_to_dc(dc, std::move(ev));
return &dc->events[idx];
}
/* Remove given event from dive computer. Returns the removed event. */
struct event remove_event_from_dc(struct divecomputer *dc, int idx)
{
if (idx < 0 || static_cast<size_t>(idx) > dc->events.size()) {
report_info("removing invalid event %d", idx);
return event();
}
event res = std::move(dc->events[idx]);
dc->events.erase(dc->events.begin() + idx);
return res;
}
struct event *get_event(struct divecomputer *dc, int idx)
{
if (idx < 0 || static_cast<size_t>(idx) > dc->events.size()) {
report_info("accessing invalid event %d", idx);
return nullptr;
}
return &dc->events[idx];
}
void add_extra_data(struct divecomputer *dc, const std::string &key, const std::string &value)
{
if (key == "Serial") {
dc->deviceid = calculate_string_hash(value.c_str());
dc->serial = value;
}
if (key == "FW Version")
dc->fw_version = value;
dc->extra_data.push_back(extra_data { key, value });
}
/*
* Match two dive computer entries against each other, and
* tell if it's the same dive. Return 0 if "don't know",
* positive for "same dive" and negative for "definitely
* not the same dive"
*/
int match_one_dc(const struct divecomputer &a, const struct divecomputer &b)
{
/* Not same model? Don't know if matching.. */
if (a.model.empty() || b.model.empty())
return 0;
if (strcasecmp(a.model.c_str(), b.model.c_str()))
return 0;
/* Different device ID's? Don't know */
if (a.deviceid != b.deviceid)
return 0;
/* Do we have dive IDs? */
if (!a.diveid || !b.diveid)
return 0;
/*
* If they have different dive ID's on the same
* dive computer, that's a definite "same or not"
*/
return a.diveid == b.diveid && a.when == b.when ? 1 : -1;
}
static const char *planner_dc_name = "planned dive";
bool is_dc_planner(const struct divecomputer *dc)
{
return dc->model == planner_dc_name;
}
void make_planner_dc(struct divecomputer *dc)
{
dc->model = planner_dc_name;
}
const char *manual_dc_name = "manually added dive";
bool is_dc_manually_added_dive(const struct divecomputer *dc)
{
return dc->model == manual_dc_name;
}
void make_manually_added_dive_dc(struct divecomputer *dc)
{
dc->model = manual_dc_name;
}

View file

@ -4,12 +4,11 @@
#include "divemode.h" #include "divemode.h"
#include "units.h" #include "units.h"
#include <string>
#ifdef __cplusplus #include <vector>
extern "C" {
#endif
struct extra_data; struct extra_data;
struct event;
struct sample; struct sample;
/* Is this header the correct place? */ /* Is this header the correct place? */
@ -29,44 +28,40 @@ struct sample;
* A deviceid or diveid of zero is assumed to be "no ID". * A deviceid or diveid of zero is assumed to be "no ID".
*/ */
struct divecomputer { struct divecomputer {
timestamp_t when; timestamp_t when = 0;
duration_t duration, surfacetime, last_manual_time; duration_t duration, surfacetime, last_manual_time;
depth_t maxdepth, meandepth; depth_t maxdepth, meandepth;
temperature_t airtemp, watertemp; temperature_t airtemp, watertemp;
pressure_t surface_pressure; pressure_t surface_pressure;
enum divemode_t divemode; // dive computer type: OC(default) or CCR enum divemode_t divemode = OC; // dive computer type: OC(default) or CCR
uint8_t no_o2sensors; // rebreathers: number of O2 sensors used uint8_t no_o2sensors = 0; // rebreathers: number of O2 sensors used
int salinity; // kg per 10000 l int salinity = 0; // kg per 10000 l
const char *model, *serial, *fw_version; std::string model, serial, fw_version;
uint32_t deviceid, diveid; uint32_t deviceid = 0, diveid = 0;
int samples, alloc_samples; // Note: ve store samples, events and extra_data in std::vector<>s.
struct sample *sample; // This means that pointers to these items are *not* stable.
struct event *events; std::vector<struct sample> samples;
struct extra_data *extra_data; std::vector<struct event> events;
struct divecomputer *next; std::vector<struct extra_data> extra_data;
divecomputer();
~divecomputer();
divecomputer(const divecomputer &);
divecomputer(divecomputer &&);
divecomputer &operator=(const divecomputer &);
}; };
extern void fake_dc(struct divecomputer *dc); extern void fake_dc(struct divecomputer *dc);
extern void free_dc(struct divecomputer *dc);
extern void free_dc_contents(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 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 struct sample *prepare_sample(struct divecomputer *dc);
extern void finish_sample(struct divecomputer *dc); extern void append_sample(const struct sample &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 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 unsigned int dc_airtemp(const struct divecomputer *dc); extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const std::string &name);
extern unsigned int dc_watertemp(const struct divecomputer *dc); extern struct event remove_event_from_dc(struct divecomputer *dc, int idx);
extern void copy_events(const struct divecomputer *s, struct divecomputer *d); struct event *get_event(struct divecomputer *dc, int idx);
extern void swap_event(struct divecomputer *dc, struct event *from, struct event *to); extern void add_extra_data(struct divecomputer *dc, const std::string &key, const std::string &value);
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 uint32_t calculate_string_hash(const char *str); extern uint32_t calculate_string_hash(const char *str);
extern bool is_dc_planner(const struct divecomputer *dc); extern bool is_dc_planner(const struct divecomputer *dc);
extern void make_planner_dc(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); 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) */ /* 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); extern int match_one_dc(const struct divecomputer &a, const struct divecomputer &b);
#ifdef __cplusplus
}
#endif
#endif #endif

View file

@ -5,6 +5,7 @@
#include "divelog.h" #include "divelog.h"
#include "gettextfromc.h" #include "gettextfromc.h"
#include "qthelper.h" #include "qthelper.h"
#include "range.h"
#include "selection.h" #include "selection.h"
#include "subsurface-qt/divelistnotifier.h" #include "subsurface-qt/divelistnotifier.h"
#if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER) #if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER)
@ -61,7 +62,7 @@ ShownChange DiveFilter::update(const QVector<dive *> &dives) const
std::vector<dive *> removeFromSelection; std::vector<dive *> removeFromSelection;
for (dive *d: dives) { for (dive *d: dives) {
// There are three modes: divesite, fulltext, normal // 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) : doFullText ? fulltext_dive_matches(d, filterData.fullText, filterData.fulltextStringMode) && showDive(d) :
showDive(d); showDive(d);
updateDiveStatus(d, newStatus, res, removeFromSelection); updateDiveStatus(d, newStatus, res, removeFromSelection);
@ -73,10 +74,8 @@ ShownChange DiveFilter::update(const QVector<dive *> &dives) const
void DiveFilter::reset() void DiveFilter::reset()
{ {
int i; shown_dives = static_cast<int>(divelog.dives.size());
dive *d; for (auto &d: divelog.dives)
shown_dives = divelog.dives->nr;
for_each_dive(i, d)
d->hidden_by_filter = false; d->hidden_by_filter = false;
updateAll(); updateAll();
} }
@ -84,26 +83,24 @@ void DiveFilter::reset()
ShownChange DiveFilter::updateAll() const ShownChange DiveFilter::updateAll() const
{ {
ShownChange res; ShownChange res;
int i;
dive *d;
std::vector<dive *> selection = getDiveSelection(); std::vector<dive *> selection = getDiveSelection();
std::vector<dive *> removeFromSelection; std::vector<dive *> removeFromSelection;
// There are three modes: divesite, fulltext, normal // There are three modes: divesite, fulltext, normal
if (diveSiteMode()) { if (diveSiteMode()) {
for_each_dive(i, d) { for (auto &d: divelog.dives) {
bool newStatus = dive_sites.contains(d->dive_site); bool newStatus = range_contains(dive_sites, d->dive_site);
updateDiveStatus(d, newStatus, res, removeFromSelection); updateDiveStatus(d.get(), newStatus, res, removeFromSelection);
} }
} else if (filterData.fullText.doit()) { } else if (filterData.fullText.doit()) {
FullTextResult ft = fulltext_find_dives(filterData.fullText, filterData.fulltextStringMode); FullTextResult ft = fulltext_find_dives(filterData.fullText, filterData.fulltextStringMode);
for_each_dive(i, d) { for (auto &d: divelog.dives) {
bool newStatus = ft.dive_matches(d) && showDive(d); bool newStatus = ft.dive_matches(d.get()) && showDive(d.get());
updateDiveStatus(d, newStatus, res, removeFromSelection); updateDiveStatus(d.get(), newStatus, res, removeFromSelection);
} }
} else { } else {
for_each_dive(i, d) { for (auto &d: divelog.dives) {
bool newStatus = showDive(d); bool newStatus = showDive(d.get());
updateDiveStatus(d, newStatus, res, removeFromSelection); updateDiveStatus(d.get(), newStatus, res, removeFromSelection);
} }
} }
updateSelection(selection, std::vector<dive *>(), removeFromSelection); updateSelection(selection, std::vector<dive *>(), removeFromSelection);
@ -142,7 +139,7 @@ bool DiveFilter::showDive(const struct dive *d) const
} }
#if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER) #if !defined(SUBSURFACE_MOBILE) && !defined(SUBSURFACE_DOWNLOADER)
void DiveFilter::startFilterDiveSites(QVector<dive_site *> ds) void DiveFilter::startFilterDiveSites(std::vector<dive_site *> ds)
{ {
if (++diveSiteRefCount > 1) { if (++diveSiteRefCount > 1) {
setFilterDiveSite(std::move(ds)); setFilterDiveSite(std::move(ds));
@ -169,7 +166,7 @@ void DiveFilter::stopFilterDiveSites()
#endif #endif
} }
void DiveFilter::setFilterDiveSite(QVector<dive_site *> ds) void DiveFilter::setFilterDiveSite(std::vector<dive_site *> ds)
{ {
// If the filter didn't change, return early to avoid a full // If the filter didn't change, return early to avoid a full
// map reload. For a well-defined comparison, sort the vector first. // map reload. For a well-defined comparison, sort the vector first.
@ -185,7 +182,7 @@ void DiveFilter::setFilterDiveSite(QVector<dive_site *> ds)
MainWindow::instance()->diveList->expandAll(); MainWindow::instance()->diveList->expandAll();
} }
const QVector<dive_site *> &DiveFilter::filteredDiveSites() const const std::vector<dive_site *> &DiveFilter::filteredDiveSites() const
{ {
return dive_sites; return dive_sites;
} }
@ -203,7 +200,7 @@ bool DiveFilter::diveSiteMode() const
QString DiveFilter::shownText() const QString DiveFilter::shownText() const
{ {
int num = divelog.dives->nr; size_t num = divelog.dives.size();
if (diveSiteMode() || filterData.validFilter()) if (diveSiteMode() || filterData.validFilter())
return gettextFromC::tr("%L1/%L2 shown").arg(shown_dives).arg(num); return gettextFromC::tr("%L1/%L2 shown").arg(shown_dives).arg(num);
else else
@ -229,11 +226,9 @@ std::vector<dive *> DiveFilter::visibleDives() const
std::vector<dive *> res; std::vector<dive *> res;
res.reserve(shown_dives); res.reserve(shown_dives);
int i; for (auto &d: divelog.dives) {
dive *d;
for_each_dive(i, d) {
if (!d->hidden_by_filter) if (!d->hidden_by_filter)
res.push_back(d); res.push_back(d.get());
} }
return res; return res;
} }

View file

@ -45,9 +45,9 @@ public:
bool diveSiteMode() const; // returns true if we're filtering on dive site (on mobile always returns false) bool diveSiteMode() const; // returns true if we're filtering on dive site (on mobile always returns false)
std::vector<dive *> visibleDives() const; std::vector<dive *> visibleDives() const;
#ifndef SUBSURFACE_MOBILE #ifndef SUBSURFACE_MOBILE
const QVector<dive_site *> &filteredDiveSites() const; const std::vector<dive_site *> &filteredDiveSites() const;
void startFilterDiveSites(QVector<dive_site *> ds); void startFilterDiveSites(std::vector<dive_site *> ds);
void setFilterDiveSite(QVector<dive_site *> ds); void setFilterDiveSite(std::vector<dive_site *> ds);
void stopFilterDiveSites(); void stopFilterDiveSites();
#endif #endif
void setFilter(const FilterData &data); void setFilter(const FilterData &data);
@ -62,7 +62,7 @@ private:
void updateDiveStatus(dive *d, bool newStatus, ShownChange &change, void updateDiveStatus(dive *d, bool newStatus, ShownChange &change,
std::vector<dive *> &removeFromSelection) const; std::vector<dive *> &removeFromSelection) const;
QVector<dive_site *> dive_sites; std::vector<dive_site *> dive_sites;
FilterData filterData; FilterData filterData;
mutable int shown_dives; mutable int shown_dives;

File diff suppressed because it is too large Load diff

1255
core/divelist.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,69 +2,55 @@
#ifndef DIVELIST_H #ifndef DIVELIST_H
#define DIVELIST_H #define DIVELIST_H
#include "triptable.h"
#include "divesitetable.h"
#include "units.h" #include "units.h"
#include <memory>
#ifdef __cplusplus #include <optional>
extern "C" {
#endif
struct dive; struct dive;
struct divelog; struct divelog;
struct trip_table; struct device;
struct dive_site_table;
struct device_table;
struct deco_state; struct deco_state;
struct dive_table { int comp_dives(const struct dive &a, const struct dive &b);
int nr, allocated; int comp_dives_ptr(const struct dive *a, const struct dive *b);
struct dive **dives;
struct merge_result {
std::unique_ptr<struct dive> dive;
std::optional<location_t> set_location; // change location of dive site
}; };
static const struct dive_table empty_dive_table = { 0, 0, (struct dive **)0 };
/* this is used for both git and xml format */ struct dive_table : public sorted_owning_table<dive, &comp_dives> {
#define DATAFORMAT_VERSION 3 dive *get_by_uniq_id(int id) const;
void record_dive(std::unique_ptr<dive> d); // call fixup_dive() before adding dive to table.
struct dive *register_dive(std::unique_ptr<dive> d);
std::unique_ptr<dive> unregister_dive(int idx);
std::unique_ptr<dive> default_dive(); // generate a sensible looking defaultdive 1h from now.
extern void sort_dive_table(struct dive_table *table); // Some of these functions act on dives, but need data from adjacent dives,
extern void update_cylinder_related_info(struct dive *); // notably to calculate CNS, surface interval, etc. Therefore, they are called
extern int init_decompression(struct deco_state *ds, const struct dive *dive, bool in_planner); // on the dive_table and not on the dive.
void fixup_dive(struct dive &dive) const;
void force_fixup_dive(struct dive &d) const;
int init_decompression(struct deco_state *ds, const struct dive *dive, bool in_planner) const;
void update_cylinder_related_info(struct dive &dive) const;
int get_dive_nr_at_idx(int idx) const;
timestamp_t get_surface_interval(timestamp_t when) const;
struct dive *find_next_visible_dive(timestamp_t when);
std::array<std::unique_ptr<dive>, 2> split_divecomputer(const struct dive &src, int num) const;
std::array<std::unique_ptr<dive>, 2> split_dive(const struct dive &dive) const;
std::array<std::unique_ptr<dive>, 2> split_dive_at_time(const struct dive &dive, duration_t time) const;
merge_result merge_dives(const std::vector<dive *> &dives) const;
std::unique_ptr<dive> try_to_merge(const struct dive &a, const struct dive &b, bool prefer_downloaded) const;
bool has_dive(unsigned int deviceid, unsigned int diveid) const;
std::unique_ptr<dive> clone_delete_divecomputer(const struct dive &d, int dc_number);
private:
int calculate_cns(struct dive &dive) const; // Note: writes into dive->cns
std::array<std::unique_ptr<dive>, 2> split_dive_at(const struct dive &dive, int a, int b) const;
std::unique_ptr<dive> merge_two_dives(const struct dive &a_in, const struct dive &b_in, int offset, bool prefer_downloaded) const;
};
/* divelist core logic functions */
extern void process_loaded_dives();
/* flags for process_imported_dives() */
#define IMPORT_PREFER_IMPORTED (1 << 0)
#define IMPORT_IS_DOWNLOADED (1 << 1)
#define IMPORT_MERGE_ALL_TRIPS (1 << 2)
#define IMPORT_ADD_TO_NEW_TRIP (1 << 3)
extern void add_imported_dives(struct divelog *log, int flags);
extern void process_imported_dives(struct divelog *import_log, int flags,
struct dive_table *dives_to_add, struct dive_table *dives_to_remove,
struct trip_table *trips_to_add, struct dive_site_table *sites_to_add,
struct device_table *devices_to_add);
extern int dive_table_get_insertion_index(struct dive_table *table, struct dive *dive);
extern void add_to_dive_table(struct dive_table *table, int idx, struct dive *dive);
extern void insert_dive(struct dive_table *table, struct dive *d);
extern void get_dive_gas(const struct dive *dive, int *o2_p, int *he_p, int *o2low_p);
extern int get_divenr(const struct dive *dive);
extern int remove_dive(const struct dive *dive, struct dive_table *table);
extern int get_dive_nr_at_idx(int idx);
extern timestamp_t get_surface_interval(timestamp_t when);
extern void delete_dive_from_table(struct dive_table *table, int idx);
extern struct dive *find_next_visible_dive(timestamp_t when);
extern int comp_dives(const struct dive *a, const struct dive *b);
int get_min_datafile_version();
void reset_min_datafile_version();
void report_datafile_version(int version);
void clear_dive_file_data(); void clear_dive_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 #endif // DIVELIST_H

View file

@ -3,96 +3,534 @@
#include "divelist.h" #include "divelist.h"
#include "divesite.h" #include "divesite.h"
#include "device.h" #include "device.h"
#include "dive.h"
#include "errorhelper.h" #include "errorhelper.h"
#include "filterpreset.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" #include "trip.h"
struct divelog divelog; struct divelog divelog;
// We can't use smart pointers, since this is used from C divelog::divelog() = default;
// and it would be bold to presume that std::unique_ptr<> divelog::~divelog() = default;
// and a plain pointer have the same memory layout. divelog::divelog(divelog &&) = default;
divelog::divelog() : struct divelog &divelog::operator=(divelog &&) = default;
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;
}
/* this implements the mechanics of removing the dive from the /* this implements the mechanics of removing the dive from the
* dive log and the trip, but doesn't deal with updating dive trips, etc */ * 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) { if (idx < 0 || static_cast<size_t>(idx) >= dives.size()) {
report_info("Warning: deleting unexisting dive with index %d", idx); report_info("Warning: deleting non-existing dive with index %d", idx);
return; return;
} }
struct dive *dive = log->dives->dives[idx]; struct dive *dive = dives[idx].get();
remove_dive_from_trip(dive, log->trips); 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); unregister_dive_from_dive_site(dive);
delete_dive_from_table(log->dives, idx); dives.erase(dives.begin() + idx);
}
void divelog::delete_multiple_dives(const std::vector<dive *> &dives_to_delete)
{
bool trips_changed = false;
for (dive *d: dives_to_delete) {
// Delete dive from trip and delete trip if empty
struct dive_trip *trip = unregister_dive_from_trip(d);
if (trip && trip->dives.empty()) {
trips_changed = true;
trips.pull(trip);
}
unregister_dive_from_dive_site(d);
dives.pull(d);
}
// Deleting a dive may change the order of trips!
if (trips_changed)
trips.sort();
} }
void divelog::clear() void divelog::clear()
{ {
while (dives->nr > 0) dives.clear();
delete_single_dive(this, dives->nr - 1); sites.clear();
while (sites->nr) trips.clear();
delete_dive_site(get_dive_site(0, sites), sites); devices.clear();
if (trips->nr != 0) { filter_presets.clear();
report_info("Warning: trip table not empty in divelog::clear()!");
trips->nr = 0;
}
clear_device_table(devices);
filter_presets->clear();
} }
extern "C" void clear_divelog(struct divelog *log) /* check if we have a trip right before / after this dive */
bool divelog::is_trip_before_after(const struct dive *dive, bool before) const
{ {
log->clear(); auto it = std::find_if(dives.begin(), dives.end(),
[dive](auto &d) { return d.get() == dive; });
if (it == dives.end())
return false;
if (before) {
do {
if (it == dives.begin())
return false;
--it;
} while ((*it)->invalid);
return (*it)->divetrip != nullptr;
} else {
++it;
while (it != dives.end() && (*it)->invalid)
++it;
return it != dives.end() && (*it)->divetrip != nullptr;
}
}
/*
* Merge subsequent dives in a table, if mergeable. This assumes
* that the dives are neither selected, not part of a trip, as
* is the case of freshly imported dives.
*/
static void merge_imported_dives(struct dive_table &table)
{
for (size_t i = 1; i < table.size(); i++) {
auto &prev = table[i - 1];
auto &dive = table[i];
/* only try to merge overlapping dives - or if one of the dives has
* zero duration (that might be a gps marker from the webservice) */
if (prev->duration.seconds && dive->duration.seconds &&
prev->endtime() < dive->when)
continue;
auto merged = table.try_to_merge(*prev, *dive, false);
if (!merged)
continue;
/* Add dive to dive site; try_to_merge() does not do that! */
struct dive_site *ds = merged->dive_site;
if (ds) {
merged->dive_site = NULL;
ds->add_dive(merged.get());
}
unregister_dive_from_dive_site(prev.get());
unregister_dive_from_dive_site(dive.get());
unregister_dive_from_trip(prev.get());
unregister_dive_from_trip(dive.get());
/* Overwrite the first of the two dives and remove the second */
table[i - 1] = std::move(merged);
table.erase(table.begin() + i);
/* Redo the new 'i'th dive */
i--;
}
}
/*
* Walk the dives from the oldest dive in the given table, and see if we
* can autogroup them. But only do this when the user selected autogrouping.
*/
static void autogroup_dives(struct divelog &log)
{
if (!log.autogroup)
return;
for (auto &entry: get_dives_to_autogroup(log.dives)) {
for (auto it = log.dives.begin() + entry.from; it != log.dives.begin() + entry.to; ++it)
entry.trip->add_dive(it->get());
/* If this was newly allocated, add trip to list */
if (entry.created_trip)
log.trips.put(std::move(entry.created_trip));
}
log.trips.sort(); // Necessary?
#ifdef DEBUG_TRIP
dump_trip_list();
#endif
}
/* Check if a dive is ranked after the last dive of the global dive list */
static bool dive_is_after_last(const struct dive &d)
{
if (divelog.dives.empty())
return true;
return dive_less_than(*divelog.dives.back(), d);
}
/*
* Try to merge a new dive into the dive at position idx. Return
* true on success. On success, the old dive will be added to the
* dives_to_remove table and the merged dive to the dives_to_add
* table. On failure everything stays unchanged.
* If "prefer_imported" is true, use data of the new dive.
*/
static bool try_to_merge_into(struct dive &dive_to_add, struct dive *old_dive, bool prefer_imported,
/* output parameters: */
struct dive_table &dives_to_add, struct std::vector<dive *> &dives_to_remove)
{
auto merged = dives_to_add.try_to_merge(*old_dive, dive_to_add, prefer_imported);
if (!merged)
return false;
merged->divetrip = old_dive->divetrip;
range_insert_sorted(dives_to_remove, old_dive, comp_dives_ptr);
dives_to_add.put(std::move(merged));
return true;
}
/* Merge dives from "dives_from", owned by "delete" into the owned by "dives_to".
* Overlapping dives will be merged, non-overlapping dives will be moved. The results
* will be added to the "dives_to_add" table. Dives that were merged are added to
* the "dives_to_remove" table. Any newly added (not merged) dive will be assigned
* to the trip of the "trip" paremeter. If "delete_from" is non-null dives will be
* removed from this table.
* This function supposes that all input tables are sorted.
* Returns true if any dive was added (not merged) that is not past the
* last dive of the global dive list (i.e. the sequence will change).
* The integer referenced by "num_merged" will be increased for every
* merged dive that is added to "dives_to_add" */
static bool merge_dive_tables(const std::vector<dive *> &dives_from, struct dive_table &delete_from,
const std::vector<dive *> &dives_to,
bool prefer_imported, struct dive_trip *trip,
/* output parameters: */
struct dive_table &dives_to_add, struct std::vector<dive *> &dives_to_remove,
int &num_merged)
{
bool sequence_changed = false;
/* Merge newly imported dives into the dive table.
* Since both lists (old and new) are sorted, we can step
* through them concurrently and locate the insertions points.
* Once found, check if the new dive can be merged in the
* previous or next dive.
* Note that this doesn't consider pathological cases such as:
* - New dive "connects" two old dives (turn three into one).
* - New dive can not be merged into adjacent but some further dive.
*/
size_t j = 0; /* Index in dives_to */
size_t last_merged_into = std::string::npos;
for (dive *add: dives_from) {
/* This gets an owning pointer to the dive to add and removes it from
* the delete_from table. If the dive is not explicitly stored, it will
* be automatically deleting when ending the loop iteration */
auto [dive_to_add, idx] = delete_from.pull(add);
if (!dive_to_add) {
report_info("merging unknown dives!");
continue;
}
/* Find insertion point. */
while (j < dives_to.size() && dive_less_than(*dives_to[j], *dive_to_add))
j++;
/* Try to merge into previous dive.
* We are extra-careful to not merge into the same dive twice, as that
* would put the merged-into dive twice onto the dives-to-delete list.
* In principle that shouldn't happen as all dives that compare equal
* by is_same_dive() were already merged, and is_same_dive() should be
* transitive. But let's just go *completely* sure for the odd corner-case. */
if (j > 0 && (last_merged_into == std::string::npos || j > last_merged_into + 1) &&
dives_to[j - 1]->endtime() > dive_to_add->when) {
if (try_to_merge_into(*dive_to_add, dives_to[j - 1], prefer_imported,
dives_to_add, dives_to_remove)) {
last_merged_into = j - 1;
num_merged++;
continue;
}
}
/* That didn't merge into the previous dive.
* Try to merge into next dive. */
if (j < dives_to.size() && (last_merged_into == std::string::npos || j > last_merged_into) &&
dive_to_add->endtime() > dives_to[j]->when) {
if (try_to_merge_into(*dive_to_add, dives_to[j], prefer_imported,
dives_to_add, dives_to_remove)) {
last_merged_into = j;
num_merged++;
continue;
}
}
sequence_changed |= !dive_is_after_last(*dive_to_add);
dives_to_add.put(std::move(dive_to_add));
}
return sequence_changed;
}
/* Helper function for process_imported_dives():
* Try to merge a trip into one of the existing trips.
* The bool pointed to by "sequence_changed" is set to true, if the sequence of
* the existing dives changes.
* The int pointed to by "start_renumbering_at" keeps track of the first dive
* to be renumbered in the dives_to_add table.
* For other parameters see process_imported_dives()
* Returns true if trip was merged. In this case, the trip will be
* freed.
*/
static bool try_to_merge_trip(dive_trip &trip_import, struct dive_table &import_table, bool prefer_imported,
/* output parameters: */
struct dive_table &dives_to_add, std::vector<dive *> &dives_to_remove,
bool &sequence_changed, int &start_renumbering_at)
{
for (auto &trip_old: divelog.trips) {
if (trips_overlap(trip_import, *trip_old)) {
sequence_changed |= merge_dive_tables(trip_import.dives, import_table, trip_old->dives,
prefer_imported, trip_old.get(),
dives_to_add, dives_to_remove,
start_renumbering_at);
/* we took care of all dives of the trip, clean up the table */
trip_import.dives.clear();
return true;
}
}
return false;
}
// Helper function to convert a table of owned dives into a table of non-owning pointers.
// Used to merge *all* dives of a log into a different table.
static std::vector<dive *> dive_table_to_non_owning(const dive_table &dives)
{
std::vector<dive *> res;
res.reserve(dives.size());
for (auto &d: dives)
res.push_back(d.get());
return res;
}
/* Process imported dives: take a log.trips of dives to be imported and
* generate five lists:
* 1) Dives to be added (newly created, owned)
* 2) Dives to be removed (old, non-owned, references global divelog)
* 3) Trips to be added (newly created, owned)
* 4) Dive sites to be added (newly created, owned)
* 5) Devices to be added (newly created, owned)
* The dives, trips and sites in import_log are consumed.
* On return, the tables have * size 0.
*
* Note: The new dives will have their divetrip- and divesites-fields
* set, but will *not* be part of the trip and site. The caller has to
* add them to the trip and site.
*
* The lists are generated by merging dives if possible. This is
* performed trip-wise. Finer control on merging is provided by
* the "flags" parameter:
* - If import_flags::prefer_imported is set, data of the new dives are
* prioritized on merging.
* - If import_flags::merge_all_trips is set, all overlapping trips will
* be merged, not only non-autogenerated trips.
* - If import_flags::is_downloaded is true, only the divecomputer of the
* first dive will be considered, as it is assumed that all dives
* come from the same computer.
* - If import_flags::add_to_new_trip is true, dives that are not assigned
* to a trip will be added to a newly generated trip.
*/
divelog::process_imported_dives_result divelog::process_imported_dives(struct divelog &import_log, int flags)
{
int start_renumbering_at = 0;
bool sequence_changed = false;
bool last_old_dive_is_numbered;
process_imported_dives_result res;
/* If no dives were imported, don't bother doing anything */
if (import_log.dives.empty())
return res;
/* Check if any of the new dives has a number. This will be
* important later to decide if we want to renumber the added
* dives */
bool new_dive_has_number = std::any_of(import_log.dives.begin(), import_log.dives.end(),
[](auto &d) { return d->number > 0; });
/* Add only the devices that we don't know about yet. */
for (auto &dev: import_log.devices) {
if (!device_exists(devices, dev))
add_to_device_table(res.devices_to_add, dev);
}
/* Sort the table of dives to be imported and combine mergable dives */
import_log.dives.sort();
merge_imported_dives(import_log.dives);
/* Autogroup tripless dives if desired by user. But don't autogroup
* if tripless dives should be added to a new trip. */
if (!(flags & import_flags::add_to_new_trip))
autogroup_dives(import_log);
/* If dive sites already exist, use the existing versions. */
for (auto &new_ds: import_log.sites) {
/* Check if it dive site is actually used by new dives. */
if (std::none_of(import_log.dives.begin(), import_log.dives.end(), [ds=new_ds.get()]
(auto &d) { return d->dive_site == ds; }))
continue;
struct dive_site *old_ds = sites.get_same(*new_ds);
if (!old_ds) {
/* Dive site doesn't exist. Add it to list of dive sites to be added. */
new_ds->dives.clear(); /* Caller is responsible for adding dives to site */
res.sites_to_add.put(std::move(new_ds));
} else {
/* Dive site already exists - use the old one. */
for (auto &d: import_log.dives) {
if (d->dive_site == new_ds.get())
d->dive_site = old_ds;
}
}
}
import_log.sites.clear();
/* Merge overlapping trips. Since both trip tables are sorted, we
* could be smarter here, but realistically not a whole lot of trips
* will be imported so do a simple n*m loop until someone complains.
*/
for (auto &trip_import: import_log.trips) {
if ((flags & import_flags::merge_all_trips) || trip_import->autogen) {
if (try_to_merge_trip(*trip_import, import_log.dives, flags & import_flags::prefer_imported,
res.dives_to_add, res.dives_to_remove,
sequence_changed, start_renumbering_at))
continue;
}
/* If no trip to merge-into was found, add trip as-is.
* First, add dives to list of dives to add */
for (struct dive *d: trip_import->dives) {
/* Add dive to list of dives to-be-added. */
auto [owned, idx] = import_log.dives.pull(d);
if (!owned)
continue;
sequence_changed |= !dive_is_after_last(*owned);
res.dives_to_add.put(std::move(owned));
}
trip_import->dives.clear(); /* Caller is responsible for adding dives to trip */
/* Finally, add trip to list of trips to add */
res.trips_to_add.put(std::move(trip_import));
}
import_log.trips.clear(); /* All trips were consumed */
if ((flags & import_flags::add_to_new_trip) && !import_log.dives.empty()) {
/* Create a new trip for unassigned dives, if desired. */
auto [new_trip, idx] = res.trips_to_add.put(
create_trip_from_dive(import_log.dives.front().get())
);
/* Add all remaining dives to this trip */
for (auto &d: import_log.dives) {
sequence_changed |= !dive_is_after_last(*d);
d->divetrip = new_trip;
res.dives_to_add.put(std::move(d));
}
import_log.dives.clear(); /* All dives were consumed */
} else if (!import_log.dives.empty()) {
/* The remaining dives in import_log.dives are those that don't belong to
* a trip and the caller does not want them to be associated to a
* new trip. Merge them into the global table. */
sequence_changed |= merge_dive_tables(dive_table_to_non_owning(import_log.dives),
import_log.dives,
dive_table_to_non_owning(dives),
flags & import_flags::prefer_imported, NULL,
res.dives_to_add, res.dives_to_remove, start_renumbering_at);
}
/* If new dives were only added at the end, renumber the added dives.
* But only if
* - The last dive in the old dive table had a number itself (if there is a last dive).
* - None of the new dives has a number.
*/
last_old_dive_is_numbered = dives.empty() || dives.back()->number > 0;
/* We counted the number of merged dives that were added to dives_to_add.
* Skip those. Since sequence_changed is false all added dives are *after*
* all merged dives. */
if (!sequence_changed && last_old_dive_is_numbered && !new_dive_has_number) {
int nr = !dives.empty() ? dives.back()->number : 0;
for (auto it = res.dives_to_add.begin() + start_renumbering_at; it < res.dives_to_add.end(); ++it)
(*it)->number = ++nr;
}
return res;
}
// TODO: This accesses global state, namely fulltext and selection.
// TODO: Move up the call chain?
void divelog::process_loaded_dives()
{
dives.sort();
trips.sort();
/* Autogroup dives if desired by user. */
autogroup_dives(*this);
fulltext_populate();
/* Inform frontend of reset data. This should reset all the models. */
emit_reset_signal();
/* Now that everything is settled, select the newest dive. */
select_newest_visible_dive();
}
/* Merge the dives of the trip "from" and the dive_table "dives_from" into the trip "to"
* and dive_table "dives_to". If "prefer_imported" is true, dive data of "from" takes
* precedence */
void divelog::add_imported_dives(struct divelog &import_log, int flags)
{
/* Process imported dives and generate lists of dives
* to-be-added and to-be-removed */
auto [dives_to_add, dives_to_remove, trips_to_add, dive_sites_to_add, devices_to_add] =
process_imported_dives(import_log, flags);
/* Start by deselecting all dives, so that we don't end up with an invalid selection */
select_single_dive(NULL);
/* Add new dives to trip and site to get reference count correct. */
for (auto &d: dives_to_add) {
struct dive_trip *trip = d->divetrip;
struct dive_site *site = d->dive_site;
d->divetrip = NULL;
d->dive_site = NULL;
trip->add_dive(d.get());
if (site)
site->add_dive(d.get());
}
/* Remove old dives */
delete_multiple_dives(dives_to_remove);
/* Add new dives */
for (auto &d: dives_to_add)
dives.put(std::move(d));
dives_to_add.clear();
/* Add new trips */
for (auto &trip: trips_to_add)
trips.put(std::move(trip));
trips_to_add.clear();
/* Add new dive sites */
for (auto &ds: dive_sites_to_add)
sites.register_site(std::move(ds));
/* Add new devices */
for (auto &dev: devices_to_add)
add_to_device_table(devices, dev);
/* We might have deleted the old selected dive.
* Choose the newest dive as selected (if any) */
current_dive = !dives.empty() ? dives.back().get() : nullptr;
/* Inform frontend of reset data. This should reset all the models. */
emit_reset_signal();
} }

View file

@ -1,43 +1,57 @@
// SPDX-License-Identifier: GPL-2.0 // 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 #ifndef DIVELOG_H
#define DIVELOG_H #define DIVELOG_H
struct dive_table; #include "divelist.h"
struct trip_table; #include "divesitetable.h"
struct dive_site_table; #include "filterpresettable.h"
struct device_table; #include "triptable.h"
struct filter_preset_table;
#include <stdbool.h> #include <vector>
struct device;
/* flags for process_imported_dives() */
struct import_flags {
static constexpr int prefer_imported = 1 << 0;
static constexpr int is_downloaded = 1 << 1;
static constexpr int merge_all_trips = 1 << 2;
static constexpr int add_to_new_trip = 1 << 3;
};
struct divelog { struct divelog {
struct dive_table *dives; dive_table dives;
struct trip_table *trips; trip_table trips;
struct dive_site_table *sites; dive_site_table sites;
struct device_table *devices; std::vector<device> devices;
struct filter_preset_table *filter_presets; filter_preset_table filter_presets;
bool autogroup; bool autogroup = false;
#ifdef __cplusplus
void clear();
divelog(); divelog();
~divelog(); ~divelog();
divelog(divelog &&log); // move constructor (argument is consumed). divelog(divelog &&); // move constructor (argument is consumed).
divelog &operator=(divelog &&log); // move assignment (argument is consumed). divelog &operator=(divelog &&); // move assignment (argument is consumed).
#endif
void delete_single_dive(int idx);
void delete_multiple_dives(const std::vector<dive *> &dives);
void clear();
bool is_trip_before_after(const struct dive *dive, bool before) const;
struct process_imported_dives_result {
dive_table dives_to_add;
std::vector<dive *> dives_to_remove;
trip_table trips_to_add;
dive_site_table sites_to_add;
std::vector<device> devices_to_add;
};
/* divelist core logic functions */
process_imported_dives_result process_imported_dives(struct divelog &import_log, int flags); // import_log will be consumed
void process_loaded_dives();
void add_imported_dives(struct divelog &log, int flags); // log will be consumed
}; };
extern struct divelog divelog; 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 #endif

View file

@ -75,45 +75,42 @@ static void exportHTMLstatistics(const QString filename, struct htmlExportSettin
QFile file(filename); QFile file(filename);
file.open(QIODevice::WriteOnly | QIODevice::Text); file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file); QTextStream out(&file);
stats_summary_auto_free stats;
stats_t total_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.selection_size = 0;
total_stats.total_time.seconds = 0; total_stats.total_time = 0_sec;
int i = 0;
out << "divestat=["; out << "divestat=[";
if (hes.yearlyStatistics) { if (hes.yearlyStatistics) {
while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) { for (const auto &s: stats.stats_yearly) {
out << "{"; out << "{";
out << "\"YEAR\":\"" << stats.stats_yearly[i].period << "\","; out << "\"YEAR\":\"" << s.period << "\",";
out << "\"DIVES\":\"" << stats.stats_yearly[i].selection_size << "\","; out << "\"DIVES\":\"" << s.selection_size << "\",";
out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(stats.stats_yearly[i].total_time.seconds, out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(s.total_time.seconds,
gettextFromC::tr("h"), gettextFromC::tr("min"), gettextFromC::tr("sec"), " ") << "\","; 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 << "\"AVERAGE_TIME\":\"" << formatMinutes(s.total_time.seconds / s.selection_size) << "\",";
out << "\"SHORTEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].shortest_time.seconds) << "\","; out << "\"SHORTEST_TIME\":\"" << formatMinutes(s.shortest_time.seconds) << "\",";
out << "\"LONGEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].longest_time.seconds) << "\","; out << "\"LONGEST_TIME\":\"" << formatMinutes(s.longest_time.seconds) << "\",";
out << "\"AVG_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].avg_depth) << "\","; out << "\"AVG_DEPTH\":\"" << get_depth_string(s.avg_depth) << "\",";
out << "\"MIN_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].min_depth) << "\","; out << "\"MIN_DEPTH\":\"" << get_depth_string(s.min_depth) << "\",";
out << "\"MAX_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].max_depth) << "\","; out << "\"MAX_DEPTH\":\"" << get_depth_string(s.max_depth) << "\",";
out << "\"AVG_SAC\":\"" << get_volume_string(stats.stats_yearly[i].avg_sac) << "\","; out << "\"AVG_SAC\":\"" << get_volume_string(s.avg_sac) << "\",";
out << "\"MIN_SAC\":\"" << get_volume_string(stats.stats_yearly[i].min_sac) << "\","; out << "\"MIN_SAC\":\"" << get_volume_string(s.min_sac) << "\",";
out << "\"MAX_SAC\":\"" << get_volume_string(stats.stats_yearly[i].max_sac) << "\","; out << "\"MAX_SAC\":\"" << get_volume_string(s.max_sac) << "\",";
if (stats.stats_yearly[i].combined_count) { if (s.combined_count) {
temperature_t avg_temp; 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) << "\","; out << "\"AVG_TEMP\":\"" << get_temperature_string(avg_temp) << "\",";
} else { } else {
out << "\"AVG_TEMP\":\"0.0\","; 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 << "\"MIN_TEMP\":\"" << (s.min_temp.mkelvin == 0 ? 0 : get_temperature_string(s.min_temp)) << "\",";
out << "\"MAX_TEMP\":\"" << (stats.stats_yearly[i].max_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].max_temp)) << "\","; out << "\"MAX_TEMP\":\"" << (s.max_temp.mkelvin == 0 ? 0 : get_temperature_string(s.max_temp)) << "\",";
out << "},"; out << "},";
total_stats.selection_size += stats.stats_yearly[i].selection_size; total_stats.selection_size += s.selection_size;
total_stats.total_time.seconds += stats.stats_yearly[i].total_time.seconds; total_stats.total_time += s.total_time;
i++;
} }
exportHTMLstatisticsTotal(out, &total_stats); exportHTMLstatisticsTotal(out, &total_stats);
} }

View file

@ -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 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 #endif

View file

@ -1,53 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include "divesite.h"
#include "pref.h"
#include "gettextfromc.h"
QString constructLocationTags(struct taxonomy_data *taxonomy, bool for_maintab)
{
QString locationTag;
if (!taxonomy->nr)
return locationTag;
/* Check if the user set any of the 3 geocoding categories */
bool prefs_set = false;
for (int i = 0; i < 3; i++) {
if (prefs.geocoding.category[i] != TC_NONE)
prefs_set = true;
}
if (!prefs_set && !for_maintab) {
locationTag = QString("<small><small>") + gettextFromC::tr("No dive site layout categories set in preferences!") +
QString("</small></small>");
return locationTag;
}
else if (!prefs_set)
return locationTag;
if (for_maintab)
locationTag = QString("<small><small>(") + gettextFromC::tr("Tags") + QString(": ");
else
locationTag = QString("<small><small>");
QString connector;
for (int i = 0; i < 3; i++) {
if (prefs.geocoding.category[i] == TC_NONE)
continue;
for (int j = 0; j < taxonomy->nr; j++) {
if (taxonomy->category[j].category == prefs.geocoding.category[i]) {
QString tag = taxonomy->category[j].value;
if (!tag.isEmpty()) {
locationTag += connector + tag;
connector = " / ";
}
break;
}
}
}
if (for_maintab)
locationTag += ")</small></small>";
else
locationTag += "</small></small>";
return locationTag;
}

View file

@ -1,401 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/* divesite.c */
#include "divesite.h"
#include "dive.h"
#include "divelist.h"
#include "divelog.h"
#include "errorhelper.h"
#include "membuffer.h"
#include "subsurface-string.h"
#include "table.h"
#include "sha1.h"
#include <math.h>
int get_divesite_idx(const struct dive_site *ds, struct dive_site_table *ds_table)
{
int i;
const struct dive_site *d;
// tempting as it may be, don't die when called with ds=NULL
if (ds)
for_each_dive_site(i, d, ds_table) {
if (d == ds)
return i;
}
return -1;
}
// TODO: keep table sorted by UUID and do a binary search?
struct dive_site *get_dive_site_by_uuid(uint32_t uuid, struct dive_site_table *ds_table)
{
int i;
struct dive_site *ds;
for_each_dive_site (i, ds, ds_table)
if (ds->uuid == uuid)
return get_dive_site(i, ds_table);
return NULL;
}
/* there could be multiple sites of the same name - return the first one */
struct dive_site *get_dive_site_by_name(const char *name, struct dive_site_table *ds_table)
{
int i;
struct dive_site *ds;
for_each_dive_site (i, ds, ds_table) {
if (same_string(ds->name, name))
return ds;
}
return NULL;
}
/* there could be multiple sites at the same GPS fix - return the first one */
struct dive_site *get_dive_site_by_gps(const location_t *loc, struct dive_site_table *ds_table)
{
int i;
struct dive_site *ds;
for_each_dive_site (i, ds, ds_table) {
if (same_location(loc, &ds->location))
return ds;
}
return NULL;
}
/* to avoid a bug where we have two dive sites with different name and the same GPS coordinates
* and first get the gps coordinates (reading a V2 file) and happen to get back "the other" name,
* this function allows us to verify if a very specific name/GPS combination already exists */
struct dive_site *get_dive_site_by_gps_and_name(const char *name, const location_t *loc, struct dive_site_table *ds_table)
{
int i;
struct dive_site *ds;
for_each_dive_site (i, ds, ds_table) {
if (same_location(loc, &ds->location) && same_string(ds->name, name))
return ds;
}
return NULL;
}
// Calculate the distance in meters between two coordinates.
unsigned int get_distance(const location_t *loc1, const location_t *loc2)
{
double lat1_r = udeg_to_radians(loc1->lat.udeg);
double lat2_r = udeg_to_radians(loc2->lat.udeg);
double lat_d_r = udeg_to_radians(loc2->lat.udeg - loc1->lat.udeg);
double lon_d_r = udeg_to_radians(loc2->lon.udeg - loc1->lon.udeg);
double a = sin(lat_d_r/2) * sin(lat_d_r/2) +
cos(lat1_r) * cos(lat2_r) * sin(lon_d_r/2) * sin(lon_d_r/2);
if (a < 0.0) a = 0.0;
if (a > 1.0) a = 1.0;
double c = 2 * atan2(sqrt(a), sqrt(1.0 - a));
// Earth radius in metres
return lrint(6371000 * c);
}
/* find the closest one, no more than distance meters away - if more than one at same distance, pick the first */
struct dive_site *get_dive_site_by_gps_proximity(const location_t *loc, int distance, struct dive_site_table *ds_table)
{
int i;
struct dive_site *ds, *res = NULL;
unsigned int cur_distance, min_distance = distance;
for_each_dive_site (i, ds, ds_table) {
if (dive_site_has_gps_location(ds) &&
(cur_distance = get_distance(&ds->location, loc)) < min_distance) {
min_distance = cur_distance;
res = ds;
}
}
return res;
}
int register_dive_site(struct dive_site *ds)
{
return add_dive_site_to_table(ds, divelog.sites);
}
static int compare_sites(const struct dive_site *a, const struct dive_site *b)
{
return a->uuid > b->uuid ? 1 : a->uuid == b->uuid ? 0 : -1;
}
static int site_less_than(const struct dive_site *a, const struct dive_site *b)
{
return compare_sites(a, b) < 0;
}
static MAKE_GROW_TABLE(dive_site_table, struct dive_site *, dive_sites)
static MAKE_GET_INSERTION_INDEX(dive_site_table, struct dive_site *, dive_sites, site_less_than)
static MAKE_ADD_TO(dive_site_table, struct dive_site *, dive_sites)
static MAKE_REMOVE_FROM(dive_site_table, dive_sites)
static MAKE_GET_IDX(dive_site_table, struct dive_site *, dive_sites)
MAKE_SORT(dive_site_table, struct dive_site *, dive_sites, compare_sites)
static MAKE_REMOVE(dive_site_table, struct dive_site *, dive_site)
MAKE_CLEAR_TABLE(dive_site_table, dive_sites, dive_site)
MAKE_MOVE_TABLE(dive_site_table, dive_sites)
int add_dive_site_to_table(struct dive_site *ds, struct dive_site_table *ds_table)
{
/* If the site doesn't yet have an UUID, create a new one.
* Make this deterministic for testing. */
if (!ds->uuid) {
SHA_CTX ctx;
uint32_t csum[5];
SHA1_Init(&ctx);
if (ds->name)
SHA1_Update(&ctx, ds->name, strlen(ds->name));
if (ds->description)
SHA1_Update(&ctx, ds->description, strlen(ds->description));
if (ds->notes)
SHA1_Update(&ctx, ds->notes, strlen(ds->notes));
SHA1_Final((unsigned char *)csum, &ctx);
ds->uuid = csum[0];
}
/* Take care to never have the same uuid twice. This could happen on
* reimport of a log where the dive sites have diverged */
while (ds->uuid == 0 || get_dive_site_by_uuid(ds->uuid, ds_table) != NULL)
++ds->uuid;
int idx = dive_site_table_get_insertion_index(ds_table, ds);
add_to_dive_site_table(ds_table, idx, ds);
return idx;
}
struct dive_site *alloc_dive_site()
{
struct dive_site *ds;
ds = calloc(1, sizeof(*ds));
if (!ds)
exit(1);
return ds;
}
struct dive_site *alloc_dive_site_with_name(const char *name)
{
struct dive_site *ds = alloc_dive_site();
ds->name = copy_string(name);
return ds;
}
struct dive_site *alloc_dive_site_with_gps(const char *name, const location_t *loc)
{
struct dive_site *ds = alloc_dive_site_with_name(name);
ds->location = *loc;
return ds;
}
/* when parsing, dive sites are identified by uuid */
struct dive_site *alloc_or_get_dive_site(uint32_t uuid, struct dive_site_table *ds_table)
{
struct dive_site *ds;
if (uuid && (ds = get_dive_site_by_uuid(uuid, ds_table)) != NULL)
return ds;
ds = alloc_dive_site();
ds->uuid = uuid;
add_dive_site_to_table(ds, ds_table);
return ds;
}
int nr_of_dives_at_dive_site(struct dive_site *ds)
{
return ds->dives.nr;
}
bool is_dive_site_selected(struct dive_site *ds)
{
int i;
for (i = 0; i < ds->dives.nr; i++) {
if (ds->dives.dives[i]->selected)
return true;
}
return false;
}
void free_dive_site(struct dive_site *ds)
{
if (ds) {
free(ds->name);
free(ds->notes);
free(ds->description);
free(ds->dives.dives);
free_taxonomy(&ds->taxonomy);
free(ds);
}
}
int unregister_dive_site(struct dive_site *ds)
{
return remove_dive_site(ds, divelog.sites);
}
void delete_dive_site(struct dive_site *ds, struct dive_site_table *ds_table)
{
if (!ds)
return;
remove_dive_site(ds, ds_table);
free_dive_site(ds);
}
/* allocate a new site and add it to the table */
struct dive_site *create_dive_site(const char *name, struct dive_site_table *ds_table)
{
struct dive_site *ds = alloc_dive_site_with_name(name);
add_dive_site_to_table(ds, ds_table);
return ds;
}
/* same as before, but with GPS data */
struct dive_site *create_dive_site_with_gps(const char *name, const location_t *loc, struct dive_site_table *ds_table)
{
struct dive_site *ds = alloc_dive_site_with_gps(name, loc);
add_dive_site_to_table(ds, ds_table);
return ds;
}
/* if all fields are empty, the dive site is pointless */
bool dive_site_is_empty(struct dive_site *ds)
{
return !ds ||
(empty_string(ds->name) &&
empty_string(ds->description) &&
empty_string(ds->notes) &&
!has_location(&ds->location));
}
void copy_dive_site(struct dive_site *orig, struct dive_site *copy)
{
free(copy->name);
free(copy->notes);
free(copy->description);
copy->location = orig->location;
copy->name = copy_string(orig->name);
copy->notes = copy_string(orig->notes);
copy->description = copy_string(orig->description);
copy_taxonomy(&orig->taxonomy, &copy->taxonomy);
}
static void merge_string(char **a, char **b)
{
char *s1 = *a, *s2 = *b;
if (!s2)
return;
if (same_string(s1, s2))
return;
if (!s1) {
*a = strdup(s2);
return;
}
*a = format_string("(%s) or (%s)", s1, s2);
free(s1);
}
/* Used to check on import if two dive sites are equivalent.
* Since currently no merging is performed, be very conservative
* and only consider equal dive sites that are exactly the same.
* Taxonomy is not compared, as no taxonomy is generated on
* import.
*/
static bool same_dive_site(const struct dive_site *a, const struct dive_site *b)
{
return same_string(a->name, b->name)
&& same_location(&a->location, &b->location)
&& same_string(a->description, b->description)
&& same_string(a->notes, b->notes);
}
struct dive_site *get_same_dive_site(const struct dive_site *site)
{
int i;
struct dive_site *ds;
for_each_dive_site (i, ds, divelog.sites)
if (same_dive_site(ds, site))
return ds;
return NULL;
}
void merge_dive_site(struct dive_site *a, struct dive_site *b)
{
if (!has_location(&a->location)) a->location = b->location;
merge_string(&a->name, &b->name);
merge_string(&a->notes, &b->notes);
merge_string(&a->description, &b->description);
if (!a->taxonomy.category) {
a->taxonomy = b->taxonomy;
memset(&b->taxonomy, 0, sizeof(b->taxonomy));
}
}
struct dive_site *find_or_create_dive_site_with_name(const char *name, struct dive_site_table *ds_table)
{
int i;
struct dive_site *ds;
for_each_dive_site(i,ds, ds_table) {
if (same_string(name, ds->name))
break;
}
if (ds)
return ds;
return create_dive_site(name, ds_table);
}
void purge_empty_dive_sites(struct dive_site_table *ds_table)
{
int i, j;
struct dive *d;
struct dive_site *ds;
for (i = 0; i < ds_table->nr; i++) {
ds = get_dive_site(i, ds_table);
if (!dive_site_is_empty(ds))
continue;
for_each_dive(j, d) {
if (d->dive_site == ds)
unregister_dive_from_dive_site(d);
}
}
}
void add_dive_to_dive_site(struct dive *d, struct dive_site *ds)
{
int idx;
if (!d) {
report_info("Warning: add_dive_to_dive_site called with NULL dive");
return;
}
if (!ds) {
report_info("Warning: add_dive_to_dive_site called with NULL dive site");
return;
}
if (d->dive_site == ds)
return;
if (d->dive_site) {
report_info("Warning: adding dive that already belongs to a dive site to a different site");
unregister_dive_from_dive_site(d);
}
idx = dive_table_get_insertion_index(&ds->dives, d);
add_to_dive_table(&ds->dives, idx, d);
d->dive_site = ds;
}
struct dive_site *unregister_dive_from_dive_site(struct dive *d)
{
struct dive_site *ds = d->dive_site;
if (!ds)
return NULL;
remove_dive(d, &ds->dives);
d->dive_site = NULL;
return ds;
}

262
core/divesite.cpp Normal file
View file

@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0
/* divesite.c */
#include "divesite.h"
#include "dive.h"
#include "divelist.h"
#include "errorhelper.h"
#include "format.h"
#include "membuffer.h"
#include "subsurface-string.h"
#include "sha1.h"
#include <math.h>
int divesite_comp_uuid(const dive_site &ds1, const dive_site &ds2)
{
if (ds1.uuid == ds2.uuid)
return 0;
return ds1.uuid < ds2.uuid ? -1 : 1;
}
template <typename PRED>
dive_site *get_by_predicate(const dive_site_table &ds_table, PRED pred)
{
auto it = std::find_if(ds_table.begin(), ds_table.end(), pred);
return it != ds_table.end() ? it->get() : NULL;
}
dive_site *dive_site_table::get_by_uuid(uint32_t uuid) const
{
// The table is sorted by uuid
auto it = std::lower_bound(begin(), end(), uuid,
[] (const auto &ds, auto uuid) { return ds->uuid < uuid; });
return it != end() && (*it)->uuid == uuid ? it->get() : NULL;
}
/* there could be multiple sites of the same name - return the first one */
dive_site *dive_site_table::get_by_name(const std::string &name) const
{
return get_by_predicate(*this, [&name](const auto &ds) { return ds->name == name; });
}
/* there could be multiple sites at the same GPS fix - return the first one */
dive_site *dive_site_table::get_by_gps(const location_t *loc) const
{
return get_by_predicate(*this, [loc](const auto &ds) { return ds->location == *loc; });
}
/* to avoid a bug where we have two dive sites with different name and the same GPS coordinates
* and first get the gps coordinates (reading a V2 file) and happen to get back "the other" name,
* this function allows us to verify if a very specific name/GPS combination already exists */
dive_site *dive_site_table::get_by_gps_and_name(const std::string &name, const location_t loc) const
{
return get_by_predicate(*this, [&name, loc](const auto &ds) { return ds->location == loc &&
ds->name == name; });
}
/* find the closest one, no more than distance meters away - if more than one at same distance, pick the first */
dive_site *dive_site_table::get_by_gps_proximity(location_t loc, int distance) const
{
struct dive_site *res = nullptr;
unsigned int cur_distance, min_distance = distance;
for (const auto &ds: *this) {
if (ds->has_gps_location() &&
(cur_distance = get_distance(ds->location, loc)) < min_distance) {
min_distance = cur_distance;
res = ds.get();
}
}
return res;
}
dive_site_table::put_result dive_site_table::register_site(std::unique_ptr<dive_site> ds)
{
/* If the site doesn't yet have an UUID, create a new one.
* Make this deterministic for testing. */
if (!ds->uuid) {
SHA1 sha;
if (!ds->name.empty())
sha.update(ds->name);
if (!ds->description.empty())
sha.update(ds->description);
if (!ds->notes.empty())
sha.update(ds->notes);
ds->uuid = sha.hash_uint32();
}
/* Take care to never have the same uuid twice. This could happen on
* reimport of a log where the dive sites have diverged */
while (ds->uuid == 0 || get_by_uuid(ds->uuid) != NULL)
++ds->uuid;
return put(std::move(ds));
}
dive_site::dive_site()
{
}
dive_site::dive_site(const std::string &name) : name(name)
{
}
dive_site::dive_site(const std::string &name, const location_t loc) : name(name), location(loc)
{
}
dive_site::dive_site(uint32_t uuid) : uuid(uuid)
{
}
dive_site::~dive_site()
{
}
/* when parsing, dive sites are identified by uuid */
dive_site *dive_site_table::alloc_or_get(uint32_t uuid)
{
struct dive_site *ds;
if (uuid && (ds = get_by_uuid(uuid)) != NULL)
return ds;
return register_site(std::make_unique<dive_site>(uuid)).ptr;
}
size_t dive_site::nr_of_dives() const
{
return dives.size();
}
bool dive_site::is_selected() const
{
return any_of(dives.begin(), dives.end(),
[](dive *dive) { return dive->selected; });
}
bool dive_site::has_gps_location() const
{
return has_location(&location);
}
/* allocate a new site and add it to the table */
dive_site *dive_site_table::create(const std::string &name)
{
return register_site(std::make_unique<dive_site>(name)).ptr;
}
/* same as before, but with GPS data */
dive_site *dive_site_table::create(const std::string &name, const location_t loc)
{
return register_site(std::make_unique<dive_site>(name, loc)).ptr;
}
/* if all fields are empty, the dive site is pointless */
bool dive_site::is_empty() const
{
return name.empty() &&
description.empty() &&
notes.empty() &&
!has_location(&location);
}
static void merge_string(std::string &a, const std::string &b)
{
if (b.empty())
return;
if (a == b)
return;
if (a.empty()) {
a = b;
return;
}
a = format_string_std("(%s) or (%s)", a.c_str(), b.c_str());
}
/* Used to check on import if two dive sites are equivalent.
* Since currently no merging is performed, be very conservative
* and only consider equal dive sites that are exactly the same.
* Taxonomy is not compared, as no taxonomy is generated on
* import.
*/
static bool same(const struct dive_site &a, const struct dive_site &b)
{
return a.name == b.name
&& a.location == b.location
&& a.description == b.description
&& a.notes == b.notes;
}
dive_site *dive_site_table::get_same(const struct dive_site &site) const
{
return get_by_predicate(*this, [site](const auto &ds) { return same(*ds, site); });
}
void dive_site::merge(dive_site &b)
{
if (!has_location(&location)) location = b.location;
merge_string(name, b.name);
merge_string(notes, b.notes);
merge_string(description, b.description);
if (taxonomy.empty())
taxonomy = std::move(b.taxonomy);
}
dive_site *dive_site_table::find_or_create(const std::string &name)
{
struct dive_site *ds = get_by_name(name);
if (ds)
return ds;
return create(name);
}
void dive_site_table::purge_empty()
{
for (const auto &ds: *this) {
if (!ds->is_empty())
continue;
while (!ds->dives.empty()) {
struct dive *d = ds->dives.back();
if (d->dive_site != ds.get()) {
report_info("Warning: dive %d registered to wrong dive site in %s", d->number, __func__);
ds->dives.pop_back();
} else {
unregister_dive_from_dive_site(d);
}
}
}
}
void dive_site::add_dive(struct dive *d)
{
if (!d) {
report_info("Warning: dive_site::add_dive() called with NULL dive");
return;
}
if (d->dive_site == this)
return;
if (d->dive_site) {
report_info("Warning: adding dive that already belongs to a dive site to a different site");
unregister_dive_from_dive_site(d);
}
dives.push_back(d);
d->dive_site = this;
}
struct dive_site *unregister_dive_from_dive_site(struct dive *d)
{
struct dive_site *ds = d->dive_site;
if (!ds)
return nullptr;
auto it = std::find(ds->dives.begin(), ds->dives.end(), d);
if (it != ds->dives.end())
ds->dives.erase(it);
else
report_info("Warning: dive not found in divesite table, even though it should be registered there.");
d->dive_site = nullptr;
return ds;
}

View file

@ -2,88 +2,40 @@
#ifndef DIVESITE_H #ifndef DIVESITE_H
#define DIVESITE_H #define DIVESITE_H
#include "units.h"
#include "taxonomy.h"
#include "divelist.h" #include "divelist.h"
#include "taxonomy.h"
#include "units.h"
#include <stdlib.h> #include <stdlib.h>
#ifdef __cplusplus
#include <QString>
#include <QObject>
extern "C" {
#else
#include <stdbool.h>
#endif
struct dive_site struct dive_site
{ {
uint32_t uuid; uint32_t uuid = 0;
char *name; std::string name;
struct dive_table dives; std::vector<dive *> dives;
location_t location; location_t location;
char *description; std::string description;
char *notes; std::string notes;
struct taxonomy_data taxonomy; 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); struct dive_site *unregister_dive_from_dive_site(struct dive *d);
int divesite_comp_uuid(const dive_site &ds1, const dive_site &ds2);
#ifdef __cplusplus
}
QString constructLocationTags(struct taxonomy_data *taxonomy, bool for_maintab);
/* Make pointer-to-dive_site a "Qt metatype" so that we can pass it through QVariants */ /* Make pointer-to-dive_site a "Qt metatype" so that we can pass it through QVariants */
#include <QObject>
Q_DECLARE_METATYPE(dive_site *); Q_DECLARE_METATYPE(dive_site *);
#endif
#endif // DIVESITE_H #endif // DIVESITE_H

View file

@ -84,14 +84,14 @@ taxonomy_data reverseGeoLookup(degrees_t latitude, degrees_t longitude)
QString url; QString url;
QJsonObject obj; QJsonObject obj;
taxonomy_data taxonomy = { 0, 0 }; taxonomy_data taxonomy;
// check the oceans API to figure out the body of water // 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); 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 obj = doAsyncRESTGetRequest(url, 5000); // 5 secs. timeout
QVariantMap oceanName = obj.value("ocean").toVariant().toMap(); QVariantMap oceanName = obj.value("ocean").toVariant().toMap();
if (oceanName["name"].isValid()) 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 // 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); 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++) { for (int idx = TC_COUNTRY; idx < TC_NR_CATEGORIES; idx++) {
if (firstData[taxonomy_api_names[idx]].isValid()) { if (firstData[taxonomy_api_names[idx]].isValid()) {
QString value = firstData[taxonomy_api_names[idx]].toString(); 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); std::string l3 = taxonomy_get_value(taxonomy, TC_ADMIN_L3);
const char *lt = taxonomy_get_value(&taxonomy, TC_LOCALNAME); std::string lt = taxonomy_get_value(taxonomy, TC_LOCALNAME);
if (empty_string(l3) && !empty_string(lt)) { if (!l3.empty() && !lt.empty()) {
// basically this means we did get a local name (what we call town), but just like most places // 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, // 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 // 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 { } else {
report_error("geonames.org did not provide reverse lookup information"); report_error("geonames.org did not provide reverse lookup information");

27
core/divesitetable.h Normal file
View file

@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef DIVESITETABLE_H
#define DIVESITETABLE_H
#include "owning_table.h"
#include "units.h"
struct dive_site;
int divesite_comp_uuid(const dive_site &ds1, const dive_site &ds2);
class dive_site_table : public sorted_owning_table<dive_site, &divesite_comp_uuid> {
public:
put_result register_site(std::unique_ptr<dive_site> site); // Creates or changes UUID if duplicate
dive_site *get_by_uuid(uint32_t uuid) const;
dive_site *alloc_or_get(uint32_t uuid);
dive_site *create(const std::string &name);
dive_site *create(const std::string &name, const location_t);
dive_site *find_or_create(const std::string &name);
dive_site *get_by_name(const std::string &name) const;
dive_site *get_by_gps(const location_t *) const;
dive_site *get_by_gps_and_name(const std::string &name, const location_t) const;
dive_site *get_by_gps_proximity(location_t, int distance) const;
dive_site *get_same(const struct dive_site &) const;
void purge_empty();
};
#endif // DIVESITETABLE_H

Some files were not shown because too many files have changed in this diff Show more