From 254497d07bdd8c2a8f57a38f08b2fdc716003784 Mon Sep 17 00:00:00 2001 From: geojeff Date: Sun, 16 Dec 2012 10:52:41 -0600 Subject: [PATCH] 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. --- examples/widgets/lists/fruit_detail_view.py | 5 +- examples/widgets/lists/list_simple_in_kv.py | 37 ++++++ examples/widgets/lists/list_simple_in_kv_2.py | 49 ++++++++ kivy/tests/test_uix_listview.py | 70 ++++++++++- kivy/tools/pep8checker/pre-commit.githook | 23 +++- kivy/uix/listview.py | 117 ++++++++++++++++-- kivy/uix/observerview.py | 66 ---------- 7 files changed, 283 insertions(+), 84 deletions(-) create mode 100644 examples/widgets/lists/list_simple_in_kv.py create mode 100644 examples/widgets/lists/list_simple_in_kv_2.py delete mode 100644 kivy/uix/observerview.py diff --git a/examples/widgets/lists/fruit_detail_view.py b/examples/widgets/lists/fruit_detail_view.py index eccb2e955..06d23cb4b 100644 --- a/examples/widgets/lists/fruit_detail_view.py +++ b/examples/widgets/lists/fruit_detail_view.py @@ -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('') @@ -124,7 +121,7 @@ class FruitImageDetailView(BoxLayout): # Is selected_object an instance of ThumbnailedListItem (composite)? # # Or is it a ListItemButton? - # + # if hasattr(selected_object, 'fruit_name'): self.fruit_name = selected_object.fruit_name else: diff --git a/examples/widgets/lists/list_simple_in_kv.py b/examples/widgets/lists/list_simple_in_kv.py new file mode 100644 index 000000000..37b86ef7c --- /dev/null +++ b/examples/widgets/lists/list_simple_in_kv.py @@ -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(""" +: + 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)) diff --git a/examples/widgets/lists/list_simple_in_kv_2.py b/examples/widgets/lists/list_simple_in_kv_2.py new file mode 100644 index 000000000..0767bc69a --- /dev/null +++ b/examples/widgets/lists/list_simple_in_kv_2.py @@ -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 + +: + 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)) diff --git a/kivy/tests/test_uix_listview.py b/kivy/tests/test_uix_listview.py index 9c43f138e..aceb4ece4 100644 --- a/kivy/tests/test_uix_listview.py +++ b/kivy/tests/test_uix_listview.py @@ -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 + +: + 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 + +: + 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) diff --git a/kivy/tools/pep8checker/pre-commit.githook b/kivy/tools/pep8checker/pre-commit.githook index 0681f48bd..5659efb1b 100755 --- a/kivy/tools/pep8checker/pre-commit.githook +++ b/kivy/tools/pep8checker/pre-commit.githook @@ -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']) diff --git a/kivy/uix/listview.py b/kivy/uix/listview.py index e34003870..579c29d6a 100644 --- a/kivy/uix/listview.py +++ b/kivy/uix/listview.py @@ -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(""" + : + 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 + + : + 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 diff --git a/kivy/uix/observerview.py b/kivy/uix/observerview.py deleted file mode 100644 index e8f7642db..000000000 --- a/kivy/uix/observerview.py +++ /dev/null @@ -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)