1from kivy.uix.boxlayout import BoxLayout
2from kivy.adapters.dictadapter import DictAdapter
3from kivy.adapters.listadapter import ListAdapter
4from kivy.properties import ObjectProperty, ListProperty, AliasProperty
5from kivy.uix.listview import (ListItemButton, ListItemLabel, CompositeListItem,
6                               ListView)
7from kivy.lang import Builder
8from kivy.metrics import dp, sp
9
10Builder.load_string('''
11<GridView>
12    header_view: header_view
13    content_view: content_view
14    BoxLayout:
15        orientation: 'vertical'
16        padding: '0dp', '2dp'
17        BoxLayout:
18            id: header_box
19            orientation: 'vertical'
20            size_hint: 1, None
21            height: '30dp'
22            ListView:
23                id: header_view
24        BoxLayout:
25            id: content_box
26            orientation: 'vertical'
27            ListView:
28                id: content_view
29
30<-HorizVertGrid>
31    header_view: header_view
32    content_view: content_view
33    ScrollView:
34        id: scrl
35        do_scroll_y: False
36        RelativeLayout:
37            size_hint_x: None
38            width: max(scrl.width, dp(sum(root.widths)))
39            BoxLayout:
40                orientation: 'vertical'
41                padding: '0dp', '2dp'
42                BoxLayout:
43                    id: header_box
44                    orientation: 'vertical'
45                    size_hint: 1, None
46                    height: '30dp'
47                    ListView:
48                        id: header_view
49                BoxLayout:
50                    id: content_box
51                    orientation: 'vertical'
52                    ListView:
53                        id: content_view
54
55''')
56
57class GridView(BoxLayout):
58    """Workaround solution for grid view by using 2 list view.
59    Sometimes the height of lines is shown properly."""
60
61    def _get_hd_adpt(self):
62        return self.ids.header_view.adapter
63
64    header_adapter = AliasProperty(_get_hd_adpt, None)
65    '''
66    '''
67
68    def _get_cnt_adpt(self):
69        return self.ids.content_view.adapter
70
71    content_adapter = AliasProperty(_get_cnt_adpt, None)
72    '''
73    '''
74
75    headers = ListProperty([])
76    '''
77    '''
78
79    widths = ListProperty([])
80    '''
81    '''
82
83    data = ListProperty([])
84    '''
85    '''
86
87    getter = ObjectProperty(lambda item, i: item[i])
88    '''
89    '''
90    on_context_menu = ObjectProperty(None)
91
92    def __init__(self, **kwargs):
93        self._from_widths = False
94        super(GridView, self).__init__(**kwargs)
95        #self.on_headers(self, self.headers)
96
97    def on_widths(self, instance, value):
98        if not self.get_root_window():
99            return
100        self._from_widths = True
101        self.on_headers(instance, self.headers)
102        self._from_widths = False
103
104    def on_headers(self, instance, value):
105        if not self._from_widths:
106            return
107        if not (value and self.canvas and self.headers):
108            return
109        widths = self.widths
110        if len(self.widths) != len(value):
111            return
112        #if widths is not None:
113        #    widths = ['%sdp' % i for i in widths]
114
115        def generic_args_converter(row_index,
116                                   item,
117                                   is_header=True,
118                                   getter=self.getter):
119            cls_dicts = []
120            _widths = self.widths
121            getter = self.getter
122            on_context_menu = self.on_context_menu
123
124            for i, header in enumerate(self.headers):
125                kwargs = {
126                    'padding': ('2dp','2dp'),
127                    'halign': 'center',
128                    'valign': 'middle',
129                    'size_hint_y': None,
130                    'shorten': True,
131                    'height': '30dp',
132                    'text_size': (_widths[i], dp(30)),
133                    'text': getter(item, i),
134                }
135
136                kwargs['font_size'] = '9sp'
137                if is_header:
138                    kwargs['deselected_color'] = kwargs['selected_color']  =\
139                        [0, 1, 1, 1]
140                else:  # this is content
141                    kwargs['deselected_color'] = 1, 1, 1, 1
142                    if on_context_menu is not None:
143                        kwargs['on_press'] = on_context_menu
144
145                if widths is not None:  # set width manually
146                    kwargs['size_hint_x'] = None
147                    kwargs['width'] = widths[i]
148
149                cls_dicts.append({
150                    'cls': ListItemButton,
151                    'kwargs': kwargs,
152                })
153
154            return {
155                'id': item[-1],
156                'size_hint_y': None,
157                'height': '30dp',
158                'cls_dicts': cls_dicts,
159            }
160
161        def header_args_converter(row_index, item):
162            return generic_args_converter(row_index, item)
163
164        def content_args_converter(row_index, item):
165            return generic_args_converter(row_index, item, is_header=False)
166
167
168        self.ids.header_view.adapter = ListAdapter(data=[self.headers],
169                                   args_converter=header_args_converter,
170                                   selection_mode='single',
171                                   allow_empty_selection=False,
172                                   cls=CompositeListItem)
173
174        self.ids.content_view.adapter = ListAdapter(data=self.data,
175                                   args_converter=content_args_converter,
176                                   selection_mode='single',
177                                   allow_empty_selection=False,
178                                   cls=CompositeListItem)
179        self.content_adapter.bind_triggers_to_view(self.ids.content_view._trigger_reset_populate)
180
181class HorizVertGrid(GridView):
182    pass
183
184
185if __name__ == "__main__":
186    from kivy.app import App
187    class MainApp(App):
188
189        def build(self):
190            data = []
191            for i in range(90):
192                data.append((str(i), str(i)))
193            self.data = data
194            return Builder.load_string('''
195BoxLayout:
196    orientation: 'vertical'
197    HorizVertGrid:
198        on_parent: if args[1]: self.content_adapter.data = app.data
199        headers:['Address', 'Previous output']
200        widths: [400, 500]
201
202<Label>
203    font_size: '16sp'
204''')
205    MainApp().run()
206