The mode of the profile (profile, edit, plan) was set in
MainWindow and ProfileWidget. For consistency move the one
setProfileState() call from MainWindow to ProfileWidget.
This removes a direct access to the profile-view and
therefore improves encapsulation.
Also, clear the profile, when no dive is shown to remove
any potentially dangling references.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This was moved to the desktop version. Enter the profile in
the constructor. Somewhat surprisingly, this seems to work.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Create a new class that encapsulates the profile-widget UI.
This is called ProfileWidget, which might be confusing since
the actual display is called ProfileWidget2. However, the
plan is to rename the latter to ProfileView. After all, it
is also used to print and to show the profile on mobile.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The MainWindow::configureToolBar() function is called every
time plotCurrentDive() is called. Moreover, this is the only
time that it is called. We might just fold the former into
the latter.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The changes in commit 4daf687876 ("profile: remove [disable|enable]Shortcuts()
signals") resulted in us no longer enabling the shortcuts on the desktop (at
least on macOS where I debugged this). This placement of the call feels like a
bit of overkill, but at least it shouldn't be wrong.
Fixes#3293
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This occurs upon importing dives for example via CSV.
Make sure the profile display is cleared when selecting
such a dive rather than showing a different dive.
Allow editing the profile for such a dive.
Signed-off-by: Robert C. Helling <helling@atdotde.de>
The ADD state is not used for adding dives since adding dives
was made undoable. Therefore, rename it to EDIT state, since
that is what it is used for.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This flag is handled directly by the profile code
since 2015 (000c9cc21c).
The function therefore can be removed.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
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>
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>
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>
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>
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>
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>
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 application state is a desktop-only thing. The mobile UI
also has its application state, but that is something completely
different.
The last remaining user of the application state was to flag
whether the planner is active. Since this has all been
unglobalized, the ApplicationState structure can be moved
from core to the desktop UI. And there it can be made local
to the MainWindow class.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The TemplateLayout prints different dives depending on
whether the planner is active. Instead of accessing a
global variable, pass the status down from the MainWindow.
That's all quite convoluted, since there are multiple
layers involved.
On the positive side, the in_planner() function has now
no users an can be removed.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
On first startup, use the splitter values suggested by Dirk:
Top/Bottom: 60/40
Info/Profile: 50/50
List/Map: 60/40
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
On state change, the splitters were completely emptied and
refilled. Instead try to reuse already existing splitter
slots. This reduces annoying flickering.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The memory management of the quadrant widgets is a total mess:
When setting the widget, the QSplitters take ownership, which
means that they will delete the widget in their destructor.
This is inherently incompatible with singletons, which must
not be deleted.
To avoid all these troubles, remove the widgets from the
QSplitters in the desctructor of the MainWindow. This of
course means that we now have to take care about deletion
of the widgets.
For local widgets use std::unique_ptr, for singletons use
a static variable that is deleted on application exit.
Sadly, for the map widget we can't use a normal singleton,
because the QML MapWidget's memory management is buggy.
Add a comment in the source code explaining this.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When in planner mode, don't allow the user to change the application
state. This brought us nothing but troubles and inconsistencies.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
There was the "application state", which decided what to show
in the "quadrants" and the "view state" which decided which
quadrant to show. These interacted in a hard-to-grasp way.
The "view state" is used to show the map or dive list in
full screen.
I simply couldn't get these two orthogonal states to interact
properly. Moreover the thing was buggy: If a quadrant was hidden,
the user could still show it, by dragging from the side of the
window, at least under KDE.
To solve these woes, merge the two states into a single
application state. If the widget of a quadrant is set to null,
don't show it. So the four "view states" are now "application
states" where three of the four quadrants are not shown.
This also changes the memory management of the widgets:
widgets that are not shown are now removed from the QSplitter
objects. This makes it possible that the same widget is
shown in *different* quadrants.
While writing this, I stumbled upon a Qt bug, which is known
since 2014:
https://forum.qt.io/topic/43176/qsplitter-sizes-return-0
When restoring the quadrant sizes there was a test whether
the quadrant size is 0. If that was the case, a default size
was set. This seems not to work if the widgets were recently
added. Since this test now always fails, make the quadrants
non-collapsible and thus guarantee that 0 is never saved as
a size.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Add a new "statistics" application state. In the statistics state
show the statistics widget and the filter in the top quadrants.
The idea is to allow filtering and doing statistics at the same
time.
Sadly, we can't use the filter-widget in different quadrants,
because Qt's ownership model is completely broken / inflexible.
It does not support a widget having different parents and
thus a widget can only belong to one QStackedWidget.
Hiding the map in the statistics view is quite hacky:
Since the view of the quadrants is not determined by the
"ApplicationState", we have to restore the original quadrant
visibility when exiting the stats mode. Therefore, set the
original visibility-state when changing application state.
The MainWindow-quadrant code really needs to be rewritten!
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The mainwindow was connecting preferences changes to the profile.
Do this directly in the profile.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Around 2015 there was a push to move planner UI code from
mainwindow.cpp to diveplanner.cpp. That never was completed,
presumably because the planner is actually three widgets.
Collect these widgets in one PlannerWidgets class and move
the code there.
This is not a full dis-entanglement, as the plannerwidgets
have to access the profile via the mainwindow. But at least
it collects the planner UI code at a single place.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The main window called a function to init the header actions
(i.e. the context menu) of the dive-list. There is no reason why
this shouldn't be done in the constructor of the dive list, since
it only accesses the QSettings, which are available at application
startup. This improves modularity of the code (by a tiny, tiny bit).
Moreover, the initialization function was at the same time the
header-reloading function. That function can now be folded
into the settings-changed function, since that is the only
remaining user.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
So far, the PreferencesDialog emitted a settingsChanged signal.
This meant that models that listened to that signal had to
conditionally compile out the code for mobile or the connection
had to be made in MainWindow.
Instead, introduce a global signal that does this and move
the connects to the listeners to remove inter-dependencies.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This used to reload the completion models. Moreover, remove two
obsolete member-function declarations.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This function was used to unify both methods of tracking unsaved
changes. Since desktop now only uses the undo system, it can
be replaced by a single call to "Command::setClean()".
Arguably, the UI is the wrong place to do this and the appropriate
calls should be done by the core. However, let's play it safe
for now and avoid any breaking change.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This function was used to unify both methods of checking for
unsaved changes: the global unsaved_changes() flag and the
Command::clean() function of the undo-system.
However, all desktop functions are now undoable and therefore
the function is not needed and can be replaced by calls
to !Command::clean().
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Add a device_table parameters to Command::importTable() and
add_imported_dives(). The content of this table will be added
to the global device list (respectively removed on undo).
This is currently a no-op, as the parser doesn't yet fill
out the device table, but adds devices directly to the global
device table.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
If we want to avoid the parsers to directly modify global data,
we have to provide a device_table to parse into. This adds such
a state and the corresponding function parameters. However,
for now this is unused.
Adding new parameters is very painful and this commit shows that
we urgently need a "struct divelog" collecting all those tables!
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When importing dive-sites we would add to the global filter-preset
table. This data should be thrown away, just like the other tables
that might be imported.
This shows that we really should introduce a "struct divelog",
which collects all those tables into a single structure.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
We used a typedef "filter_preset_table_t" for the filter preset table,
because it is a "std::vector<filter_preset>". However, that is in
contrast to all the other global tables (dives, trips, sites) that we
have.
Therefore, turn this into a standard struct, which simply inherits
from "std::vector<filter_preset>". Note that while inheriting from
std::vector<> is generally not recommended, it is not a problem
here, because we don't modify it in any shape or form.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The name "FilterWidget2" is historical and has no meaning anymore,
since the current version has little to nothing to do with the
"second" version of the widget.
Rename the class and source files accordingly.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When importing a divelog, import filter presets. If there are
equal names, import only if the presets differ. In that case,
disambiguate the name. This made things a bit more complicated,
as comparison of filter presets had to be implemented.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
The filter widget was caching whether the filter was active and
used that flag to calculate the "# dives shown" string. Move this
directly to the DiveFilter class to remove interdependencies and
to unify with mobile.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This is a bit painful: since we don't want to modify the filter
presets when the user imports (as opposed to opens) a log,
we have to provide a table where the parser stores the presets.
Calling the parser is getting quite unwieldy, since many tables
are passed. We probably should introduce a structure representing
a full log-book at one point, which collects all the things that
are saved to the log.
Apart from that, this is simply the counterpart to saving to XML.
The interpretation of the string data is performed by core
functions, not the parser itself to avoid code duplication with
the git parser.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When planning a dive, dc_number was set to 1, which actually is
the second dc! The code seems to handle this gracefully, however
it appears weird. Let's set dc_number to 0 instead.
Originally, the assignment was introduced in a422957cd6
and moved later in 4f5621c4c6.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
When the dive has no explicity salinity, our conversion
between pressure and depth assumed salt water. Make this
explicity by using the corresponding macro.
When the planner starts and no salinity is set explicity,
set the water type chooser to salt water to reflect
our default assumption.
Signed-off-by: Robert C. Helling <helling@atdotde.de>
There is no reason that this dialog is a singleton. Since it is modal,
it can be created on demand. This simplifies code, because the mode
(selected-only or all-dives) can be set in the constructor.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>