kivy/examples/widgets/list_disclosure.py

267 lines
11 KiB
Python

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, \
NumericProperty, ListProperty, \
StringProperty
from kivy.uix.listview import ListView, ListItemButton
from kivy.adapters.mixins.selection import SelectionObserver, SelectableItem
from kivy.adapters.listadapter import ListAdapter
# [TODO] NOTE -- This is a copy of the old version of list_master_detail.py,
# because it contains an example of how to make a custom
# ListItem.
#
# Calling this example "disclosure" because plan is to try
# expanding a row to show a detail view when the disclosure
# button for a row is clicked.
#
# Something like this:
#
# http://www.zkoss.org/zkdemo/grid/master_detail
#
# Master-Detail view for showing a list on the left (the master) and a view
# on the right for the detail view.
# A "master-detail" view is a good way to experiment with a listview
# (the master) and another view (detail view) that gets updated upon selection.
# MASTER list
# For the master list, we need to create a custom "list item" type that
# subclasses SelectableItem.
# A composite list item could consist of a button on the left, several labels
# in the middle, and another button on the right. Not sure of the merit of
# allowing, perhaps, some "sub" list items to react to touches and others not,
# if that were to be enabled.
class CompositeListItem(SelectableItem, BoxLayout):
# CompositeListItem (BoxLayout) by default uses orientation='horizontal',
# but could be used also for a side-to-side display of items.
#
# ListItemButton sublasses Button, which has background_color.
# Here 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])
icon_button = ObjectProperty(None)
content_button = ObjectProperty(None)
def __init__(self, **kwargs):
super(CompositeListItem, self).__init__(**kwargs)
# Now this button just has text '>', but it would be neat to make the
# left button hold icons -- the list would be heterogeneous, containing
# different list item types that could be filtered perhaps (an option
# for selecting all of a given type, for example).
# For sub list items, set selection_target to self (this is a kind of
# composite list item) so that when they are touched, the composite
# list item is selected, not the components. Any list item component
# that should be selected individually, would need to override this.
kwargs['selection_target'] = self
# Make a copy of kwargs and add specific args for the "disclosure"
# icon button.
icon_kwargs = kwargs.copy()
icon_kwargs['text'] = '>'
icon_kwargs['size_hint_x'] = .05
self.icon_button = ListItemButton(**icon_kwargs)
# Use the passed in kwargs for the "content" button.
self.content_button = ListItemButton(**kwargs)
self.add_widget(self.icon_button)
self.add_widget(self.content_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.content_button is not None:
return self.content_button.text
else:
return 'empty'
class DetailView(SelectionObserver, GridLayout):
fruit_name = StringProperty('')
def __init__(self, **kwargs):
kwargs['cols'] = 2
super(DetailView, self).__init__(**kwargs)
self.bind(fruit_name=self.redraw)
self.fruit_name = str(self.observed_list_adapter.selection[0])
def redraw(self, *args):
self.clear_widgets()
self.add_widget(Label(text="Name:", halign='right'))
self.add_widget(Label(text=self.fruit_name))
for category in descriptors:
self.add_widget(Label(text="{0}:".format(category),
halign='right'))
self.add_widget(
Label(text=str(fruit_data[self.fruit_name][category])))
def observed_selection_changed(self, list_adapter, selection):
if len(list_adapter.selection) == 0:
return
selected_object = list_adapter.selection[0]
if type(selected_object) is str:
self.fruit_name = selected_object
else:
self.fruit_name = str(selected_object)
print 'just added or updated detail label'
class MasterDetailView(GridLayout):
'''Implementation of an MasterDetailView with a vertical scrollable list
on the left (the master, or source list) and a detail view on the right.
'''
list_adapter = ObjectProperty(None)
master_list_view = ObjectProperty(None)
divider = ObjectProperty(None)
divider_width = NumericProperty(2)
detail_view = ObjectProperty(None)
def __init__(self, items, **kwargs):
#kwargs['orientation'] = 'horizontal'
kwargs['cols'] = 2
kwargs['size_hint'] = (1.0, 1.0)
super(MasterDetailView, self).__init__(**kwargs)
args_converter = lambda x: {'text': x,
'size_hint_y': None,
'height': 25}
self.list_adapter = ListAdapter(items,
args_converter=args_converter,
selection_mode='single',
allow_empty_selection=False,
cls=CompositeListItem)
self.master_list_view = ListView(adapter=self.list_adapter,
size_hint=(.3, 1.0))
self.add_widget(self.master_list_view)
self.detail_view = DetailView(
observed_list_adapter=self.list_adapter,
size_hint=(.7, 1.0))
self.add_widget(self.detail_view)
self.list_adapter.bind(
selection=self.detail_view.observed_selection_changed)
self.list_adapter.check_for_empty_selection()
# Data from http://www.fda.gov/Food/LabelingNutrition/\
# FoodLabelingGuidanceRegulatoryInformation/\
# InformationforRestaurantsRetailEstablishments/\
# ucm063482.htm
fruit_categories = \
{'Melons': ['Cantaloupe', 'Honeydew Melon', 'Watermelon'],
'Tree Fruits': ['Apple', 'Avocado, California', 'Banana', 'Nectarine',
'Peach', 'Pear', 'Pineapple', 'Plums',
'Sweet Cherries'],
'Citrus Fruits': ['Grapefruit', 'Lemon', 'Lime', 'Orange',
'Tangerine'],
'Miscellaneous Fruits': ['Grapes', 'Kiwifruit', 'Strawberries']}
descriptors = """(gram weight/ ounce weight) Calories Calories from Fa
t Total Fat Sodium Potassium Total Carbo-hydrate Dietary Fiber Suga
rs Protein Vitamin A Vitamin C Calcium Iron""".replace('\n', '')
descriptors = [item.strip() for item in descriptors.split('\t')]
units = """(g) (%DV) (mg) (%DV) (mg) (%DV) (g) (%DV) (g)
(%DV) (g) (g) (%DV) (%DV) (%DV) (%DV)""".replace('\n', '')
units = [item.strip() for item in units.split('\t')]
raw_fruit_data = [
{'name':'Apple',
'Serving Size': '1 large (242 g/8 oz)',
'data': [130, 0, 0, 0, 0, 0, 260, 7, 34, 11, 5, 20, 25, 1, 2, 8, 2, 2]},
{'name':'Avocado, California',
'Serving Size': '1/5 medium (30 g/1.1 oz)',
'data': [50, 35, 4.5, 7, 0, 0, 140, 4, 3, 1, 1, 4, 0, 1, 0, 4, 0, 2]},
{'name':'Banana',
'Serving Size': '1 medium (126 g/4.5 oz)',
'data': [110, 0, 0, 0, 0, 0, 450, 13, 30, 10, 3, 12, 19, 1, 2, 15, 0, 2]},
{'name':'Cantaloupe',
'Serving Size': '1/4 medium (134 g/4.8 oz)',
'data': [50, 0, 0, 0, 20, 1, 240, 7, 12, 4, 1, 4, 11, 1, 120, 80, 2, 2]},
{'name':'Grapefruit',
'Serving Size': '1/2 medium (154 g/5.5 oz)',
'data': [60, 0, 0, 0, 0, 0, 160, 5, 15, 5, 2, 8, 11, 1, 35, 100, 4, 0]},
{'name':'Grapes',
'Serving Size': '3/4 cup (126 g/4.5 oz)',
'data': [90, 0, 0, 0, 15, 1, 240, 7, 23, 8, 1, 4, 20, 0, 0, 2, 2, 0]},
{'name':'Honeydew Melon',
'Serving Size': '1/10 medium melon (134 g/4.8 oz)',
'data': [50, 0, 0, 0, 30, 1, 210, 6, 12, 4, 1, 4, 11, 1, 2, 45, 2, 2]},
{'name':'Kiwifruit',
'Serving Size': '2 medium (148 g/5.3 oz)',
'data': [90, 10, 1, 2, 0, 0, 450, 13, 20, 7, 4, 16, 13, 1, 2, 240, 4, 2]},
{'name':'Lemon',
'Serving Size': '1 medium (58 g/2.1 oz)',
'data': [15, 0, 0, 0, 0, 0, 75, 2, 5, 2, 2, 8, 2, 0, 0, 40, 2, 0]},
{'name':'Lime',
'Serving Size': '1 medium (67 g/2.4 oz)',
'data': [20, 0, 0, 0, 0, 0, 75, 2, 7, 2, 2, 8, 0, 0, 0, 35, 0, 0]},
{'name':'Nectarine',
'Serving Size': '1 medium (140 g/5.0 oz)',
'data': [60, 5, 0.5, 1, 0, 0, 250, 7, 15, 5, 2, 8, 11, 1, 8, 15, 0, 2]},
{'name':'Orange',
'Serving Size': '1 medium (154 g/5.5 oz)',
'data': [80, 0, 0, 0, 0, 0, 250, 7, 19, 6, 3, 12, 14, 1, 2, 130, 6, 0]},
{'name':'Peach',
'Serving Size': '1 medium (147 g/5.3 oz)',
'data': [60, 0, 0.5, 1, 0, 0, 230, 7, 15, 5, 2, 8, 13, 1, 6, 15, 0, 2]},
{'name':'Pear',
'Serving Size': '1 medium (166 g/5.9 oz)',
'data': [100, 0, 0, 0, 0, 0, 190, 5, 26, 9, 6, 24, 16, 1, 0, 10, 2, 0]},
{'name':'Pineapple',
'Serving Size': '2 slices, 3" diameter, 3/4" thick (112 g/4 oz)',
'data': [50, 0, 0, 0, 10, 0, 120, 3, 13, 4, 1, 4, 10, 1, 2, 50, 2, 2]},
{'name':'Plums',
'Serving Size': '2 medium (151 g/5.4 oz)',
'data': [70, 0, 0, 0, 0, 0, 230, 7, 19, 6, 2, 8, 16, 1, 8, 10, 0, 2]},
{'name':'Strawberries',
'Serving Size': '8 medium (147 g/5.3 oz)',
'data': [50, 0, 0, 0, 0, 0, 170, 5, 11, 4, 2, 8, 8, 1, 0, 160, 2, 2]},
{'name':'Sweet Cherries',
'Serving Size': '21 cherries; 1 cup (140 g/5.0 oz)',
'data': [100, 0, 0, 0, 0, 0, 350, 10, 26, 9, 1, 4, 16, 1, 2, 15, 2, 2]},
{'name':'Tangerine',
'Serving Size': '1 medium (109 g/3.9 oz)',
'data': [50, 0, 0, 0, 0, 0, 160, 5, 13, 4, 2, 8, 9, 1, 6, 45, 4, 0]},
{'name':'Watermelon',
'Serving Size': '1/18 medium melon; 2 cups diced pieces (280 g/10.0 oz)',
'data': [80, 0, 0, 0, 0, 0, 270, 8, 21, 7, 1, 4, 20, 1, 30, 25, 2, 4]}]
fruit_data = {}
descriptors_and_units = dict(zip(descriptors, units))
for row in raw_fruit_data:
fruit_data[row['name']] = {}
fruit_data[row['name']] = dict({'Serving Size': row['Serving Size']},
**dict(zip(descriptors_and_units.keys(), row['data'])))
if __name__ == '__main__':
from kivy.base import runTouchApp
master_detail = MasterDetailView(fruit_data.keys(), width=800)
runTouchApp(master_detail)