"""
extended kivy file chooser widget
=================================
This ae namespace portion provides the :class:`FileChooserPopup` widget (`chooser_popup`) which is embedding Kivy's
:class:`~kivy.uix.filechooser.FileChooser` class in a dropdown window (:class:`~ae.kivy.widgets.FlowDropDown`), and
extending it with a path selector and a button to switch between list and icon view.
file chooser dropdown usage
---------------------------
The :class:`FileChooserPopup` widget can be used like any :class:`Kivy DropDown widget <kivy.uix.dropdown.DropDown>` -
see the python and kv lang examples in the doc strings of the :mod:`~kivy.uix.dropdown` module. Additionally all the
features of the :class:`~ae.kivy.widgets.FlowDropDown` like e.g. the
:attr:`~ae.kivy_dyn_chi.DynamicChildrenBehavior.child_data_maps` are available.
Alternatively (and without the need to explicitly instantiate the file chooser dropdown widget) you simply have to
:meth:`change the application flow <ae.gui_app.change_flow>` to `id_of_flow('open', 'file_chooser')` to open this
file chooser (see also :ref:`application flow`)::
main_app.change_flow(id_of_flow('open', 'file_chooser'),
**update_tap_kwargs(open_button))
The variable `open_button` in this example represents a button widget instance that opens the file chooser dropdown (and
to which the file chooser gets attached to).
Use the :attr:`~FileChooserPopup.submit_to` property to distinguish multiple usages of the file chooser in a single
app::
main_app.change_flow(id_of_flow('open', 'file_chooser'),
**update_tap_kwargs(open_button,
popup_kwargs=dict(submit_to=submit_to_str_or_callable))
The variable `submit_to_str_or_callable` of the above example can be either a string or a callable. If you pass a
callable, :class:`FileChooserPopup` will call it if the user has selected a file (by touching or double clicking on a
file entry). This callback receives two arguments: the file path of just selected file and the :class:`FileChooser`
dropdown widget instance an and can be declared like::
def submit_to_callable(file_path: str, chooser_popup: Widget):
Passing a string to `submit_to` (or if it get not specified at all) the hard-coded `on_file_chooser_submit` event
handler callback method of your main app instance will be executed with the same two arguments::
def on_file_chooser_submit(self, file_path: str, chooser_popup: Widget):
if chooser_popup.submit_to == 'usage1':
usage1_object_or_process.file_path = file_path
chooser_popup.dismiss()
elif chooser_popup.submit_to == 'usage2':
...
elif chooser_popup.submit_to == '': # w/o specifying `submit_to`
...
Use the key of the :attr:`~ae.kivy.widgets.FlowButton.tap_flow_id` property of the :class:`~ae.kivy.widgets.FlowButton`
to provide a separate i18n help text for each individual button.
The :attr:`~FileChooserPopup.filters` property of Kivy's :class:`kivy.uix.filechooser.FileChooser` can be used
to filter the files displayed in this file chooser widget.
The path selector dropdown (:class:`FileChooserPathSelectPopup`) situated at the top of this file chooser dropdown
is providing all common OS and app specific paths that are registered in the :data:`~ae.paths.PATH_PLACEHOLDERS` dict.
The keys of this dict will be displayed as shortcut path names instead of the full path strings. Additionally
translation texts can be provided for the shortcut path names to display them in the language selected by the app user.
To extend the path selector dropdown with additional paths you can either register them within
:data:`~ae.paths.PATH_PLACEHOLDERS`, or you add them to the optional app state variable `file_chooser_paths` by calling
the method :meth:`~FileChooserPopup.register_file_path`.
By adding the list `file_chooser_paths` to the :ref:`app state variables` of your app, the paths provided by the path
selector widget will automatically maintain and keep the OS and user paths persistent between app runs.
to record and remember the last selected path add also the app state `file_chooser_initial_path` to the
`:ref:`app state variables` of your app.
Override the method :meth:`~ae.console.ConsoleApp._init_default_user_cfg_vars` within the main app instance of your app
to make these two persistent :ref:`app state variables` user-specific::
def _init_default_user_cfg_vars(self):
super()._init_default_user_cfg_vars()
self.user_specific_cfg_vars |= {
(APP_STATE_SECTION_NAME, 'file_chooser_initial_path'),
(APP_STATE_SECTION_NAME, 'file_chooser_paths'),
}
.. hint::
you don't need to override :meth:`~ae.console.ConsoleApp._init_default_user_cfg_vars` if your app is embedding the
ae portion :mod:`ae.kivy_sideloading`.
"""
import os
from typing import Any, List
from kivy.app import App # type: ignore
from kivy.lang import Builder # type: ignore
from kivy.properties import ( # type: ignore # pylint: disable=no-name-in-module
ListProperty, ObjectProperty, StringProperty)
from kivy.uix.widget import Widget # type: ignore
from ae.base import norm_path # type: ignore
from ae.kivy.widgets import FlowDropDown # type: ignore
__version__ = '0.3.11'
Builder.load_string('''\
#: import os os
<FileChooserPopup>
do_scroll_x: True
do_scroll_y: False
on_initial_path: app.main_app.change_app_state('file_chooser_initial_path', self.initial_path)
auto_width_minimum: min(Window.width * 0.96, app.app_states['font_size'] * 24)
BoxLayout:
size_hint_y: None
height: app.app_states['font_size'] * 1.8
padding: '3sp'
spacing: '9sp'
FlowButton:
tap_flow_id: id_of_flow('select', 'file_chooser_path', root.initial_path)
tap_kwargs: update_tap_kwargs(self)
text: path_name(root.initial_path) and _(path_name(root.initial_path)) or root.initial_path
text_size: self.width, None
halign: 'center'
shorten: True
shorten_from: 'left'
relief_square_inner_colors: relief_colors((1, 1, 0))
relief_square_inner_lines: int(sp(6))
FlowToggler:
tap_flow_id: id_of_flow('toggle', 'file_chooser_view')
tap_kwargs: update_tap_kwargs(self)
icon_name: chooser_widget.view_mode + "_view"
on_state: chooser_widget.view_mode = 'icon' if self.state == 'down' else 'list'
size_hint_x: None
width: self.height
FileChooser:
id: chooser_widget
path: root.initial_path
filters: root.filters
size_hint_y: None
height: (root.attach_to.top if root.attach_to else Window.height) * 0.96 - app.main_app.font_size * 3.0
multiselect: True
on_selection:
args[1] and \
(root.submit_to if callable(root.submit_to) else app.main_app.on_file_chooser_submit)(args[1][-1], root)
on_entry_added: root.on_file_chooser_entry_added(args[1])
on_subentry_to_entry: root.on_file_chooser_entry_added(args[1])
FileChooserListLayout
FileChooserIconLayout
<FileChooserPathSelectPopup>
paths:
[normalize(path) for path in set(list(PATH_PLACEHOLDERS.values()) + app.app_states['file_chooser_paths']) \
if os.path.exists(normalize(path)) and os.path.isdir(normalize(path))]
on_paths: app.main_app.change_app_state('file_chooser_paths', list(self.paths))
on_select: self.attach_to.parent.parent.parent.initial_path = args[1]
child_data_maps:
[dict(cls='FlowButton', kwargs=dict( \
text=_(path_name(path)) + (" (" + path + ")" if app.landscape else "") if path_name(path) else path, \
tap_flow_id=id_of_flow('change', 'file_chooser_path', path), \
on_release=lambda btn: self.select(flow_key(btn.tap_flow_id)))) \
for path in self.paths]
''')