Move the ARRAY_SIZE macro into a header file and use it to determine the
number of cloud servers that we need to check.
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
If we can't reach the cloud server in the URL (which might come from the
settings or be passed in by the user), we try the alternative server(s).
If we end up changing servers, we need to update the remote that we have
already parsed from the URL.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
If we can't reach our preferred server, try using a different one.
The diff makes more sense when ignoring white space.
With this we check the connection to the cloud server much earlier and
in case of failure to connect try a different cloud_base_url.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
The code assumes that prefs.cloud_base_url is non NULL. Allowing that to
be NULL makes no sense during normal operation of the app. Yet, most of
the tests don't initialize the prefs at all.
Making things worse, if we do correctly initialize the prefs (so as to
reasonably approximate the behavior when running the app), things break
because some of the reference outputs assume that the prefs are unset.
This deserves fixing.
For now, simply make sure that cloud_base_url is set for all the tests
that try to parse files.
Additionally, the semantics how cloud_base_url is saved to disk have
changed, so adjust the test for those prefs accordingly.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
With the new names for the cloud server we'd get different local cache
directory names depending on which server gets used. In order to avoid
that, normalize the name before generating the hash that determines the
local directory name.
Additionally, the old code had an extra '/' in the URL, due to the way
the URL was assembled. Again, to match the existing hash for people
upgrading from older Subsurface versions, add that to our normalized
name as well.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
The backend infrastructure will soon be able to support more than one
cloud server which automagically stay in sync with each other.
One critical requirement for that to work is that once a session was
started with one of the servers, the complete session happens with that
server - we must not switch from server to server while doing a git
transaction. To make sure that's the case, we aren't trying to use DNS
tricks to make this load balancing scheme work, but instead try to
determine at program start which server is the best one to use.
Right now this is super simplistic. Two servers, one in the US, one in
Europe. By default we use the European server (most of our users appear
to be in Europe), but if we can figure out that the client is actually
in the Americas, use the US server. We might improve that heuristic over
time, but as a first attempt it seems not entirely bogus.
The way this is implemented is a simple combination of two free
webservices that together appear to give us a very reliable estimate
which continent the user is located on.
api.ipify.org gives us our external IP address
ip-api.com gives us the continent that IP address is on
If any of this fails or takes too long to respond, we simply ignore it
since either server will work. One oddity is that if we decide to change
servers we only change the settings that are stored on disk, not the
runtime preferences. This goes back to the comment above that we have to
avoid changing servers in mid sync.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
We know the preference is never empty, so stop testing for this. But
don't maintain two different preferences with basically the same
content. Instead add the '/git' suffix where needed and keep this all in
one place.
Simplify the extraction of the branch name from the cloud URL.
Also a typo fix and a new comment.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This was a hack for a very early SSL certificate that was rejected on
some platforms. We haven't used that one in ages, so let's just remove
the whole hack - but always show in the console output when there was an
SSL error.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
When showing the "empty-state", the profile toolbar was
disabled. This was done via a "reverse" signal from the
profile to the MainWindow. Instead control the toolbar
in the MainWindow directly. Break out the plot-dive
functionality into a member function and there test
whether a dive is shown or not.
The signal makes no sense in the context of mobile
or printing.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When switching to the "plan" or "add" (which should rather be
called "edit", by the way) mode of the profile, the "shortcuts"
for copy&paste, undo&redo, etc. are disabled. When switching
to "profile" mode, they are reenabled.
This was done in a most convoluted way:
- The MainWindow calls the set*State() function of the profile.
- The Profile emits [disable|enable]Shortcuts() signals.
- The MainWindow catches these signals and does the enabling
or disabling.
Not only is this very hard to reason about, it is also in
contradiction to the profile being part of the display layer.
Moreover, in editCurrentDive() the MainWindow disabled the
shortcuts itself, so this was all redundant.
For the sake of sanity, let's just move this logic to the
MainWindow, unslotify the [disable|enable]Shortcuts() functions
and make them private.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
There are two cases in this function: with and without holding
the control-key. The former deletes one point, the latter all
points starting with the selected point to the end.
The code was interlaced making it very hard to reason about.
Notably, it was buggy: with control, all points could be
deleted, leading to a crash.
Split the function in two versions, with their own bound
checking. This produces a bit of duplicate code, which
might be broken out later.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When updating the dive profile, a thread is started to calculate
plan-variations. This is done even when only editing the profile
or when variation calculation is disabled by the user. The thread
then exits if it shouldn't calculate the variations.
Turn this around: test whether variations should be calculated
before starting the thread.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
removeDeco() was called by addStop() if the recalc flag was
set. If the caller didn't want to call removeDeco() it had
to clear and restore the flag.
Instead, call removeDeco() explicitly when needed.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
There are no more external users of this flag, therefore clearing
that flag is a no-op.
Moreover, clear the cylinders array and the preserved_until
flag befor emitting the model-reset signal.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Split the function in one external version, that updates the
dive profile and cylinders and one internal version, that
does no recalculations. In the latter case, the caller is
responsible for updating the dive.
Thus, the recalculation flag-clearing can be removed from
removeDeco().
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
In planner or profile-edit mode, the plotDive() function takes
the current plan and turns it into a dive profile. Not only
is this a layering violation (the display layer modifying the
dive), it is also fundamentally flawed. The control-flow is
out of control, if you wish. There are numerous reasons why
the profile needs to be replot, many of which do not need
a recalculated dive profile.
Move the code that updates the dive-profile to the
DivePlannerPointsModel. Thus, the profile recalculations
and replots can be pooled. This will break the planner, since
there now might be missing calls to the profile recalculation.
But it already has some positive effects: when removing
multiple points, the profile is only recalculated once.
This will need much more work, but it is a start.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The DivePlannerPointsModel::addStop() function is called by
the profile to add a planner-stop. It is also used internally
to create profiles.
If we ever want to include this in the undo system, we have
to split these into to versions. One will ultimately place
an undo command and update the profile, the other one doesn't.
For now, this makes the external interface simpler, as some
parameters are redundant.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The dive handlers are only updated by signals. This means that
switching into edit-mode has to be done in steps:
1) initialize the DivePointsPlannerModel
2) switch profile mode
3) load dive into DivePointsPlannerModel
2) and 3) cannot be exchanged, or the dive handlers are not
initialized.
To avoid this sandwitching of profile- and model-initialization,
populate the dive handlers when switching the profile mode.
Thus, the profile can be switched into edit/plan mode when
the DivePointsPlannerModel is fully initialized.
This will be important in upcoming commits, when the initialization
of the dive is moved from the profile to the DivePointsPlannerModel.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The DivePlannerPointsModel::createTemporaryPlan() function had
two distinct and independent parts:
1) create the data points.
2) create the dive sample and calculate variations.
The second part was only exectuted if the recalc flag was set.
Out of the two callers, one was explicitly disabling and setting
the recalc flag to avoid the second part.
The much more logical thing is to simply split the function in
two and only call the first part.
To avoid any functional change, the second caller (the profile)
still tests for the recalc flag. However, if it shouldn't replot
a new plan, why calculate it in the first place!? And why does
the display function change the plan at all? This appears all
very ill-thought out and should be changed in due course.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The way the blocks in DivePlannerPointsModel::setData()'s
switch statement were demarked messed with my mind.
There were at least three variants. Let's try to be consistent.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The only external user of setRecalc() was turning recalculation
on. In fact, this happened when constructing the planner-widget.
However, for example editing of the profile only works when
the recalc flag is on.
This is all very confusing, let's just turn the flag on by
default and remove the accessor. Internally, the planner can
simply use the std::exchange function to set and reset the
recalc flag.
Perhaps the setting/resetting can be replaced by simple
recalc = true;
...
recalc = false;
pairs. It is unclear whether there is need for recursion.
Something to be investigated.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When moving "dive handlers" with the cursor keys, the
profile was replot twice:
- First the recalculation of the planner model was suspended.
- The "stop" was moved.
- This led to a replot by a signal from the planner model.
However, the old profile was shown, since the recalculation
was suspended.
- The recalculation was reenabled.
- The profile war replot, resulting now in the correct profile.
A classical case of bit rot.
Instead, don't suspend calculation in the first place. This
shows the correct profile on the first replot and the second
replot can be removed.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The ItemPos structure describes the position of various chart
elements on the scene. It had two problems:
- The identifiers were starting with an underscore followed
by a capital letter. This is reserved to the compiler.
- The global object was initialized in the ProfileWidget's
constructor. This means that if there are multiple
ProfileWidgets, the structure is reinitialized even though
it is constant.
Remove the underscores (what was the point anyway?) and
initialize the structure in its own constructor. Moreover,
make the object const to drive the point home.
If this ever needs to be variable, each ProfileWidget
should get its own copy of the object.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
MainTab::updateDiveInfo() is not executed when in the planner.
To decide whether the application is in the planner state,
it queried the profile. Instead, query the DivePlannerPointsModel.
Currently, there is no autoritative carrier of that flag.
However, the MainTab has a dependency on DivePlannerPointsModel
anyway, and therefore this removes a dependency on the
profile. This brings us closer to a state where we can have
multiple profiles.
Ultimately, it is hoped that the whole check can be removed
at this place, making the point moot.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Remove mainwindow-access from the planner, by setting
the profile to planner state in the owner of the profile,
viz. the MainWindow.
The MainWindow sets the application state to planner, so
it seems legit that it also sets the profile state.
This removes a further interdependency.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The accept / reject message is only shown in edit-mode, no
need to check it. This is a step in simplification / removal
of the edit mode.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
So far the profile operated on the global displayed_dive. Instead,
take the dive to be displayed as a parameter to the plotDive()
functions.
This is necessary if we want to have multiple concurrent
profile objects. Think for example for printing or for mobile
where multiple dive objects are active at the same time.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When moving a planner point with the cursor, nothing
is wrong with extending the dive time by stepping
beyond the current maximum. Same for depth.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The code took care to not delete planner-points when no
points are selected. However, it assumed that all selected
objects are planner-points. But then it checked whether
the selected object actually is a planner-point. So which
is it?
Remove the outter check for an empty selection. This makes
things more logical and more robust, should there ever
be other objects that can be selected.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
To remove global state, make the dive that DivePlannerModel
works on a member variable. Pass the dive in createSimpleDive()
and loadFromDive(). Moreover, this should pave the way to more
fine-grained undo in the planner. Ultimately, the planner
should not be modal.
Attention: for now, the dive must still be displayed_dive,
because of the convoluted way in which the profile and the
planner work on the same dive.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Both loadFromDive() callers were clearing the model before
calling loadFromDive(). Move the clearing into that function
since it makes no sense to load into a non-cleared model.
Apparently this changes the way that no-cylinder dives are
treated and the code in ProfileWidget2::repositionDiveHandlers()
must now explicitly check for that condition.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
In DivePlannerPointsModel::clear(), the cylinder model is
updated before it is cleared. This must be an artifact.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
There must not be two dive planner points at the same time
stamp, as this violates the laws of physics (and internal
assumptions).
The corresponding test was done in the profile code at
two different places with floating point arithmetics.
This is a bad idea, because
1) code duplication
2) danger of rounding issues
Instead, do this in one central point in the planner model
and use integer arithmetics. Simply add a few seconds until
a unique timestamp is obtained.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When moving the handle with the mouse, the old code tried
to be smart about changing the active handle when crossing
handles.
To me this always felt weird and it was inconsistent with
mouse-move. Theregore, simply do nothing special at all. The
user should hopefully get an intiutive grasp of what's going
on when moving one handler across another.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This model is only needed when in plan mode. To enable multiple
profilewidgets at the same time (e.g. for the mobile app or
for printing), make the pointer to DivePlannerPointsModel a
member variable that is initialized at construction time.
Moreover, allow passing null as the DivePlannerPointsModel,
in which case planning will be disabled. This will be useful
for simple printing.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The connection to the DivePointsPlannerModel was done in two
distinct functions: setAddState() and setPlanState(), which
means that these could easily get out-of-sync. Factor this out
into a single function.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When reordering the points, the DivePlannerPointsModel would
not emit the appropriate move signals, but simply a data-changed
signal over all elements. This obviously violates Qt's
model/view API, though it is probably harmless. Let's do
the right thing so that the frontend knows that the selected
item changed place.
Also, emit dataChanged only on the actually changed element,
not all elements.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The ProfileWidget2 slots, which reacted to model changes were
broken. They did not add / remove items at the changed positions,
but arbitrarily at the end. Moreover, they assumed that only
a single item was added / removed and thus violated the model/view
API.
This worked because the handles are completely reset after each
operation and the model only ever touched single items.
Nevertheless, this has to be fixed if we ever want finer grained
undo.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Instead of manually deleting them (and the gases). Currently
there is only one point where these are deleted, but if
we implement proper Qt model/view semantics, this makes things
less headachy.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The beginRemoveRows() function was fed erroneous values. It
is a mystery why this didn't crash. In any case, deletion
of multiple points did not work properly. Instead of trying
to be fancy, remove each point one-by-one.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Instead of inserting the point at the calculated
position, the DivePlannerPointsModel would append it
at the end and then resort the vector. That's just
silly.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When clearing the model, use "beginResetModel/endResetModel"
instead of "beginRemoveRows/endRemoveRows".
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When clicking on "+" in the planner, a default stop point was
added using a signal/slot connection. This used the archaic
string-based connect syntax, because it was realized with
default parameters passed to "addStop()". Instead, add a
"addDefaultStop()" slot, which passes the default parameters.
Since all other callers do not use callbacks, unslotify
"addStop()". The slot was the only user of the default parameters,
so they can be removed alltogether.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>