Changed arguments checking in ListView to account for possible use of kv in the definition of a ListView. Added several new examples and tests, and new entries in ListView docs. Also deleted ObserverView, which is not used anywhere. The pre-commit hook was not allowing this commit, because a file was deleted and the style check failed to find it. So, modified the pre-commit hook to use a different method to find staged files.

This commit is contained in:
geojeff 2012-12-16 10:52:41 -06:00
parent e9c5b01231
commit 254497d07b
7 changed files with 283 additions and 84 deletions

View File

@ -44,9 +44,6 @@ class FruitDetailView(GridLayout):
self.redraw()
# Used in the list_cascade_oo.py example (ObjectAdapter and ObserverView
# example).
#
class FruitObserverDetailView(GridLayout):
fruit_name = StringProperty('')

View File

@ -0,0 +1,37 @@
from kivy.uix.modalview import ModalView
from kivy.uix.listview import ListView
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
Builder.load_string("""
<ListViewModal>:
size_hint: None,None
size: 400,400
ListView:
size_hint: .8,.8
item_strings: [str(index) for index in xrange(100)]
""")
class ListViewModal(ModalView):
def __init__(self, **kwargs):
super(ListViewModal, self).__init__(**kwargs)
class MainView(GridLayout):
"""Implementation of a list view declared in a kv template.
"""
def __init__(self, **kwargs):
kwargs['cols'] = 1
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
listview_modal = ListViewModal()
self.add_widget(listview_modal)
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))

View File

@ -0,0 +1,49 @@
from kivy.uix.modalview import ModalView
from kivy.uix.listview import ListView
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.factory import Factory
# Note the special nature of indentation in the adapter declaration, where
# the adapter: is on one line, then the value side must be given at one level
# of indentation.
Builder.load_string("""
#:import label kivy.uix.label
#:import sla kivy.adapters.simplelistadapter
<ListViewModal>:
size_hint: None,None
size: 400,400
ListView:
size_hint: .8,.8
adapter:
sla.SimpleListAdapter(
data=["Item #{0}".format(i) for i in xrange(100)],
cls=label.Label)
""")
class ListViewModal(ModalView):
def __init__(self, **kwargs):
super(ListViewModal, self).__init__(**kwargs)
class MainView(GridLayout):
"""
Implementation of a ListView using the kv language.
"""
def __init__(self, **kwargs):
kwargs['cols'] = 1
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
listview_modal = ListViewModal()
self.add_widget(listview_modal)
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))

View File

@ -88,9 +88,69 @@ class ListViewTestCase(unittest.TestCase):
del list_view.adapter.data[49]
self.assertEqual(len(list_view.adapter.data), 99)
def test_list_view_bad_instantiation(self):
with self.assertRaises(Exception) as cm:
listview = ListView()
def test_list_view_declared_in_kv_with_item_strings(self):
from kivy.lang import Builder
from kivy.uix.modalview import ModalView
from kivy.uix.widget import Widget
from kivy.factory import Factory
from kivy.properties import StringProperty, ObjectProperty, \
BooleanProperty
msg = 'ListView: item_strings needed or an adapter'
self.assertEqual(str(cm.exception), msg)
Builder.load_string("""
#:import label kivy.uix.label
#:import sla kivy.adapters.simplelistadapter
<ListViewModal>:
size_hint: None,None
size: 400,400
lvm: lvm
ListView:
id: lvm
size_hint: .8,.8
item_strings: ["Item #{0}".format(i) for i in xrange(100)]
""")
class ListViewModal(ModalView):
def __init__(self, **kwargs):
super(ListViewModal, self).__init__(**kwargs)
list_view_modal = ListViewModal()
list_view = list_view_modal.lvm
self.assertEqual(len(list_view.adapter.data), 100)
def test_list_view_declared_in_kv_with_adapter(self):
from kivy.lang import Builder
from kivy.uix.modalview import ModalView
from kivy.uix.widget import Widget
from kivy.factory import Factory
from kivy.properties import StringProperty, ObjectProperty, \
BooleanProperty
Builder.load_string("""
#:import label kivy.uix.label
#:import sla kivy.adapters.simplelistadapter
<ListViewModal>:
size_hint: None,None
size: 400,400
lvm: lvm
ListView:
id: lvm
size_hint: .8,.8
adapter:
sla.SimpleListAdapter(
data=["Item #{0}".format(i) for i in xrange(100)],
cls=label.Label)
""")
class ListViewModal(ModalView):
def __init__(self, **kwargs):
super(ListViewModal, self).__init__(**kwargs)
list_view_modal = ListViewModal()
list_view = list_view_modal.lvm
self.assertEqual(len(list_view.adapter.data), 100)

