"""
dynamic children mix-in for kivy container widgets
==================================================
this ae portion is providing the mixin class :class:`DynamicChildrenBehavior` to add children widgets dynamically
and data-driven to your kivy popup widget (like DropDowns, Popups, Menus, Selectors).
"""
from collections import Counter
from typing import Any, Callable, Dict, List, Union
from kivy.factory import Factory # type: ignore
# pylint: disable=no-name-in-module
from kivy.properties import ListProperty # type: ignore # noqa: E0611
from kivy.uix.popup import Popup # type: ignore
from kivy.uix.widget import Widget # type: ignore
from ae.base import UNSET # type: ignore
from ae.deep import deep_replace, deep_update, key_path_object # type: ignore
__version__ = '0.3.9'
AttrMapType = Dict[str, Any] #: child attribute (value of the 'kwargs' and 'attributes' keys)
DataItemValueType = Union[str, AttrMapType] #: item dict value (str for 'cls' key, dict for 'kwargs'/'attributes')
DataItemType = Dict[str, DataItemValueType] #: :attr:`DynamicChildrenBehavior.child_data_maps` item type
ChildrenDataType = List[DataItemType] #: :attr:`DynamicChildrenBehavior.child_data_maps` type
[docs]def _child_data_dict(child_data: DataItemType, cls: str, key: str, defaults: Dict[str, Dict[str, AttrMapType]]
) -> AttrMapType:
""" determine child data dict values and put children default values for the keys not specified in child data. """
val = defaults.get('', {}).get(key, {}).copy() # copy the defaults for all classes
deep_update(val, defaults.get(cls, {}).get(key, {})) # merge/update with the defaults for the specified class
deep_update(val, child_data.get(key, {})) # finally overwrite defaults with child data
return val
[docs]class DynamicChildrenBehavior:
""" mixin class for the dynamic creation/refresh of child widgets from a data map.
at least one of the classes that is mixing in this class has to inherit from Widget (or EventDispatcher) to get the
:attr:`~DynamicChildrenBehavior.child_data_maps` attribute correctly initialized and firing on change.
"""
child_data_defaults: ChildrenDataType = ListProperty()
""" child data default values for all the children with the same `cls` key that are specified via the
:attr:`ae.kivy_dyn_chi.DynamicChildrenBehavior.child_data_maps` property. if the `cls` key is missing or its
item value is empty then the defaults in the other item values will be used for all children.
:attr:`child_data_defaults` is a :class:`~kivy.properties.ListProperty` and defaults to an empty list.
"""
child_data_maps: ChildrenDataType = ListProperty()
""" list of child data dicts to instantiate the children of the inheriting layout/widget.
each child data dict is defining its own widget with the following keys:
* `cls`: either the class name or the class/type object of the widget to be created dynamically.
* `kwargs`: dict of keyword arguments that will be passed to the constructor method of the widget.
all values in this dict with the magic string `'replace_with_data_map_popup'` will be
replaced with the instance of the container before it gets passed to the `__init__` method
of the child (see :func:`~ae.deep.deep_replace`).
* `attributes`: dict of attributes where the key is specifying the attribute name/path and the
value the finally assigned attribute value.
the attribute name (the key of this dict) can be a deep/combined attribute/index path
which allows to update deeper objects within the child object (via :func:`~ae.deep.key_path_object`).
all values in this dict with the magic string `'replace_with_data_map_popup'` will be
replaced with the instance of the container before the child attributes get updated.
all values in this dict with the magic string `'replace_with_data_map_child'` will be
replaced with the instance of the child before they get applied to it (via :func:`~ae.deep.deep_replace`).
:attr:`child_data_maps` is a :class:`~kivy.properties.ListProperty` and defaults to an empty list.
"""
bind: Callable
container: Widget
_container: Widget
[docs] def __init__(self, **kwargs):
""" add dynamic creation and refresh of children to this layout (Popup/Dropdown/...) widget. """
super().__init__(**kwargs)
self._is_popup = isinstance(self, Popup) # True if inherits from :class:`~kivy.uix.popup.Popup`
self._map_children = []
self.bind(child_data_defaults=self.refresh_child_data_widgets, child_data_maps=self.refresh_child_data_widgets)
self.refresh_child_data_widgets(self, **kwargs)
Factory.register('DynamicChildrenBehavior', cls=DynamicChildrenBehavior)