comparison browser/sat_browser/xmlui.py @ 1124:28e3eb3bb217

files reorganisation and installation rework: - files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory) - VERSION file is now used, as for other SàT projects - replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly - removed check for data_dir if it's empty - installation tested working in virtual env - libervia launching script is now in bin/libervia
author Goffi <goffi@goffi.org>
date Sat, 25 Aug 2018 17:59:48 +0200
parents src/browser/sat_browser/xmlui.py@2066d4fd9036
children ab78374e1117
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from sat.core.log import getLogger
21 log = getLogger(__name__)
22 from sat_frontends.tools import xmlui
23 from sat_browser import strings
24 from sat_frontends.tools import jid
25 from sat_browser.constants import Const as C
26 from sat_browser import dialog
27 from sat_browser import html_tools
28 from sat_browser import contact_panel
29
30 from pyjamas.ui.VerticalPanel import VerticalPanel
31 from pyjamas.ui.HorizontalPanel import HorizontalPanel
32 from pyjamas.ui.TabPanel import TabPanel
33 from pyjamas.ui.Grid import Grid
34 from pyjamas.ui.Label import Label
35 from pyjamas.ui.TextBox import TextBox
36 from pyjamas.ui.PasswordTextBox import PasswordTextBox
37 from pyjamas.ui.TextArea import TextArea
38 from pyjamas.ui.CheckBox import CheckBox
39 from pyjamas.ui.ListBox import ListBox
40 from pyjamas.ui.Button import Button
41 from pyjamas.ui.HTML import HTML
42
43 import nativedom
44
45
46 class EmptyWidget(xmlui.EmptyWidget, Label):
47
48 def __init__(self, xmlui_main, xmlui_parent):
49 Label.__init__(self, '')
50
51
52 class TextWidget(xmlui.TextWidget, Label):
53
54 def __init__(self, xmlui_main, xmlui_parent, value):
55 Label.__init__(self, value)
56
57
58 class LabelWidget(xmlui.LabelWidget, TextWidget):
59
60 def __init__(self, xmlui_main, xmlui_parent, value):
61 TextWidget.__init__(self, xmlui_main, xmlui_parent, value + ": ")
62
63
64 class JidWidget(xmlui.JidWidget, TextWidget):
65
66 def __init__(self, xmlui_main, xmlui_parent, value):
67 TextWidget.__init__(self, xmlui_main, xmlui_parent, value)
68
69
70 class DividerWidget(xmlui.DividerWidget, HTML):
71
72 def __init__(self, xmlui_main, xmlui_parent, style='line'):
73 """Add a divider
74
75 @param xmlui_parent
76 @param style (unicode): one of:
77 - line: a simple line
78 - dot: a line of dots
79 - dash: a line of dashes
80 - plain: a full thick line
81 - blank: a blank line/space
82 """
83 HTML.__init__(self, "<hr/>")
84 self.addStyleName(style)
85
86
87 class StringWidget(xmlui.StringWidget, TextBox):
88
89 def __init__(self, xmlui_main, xmlui_parent, value, read_only=False):
90 TextBox.__init__(self)
91 self.setText(value)
92 self.setReadonly(read_only)
93
94 def _xmluiSetValue(self, value):
95 self.setText(value)
96
97 def _xmluiGetValue(self):
98 return self.getText()
99
100 def _xmluiOnChange(self, callback):
101 self.addChangeListener(callback)
102
103
104 class JidInputWidget(xmlui.JidInputWidget, StringWidget):
105
106 def __init__(self, xmlui_main, xmlui_parent, value, read_only=False):
107 StringWidget.__init__(self, xmlui_main, xmlui_parent, value, read_only)
108
109
110 class PasswordWidget(xmlui.PasswordWidget, PasswordTextBox):
111
112 def __init__(self, xmlui_main, xmlui_parent, value, read_only=False):
113 PasswordTextBox.__init__(self)
114 self.setText(value)
115 self.setReadonly(read_only)
116
117 def _xmluiSetValue(self, value):
118 self.setText(value)
119
120 def _xmluiGetValue(self):
121 return self.getText()
122
123 def _xmluiOnChange(self, callback):
124 self.addChangeListener(callback)
125
126
127 class TextBoxWidget(xmlui.TextBoxWidget, TextArea):
128
129 def __init__(self, xmlui_main, xmlui_parent, value, read_only=False):
130 TextArea.__init__(self)
131 self.setText(value)
132 self.setReadonly(read_only)
133
134 def _xmluiSetValue(self, value):
135 self.setText(value)
136
137 def _xmluiGetValue(self):
138 return self.getText()
139
140 def _xmluiOnChange(self, callback):
141 self.addChangeListener(callback)
142
143
144 class BoolWidget(xmlui.BoolWidget, CheckBox):
145
146 def __init__(self, xmlui_main, xmlui_parent, state, read_only=False):
147 CheckBox.__init__(self)
148 self.setChecked(state)
149 self.setReadonly(read_only)
150
151 def _xmluiSetValue(self, value):
152 self.setChecked(value == "true")
153
154 def _xmluiGetValue(self):
155 return "true" if self.isChecked() else "false"
156
157 def _xmluiOnChange(self, callback):
158 self.addClickListener(callback)
159
160
161 class IntWidget(xmlui.IntWidget, TextBox):
162
163 def __init__(self, xmlui_main, xmlui_parent, value, read_only=False):
164 TextBox.__init__(self)
165 self.setText(value)
166 self.setReadonly(read_only)
167
168 def _xmluiSetValue(self, value):
169 self.setText(value)
170
171 def _xmluiGetValue(self):
172 return self.getText()
173
174 def _xmluiOnChange(self, callback):
175 self.addChangeListener(callback)
176
177
178 class ButtonWidget(xmlui.ButtonWidget, Button):
179
180 def __init__(self, xmlui_main, xmlui_parent, value, click_callback):
181 Button.__init__(self, value, click_callback)
182
183 def _xmluiOnClick(self, callback):
184 self.addClickListener(callback)
185
186
187 class ListWidget(xmlui.ListWidget, ListBox):
188
189 def __init__(self, xmlui_main, xmlui_parent, options, selected, flags):
190 ListBox.__init__(self)
191 multi_selection = 'single' not in flags
192 self.setMultipleSelect(multi_selection)
193 if multi_selection:
194 self.setVisibleItemCount(5)
195 for option in options:
196 self.addItem(option[1])
197 self._xmlui_attr_map = {label: value for value, label in options}
198 self._xmluiSelectValues(selected)
199
200 def _xmluiSelectValue(self, value):
201 """Select a value checking its item"""
202 try:
203 label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0]
204 except IndexError:
205 log.warning(u"Can't find value [%s] to select" % value)
206 return
207 self.selectItem(label)
208
209 def _xmluiSelectValues(self, values):
210 """Select multiple values, ignore the items"""
211 self.setValueSelection(values)
212
213 def _xmluiGetSelectedValues(self):
214 ret = []
215 for label in self.getSelectedItemText():
216 ret.append(self._xmlui_attr_map[label])
217 return ret
218
219 def _xmluiOnChange(self, callback):
220 self.addChangeListener(callback)
221
222 def _xmluiAddValues(self, values, select=True):
223 selected = self._xmluiGetSelectedValues()
224 for value in values:
225 if value not in self._xmlui_attr_map.values():
226 self.addItem(value)
227 self._xmlui_attr_map[value] = value
228 if value not in selected:
229 selected.append(value)
230 self._xmluiSelectValues(selected)
231
232
233 class JidsListWidget(contact_panel.ContactsPanel, xmlui.JidsListWidget):
234
235 def __init__(self, xmlui_main, xmlui_parent, jids, styles):
236 contact_panel.ContactsPanel.__init__(self, xmlui_main.host, merge_resources=False)
237 self.addStyleName("xmlui-JidsListWidget")
238 self.setList([jid.JID(jid_) for jid_ in jids])
239
240 def _xmluiGetSelectedValues(self):
241 # XXX: there is not selection in this list, so we return all non empty values
242 return self.getJids()
243
244
245
246 class LiberviaContainer(object):
247
248 def _xmluiAppend(self, widget):
249 self.append(widget)
250
251
252 class AdvancedListContainer(xmlui.AdvancedListContainer, Grid):
253
254 def __init__(self, xmlui_main, xmlui_parent, columns, selectable='no'):
255 Grid.__init__(self, 0, columns)
256 self.columns = columns
257 self.row = -1
258 self.col = 0
259 self._xmlui_rows_idx = []
260 self._xmlui_selectable = selectable != 'no'
261 self._xmlui_selected_row = None
262 self.addTableListener(self)
263 if self._xmlui_selectable:
264 self.addStyleName('AdvancedListSelectable')
265
266 def onCellClicked(self, grid, row, col):
267 if not self._xmlui_selectable:
268 return
269 self._xmlui_selected_row = row
270 try:
271 self._xmlui_select_cb(self)
272 except AttributeError:
273 log.warning("no select callback set")
274
275 def _xmluiAppend(self, widget):
276 self.setWidget(self.row, self.col, widget)
277 self.col += 1
278
279 def _xmluiAddRow(self, idx):
280 self.row += 1
281 self.col = 0
282 self._xmlui_rows_idx.insert(self.row, idx)
283 self.resizeRows(self.row + 1)
284
285 def _xmluiGetSelectedWidgets(self):
286 return [self.getWidget(self._xmlui_selected_row, col) for col in range(self.columns)]
287
288 def _xmluiGetSelectedIndex(self):
289 try:
290 return self._xmlui_rows_idx[self._xmlui_selected_row]
291 except TypeError:
292 return None
293
294 def _xmluiOnSelect(self, callback):
295 self._xmlui_select_cb = callback
296
297
298 class PairsContainer(xmlui.PairsContainer, Grid):
299
300 def __init__(self, xmlui_main, xmlui_parent):
301 Grid.__init__(self, 0, 0)
302 self.row = 0
303 self.col = 0
304
305 def _xmluiAppend(self, widget):
306 if self.col == 0:
307 self.resize(self.row + 1, 2)
308 self.setWidget(self.row, self.col, widget)
309 self.col += 1
310 if self.col == 2:
311 self.row += 1
312 self.col = 0
313
314
315 class LabelContainer(PairsContainer, xmlui.LabelContainer):
316 pass
317
318
319 class TabsContainer(LiberviaContainer, xmlui.TabsContainer, TabPanel):
320
321 def __init__(self, xmlui_main, xmlui_parent):
322 TabPanel.__init__(self)
323 self.setStyleName('liberviaTabPanel')
324
325 def _xmluiAddTab(self, label, selected):
326 tab_panel = VerticalContainer(self)
327 self.add(tab_panel, label)
328 count = len(self.getChildren())
329 if count == 1 or selected:
330 self.selectTab(count - 1)
331 return tab_panel
332
333
334 class VerticalContainer(LiberviaContainer, xmlui.VerticalContainer, VerticalPanel):
335 __bases__ = (LiberviaContainer, xmlui.VerticalContainer, VerticalPanel)
336
337 def __init__(self, xmlui_main, xmlui_parent):
338 VerticalPanel.__init__(self)
339
340
341 ## Dialogs ##
342
343
344 class Dlg(object):
345
346 def _xmluiShow(self):
347 self.show()
348
349 def _xmluiClose(self):
350 pass
351
352
353 class MessageDialog(Dlg, xmlui.MessageDialog, dialog.InfoDialog):
354
355 def __init__(self, xmlui_main, xmlui_parent, title, message, level):
356 #TODO: level is not managed
357 title = html_tools.html_sanitize(title)
358 message = strings.addURLToText(html_tools.XHTML2Text(message))
359 Dlg.__init__(self)
360 xmlui.MessageDialog.__init__(self, xmlui_main, xmlui_parent)
361 dialog.InfoDialog.__init__(self, title, message, self._xmluiValidated())
362
363
364 class NoteDialog(xmlui.NoteDialog, MessageDialog):
365 # TODO: separate NoteDialog
366
367 def __init__(self, xmlui_main, xmlui_parent, title, message, level):
368 xmlui.NoteDialog.__init__(self, xmlui_main, xmlui_parent)
369 MessageDialog.__init__(self, xmlui_main, xmlui_parent, title, message, level)
370
371
372 class ConfirmDialog(xmlui.ConfirmDialog, Dlg, dialog.ConfirmDialog):
373
374 def __init__(self, xmlui_main, xmlui_parent, title, message, level):
375 #TODO: level is not managed
376 title = html_tools.html_sanitize(title)
377 message = strings.addURLToText(html_tools.XHTML2Text(message))
378 xmlui.ConfirmDialog.__init__(self, xmlui_main, xmlui_parent)
379 Dlg.__init__(self)
380 dialog.ConfirmDialog.__init__(self, self.answered, message, title)
381
382 def answered(self, validated):
383 if validated:
384 self._xmluiValidated()
385 else:
386 self._xmluiCancelled()
387
388
389 class FileDialog(xmlui.FileDialog, Dlg):
390 #TODO:
391
392 def __init__(self, xmlui_main, xmlui_parent, title, message, level, filetype):
393 raise NotImplementedError("FileDialog is not implemented in Libervia yet")
394
395
396 class GenericFactory(object):
397 # XXX: __getattr__ doens't work here with pyjamas for an unknown reason
398 # so an introspection workaround is used
399
400 def __init__(self, xmlui_main):
401 self.xmlui_main = xmlui_main
402 for name, cls in globals().items():
403 if name.endswith("Widget") or name.endswith("Container") or name.endswith("Dialog"):
404 log.info("registering: %s" % name)
405 def createCreater(cls):
406 return lambda *args, **kwargs: self._genericCreate(cls, *args, **kwargs)
407 setattr(self, "create%s" % name, createCreater(cls))
408
409 def _genericCreate(self, cls, *args, **kwargs):
410 instance = cls(self.xmlui_main, *args, **kwargs)
411 return instance
412
413 # def __getattr__(self, attr):
414 # if attr.startswith("create"):
415 # cls = globals()[attr[6:]]
416 # cls._xmlui_main = self._xmlui_main
417 # return cls
418
419
420 class WidgetFactory(GenericFactory):
421
422 def _genericCreate(self, cls, *args, **kwargs):
423 instance = GenericFactory._genericCreate(self, cls, *args, **kwargs)
424 return instance
425
426 class LiberviaXMLUIBase(object):
427
428 def _xmluiLaunchAction(self, action_id, data):
429 self.host.launchAction(action_id, data, callback=self._defaultCb)
430
431
432 class XMLUIPanel(LiberviaXMLUIBase, xmlui.XMLUIPanel, VerticalPanel):
433
434 def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE):
435 self.widget_factory = WidgetFactory(self)
436 self.host = host
437 VerticalPanel.__init__(self)
438 self.setSize('100%', '100%')
439 xmlui.XMLUIPanel.__init__(self,
440 host,
441 parsed_xml,
442 title = title,
443 flags = flags,
444 callback = callback,
445 profile = profile)
446
447 def setCloseCb(self, close_cb):
448 self.close_cb = close_cb
449
450 def _xmluiClose(self):
451 if self.close_cb:
452 self.close_cb()
453 else:
454 log.warning("no close method defined")
455
456 def _xmluiSetParam(self, name, value, category):
457 self.host.bridge.call('setParam', None, name, value, category)
458
459 def constructUI(self, parsed_dom):
460 super(XMLUIPanel, self).constructUI(parsed_dom)
461 self.add(self.main_cont)
462 self.setCellHeight(self.main_cont, '100%')
463 if self.type == 'form':
464 hpanel = HorizontalPanel()
465 hpanel.setStyleName('marginAuto')
466 hpanel.add(Button('Submit', self.onFormSubmitted))
467 if not 'NO_CANCEL' in self.flags:
468 hpanel.add(Button('Cancel', self.onFormCancelled))
469 self.add(hpanel)
470 elif self.type == 'param':
471 assert(isinstance(self.children[0][0], TabPanel))
472 hpanel = HorizontalPanel()
473 hpanel.setStyleName('marginAuto')
474 hpanel.add(Button('Save', self.onSaveParams))
475 hpanel.add(Button('Cancel', lambda ignore: self._xmluiClose()))
476 self.add(hpanel)
477
478 def show(self):
479 options = ['NO_CLOSE'] if self.type == C.XMLUI_FORM else []
480 _dialog = dialog.GenericDialog(self.xmlui_title, self, options=options)
481 self.setCloseCb(_dialog.close)
482 _dialog.show()
483
484
485 class XMLUIDialog(LiberviaXMLUIBase, xmlui.XMLUIDialog):
486 dialog_factory = GenericFactory()
487
488 def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE):
489 self.dialog_factory = GenericFactory(self)
490 xmlui.XMLUIDialog.__init__(self,
491 host,
492 parsed_dom,
493 title=title,
494 flags=flags,
495 callback=callback,
496 ignore=ignore,
497 profile=profile)
498 self.host = host
499
500 xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel)
501 xmlui.registerClass(xmlui.CLASS_DIALOG, XMLUIDialog)
502
503 def create(*args, **kwargs):
504 dom = nativedom.NativeDOM()
505 kwargs['dom_parse'] = lambda xml_data: dom.parseString(xml_data)
506 return xmlui.create(*args, **kwargs)