Mercurial > libervia-desktop-kivy
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: |