'''
RecycleView Data Model
======================
.. versionadded:: 1.10.0
The data model part of the RecycleView model-view-controller pattern.
It defines the models (classes) that store the data associated with a
:class:`~kivy.uix.recycleview.RecycleViewBehavior`. Each model (class)
determines how the data is stored and emits requests to the controller
(:class:`~kivy.uix.recycleview.RecycleViewBehavior`) when the data is
modified.
'''
from kivy.properties import ListProperty, ObservableDict, ObjectProperty
from kivy.event import EventDispatcher
from functools import partial
__all__ = ('RecycleDataModelBehavior', 'RecycleDataModel')
def recondition_slice_assign(val, last_len, new_len):
if not isinstance(val, slice):
return slice(val, val + 1)
diff = new_len - last_len
start, stop, step = val.start, val.stop, val.step
if stop <= start:
return slice(0, 0)
if step is not None and step != 1:
assert last_len == new_len
if stop < 0:
stop = max(0, last_len + stop)
stop = min(last_len, stop)
if start < 0:
start = max(0, last_len + start)
start = min(last_len, start)
return slice(start, stop, step)
if start < 0:
start = last_len + start
if stop < 0:
stop = last_len + stop
# whatever, too complicated don't try to compute it
if (start < 0 or stop < 0 or start > last_len or stop > last_len or
new_len != last_len):
return None
return slice(start, stop)
[docs]class RecycleDataModelBehavior(object):
""":class:`RecycleDataModelBehavior` is the base class for the models
that describes and provides the data for the
:class:`~kivy.uix.recycleview.RecycleViewBehavior`.
:Events:
`on_data_changed`:
Fired when the data changes. The event may dispatch
keyword arguments specific to each implementation of the data
model.
When dispatched, the event and keyword arguments are forwarded to
:meth:`~kivy.uix.recycleview.RecycleViewBehavior.\
refresh_from_data`.
"""
__events__ = ("on_data_changed", )
recycleview = ObjectProperty(None, allownone=True)
'''The
:class:`~kivy.uix.recycleview.RecycleViewBehavior` instance
associated with this data model.
'''
[docs] def attach_recycleview(self, rv):
'''Associates a
:class:`~kivy.uix.recycleview.RecycleViewBehavior` with
this data model.
'''
self.recycleview = rv
if rv:
self.fbind('on_data_changed', rv.refresh_from_data)
[docs] def detach_recycleview(self):
'''Removes the
:class:`~kivy.uix.recycleview.RecycleViewBehavior`
associated with this data model.
'''
rv = self.recycleview
if rv:
self.funbind('on_data_changed', rv.refresh_from_data)
self.recycleview = None
def on_data_changed(self, *largs, **kwargs):
pass
[docs]class RecycleDataModel(RecycleDataModelBehavior, EventDispatcher):
'''An implementation of :class:`RecycleDataModelBehavior` that keeps the
data in a indexable list. See :attr:`data`.
When data changes this class currently dispatches `on_data_changed` with
one of the following additional keyword arguments.
`none`: no keyword argument
With no additional argument it means a generic data change.
`removed`: a slice or integer
The value is a slice or integer indicating the indices removed.
`appended`: a slice
The slice in :attr:`data` indicating the first and last new items
(i.e. the slice pointing to the new items added at the end).
`inserted`: a integer
The index in :attr:`data` where a new data item was inserted.
`modified`: a slice
The slice with the indices where the data has been modified.
This currently does not allow changing of size etc.
'''
data = ListProperty([])
'''Stores the model's data using a list.
The data for a item at index `i` can also be accessed with
:class:`RecycleDataModel` `[i]`.
'''
_last_len = 0
def __init__(self, **kwargs):
self.fbind('data', self._on_data_callback)
super(RecycleDataModel, self).__init__(**kwargs)
def __getitem__(self, index):
return self.data[index]
@property
def observable_dict(self):
'''A dictionary instance, which when modified will trigger a `data` and
consequently an `on_data_changed` dispatch.
'''
return partial(ObservableDict, self.__class__.data, self)
[docs] def attach_recycleview(self, rv):
super(RecycleDataModel, self).attach_recycleview(rv)
if rv:
self.fbind('data', rv._dispatch_prop_on_source, 'data')
[docs] def detach_recycleview(self):
rv = self.recycleview
if rv:
self.funbind('data', rv._dispatch_prop_on_source, 'data')
super(RecycleDataModel, self).detach_recycleview()
def _on_data_callback(self, instance, value):
last_len = self._last_len
new_len = self._last_len = len(self.data)
op, val = value.last_op
if op == '__setitem__':
val = recondition_slice_assign(val, last_len, new_len)
if val is not None:
self.dispatch('on_data_changed', modified=val)
else:
self.dispatch('on_data_changed')
elif op == '__delitem__':
self.dispatch('on_data_changed', removed=val)
elif op == '__setslice__':
val = recondition_slice_assign(slice(*val), last_len, new_len)
if val is not None:
self.dispatch('on_data_changed', modified=val)
else:
self.dispatch('on_data_changed')
elif op == '__delslice__':
self.dispatch('on_data_changed', removed=slice(*val))
elif op == '__iadd__' or op == '__imul__':
self.dispatch('on_data_changed', appended=slice(last_len, new_len))
elif op == 'append':
self.dispatch('on_data_changed', appended=slice(last_len, new_len))
elif op == 'insert':
self.dispatch('on_data_changed', inserted=val)
elif op == 'pop':
if val:
self.dispatch('on_data_changed', removed=val[0])
else:
self.dispatch('on_data_changed', removed=last_len - 1)
elif op == 'extend':
self.dispatch('on_data_changed', appended=slice(last_len, new_len))
else:
self.dispatch('on_data_changed')