View File

@ -28,9 +28,28 @@ srcdir = join(kivydir, 'kivy')
script = join(srcdir, 'tools', 'pep8checker', 'pep8kivy.py')
# Only check the files that were staged
proc = Popen(['git', 'diff', '--cached', '--name-only', 'HEAD'], stdout=PIPE)
#proc = Popen(['git', 'diff', '--cached', '--name-only', 'HEAD'], stdout=PIPE)
#targets = [join(kivydir, target) for target in proc.stdout]
# Correction: only check the files that were staged, but do not include
# deleted files.
proc = Popen(['git', 'diff', '--cached', '--name-status', 'HEAD'], stdout=PIPE)
proc.wait()
targets = [join(kivydir, target) for target in proc.stdout]
# This gives output like the following:
#
# A examples/widgets/lists/list_simple_in_kv.py
# A examples/widgets/lists/list_simple_in_kv_2.py
# D kivy/uix/observerview.py
#
# So check for D entries and remove them from targets.
#
targets = []
for target in proc.stdout:
parts = [p.strip() for p in target.split()]
if parts[0] != 'D':
targets.append(join(kivydir, target))
call(['git', 'stash', 'save', '--keep-index', '--quiet'])
retval = call([sys.executable, script, srcdir] + targets)
call(['git', 'stash', 'pop', '--quiet'])

View File

@ -33,6 +33,8 @@ from the names of the examples that they illustrate the "ramping up" from
simple to advanced:
* kivy/examples/widgets/lists/list_simple.py
* kivy/examples/widgets/lists/list_simple_in_kv.py
* kivy/examples/widgets/lists/list_simple_in_kv_2.py
* kivy/examples/widgets/lists/list_master_detail.py
* kivy/examples/widgets/lists/list_two_up.py
* kivy/examples/widgets/lists/list_kv.py
@ -78,6 +80,44 @@ In its simplest form, we make a listview with 100 items::
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
Or, we could declare the listview in using the kv language::
from kivy.uix.modalview import ModalView
from kivy.uix.listview import ListView
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
Builder.load_string("""
<ListViewModal>:
size_hint: None,None
size: 400,400
ListView:
size_hint: .8,.8
item_strings: [str(index) for index in xrange(100)]
""")
class ListViewModal(ModalView):
def __init__(self, **kwargs):
super(ListViewModal, self).__init__(**kwargs)
class MainView(GridLayout):
def __init__(self, **kwargs):
kwargs['cols'] = 1
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
listview_modal = ListViewModal()
self.add_widget(listview_modal)
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
Using an Adapter
-------------------
@ -108,6 +148,56 @@ strings. Each item string is set by
:class:`~kivy.adapters.simplelistadapter.SimpleListAdapter` as the *text*
argument for each Label instantiation.
You can declare a ListView with an adapter in a kv file, with special attention
given to the way longer python blocks are indented::
from kivy.uix.modalview import ModalView
from kivy.uix.listview import ListView
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.factory import Factory
# Note the special nature of indentation in the adapter declaration, where
# the adapter: is on one line, then the value side must be given at one
# level of indentation.
Builder.load_string("""
#:import label kivy.uix.label
#:import sla kivy.adapters.simplelistadapter
<ListViewModal>:
size_hint: None,None
size: 400,400
ListView:
size_hint: .8,.8
adapter:
sla.SimpleListAdapter(
data=["Item #{0}".format(i) for i in xrange(100)],
cls=label.Label)
""")
class ListViewModal(ModalView):
def __init__(self, **kwargs):
super(ListViewModal, self).__init__(**kwargs)
class MainView(GridLayout):
def __init__(self, **kwargs):
kwargs['cols'] = 1
kwargs['size_hint'] = (1.0, 1.0)
super(MainView, self).__init__(**kwargs)
listview_modal = ListViewModal()
self.add_widget(listview_modal)
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
ListAdapter and DictAdapter
---------------------------
@ -804,15 +894,22 @@ class ListView(AbstractView, EventDispatcher):
def __init__(self, **kwargs):
# Check for an adapter argument. If it doesn't exist, we
# assume that item_strings is to be used with SimpleListAdapter
# to make a simple list. In this case, if item_strings was not
# provided, raise an exception.
# check for item_strings in use with SimpleListAdapter
# to make a simple list.
if 'adapter' not in kwargs:
if 'item_strings' not in kwargs:
raise Exception('ListView: item_strings needed or an adapter')
list_adapter = SimpleListAdapter(data=kwargs['item_strings'],
cls=Label)
# Could be missing, or it could be that the ListView is
# declared in a kv file. If kv is in use, and item_strings is
# declared there, then item_strings will not be set until after
# __init__(). So, the data=[] set will temporarily serve for
# SimpleListAdapter instantiation, with the binding to
# item_strings_changed() handling the eventual set of the
# item_strings property from the application of kv rules.
list_adapter = SimpleListAdapter(data=[],
cls=Label)
else:
list_adapter = SimpleListAdapter(data=kwargs['item_strings'],
cls=Label)
kwargs['adapter'] = list_adapter
self.register_event_type('on_scroll_complete')
@ -823,6 +920,7 @@ class ListView(AbstractView, EventDispatcher):
self.bind(size=self._trigger_populate,
pos=self._trigger_populate,
item_strings=self.item_strings_changed,
adapter=self._trigger_populate)
# The bindings setup above sets self._trigger_populate() to fire
@ -832,6 +930,11 @@ class ListView(AbstractView, EventDispatcher):
# bindings back to the view updating function here.
self.adapter.bind_triggers_to_view(self._trigger_populate)
# Added to set data when item_strings is set in a kv template, but it will
# be good to have also if item_strings is reset generally.
def item_strings_changed(self, *args):
self.adapter.data = self.item_strings
def _scroll(self, scroll_y):
if self.row_height is None:
return

