comparison src/cagou/core/xmlui.py @ 53:65775152aac1

xmlui: implemented most of XMLUI, not finished yet most of XMLUI should be working now, some elements are still missing (notably TabsContainer), but they will be implemented soon.
author Goffi <goffi@goffi.org>
date Sun, 11 Sep 2016 23:27:16 +0200
parents c21d1be2e54c
children b0011c6dc7dc
comparison
equal deleted inserted replaced
52:647f32d0a004 53:65775152aac1
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat_frontends.constants import Const as C 21 from sat_frontends.constants import Const as C
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23 log = getLogger(__name__) 23 log = getLogger(__name__)
24 from sat_frontends.tools import xmlui 24 from sat_frontends.tools import xmlui
25 from kivy.uix.boxlayout import BoxLayout 25 from kivy.uix.scrollview import ScrollView
26 from kivy.uix.gridlayout import GridLayout
26 from kivy.uix.textinput import TextInput 27 from kivy.uix.textinput import TextInput
27 from kivy.uix.label import Label 28 from kivy.uix.label import Label
28 from kivy.uix.button import Button 29 from kivy.uix.button import Button
30 from kivy.uix.togglebutton import ToggleButton
29 from kivy.uix.widget import Widget 31 from kivy.uix.widget import Widget
32 from kivy.uix.dropdown import DropDown
33 from kivy.uix.switch import Switch
34 from kivy import properties
30 from cagou import G 35 from cagou import G
31 36
32 37
33 ## Widgets ## 38 ## Widgets ##
39
40
41 class EmptyWidget(xmlui.EmptyWidget, Widget):
42
43 def __init__(self, _xmlui_parent):
44 Widget.__init__(self)
34 45
35 46
36 class TextWidget(xmlui.TextWidget, Label): 47 class TextWidget(xmlui.TextWidget, Label):
37 48
38 def __init__(self, xmlui_parent, value): 49 def __init__(self, xmlui_parent, value):
39 Label.__init__(self, text=value) 50 Label.__init__(self, text=value)
51
52
53 class LabelWidget(xmlui.LabelWidget, TextWidget):
54 pass
55
56
57 class JidWidget(xmlui.JidWidget, TextWidget):
58 pass
59
60
61 class StringWidget(xmlui.StringWidget, TextInput):
62
63 def __init__(self, xmlui_parent, value, read_only=False):
64 TextInput.__init__(self, text=value, multiline=False)
65 self.readonly = read_only
66
67 def _xmluiSetValue(self, value):
68 self.text = value
69
70 def _xmluiGetValue(self):
71 return self.text
72
73
74 class JidInputWidget(xmlui.JidInputWidget, StringWidget):
75 pass
76
77
78 class ButtonWidget(xmlui.ButtonWidget, Button):
79
80 def __init__(self, _xmlui_parent, value, click_callback):
81 Button.__init__(self)
82 self.text = value
83 self.callback = click_callback
84
85 def _xmluiOnClick(self, callback):
86 self.callback = callback
87
88 def on_release(self):
89 self.callback(self)
90
91
92 class DividerWidget(xmlui.DividerWidget, Widget):
93 # FIXME: not working properly + only 'line' is handled
94 style = properties.OptionProperty('line',
95 options=['line', 'dot', 'dash', 'plain', 'blank'])
96
97 def __init__(self, _xmlui_parent, style="line"):
98 Widget.__init__(self, style=style)
99
100
101 class ListWidgetItem(ToggleButton):
102 value = properties.StringProperty()
103
104 def on_release(self):
105 super(ListWidgetItem, self).on_release()
106 parent = self.parent
107 while parent is not None and not isinstance(parent, DropDown):
108 parent = parent.parent
109
110 if parent is not None and parent.attach_to is not None:
111 parent.select(self)
112
113 @property
114 def selected(self):
115 return self.state == 'down'
116
117 @selected.setter
118 def selected(self, value):
119 self.state = 'down' if value else 'normal'
120
121
122 class ListWidget(xmlui.ListWidget, Button):
123
124 def __init__(self, _xmlui_parent, options, selected, flags):
125 Button.__init__(self)
126 self.text = _(u"open list")
127 self._dropdown = DropDown()
128 self._dropdown.auto_dismiss = False
129 self._dropdown.bind(on_select = self.on_select)
130 self.multi = 'single' not in flags
131 self._dropdown.dismiss_on_select = not self.multi
132 self._values = []
133 for option in options:
134 self.addValue(option)
135 self._xmluiSelectValues(selected)
136 self._on_change = None
137
138 @property
139 def items(self):
140 return self._dropdown.children[0].children
141
142 def on_touch_down(self, touch):
143 # we simulate auto-dismiss ourself because dropdown
144 # will dismiss even if attached button is touched
145 # resulting in a dismiss just before a toggle in on_release
146 # so the dropbox would always be opened, we don't want that!
147 if super(ListWidget, self).on_touch_down(touch):
148 return True
149 if self._dropdown.parent:
150 self._dropdown.dismiss()
151
152 def on_release(self):
153 if self._dropdown.parent is not None:
154 # we want to close a list already opened
155 self._dropdown.dismiss()
156 else:
157 self._dropdown.open(self)
158
159 def on_select(self, drop_down, item):
160 if not self.multi:
161 self._xmluiSelectValues([item.value])
162 if self._on_change is not None:
163 self._on_change(self)
164
165 def addValue(self, option, selected=False):
166 """add a value in the list
167
168 @param option(tuple): value, label in a tuple
169 """
170 self._values.append(option)
171 item = ListWidgetItem()
172 item.value, item.text = option
173 item.selected = selected
174 self._dropdown.add_widget(item)
175
176 def _xmluiSelectValue(self, value):
177 self._xmluiSelectValues([value])
178
179 def _xmluiSelectValues(self, values):
180 for item in self.items:
181 item.selected = item.value in values
182 if item.selected and not self.multi:
183 self.text = item.text
184
185 def _xmluiGetSelectedValues(self):
186 return [item.value for item in self.items if item.selected]
187
188 def _xmluiAddValues(self, values, select=True):
189 values = set(values).difference([c.value for c in self.items])
190 for v in values:
191 self.addValue(v, select)
192
193 def _xmluiOnChange(self, callback):
194 self._on_change = callback
40 195
41 196
42 class PasswordWidget(xmlui.PasswordWidget, TextInput): 197 class PasswordWidget(xmlui.PasswordWidget, TextInput):
43 198
44 def __init__(self, _xmlui_parent, value, read_only=False): 199 def __init__(self, _xmlui_parent, value, read_only=False):
50 205
51 def _xmluiGetValue(self): 206 def _xmluiGetValue(self):
52 return self.text 207 return self.text
53 208
54 209
210 class BoolWidget(xmlui.BoolWidget, Switch):
211
212 def __init__(self, _xmlui_parent, state, read_only=False):
213 Switch.__init__(self, active=state)
214 if read_only:
215 self.disabled = True
216
217 def _xmluiSetValue(self, value):
218 self.active = value
219
220 def _xmluiGetValue(self):
221 return self.active
222
223
55 ## Containers ## 224 ## Containers ##
56 225
57 226
58 class VerticalContainer(xmlui.VerticalContainer, BoxLayout): 227 class VerticalContainer(xmlui.VerticalContainer, GridLayout):
59 228
60 def __init__(self, xmlui_parent): 229 def __init__(self, xmlui_parent):
61 BoxLayout.__init__(self, orientation='vertical') 230 GridLayout.__init__(self)
62 231
63 def _xmluiAppend(self, widget): 232 def _xmluiAppend(self, widget):
64 self.add_widget(widget) 233 self.add_widget(widget)
65 234
235
236 class PairsContainer(xmlui.PairsContainer, GridLayout):
237
238 def __init__(self, xmlui_parent):
239 GridLayout.__init__(self)
240
241 def _xmluiAppend(self, widget):
242 self.add_widget(widget)
243
244
245 class AdvancedListRow(GridLayout):
246 global_index = 0
247 index = properties.ObjectProperty()
248 selected = properties.BooleanProperty(False)
249
250 def __init__(self, **kwargs):
251 self.global_index = AdvancedListRow.global_index
252 AdvancedListRow.global_index += 1
253 super(AdvancedListRow, self).__init__(**kwargs)
254
255 def on_touch_down(self, touch):
256 if self.collide_point(*touch.pos):
257 parent = self.parent
258 while parent is not None and not isinstance(parent, AdvancedListContainer):
259 parent = parent.parent
260 if parent is None:
261 log.error(u"Can't find parent AdvancedListContainer")
262 else:
263 if parent.selectable:
264 self.selected = parent._xmluiToggleSelected(self)
265
266 return super(AdvancedListRow, self).on_touch_down(touch)
267
268
269 class AdvancedListContainer(xmlui.AdvancedListContainer, GridLayout):
270
271 def __init__(self, _xmlui_parent, columns, selectable='no'):
272 GridLayout.__init__(self)
273 self._columns = columns
274 self.selectable = selectable != 'no'
275 self._current_row = None
276 self._selected = []
277 self._xmlui_select_cb = None
278
279 def _xmluiToggleSelected(self, row):
280 """inverse selection status of an AdvancedListRow
281
282 @param row(AdvancedListRow): row to (un)select
283 @return (bool): True if row is selected
284 """
285 try:
286 self._selected.remove(row)
287 except ValueError:
288 self._selected.append(row)
289 if self._xmlui_select_cb is not None:
290 self._xmlui_select_cb(self)
291 return True
292 else:
293 return False
294
295 def _xmluiAppend(self, widget):
296 if self._current_row is None:
297 log.error(u"No row set, ignoring append")
298 return
299 self._current_row.add_widget(widget)
300
301 def _xmluiAddRow(self, idx):
302 self._current_row = AdvancedListRow()
303 self._current_row.cols = self._columns
304 self._current_row.index = idx
305 self.add_widget(self._current_row)
306
307 def _xmluiGetSelectedWidgets(self):
308 return self._selected
309
310 def _xmluiGetSelectedIndex(self):
311 if not self._selected:
312 return None
313 return self._selected[0].index
314
315 def _xmluiOnSelect(self, callback):
316 """ Call callback with widget as only argument """
317 self._xmlui_select_cb = callback
66 318
67 ## Dialogs ## 319 ## Dialogs ##
68 320
69 321
70 class NoteDialog(xmlui.NoteDialog): 322 class NoteDialog(xmlui.NoteDialog):
97 kwargs['size'] = (100, 25) 349 kwargs['size'] = (100, 25)
98 kwargs['size_hint'] = (1,None) 350 kwargs['size_hint'] = (1,None)
99 super(Title, self).__init__(*args, **kwargs) 351 super(Title, self).__init__(*args, **kwargs)
100 352
101 353
102 class XMLUIPanel(xmlui.XMLUIPanel, BoxLayout): 354 class FormButton(Button):
355 pass
356
357
358 class XMLUIPanelGrid(GridLayout):
359 pass
360
361 class XMLUIPanel(xmlui.XMLUIPanel, ScrollView):
103 widget_factory = WidgetFactory() 362 widget_factory = WidgetFactory()
104 363
105 def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE): 364 def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE):
365 ScrollView.__init__(self)
106 self.close_cb = None 366 self.close_cb = None
107 BoxLayout.__init__(self, orientation='vertical') 367 self._grid = XMLUIPanelGrid()
368 ScrollView.add_widget(self, self._grid)
108 xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags, callback, profile) 369 xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags, callback, profile)
370
371 def add_widget(self, wid):
372 self._grid.add_widget(wid)
109 373
110 def setCloseCb(self, close_cb): 374 def setCloseCb(self, close_cb):
111 self.close_cb = close_cb 375 self.close_cb = close_cb
112 376
113 def _xmluiClose(self): 377 def _xmluiClose(self):
120 xmlui.XMLUIPanel.constructUI(self, parsed_dom) 384 xmlui.XMLUIPanel.constructUI(self, parsed_dom)
121 if self.xmlui_title: 385 if self.xmlui_title:
122 self.add_widget(Title(text=self.xmlui_title)) 386 self.add_widget(Title(text=self.xmlui_title))
123 self.add_widget(self.main_cont) 387 self.add_widget(self.main_cont)
124 if self.type == 'form': 388 if self.type == 'form':
125 submit_btn = Button(text=_(u"Submit"), size_hint=(1,0.2)) 389 submit_btn = FormButton(text=_(u"Submit"))
126 submit_btn.bind(on_press=self.onFormSubmitted) 390 submit_btn.bind(on_press=self.onFormSubmitted)
127 self.add_widget(submit_btn) 391 self.add_widget(submit_btn)
128 if not 'NO_CANCEL' in self.flags: 392 if not 'NO_CANCEL' in self.flags:
129 cancel_btn = Button(text=_(u"Cancel"), size_hint=(1,0.2)) 393 cancel_btn = FormButton(text=_(u"Cancel"))
130 cancel_btn.bind(on_press=self.onFormCancelled) 394 cancel_btn.bind(on_press=self.onFormCancelled)
131 self.add_widget(cancel_btn) 395 self.add_widget(cancel_btn)
132 self.add_widget(Widget()) # to have elements on the top 396 self.add_widget(Widget()) # to have elements on the top
133 397
134 def show(self, *args, **kwargs): 398 def show(self, *args, **kwargs):
135 if not self.user_action and not kwargs.get("force", False): 399 if not self.user_action and not kwargs.get("force", False):
136 G.host.addNotifUI(self) 400 G.host.addNotifUI(self)
137 else: 401 else: