diff --git a/examples/widgets/list_cascade.py b/examples/widgets/list_cascade.py index b616f81e8..6e2a41265 100644 --- a/examples/widgets/list_cascade.py +++ b/examples/widgets/list_cascade.py @@ -1,5 +1,5 @@ from kivy.adapters.listadapter import ListAdapter, \ - SelectableListsAdapter + ListsAdapter from kivy.uix.gridlayout import GridLayout from kivy.uix.label import Label from kivy.uix.listview import ListView, ListItemButton @@ -78,9 +78,9 @@ class CascadingView(GridLayout): # Fruits, for a given category, in the middle: # fruits_list_adapter = \ - SelectableListsAdapter( + ListsAdapter( observed_list_adapter=fruit_categories_list_adapter, - selectable_lists_dict=fruit_categories, + lists_dict=fruit_categories, data=fruit_categories[categories[0]], args_converter=list_item_args_converter, selection_mode='single', diff --git a/examples/widgets/list_composite.py b/examples/widgets/list_composite.py index d1285eaa7..dec517c6f 100644 --- a/examples/widgets/list_composite.py +++ b/examples/widgets/list_composite.py @@ -1,11 +1,8 @@ from kivy.adapters.listadapter import ListAdapter -from kivy.adapters.mixins.selection import SelectableItem from kivy.uix.listview import ListItemButton, ListItemLabel, \ CompositeListItem, ListView -from kivy.uix.boxlayout import BoxLayout from kivy.uix.gridlayout import GridLayout -from kivy.properties import ObjectProperty, ListProperty class MainView(GridLayout): @@ -35,7 +32,7 @@ class MainView(GridLayout): 'kwargs': {'text': "Right", 'merge_text': True, 'delimiter': '-'}}]} - + # Here we create a list adapter with some item strings, passing our # CompositeListItem as the list item view class, and then we create a # list view using this adapter: diff --git a/kivy/adapters/listadapter.py b/kivy/adapters/listadapter.py index 19afdec2e..8702d40de 100644 --- a/kivy/adapters/listadapter.py +++ b/kivy/adapters/listadapter.py @@ -18,23 +18,17 @@ from kivy.adapters.adapter import Adapter from kivy.adapters.mixins.selection import SelectionSupport -class ListAdapter(SelectionSupport, Adapter): +class SimpleListAdapter(Adapter): '''ListAdapter is an adapter around a simple Python list. - From the SelectionSupport mixin, ListAdapter has these properties: - - - selection - - selection_mode - - allow_empty_selection - - and several methods used in selection operations. - - From Adapter, ListAdapter gets these properties: + From Adapter, SimpleListAdapter gets these properties: - cls, for the list item class to use to instantiate row view instances + - template, an optional kv template to use instead of a python class (to use instead of cls) + - args_converter, an optional function to transform data item argument sets, in preparation for either a cls instantiation, or a kv template invocation @@ -49,13 +43,10 @@ class ListAdapter(SelectionSupport, Adapter): def __init__(self, **kwargs): if 'data' not in kwargs: - raise Exception('ListAdapter: input must include data argument') + raise Exception('list adapter: input must include data argument') if type(kwargs['data']) not in (tuple, list): - raise Exception('ListAdapter: data must be a tuple or list') - super(ListAdapter, self).__init__(**kwargs) - - # Reset and update selection, in SelectionSupport, if data changes. - self.bind(data=self.initialize_selection) + raise Exception('list adapter: data must be a tuple or list') + super(SimpleListAdapter, self).__init__(**kwargs) def get_count(self): return len(self.data) @@ -65,11 +56,31 @@ class ListAdapter(SelectionSupport, Adapter): return None return self.data[index] + +class ListAdapter(SelectionSupport, SimpleListAdapter): + '''From the SelectionSupport mixin, ListAdapter has these properties: + + - selection + - selection_mode + - allow_empty_selection + + and several methods used in selection operations. + + If you wish to have a bare-bones list adapter, without selection, use + SimpleListAdapter. + ''' + def __init__(self, **kwargs): + super(ListAdapter, self).__init__(**kwargs) + + # Reset and update selection, in SelectionSupport, if data changes. + self.bind(data=self.initialize_selection) + def get_view(self, index): - '''This method is identical to the one in Adapter, but here we pass - self to the list item class (cls) instantiation, so that the list - item class, required to mix in SelectableItem, will have access to - ListAdapter for calling to SelectionSupport methods. + '''This method is identical to the one in Adapter and + SimpleListAdapter, but here we pass self to the list item class (cls) + instantiation, so that the list item class, required to mix in + SelectableItem, will have access to ListAdapter for calling to + SelectionSupport methods. ''' item = self.get_item(index) if item is None: @@ -91,27 +102,31 @@ class ListAdapter(SelectionSupport, Adapter): return Builder.template(self.template, **item_args) def on_selection_change(self, *args): + '''on_selection_change() is the default handler for the + on_selection_change event, which is registered in SelectionSupport. + ''' pass -class SelectableListsAdapter(ListAdapter): - '''SelectableListsAdapter is specialized for use in chaining - list_adapters in a "cascade," where selection of the first, - changes the selection of the next, and so on. +class ListsAdapter(ListAdapter): + '''ListsAdapter is specialized for managing a dict of lists. It has wide + application, because a list of lists is a common need. ListsAdapter may + be used for chaining several list_adapters in a "cascade," where selection + of the first, changes the selection of the next, and so on. ''' - selectable_lists_dict = DictProperty({}) + lists_dict = DictProperty({}) '''The selection of the observed_list_adapter, which must be single - selection here, is the key into selectable_lists_dict, which is a dict + selection here, is the key into lists_dict, which is a dict of list item lists. ''' observed_list_adapter = ObjectProperty(None) - selectable_lists_dict = DictProperty({}) + lists_dict = DictProperty({}) def __init__(self, **kwargs): - super(SelectableListsAdapter, self).__init__(**kwargs) + super(ListsAdapter, self).__init__(**kwargs) self.observed_list_adapter.bind( on_selection_change=self.on_selection_change) @@ -122,7 +137,7 @@ class SelectableListsAdapter(ListAdapter): self.data = [] return - self.data = self.selectable_lists_dict[str(observed_selection[0])] + self.data = self.lists_dict[str(observed_selection[0])] class AccumulatingListAdapter(ListAdapter): diff --git a/kivy/tests/test_selection.py b/kivy/tests/test_selection.py index eb60c3a3a..3136ff696 100644 --- a/kivy/tests/test_selection.py +++ b/kivy/tests/test_selection.py @@ -10,7 +10,7 @@ from kivy.uix.button import Button from kivy.uix.listview import ListView, ListItemButton from kivy.properties import NumericProperty, ListProperty, StringProperty from kivy.adapters.mixins.selection import SelectableItem -from kivy.adapters.listadapter import ListAdapter, SelectableListsAdapter, \ +from kivy.adapters.listadapter import ListAdapter, ListsAdapter, \ AccumulatingListAdapter @@ -192,7 +192,7 @@ class ListAdapterTestCase(unittest.TestCase): self.assertEqual(selection_observer.call_count, 2) -class SelectableListsAdapterTestCase(unittest.TestCase): +class ListsAdapterTestCase(unittest.TestCase): def setUp(self): self.args_converter = lambda x: {'text': x, @@ -220,13 +220,13 @@ class SelectableListsAdapterTestCase(unittest.TestCase): # Fruits, for a given category, are shown based on the fruit category # selected in the first categories list above. The selected item in # the first list is used as the key into a dict of lists of list - # items to reset the data in SelectableListsAdapter's + # items to reset the data in ListsAdapter's # observed_selection_changed() method. # # data is initially set to the first list of list items. # fruits_l_a = \ - SelectableListsAdapter(observed_list_adapter=fruit_categories_l_a, + ListsAdapter(observed_list_adapter=fruit_categories_l_a, selectable_lists_dict=fruit_categories, data=fruit_categories[categories[0]], args_converter=self.args_converter, diff --git a/kivy/uix/listview.py b/kivy/uix/listview.py index d66c016b9..bd4f956da 100644 --- a/kivy/uix/listview.py +++ b/kivy/uix/listview.py @@ -21,29 +21,57 @@ From AbstractView we have these properties and methods: Basic Example ------------- -The list items you put in a list view can be totally custom, but to have a -simple button, we may use the :class:`ListItem` class. - Here we make a list view with 100 items. + from kivy.uix.listview import ListView + from kivy.uix.gridlayout import GridLayout + + + class MainView(GridLayout): + + def __init__(self, **kwargs): + kwargs['cols'] = 2 + kwargs['size_hint'] = (1.0, 1.0) + super(MainView, self).__init__(**kwargs) + + list_view = ListView([str(index) for index in xrange(100)]) + + self.add_widget(list_view) + + + if __name__ == '__main__': + from kivy.base import runTouchApp + runTouchApp(MainView(width=800)) + +Using a ListAdapter +------------------- + +The basic example above uses :class:`SimpleListAdapter` internally, an adapter +that does not offer selection support. + +For most uses of a list, however, selection support is needed. It is built in +to :class:`ListAdapter`. + +The view used for items in a list view can be totally custom, but to have a +simple button, we may use the :class:`ListItemButton` class. + from kivy.adapters.list_adapter import ListAdapter from kivy.uix.listview import ListItem, ListView data = ["Item {0}".format(index) for index in xrange(100)] # A list view needs a list adapter to provide a mediating service to the - # data, in the case here our data list, passed as the first - # argument. We choose single selection mode. We may also set this for - # allowing multiple selection. We set allow_empty_selection to False so - # that there is always an item selected if at least one is in the list. - # Setting this to true will make the list useful for display only. - # Finally, we pass ListItem as the class (cls) to be instantiated by the - # list adapter for each list item. ListItem is based on Button. When an - # item is selected, its background color will change to red. + # data, which is the first argument. We choose single selection mode. We + # may also set this for allowing multiple selection. We set + # allow_empty_selection to False so that there is always an item + # selected if at least one is in the list. Setting this to true will make + # the list useful for display only. Finally, we pass ListItem as the class + # (cls) to be instantiated by the list adapter for each list item. + # When an item is selected, its background color will change to red. list_adapter = ListAdapter(data=data, selection_mode='single', allow_empty_selection=False, - cls=ListItem) + cls=ListItemButton) # The list view is a simple component. Just give it the list adapter that # will provide list item views based on its data. @@ -55,65 +83,13 @@ Composite List Item Example Let's say you would like to make a list view with composite list item views consisting of a button on the left, a label in the middle, and a second button on the right. Perhaps the buttons could be made as toggle buttons for two -separate properties pertaining to the label. First, we need to make a custom -list item, which we will base on the required SelectableItem mixin and our -choice of layout, BoxLayout: +separate properties pertaining to the label. We add default buttons and a +label in this example. from kivy.adapters.listadapter import ListAdapter - from kivy.adapters.mixins.selection import SelectableItem - from kivy.uix.listview import ListItemButton, ListItemLabel, ListView - - from kivy.uix.boxlayout import BoxLayout + from kivy.uix.listview import ListItemButton, ListItemLabel, \ + CompositeListItem, ListView from kivy.uix.gridlayout import GridLayout - from kivy.properties import ObjectProperty, ListProperty - - - class CompositeListItem(SelectableItem, BoxLayout): - # ListItem sublasses Button, which has background_color. - # For a composite list item, we must add this property. - background_color = ListProperty([1, 1, 1, 1]) - - selected_color = ListProperty([1., 0., 0., 1]) - deselected_color = ListProperty([.33, .33, .33, 1]) - - left_button = ObjectProperty(None) - middle_label = ObjectProperty(None) - right_button = ObjectProperty(None) - - def __init__(self, **kwargs): - super(CompositeListItem, self).__init__(**kwargs) - - # For the component list items in our composite list item, we want - # their individual selection to select the composite. Here we pass - # an argument for the list_adapter, required by the SelectableItem - # mixing, and a selection_target argument, setting it to self (to - # this composite list item). - kwargs['selection_target'] = self - - kwargs['text']="left-{0}".format(kwargs['text']) - self.left_button = ListItemButton(**kwargs) - - kwargs['text']="middle-{0}".format(kwargs['text']) - self.middle_label = ListItemLabel(**kwargs) - - kwargs['text']="right-{0}".format(kwargs['text']) - self.right_button = ListItemButton(**kwargs) - - self.add_widget(self.left_button) - self.add_widget(self.middle_label) - self.add_widget(self.right_button) - - def select(self, *args): - self.background_color = self.selected_color - - def deselect(self, *args): - self.background_color = self.deselected_color - - def __repr__(self): - if self.middle_label is not None: - return self.middle_label.text - else: - return 'empty' class MainView(GridLayout): @@ -123,18 +99,50 @@ choice of layout, BoxLayout: kwargs['size_hint'] = (1.0, 1.0) super(MainView, self).__init__(**kwargs) + args_converter = \ + lambda x: \ + {'text': x, + 'size_hint_y': None, + 'height': 25, + 'cls_dicts': [{'cls': ListItemButton, + 'kwargs': {'text': "Left", + 'merge_text': True, + 'delimiter': '-'}}, + {'cls': ListItemLabel, + 'kwargs': {'text': "Middle", + 'merge_text': True, + 'delimiter': '-', + 'is_representing_cls': True}}, + {'cls': ListItemButton, + 'kwargs': {'text': "Right", + 'merge_text': True, + 'delimiter': '-'}}]} + # Here we create a list adapter with some item strings, passing # our CompositeListItem as the list item view class, and then we - # create list view using this adapter: - data = ["{0}".format(index) for index in xrange(100)] - list_adapter = ListAdapter(data=data, + # create a list view using this adapter: + item_strings = ["{0}".format(index) for index in xrange(100)] + + # And now the list adapter, constructed with the item_strings as + # the data, and our args_converter() that will operate one each + # item in the data to produce list item view instances from the + # :class:`CompositeListItem` class. + list_adapter = ListAdapter(data=item_strings, + args_converter=args_converter, selection_mode='single', allow_empty_selection=False, cls=CompositeListItem) + + # Pass the adapter to ListView. list_view = ListView(adapter=list_adapter) self.add_widget(list_view) + + if __name__ == '__main__': + from kivy.base import runTouchApp + runTouchApp(MainView(width=800)) + Using With kv ------------- @@ -333,10 +341,10 @@ class ListView(AbstractView): if 'adapter' not in kwargs: if item_strings is None: raise Exception('ListView: input needed, or an adapter') - list_adapter = ListAdapter(data=item_strings, - selection_mode='single', - allow_empty_selection=False, - cls=ListItemButton) + list_adapter = SimpleListAdapter(data=item_strings, + selection_mode='single', + allow_empty_selection=False, + cls=ListItemButton) kwargs['adapter'] = list_adapter super(ListView, self).__init__(**kwargs)