Mercurial > libervia-web
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) |