View File

@ -1,66 +0,0 @@
'''
ObserverView
============
.. versionadded:: 1.5
:class:`ObserverView` is a container for a view that observes an object held
by an :class:`SelectionAdapter` instance. It is similar to :class:`ListView`
in the way an adapter and view instance are held, and in the way a get_view()
method works in concert with the adapter. It only has to manage one object,
however, so is a bit simpler. The update() method is the analog of the
populate() method in :class:`ListView`.
'''
from kivy.adapters.selectionadapter import SelectionAdapter
from kivy.adapters.collectionadapter import CollectionAdapter
from kivy.properties import ObjectProperty, ListProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
class ObserverView(BoxLayout):
adapter = ObjectProperty(None)
'''This is always a :class:`SelectionAdapter` instance. The obj value in
the object adapter is at the heart of operations here. This is the
observed value whose changes trigger refreshing of the contained view.
'''
view_instance = ObjectProperty(None, allownone=True)
'''The view_instance property holds the present contained view.
'''
target_property_name = StringProperty('')
def __init__(self, **kwargs):
if not 'observed' in kwargs:
raise Exception("ObserverView: observed object required")
if not 'target_property_name' in kwargs:
raise Exception("ObserverView: target_property_name required")
if not 'adapter' in kwargs:
if isinstance(kwargs['observed'], CollectionAdapter):
kwargs['adapter'] = SelectionAdapter(**kwargs)
print kwargs
super(ObserverView, self).__init__(**kwargs)
if isinstance(self.adapter.observed, CollectionAdapter):
self.adapter.bind(obj=self.update)
self.update(self.adapter)
def set_view(self, view):
self.view_instance = view
def get_view(self):
return self.view_instance
def update(self, adapter, *args):
print 'ObserverView, update', adapter
self.clear_widgets()
self.view_instance = adapter.get_view()
v = self.get_view()
if v is not None:
if type(self.adapter) is SelectionAdapter:
self.adapter.bind(obj=v.setter(self.target_property_name))
self.add_widget(v)