ae-gui 0.3.109__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. ae/gui/__init__.py +712 -0
  2. ae/gui/app.py +1498 -0
  3. ae/gui/img/Flag_de.png +0 -0
  4. ae/gui/img/add_item.png +0 -0
  5. ae/gui/img/app_tour.png +0 -0
  6. ae/gui/img/bubble_arrow.png +0 -0
  7. ae/gui/img/close_popup.png +0 -0
  8. ae/gui/img/copy_node.png +0 -0
  9. ae/gui/img/cut_node.png +0 -0
  10. ae/gui/img/delete_item.png +0 -0
  11. ae/gui/img/drag_item.png +0 -0
  12. ae/gui/img/edit_item.png +0 -0
  13. ae/gui/img/enter_item.png +0 -0
  14. ae/gui/img/export_node.png +0 -0
  15. ae/gui/img/filter_off.png +0 -0
  16. ae/gui/img/filter_on.png +0 -0
  17. ae/gui/img/flag_en.png +0 -0
  18. ae/gui/img/flag_es.png +0 -0
  19. ae/gui/img/help_circled.png +0 -0
  20. ae/gui/img/help_icon.png +0 -0
  21. ae/gui/img/icon_view.png +0 -0
  22. ae/gui/img/import_node.png +0 -0
  23. ae/gui/img/leave_item.png +0 -0
  24. ae/gui/img/light_1/add_item.png +0 -0
  25. ae/gui/img/light_1/app_tour.png +0 -0
  26. ae/gui/img/light_1/bubble_arrow.png +0 -0
  27. ae/gui/img/light_1/close_popup.png +0 -0
  28. ae/gui/img/light_1/copy_node.png +0 -0
  29. ae/gui/img/light_1/cut_node.png +0 -0
  30. ae/gui/img/light_1/delete_item.png +0 -0
  31. ae/gui/img/light_1/drag_item.png +0 -0
  32. ae/gui/img/light_1/edit_item.png +0 -0
  33. ae/gui/img/light_1/enter_item.png +0 -0
  34. ae/gui/img/light_1/export_node.png +0 -0
  35. ae/gui/img/light_1/filter_off.png +0 -0
  36. ae/gui/img/light_1/filter_on.png +0 -0
  37. ae/gui/img/light_1/help_circled.png +0 -0
  38. ae/gui/img/light_1/help_icon.png +0 -0
  39. ae/gui/img/light_1/icon_view.png +0 -0
  40. ae/gui/img/light_1/import_node.png +0 -0
  41. ae/gui/img/light_1/leave_item.png +0 -0
  42. ae/gui/img/light_1/list_view.png +0 -0
  43. ae/gui/img/light_1/open_node_info.png +0 -0
  44. ae/gui/img/light_1/paste_node.png +0 -0
  45. ae/gui/img/light_1/save_item.png +0 -0
  46. ae/gui/img/light_1/send_item.png +0 -0
  47. ae/gui/img/light_1/tap_pointer.png +0 -0
  48. ae/gui/img/list_view.png +0 -0
  49. ae/gui/img/open_node_info.png +0 -0
  50. ae/gui/img/paste_node.png +0 -0
  51. ae/gui/img/save_item.png +0 -0
  52. ae/gui/img/send_item.png +0 -0
  53. ae/gui/img/tap_pointer.png +0 -0
  54. ae/gui/loc/de/Msg.txt +245 -0
  55. ae/gui/loc/en/Msg.txt +214 -0
  56. ae/gui/loc/es/Msg.txt +240 -0
  57. ae/gui/snd/added.wav +0 -0
  58. ae/gui/snd/debug_draw.wav +0 -0
  59. ae/gui/snd/debug_save.wav +0 -0
  60. ae/gui/snd/deleted.wav +0 -0
  61. ae/gui/snd/edited.wav +0 -0
  62. ae/gui/snd/enter_item.wav +0 -0
  63. ae/gui/snd/error.wav +0 -0
  64. ae/gui/snd/filter_off.wav +0 -0
  65. ae/gui/snd/filter_on.wav +0 -0
  66. ae/gui/snd/leave_item.wav +0 -0
  67. ae/gui/snd/touched.wav +0 -0
  68. ae/gui/tours.py +459 -0
  69. ae/gui/utils.py +571 -0
  70. ae_gui-0.3.109.dist-info/METADATA +153 -0
  71. ae_gui-0.3.109.dist-info/RECORD +74 -0
  72. ae_gui-0.3.109.dist-info/WHEEL +5 -0
  73. ae_gui-0.3.109.dist-info/licenses/LICENSE.md +676 -0
  74. ae_gui-0.3.109.dist-info/top_level.txt +1 -0
ae/gui/__init__.py ADDED
@@ -0,0 +1,712 @@
1
+ """
2
+ helper functions and base application classes for GUI applications
3
+ ==================================================================
4
+
5
+ this ae portion is providing base constants, helper functions and classes, independent of any GUI framework,
6
+ to implement upon them Python applications with a graphical user interfaces (GUI).
7
+
8
+ in concrete this portion is providing the generic functionality for:
9
+
10
+ * app- and user-specific configurations
11
+ * persistent app state variables
12
+ * a multilingual context-sensitive help system
13
+ * generic and app-specific app tours
14
+ * app flows (to monitor and control user interaction and app context)
15
+ * app state and key press events
16
+ * app colors and (light/dark) themes
17
+
18
+ this portion is composed of the following modules:
19
+
20
+ * :mod:`~ae.gui.app`: an abstract app class to implement GUI-framework-specific main app classes upon
21
+ * :mod:`~ae.gui.tours`: base classes to offer guiding app tours
22
+ * :mod:`~ae.gui.utils`: generic GUI-specific constants and helper functions
23
+
24
+
25
+ base resources for your gui app
26
+ -------------------------------
27
+
28
+ this portion is also providing base resources of commonly used i18n translation texts, images, and audio sounds.
29
+
30
+ generic i18n translation texts are provided by this portion in the `loc` folder, and can be extended and overloaded
31
+ with app-specific translation texts.
32
+
33
+ .. hint::
34
+ the data-driven approach allows ad-hoc-changes of your app's help texts without the need of code changes or
35
+ recompilation.
36
+
37
+ the license free image resources provided by this portion are taken from:
38
+
39
+ * `iconmonstr <https://iconmonstr.com/interface/>`_.
40
+
41
+ the audio/sounds provides by this portion are taken from:
42
+
43
+ * `Erokia <https://freesound.org/people/Erokia/>`_ at `freesound.org <https://freesound.org>`_.
44
+ * `plasterbrain <https://freesound.org/people/plasterbrain/>`_ at `freesound.org <https://freesound.org>`_.
45
+
46
+
47
+ extended console application environment
48
+ ----------------------------------------
49
+
50
+ the abstract base class :class:`~ae.gui.app.MainAppBase`, provided by this portion, inherits directly from the
51
+ :class:`ae console application environment class <ae.console.ConsoleApp>` of the ae namespace.
52
+ such inherited helper methods are used to log, configure, and control the run-time of your GUI app
53
+ via command line arguments.
54
+
55
+ .. hint::
56
+ please see the documentation of :ref:`config-options` and :ref:`config-files` in the :mod:`ae.console` namespace
57
+ portion/module for more detailed information.
58
+
59
+ the class :class:`~ae.gui.app.MainAppBase` adds on top of the :class:`~ae.console.ConsoleApp` the concepts of:
60
+
61
+ * :ref:`application events`
62
+ * :ref:`application status`
63
+ * :ref:`application flow`
64
+ * a :ref:`context-sensitive help system`
65
+ * :ref:`user guiding application tours`
66
+ * :ref:`generic key press events`.
67
+
68
+
69
+ application events
70
+ ------------------
71
+
72
+ some of the events described in this section are fired on application startup and shutdown.
73
+
74
+ additional events get fired e.g., in relation to the app states (documented further down in the section
75
+ :ref:`app state events`) or on start or stop of an :ref:`app tour <app tour start and stop events>`.
76
+
77
+ the following application events are fired exactly one time at startup in the following order:
78
+
79
+ * `on_app_init`: fired **after** :class:`ConsoleApp` app instance got initialized (detected config files)
80
+ and **before** the image and sound resources and app states get loaded and the GUI framework app class instance
81
+ gets initialized.
82
+ * `on_app_run`: fired **from within** the method :meth:`~ae.gui.app.MainAppBase.run_app`, **after** the parsing of
83
+ the command line arguments and options, and **before** all portion resources got imported.
84
+ * `on_app_build`: fired **after** all portion resources got loaded/imported, and **before** the framework event
85
+ loop of the used GUI framework gets started.
86
+ * `on_app_started`: fired **after** all app initializations, and the start of and the initial processing of the
87
+ framework event loop.
88
+
89
+ .. note::
90
+ the application events `on_app_build` and `on_app_started` have to be fired by the used GUI framework.
91
+
92
+ .. hint::
93
+ depending on the used GUI framework, there can be more app start events. e.g., the :mod:`ae.kivy.apps` module
94
+ fires the events :meth:`~ae.kivy.apps.KivyMainApp.on_app_built` and :meth:`~ae.kivy.apps.KivyMainApp.on_app_start`
95
+ (all of them fired after :meth:`~ae.kivy.apps.KivyMainApp.on_app_run` and
96
+ :meth:`~ae.kivy.apps.KivyMainApp.on_app_build`). more detailed info is available in the
97
+ section :ref:`kivy application events`.
98
+
99
+ when an application gets stopped, then the following events get fired in the following order:
100
+
101
+ * `on_app_exit`: fired **after* framework win got closed and just **before** the event loop of the GUI framework
102
+ will be stopped and the app shutdown.
103
+ * `on_app_quit`: fired **after** the event loop of the GUI framework got stopped and before
104
+ the :meth:`AppBase.shutdown` method will be called.
105
+
106
+ .. note::
107
+ the `on_app_exit` events will only be fired if the app is explicitly calling the
108
+ :meth:`~ae.gui.app.MainAppBase.stop_app` method.
109
+
110
+ .. hint::
111
+ depending on the used GUI framework, there can be more events. e.g., the :mod:`~ae.kivy.apps` module fires
112
+ the event :meth:`~ae.kivy.apps.KivyMainApp.on_app_stop`, and one clock tick later
113
+ the event :meth:`~ae.kivy.apps.KivyMainApp.on_app_stopped`
114
+ (both of them before :meth:`~ae.kivy.apps.KivyMainApp.on_app_quit` get fired).
115
+ see also :ref:`kivy application events`.
116
+
117
+
118
+ application status
119
+ ------------------
120
+
121
+ any application- and user-specific configurations like e.g., the last app window position/size, the app
122
+ theme/font/language or the last selected app flow, could be included in the app status variables.
123
+
124
+ this namespace portion adds/introduces the section `aeAppState` to the app :ref:`config-files`, where any
125
+ status variable values can be stored persistently to be recovered on the next startup of your application.
126
+
127
+ .. hint::
128
+ the section name `aeAppState` is declared by the :data:`APP_STATE_SECTION_NAME` constant. to access this
129
+ config section directly, use this constant instead of the hardcoded section name.
130
+
131
+
132
+ .. _app-state-variables:
133
+
134
+ app state variables
135
+ ^^^^^^^^^^^^^^^^^^^
136
+
137
+ this module is providing/pre-defining the following application state variables:
138
+
139
+ * :attr:`~ae.gui.app.MainAppBase.app_state_version`: the version of the app states implementation
140
+ * :attr:`~ae.gui.app.MainAppBase.create_ink`: color to add/create new app data/items
141
+ * :attr:`~ae.gui.app.MainAppBase.delete_ink`: color to delete app data/item
142
+ * :attr:`~ae.gui.app.MainAppBase.error_ink`: color to display error messages/popups
143
+ * :attr:`~ae.gui.app.MainAppBase.flow_id`: current working flow id
144
+ * :attr:`~ae.gui.app.MainAppBase.flow_id_ink`: color to display/highlight widget(s) with the current flow id
145
+ * :attr:`~ae.gui.app.MainAppBase.flow_path`: stack of entered/opened app flows
146
+ * :attr:`~ae.gui.app.MainAppBase.flow_path_ink`: color to display the current flow path
147
+ * :attr:`~ae.gui.app.MainAppBase.font_size`: size of the main app font (also used to calculate the grid row height)
148
+ * :attr:`~ae.gui.app.MainAppBase.info_ink`: color to display info messages
149
+ * :attr:`~ae.gui.app.MainAppBase.lang_code`: id of the selected user language
150
+ * :attr:`~ae.gui.app.MainAppBase.light_theme`: light/dark background (for app themes)
151
+ * :attr:`~ae.gui.app.MainAppBase.read_ink`: color to display read-only data/items
152
+ * :attr:`~ae.gui.app.MainAppBase.selected_ink`: color to highlight currently selected app data/items
153
+ * :attr:`~ae.gui.app.MainAppBase.sound_volume`: audio volume of sound resources
154
+ * :attr:`~ae.gui.app.MainAppBase.theme_names`: list of user-generated themes (set of foreground/background colors)
155
+ * :attr:`~ae.gui.app.MainAppBase.unselected_ink`: color to display currently unselected app data/items
156
+ * :attr:`~ae.gui.app.MainAppBase.update_ink`: color to display/highlight an updated app data/item
157
+ * :attr:`~ae.gui.app.MainAppBase.vibration_volume`: intensity of vibration resources (on mobile devices)
158
+ * :attr:`~ae.gui.app.MainAppBase.warn_ink`: color to display warning messages/popups
159
+ * :attr:`~ae.gui.app.MainAppBase.win_rectangle`: the current window rectangle (position and size)
160
+
161
+ .. hint::
162
+ the two built-in app state variables are :attr:`~ae.gui.app.MainAppBase.flow_id` and
163
+ :attr:`~ae.gui.app.MainAppBase.flow_path` will be explained in more detail in the next section.
164
+
165
+ which app state variables are finally available in your app project depends (fully data-driven) on the app state
166
+ :ref:`config-variables` detected in all the :ref:`config-files` that are found/available at run-time of your app. the
167
+ names of all the available application state variables can be determined with the main app helper method
168
+ :meth:`~ae.gui.app.MainAppBase.app_state_keys`.
169
+
170
+ .. note::
171
+ if no config-file is provided, then this package ensures at least the proper initialization of the above
172
+ app state variables.
173
+
174
+ the :meth:`~ae.gui.app.MainBaseApp.load_app_states` method is called on instantiation from the implemented
175
+ main app class to load the values of all app state variables from the :ref:`config-files`, and is then calling
176
+ :meth:~ae.gui.app.MainAppBase.setup_app_states` for pass them into their corresponding instance attributes.
177
+
178
+ use the main app instance attribute to read/get the actual value of a single app state variable. the actual
179
+ app state variables dict is determining the method :meth:`~ae.gui.app.MainBaseApp.retrieve_app_states`, and can be saved
180
+ into the :ref:`config-files` for the next app run via the method :meth:`~ae.gui.app.MainBaseApp.save_app_states`.
181
+ this could be done e.g., after the app state has changed or at least on quiting the application.
182
+
183
+ always call the method :meth:`~ae.gui.app.MainBaseApp.change_app_state` to change an app state value to ensure:
184
+
185
+ (1) the propagation to any duplicated (observable/bound) framework property and
186
+ (2) the event notification of the related (optionally declared) main app instance method.
187
+
188
+ so e.g., if your application is supporting a user-defined font size, using the provided/pre-defined app state variable
189
+ :attr:`~ae.gui.app.MainAppBase.font_size`, then the :meth:`~ae.gui.app.MainBaseApp.change_app_state` method has to
190
+ be called with the :paramref:`~ae.gui.app.MainAppBase.change_app_state.app_state_name` argument set to `font_size`,
191
+ and the :paramref:`~ae.gui.app.MainAppBase.change_app_state.state_value` argument set to the new font size.
192
+
193
+
194
+ app theme variables
195
+ ^^^^^^^^^^^^^^^^^^^
196
+
197
+ to allow the app user to quickly change the appearance of the app, some of the app state variables are classified
198
+ as app theme variables via the :attr:`~ae.gui.app.MainAppBase.theme_specific_cfg_vars` attribute, including by default
199
+ e.g., the font size (:attr:`~ae.gui.app.MainAppBase.font_size`), the used colors and if it is a light or dark theme
200
+ (:attr:`~ae.gui.app.MainAppBase.light_theme`).
201
+
202
+ to create or update an existing app theme call the method :meth:`~ae.gui.app.MainAppBase.theme_save`.
203
+ the method :meth:`~ae.gui.app.MainAppBase.theme_load` loads an existing theme from its config-file theme section.
204
+
205
+ .. hint::
206
+ the theme config section name consists of the prefix :data:`THEME_SECTION_PREFIX` followed by the name of the theme.
207
+
208
+
209
+ .. _app-state-constants:
210
+
211
+ app state constants
212
+ ^^^^^^^^^^^^^^^^^^^
213
+
214
+ this portion is also providing some pre-defined constants that can be optionally used in your application in relation to
215
+ the app states data store and for the app state config variables :attr:`~ae.gui.app.MainAppBase.app_state_version`,
216
+ :attr:`~ae.gui.app.MainAppBase.font_size` and :attr:`~ae.gui.app.MainAppBase.light_theme`:
217
+
218
+ * :data:`APP_STATE_SECTION_NAME`: app states config section name
219
+ * :data:`APP_STATE_VERSION_VAR_NAME`: app state variable name of the current app state variable version
220
+ * :data:`MIN_FONT_SIZE`: minimum font size
221
+ * :data:`MAX_FONT_SIZE`: maximum font size
222
+ * :data:`DEFAULT_FONT_SIZE`: default font size
223
+ * :data:`THEME_LIGHT_BACKGROUND_COLOR`: light theme background color
224
+ * :data:`THEME_LIGHT_FONT_COLOR`: light theme foreground/font color
225
+ * :data:`THEME_DARK_BACKGROUND_COLOR`: dark theme background color
226
+ * :data:`THEME_DARK_FONT_COLOR`: dark theme foreground/font color
227
+
228
+
229
+ app state events
230
+ ^^^^^^^^^^^^^^^^
231
+
232
+ there are three types of notification events get fired in relation to the app state variables, using the
233
+ following method names of the main app instance:
234
+
235
+ * `on_<app_state_name>`: fired if the value of an app state variable is changing
236
+ * `on_<app_state_name>_save`: fired if an app state gets saved to the config file
237
+ * `on_app_state_version_upgrade`: fired if the user upgrades a previously installed app to a higher version
238
+
239
+ the method name of the first app state change event consists of the prefix ``on_`` followed by the variable name
240
+ of the app state. so e.g., on a change of the `font_size` app state the notification event `on_font_size` will be
241
+ fired/called (if it exists as a method of the main app instance). these events don't provide any event arguments.
242
+
243
+ the second event gets fired for each app state value just after the app states getting retrieved from the main app
244
+ instance, and before they get stored into the main config file. the method name of this event includes also the name of
245
+ the app state with the suffix `_save`, so e.g., for the app state `flow_id` the event method name will result in
246
+ :meth:`on_app_state_flow_id_save`. this event is providing one event argument with the value of the app state. if the
247
+ event method returns a value that is not `None`, then this value will be stored/saved.
248
+
249
+ the third event gets fired on app startup when the app state version (in APP_STATE_VERSION_VAR_NAME, respective
250
+ `app_state_version`) got upgraded to a higher value. then this event handler method will be called providing the
251
+ version number for each version to upgrade, starting with the version of the previously installed main config file,
252
+ until the upgrade version of the main config file gets reached. so if e.g., the previously installed app state version
253
+ was `3` and the new version number is `6`, then this event will be fired 3 times with the arguments 3, 4, and 5.
254
+ this functionality can be used e.g., to change or add app state variables or to adapt the app environment.
255
+
256
+
257
+ application flow
258
+ ----------------
259
+
260
+ to control the current state and user interaction flow (or context) of your application, and to persist it until the
261
+ next app start, :class:`MainBaseApp` provides two :ref:`app-state-variables`: :attr:`~ae.gui.app.MainAppBase.flow_id`
262
+ to store the currently working flow and :attr:`~ae.gui.app.MainAppBase.flow_path` to store the history of
263
+ entered/opened flows.
264
+
265
+
266
+ app flow id and path
267
+ ^^^^^^^^^^^^^^^^^^^^
268
+
269
+ an application flow is represented by an id string that defines three things: (1) the action to enter into the flow, (2)
270
+ the data or object that gets currently worked on and (3) an optional key string that is identifying/indexing a widget or
271
+ data item of your application context/flow.
272
+
273
+ .. note::
274
+ never concatenate a flow id string manually, use the :func:`id_of_flow` function instead.
275
+
276
+ the flow id is initially an empty string. as soon as the user is starting a new work flow or is changing
277
+ the app context (e.g., the current selection of a data item or the opening of a popup), your
278
+ application could call the method :meth:`~ae.gui.app.MainBaseApp.change_flow` passing the flow id string into the
279
+ :paramref:`~ae.gui.app.MainAppBase.change_flow.new_flow_id` argument.
280
+
281
+ for more complex applications you can specify a path of nested flows. this flow path gets represented by the app state
282
+ variable :attr:`~ae.gui.app.MainAppBase.flow_path`, which is a list of flow id strings.
283
+
284
+ to enter into a deeper/nested flow, you call :meth:`~ae.gui.app.MainBaseApp.change_flow` with one of the actions
285
+ defined in :data:`ACTIONS_EXTENDING_FLOW_PATH`.
286
+
287
+ to go back to a previous flow in the flow path, call :meth:`~ae.gui.app.MainBaseApp.change_flow` passing one of
288
+ the actions defined in :data:`ACTIONS_REDUCING_FLOW_PATH`.
289
+
290
+ .. hint::
291
+ check the ACTIONS_* constants declared on top of the module :mod:`~ae.gui.app` for this and other app flow action
292
+ classifications.
293
+
294
+
295
+ application flow change events
296
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
297
+
298
+ the flow actions specified by :data:`~ae.gui.app.ACTIONS_CHANGING_FLOW_WITHOUT_CONFIRMATION` don't need a
299
+ flow change confirmation event handler, which are in concrete:
300
+
301
+ * `'enter'` or `'leave'`: extend/reduce the flow path.
302
+ * `'focus'`: pass/change the input focus.
303
+ * `'suggest'`: for autocompletion or other suggestions.
304
+
305
+ all other flow actions need a confirmation before they get changed by :meth:`~ae.gui.app.MainAppBase.change_flow`,
306
+ either by a custom flow change confirmation method/event-handler or by declaring a related popup class. the name
307
+ of the event handler and of the popup class gets determined from the flow id.
308
+
309
+ .. hint::
310
+ the name of the flow change confirmation method that gets fired when the app wants to change the flow (via the
311
+ method :meth:`~ae.gui.app.MainAppBase.change_flow`) gets determined by the function
312
+ :func:`flow_change_confirmation_event_name`, whereas the name of the popup class gets determined by the function
313
+ :func:`flow_popup_class_name`.
314
+
315
+ if the flow-specific change confirmation event handler does not exist or returns in a boolean `False` or a `None`
316
+ value, then the main app instance method :meth:`~ae.gui.app.MainAppBase.on_flow_change` will be called.
317
+ if this call also returns `False`, then the action of the new flow id will be searched within
318
+ :data:`~ae.gui.app.ACTIONS_CHANGING_FLOW_WITHOUT_CONFIRMATION` and if not found, then the
319
+ flow change will be rejected and :meth:`~ae.gui.app.MainAppBase.change_flow` returns `False`.
320
+
321
+ if in contrary, either the flow change confirmation event handler exists and does return `True`,
322
+ or the method :meth:`~ae.gui.app.MainAppBase.on_flow_change` returns True,
323
+ or the flow action of the new flow id is in :data:`~ae.gui.app.ACTIONS_CHANGING_FLOW_WITHOUT_CONFIRMATION`
324
+ then the flow id and path will be changed accordingly.
325
+
326
+ after a positive flow id/path change confirmation, the method :meth:`~ae.gui.app.MainAppBase.change_flow` checks if
327
+ the optional `event_kwargs` key `changed_event_name` got specified, and if yes, then it calls this method.
328
+
329
+ finally, if a confirmed flow change results in a `'focus'` flow action, then the event `on_flow_widget_focused` will be
330
+ fired. this event can be used by the GUI framework to set the focus to the widget associated with the new focus flow id.
331
+
332
+
333
+ flow actions `'open'` and `'close'`
334
+ __________________________________
335
+
336
+ to display an instance of a properly named popup class, initiate the change the app flow to an appropriate
337
+ flow id (with an `'open'` flow action). in this case no change confirmation event handler is needed, because
338
+ :meth:`~ae.gui.app.MainAppBase.on_flow_change` is then automatically opening the popup.
339
+
340
+ when the popup is visible, the flow path will be extended with the respective flow id.
341
+
342
+ calling the `close` method of the popup will hide it. on closing the popup, the flow id will be reset and the opening
343
+ flow id will be removed from the flow path.
344
+
345
+ all popup classes are providing the events `on_pre_open`, `on_open`, `on_pre_dismiss` and `on_dismiss`.
346
+ the `on_dismiss` event handler can be used for data validation: returning a non-False value from it will cancel
347
+ the close.
348
+
349
+ .. hint::
350
+ see the documentation of each popup class for more details on the features of popup classes (for Kivy apps e.g.
351
+ :class:`~ae.kivy.widgets.FlowDropDown`, :class:`~ae.kivy.widgets.FlowPopup` or
352
+ :class:`~ae.kivy.widgets.FlowSelector`).
353
+
354
+
355
+ context-sensitive help system
356
+ -----------------------------
357
+
358
+ the generic functionality of a context-sensitive help system is provided by this portion. only the user interface
359
+ widgets to display the help texts have to be implemented and provided by the finally used GUI-framework.
360
+
361
+ .. hint::
362
+ help message texts are based on the multilingual translation messages, provided by the
363
+ ae namespace portion :mod:`ae.i18n`.
364
+
365
+ more details on these and other features of this help system, e.g., the usage of f-strings in the help texts, are
366
+ documented in the doc string of the :mod:`ae.i18n` module.
367
+
368
+ an example demonstrating the features of this context help system can be found in the repository of
369
+ the `kivy lisz demo app <https://gitlab.com/ae-group/kivy_lisz>`_.
370
+
371
+
372
+ i18n help ids and texts
373
+ ^^^^^^^^^^^^^^^^^^^^^^^
374
+
375
+ each multilingual help text is associated to a unique help id, the message id.
376
+
377
+ the help texts of an app are spread over multiple translation message files, which are text files
378
+ declaring a single dict literal, where the message ids are the dict keys.
379
+
380
+ each package/portion required by an app can declare and register its translation message files for their help texts.
381
+ your app can provide additional i18n translation message files for the app's help texts.
382
+
383
+ separate message files are created for each supported language. the multilingual ae namespace portions are providing
384
+ help texts for the languages English, Spanish and German. additional languages can be provided by your app, also
385
+ for the help texts of the imported ae namespace portions.
386
+
387
+
388
+ help ids of flow-changing widgets
389
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
390
+
391
+ the help id to identify the help texts for each widget is composed by the :func:`id_of_flow_help`, using the
392
+ prefix marker string defined by the constant :data:`~ae.gui.utils.FLOW_HELP_ID_PREFIX` followed by the flow id
393
+ of the flow widget.
394
+
395
+ .. hint:: more information regarding the flow id you find in the section :ref:`application flow`.
396
+
397
+ for example, the help/message id for a flow button with the flow action `'open'`, the object `'item'`
398
+ and the (optional) flow key `'456'` is resulting in the following help text message id::
399
+
400
+ 'help_flow#open_item:456'
401
+
402
+ if there is no need for a detailed message id that is taking the flow key into account, then use a help id
403
+ without the flow key. the method :meth:`~ae.gui.app.MainAppBase.help_display` does first search for a message id
404
+ including the flow key in the available help text files, and if not found, it will automatically fall back to use
405
+ a message id without the flow key::
406
+
407
+ 'help_flow#open_item'
408
+
409
+
410
+ help ids of app-state-changing widgets
411
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
412
+
413
+ the help message id for a widget changing an app state is composed by the method :func:`id_of_state_help`,
414
+ using the prefix marker string defined by the :data:`~ae.gui.utils.APP_STATE_HELP_ID_PREFIX` constant,
415
+ followed by the name of the app state.
416
+
417
+ so, the help id for a widget changing the `font_size` app state is resulting in the following help id::
418
+
419
+ 'help_app_state#font_size'
420
+
421
+
422
+ help message text f-strings
423
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
424
+
425
+ help message texts can be f-strings with access to the widget properties via the `self` variable, which
426
+ gets automatically prepared within the `help_vars` context dict by this portion.
427
+
428
+ so, the help message text of the following translation message item gets displayed with the actual slider value
429
+ of the widget that allows the user to change the font size::
430
+
431
+ {
432
+ 'help_app_state#font_size': \"\"\"move the slider to adjust the font size
433
+
434
+ the current font size is {self.value}\"\"\",
435
+ ...
436
+ }
437
+
438
+
439
+ pre- and post-change help texts
440
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
441
+
442
+ to display a different help message before and after the change of the flow id or the app state, use
443
+ instead of a simple message text, a message dict with the two keys `''` (an empty string) and `'after'`,
444
+ and then put the message texts as their values, like shown in the following example::
445
+
446
+ {
447
+ 'help_id': {
448
+ '': "help text displayed before the flow/app-state change.",
449
+ 'after': "help text displayed after the flow/app-state change",
450
+ },
451
+ ...
452
+ }
453
+
454
+ if you want to move/change the help target to another widget after a change, then the
455
+ '`next_help_id'` message dict key can be appended to the message dict::
456
+
457
+ {
458
+ 'help_id': {
459
+ '': "help text",
460
+ ...
461
+ 'next_help_id': "id_of_the_next_help_message",
462
+ },
463
+ ...
464
+ }
465
+
466
+ in this case the help target will automatically change to the widget specified by the flow id in the '`next_help_id'`
467
+ key, if the user was tapping the second time on the first/initial help target widget.
468
+
469
+
470
+ pluralize-able help texts
471
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
472
+
473
+ additional a message dict keys can be used to auto-select pluralized help texts. for that add a `count`
474
+ item to the `help_vars` context property of the help target widget and then define a help text for all
475
+ the possible count cases in the message dict like shown in the following example::
476
+
477
+ {
478
+ 'help_id': {
479
+ '': "fallback help text if count is None",
480
+ 'zero': "help text if {count} == 0",
481
+ 'one': "help text if {count} == 1",
482
+ 'many': "help text if {count} > 1",
483
+ 'negative': "help text if {count} < 0",
484
+ },
485
+ ...
486
+ }
487
+
488
+ the provided `count` value can also be included/displayed in the help text, so the help message text of
489
+ the`'zero'` count case in the above example will result in "help text if 0 == 0".
490
+
491
+
492
+ help layout implementation example
493
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
494
+
495
+ the layout of the user interface for this help system has to be provided externally on top of this module.
496
+ it can either be implemented directly in your app project or in a separate GUI-framework-specific package/portion.
497
+
498
+ use :class:`~ae.gui.app.MainAppBase` as the base class of the GUI framework specific main application class
499
+ to implementing all its abstract methods, like e.g., :meth:`~ae.gui.app.MainAppBase.init_app` and
500
+ :meth:`~ae.gui.app.MainAppBase.ensure_top_most_z_index`::
501
+
502
+ from ae.gui.app import MainAppBase
503
+
504
+ class MyMainApp(MainAppBase):
505
+ def init_app(self, framework_app_class=None):
506
+ self.framework_app = framework_app_class()
507
+ ...
508
+ return self.framework_app.run, self.framework_app.stop
509
+
510
+ def ensure_top_most_z_index(self, widget):
511
+ framework_method_to_push_widget_to_top_most(widget)
512
+ ...
513
+
514
+ to activate the help mode, the widget to display the help texts has to be assigned to the main app instance attribute
515
+ :attr:`~ae.gui.app.MainAppBase.help_layout` and to its related framework app property via
516
+ the :meth:`~ae.gui.app.MainAppBase.change_observable` method::
517
+
518
+ main_app.change_observable('help_layout', HelpScreenContainerOrWindow())
519
+
520
+ .. hint::
521
+ for example, see :attr:`~ae.kivy.apps.FrameworkApp.help_layout` as the help layout property implemented for the
522
+ `Kivy framework <https://kivy.org/>`.
523
+
524
+ the :attr:`~ae.gui.app.MainAppBase.help_layout` property is also used as a flag of the help mode activity.
525
+ by assigning `None` to this observable attribute, the help mode will get deactivated::
526
+
527
+ main_app.change_observable('help_layout', None)
528
+
529
+ use the attribute :attr:`~ae.gui.app.MainAppBase.help_activator` to specify the widget that allows the user
530
+ to toggle the help mode activation. the :meth:`~ae.gui.app.MainAppBase.help_display` is using it as the fallback widget
531
+ if no help target (or widget to be explained) got found.
532
+
533
+ .. hint::
534
+ the de-/activation method :meth:`~ae.kivy.apps.KivyMainApp.help_activation_toggle` together with the classes
535
+ :class:`~ae.kivy.behaviors.HelpBehavior`, :class:`~ae.kivy.widgets.HelpToggler` and
536
+ :class:`~ae.kivy.widgets.Tooltip` are demonstrating a typical implementation of help activator
537
+ and help text tooltip widgets.
538
+
539
+
540
+ user guiding application tours
541
+ ------------------------------
542
+
543
+ the following classes provided by this portion build a solid fundament to implement tours for your app:
544
+
545
+ * :class:`~ae.gui.tours.TourBase`: abstract base class of all app tours.
546
+ * :class:`~ae.gui.tours.TourDropdownFromButton`: abstract base class for tours on dropdown/menu widgets.
547
+ * :class:`~ae.gui.tours.OnboardingTour`: minimal app onboarding tour, extendable with app-specific tour pages.
548
+ * :class:`~ae.gui.tours.UserPreferencesTour`: minimal user preferences dropdown tour.
549
+
550
+
551
+ app tour start and stop events
552
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
553
+
554
+ the following main app event methods get called (if they exist) in relation to the start/stop of an app tour:
555
+
556
+ * `on_tour_init`: fired when the app tour instance got initialized and the app states backup got saved.
557
+ * `on_tour_start`: fired after the tour start method gets called.
558
+ * `on_tour_exit`: fired after an app tour got finished and the app states got restored to the values of the tour start.
559
+
560
+
561
+ UI-specific implementation
562
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
563
+
564
+ to complete the implementation of the app tours, the UI-specific framework has to provide a tour layout class, which is
565
+ highlighting the explained widget, displaying a tooltip and another widget to display the tour page texts.
566
+
567
+ .. hint::
568
+ the :class:`~ae.kivy.tours.TourOverlay` class, provided by the ae portion :mod:`ae.kivy`, is a
569
+ good example of a GUI-framework-specific implementation of a tour layout class for the
570
+ `Kivy framework <https://kivy.org/>`_.
571
+
572
+
573
+ generic key press events
574
+ ------------------------
575
+
576
+ to provide key press events to the applications that will use the new GUI framework, you have to catch the key press
577
+ events of the framework, convert/normalize them, and then call the method
578
+ :meth:`~ae.gui.app.MainAppBase.key_press_from_framework` with the normalized modifiers and key args.
579
+
580
+ the :paramref:`~ae.gui.app.MainAppBase.key_press_from_framework.modifiers` arg is a string that can contain several
581
+ of the following sub-strings, always in the alphabetic order (like listed below):
582
+
583
+ * Alt
584
+ * Ctrl
585
+ * Meta
586
+ * Shift
587
+
588
+ the :paramref:`~ae.gui.app.MainAppBase.key_press_from_framework.key` arg is a string specifying the last pressed key.
589
+ if the key is not representing a single character but a command key, then `key` will be one of the following
590
+ key name strings:
591
+
592
+ * escape
593
+ * tab
594
+ * backspace
595
+ * enter
596
+ * del
597
+ * enter
598
+ * up
599
+ * down
600
+ * right
601
+ * left
602
+ * home
603
+ * end
604
+ * pgup
605
+ * pgdown
606
+
607
+ on call of :meth:`~ae.gui.app.MainAppBase.key_press_from_framework` this method will dispatch the key press
608
+ event to your application. first it will check the app instance if it has declared a method with the name
609
+ `on_key_press_of_<modifiers>_<key>` and if so, it will call this method.
610
+
611
+ if this method does return False (or any other value resulting in False), then the method
612
+ :meth:`~ae.gui.app.MainAppBase.key_press_from_framework` will check for a method with the same name in lower-case,
613
+ and if it exits, it will call this method.
614
+
615
+ if also the second method is not declared or does return False, then it will try to call the event handler method
616
+ `on_key_press` of the main app instance (if it exists) with the modifiers and the key as arguments.
617
+
618
+ if the `on_key_press` method does also return False, then :meth:`~ae.gui.app.MainAppBase.key_press_from_framework`
619
+ will finally pass the key press event to the original key press handler of the GUI framework for further processing.
620
+
621
+
622
+ integrate a new gui framework
623
+ -----------------------------
624
+
625
+ the abstract class :class:`~ae.gui.app.MainAppBase`, provided by the module :mod:`ae.gui.app`, is a generic base
626
+ for the implementation of any Python GUI framework.
627
+
628
+ to integrate a new Python GUI framework, you have to declare a new class that inherits from the class
629
+ :class:`~ae.gui.app.MainAppBase` and implements at least their five abstract methods:
630
+
631
+ * :meth:`~ae.gui.app.MainAppBase.call_method_delayed`
632
+ * :meth:`~ae.gui.app.MainAppBase.call_method_repeatedly`
633
+ * :meth:`~ae.gui.app.MainAppBase.ensure_top_most_z_index`
634
+ * :meth:`~ae.gui.app.MainAppBase.help_activation_toggle`
635
+ * :meth:`~ae.gui.app.MainAppBase.init_app`
636
+
637
+ additionally and to load the resources of the app (after the portion resources got loaded), the event `on_app_build`
638
+ has to be fired, executing the :meth:`MainAppBase.on_app_build` method. this could be done directly from within
639
+ the implementation of the abstract method :meth:`~ae.gui.app.MainAppBase.init_app` or by forwarding/redirecting one
640
+ of the app instance events of the used GUI framework.
641
+
642
+ am example of a minimal implementation of the :meth:`~ae.gui.app.MainAppBase.init_app` method
643
+ could look like the following::
644
+
645
+ def init_app(self):
646
+ self.call_method('on_app_build')
647
+ return None, None
648
+
649
+ most GUI frameworks are providing classes that need to be instantiated on application startup, like e.g., the instance
650
+ of the GUI framework app class, the root widget or layout of the main GUI framework window(s). to keep a reference to
651
+ these instances within your main app class, the attributes :attr:`~ae.gui.app.MainAppBase.framework_app`,
652
+ :attr:`~ae.gui.app.MainAppBase.framework_root` and :attr:`~ae.gui.app.MainAppBase.framework_win` of the
653
+ class :class:`MainAppBase` can be used.
654
+
655
+ the initialization of the attributes :attr:`~ae.gui.app.MainAppBase.framework_app`,
656
+ :attr:`~ae.gui.app.MainAppBase.framework_root` and
657
+ :attr:`~ae.gui.app.MainAppBase.framework_win` is optional and can be done e.g., within the implementation of
658
+ :meth:`~ae.gui.app.MainAppBase.init_app` or in the `on_app_build` application event fired later
659
+ by the framework app instance.
660
+
661
+ .. note::
662
+ if :attr:`~ae.gui.app.MainAppBase.framework_win` is set to a window instance, then the window instance has
663
+ to provide a `close` method, which will be called automatically by the :meth:`~ae.gui.app.MainAppBase.stop_app`.
664
+
665
+ a typical framework-specific main app class implementation example and its `init_app` method looks like::
666
+
667
+ from new_gui_framework import NewFrameworkApp, MainWindowClassOfNewFramework
668
+
669
+ class NewFrameworkMainApp(MainAppBase):
670
+ def init_app(self):
671
+ self.framework_app = NewFrameworkAppClass()
672
+ self.framework_win = MainWindowClassOfNewFramework()
673
+
674
+ # return callables to start/stop the event loop of the GUI framework
675
+ return self.framework_app.start, self.framework_app.stop
676
+
677
+ in this example the `on_app_build` application event gets fired either from within the `start` method of the framework
678
+ app instance or by an event provided by the used GUI framework.
679
+
680
+ the method :meth:`~ae.gui.app.MainAppBase.init_app` will be executed only once at the main app class instantiation.
681
+ only the main app instance has to initialize the GUI framework to prepare the app startup and has to return at least
682
+ a callable to start the event loop of the GUI framework.
683
+
684
+ to initiate the app startup, the :meth:`~MainAppClass.run_app` method has to be called from the main module of your
685
+ app project. :meth:`~ae.gui.app.MainAppBase.run_app` will then start the GUI event loop by calling the first callable
686
+ that got returned by :meth:`~ae.gui.app.MainAppBase.init_app`.
687
+
688
+ .. hint::
689
+ an actual overview about the available GUI-framework-specific ae namespace portions can be found in the
690
+ documentation of the ae namespace demo app portion :mod:`ae.lisz_app_data`.
691
+
692
+ check out the ae namespace portion :mod:`ae.kivy` for a more detailed integration example of the
693
+ `Kivy framework <https://kivy.org/>`_.
694
+
695
+
696
+ TODO:
697
+ implement OS-independent detection of dark/light screen mode and automatic notification on day/night mode switch.
698
+ - see https://github.com/albertosottile/darkdetect for macOS, MSWindows and Ubuntu
699
+ - see https://github.com/kvdroid/Kvdroid/blob/master/kvdroid/tools/darkmode.py for Android
700
+
701
+ """
702
+ from ae.i18n import register_package_translations # type: ignore
703
+
704
+ from .utils import register_package_images, register_package_sounds
705
+
706
+
707
+ __version__ = '0.3.109'
708
+
709
+
710
+ register_package_images()
711
+ register_package_sounds()
712
+ register_package_translations()