Mercurial > libervia-web
comparison browser_side/xmlui.py @ 335:e8c26e24a6c7
browser side: refactored XMLUI to use the new sat_frontends.tools.xmlui.XMLUI class, first draft
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 04 Feb 2014 16:49:20 +0100 |
parents | d07b54fdc60a |
children | ea1be522ba88 |
comparison
equal
deleted
inserted
replaced
334:9c6be29c714a | 335:e8c26e24a6c7 |
---|---|
30 from pyjamas.ui.PasswordTextBox import PasswordTextBox | 30 from pyjamas.ui.PasswordTextBox import PasswordTextBox |
31 from pyjamas.ui.TextArea import TextArea | 31 from pyjamas.ui.TextArea import TextArea |
32 from pyjamas.ui.CheckBox import CheckBox | 32 from pyjamas.ui.CheckBox import CheckBox |
33 from pyjamas.ui.ListBox import ListBox | 33 from pyjamas.ui.ListBox import ListBox |
34 from pyjamas.ui.Button import Button | 34 from pyjamas.ui.Button import Button |
35 from pyjamas.ui.HTML import HTML | |
35 from nativedom import NativeDOM | 36 from nativedom import NativeDOM |
36 | 37 from sat_frontends.tools import xmlui |
37 | 38 |
38 class InvalidXMLUI(Exception): | 39 |
40 class EmptyWidget(xmlui.EmptyWidget, Label): | |
41 | |
42 def __init__(self, parent): | |
43 Label.__init__(self, '') | |
44 | |
45 | |
46 class TextWidget(xmlui.TextWidget, Label): | |
47 | |
48 def __init__(self, parent, value): | |
49 Label.__init__(self, value) | |
50 | |
51 | |
52 class LabelWidget(xmlui.LabelWidget, TextWidget): | |
53 | |
54 def __init__(self, parent, value): | |
55 TextWidget.__init__(self, parent, value+": ") | |
56 | |
57 | |
58 class JidWidget(xmlui.JidWidget, TextWidget): | |
39 pass | 59 pass |
40 | 60 |
41 class Pairs(Grid): | 61 |
42 | 62 class DividerWidget(xmlui.DividerWidget, HTML): |
43 def __init__(self): | 63 |
64 def __init__(self, parent, style='line'): | |
65 HTML.__init__(self, "<hr/>") # gof: TODO: | |
66 | |
67 | |
68 class StringWidget(xmlui.StringWidget, TextBox): | |
69 | |
70 def __init__(self, parent, value): | |
71 TextBox.__init__(self) | |
72 self.setText(value) | |
73 | |
74 def _xmluiGetValue(self): | |
75 return self.getText() | |
76 | |
77 def _xmluiOnChange(self, callback): | |
78 self.addhangeListener(callback) | |
79 | |
80 | |
81 class PasswordWidget(xmlui.PasswordWidget, PasswordTextBox): | |
82 | |
83 def __init__(self, parent, value): | |
84 TextBox.__init__(self) | |
85 self.setText(value) | |
86 | |
87 def _xmluiGetValue(self): | |
88 return self.getText() | |
89 | |
90 def _xmluiOnChange(self, callback): | |
91 self.addChangeListener(callback) | |
92 | |
93 | |
94 class TextBoxWidget(xmlui.TextBoxWidget, TextArea): | |
95 | |
96 def __init__(self, parent, value): | |
97 TextArea.__init__(self) | |
98 self.setText(value) | |
99 | |
100 def _xmluiGetValue(self): | |
101 return self.getText() | |
102 | |
103 def _xmluiOnChange(self, callback): | |
104 self.addChangeListener(callback) | |
105 | |
106 | |
107 class BoolWidget(xmlui.BoolWidget, CheckBox): | |
108 | |
109 def __init__(self, parent, state): | |
110 CheckBox.__init__(self) | |
111 self.setChecked(state) | |
112 | |
113 def _xmluiGetValue(self): | |
114 return "true" if self.isChecked() else "false" | |
115 | |
116 def _xmluiOnChange(self, callback): | |
117 self.addClickListener(callback) | |
118 | |
119 | |
120 class ButtonWidget(xmlui.ButtonWidget, Button): | |
121 | |
122 def __init__(self, parent, value, click_callback): | |
123 Button.__init__(self) | |
124 | |
125 def _xmluiOnClick(self, event): | |
126 self.addClickListener(callback) | |
127 | |
128 | |
129 class ListWidget(xmlui.ListWidget, ListBox): | |
130 | |
131 def __init__(self, parent, options, flags): | |
132 ListBox.__init__(self) | |
133 self.setMultipleSelect('single' not in flags) | |
134 for option in options: | |
135 self.addItem(option[0]) | |
136 self._xmlui_attr_map = {label: value for value, label in options} | |
137 | |
138 def _xmluiSelectValue(self, value): | |
139 try: | |
140 label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0] | |
141 except IndexError: | |
142 print(_("WARNING: Can't find value [%s] to select" % value)) | |
143 return | |
144 self.selectItem(label) | |
145 | |
146 def _xmluiGetSelectedValues(self): | |
147 ret = [] | |
148 for label in self.getSelectedItemText(): | |
149 ret.append(self._xmlui_attr_map[label]) | |
150 return ret | |
151 | |
152 def _xmluiOnChange(self, callback): | |
153 self.addChangeListener(callback) | |
154 | |
155 | |
156 class LiberviaContainer(object): | |
157 | |
158 def _xmluiAppend(self, widget): | |
159 self.append(widget) | |
160 | |
161 | |
162 class AdvancedListContainer(xmlui.AdvancedListContainer, Grid): | |
163 | |
164 def __init__(self, parent, columns, selectable='no'): | |
165 Grid.__init_(self, 0, columns) | |
166 self.columns = columns | |
167 self.row = 0 | |
168 self.col = 0 | |
169 self._xmlui_rows_idx = [] | |
170 self._xmlui_selectable = selectable != 'no' | |
171 self._xmlui_selected_row = None | |
172 self.addTableListener(self) | |
173 | |
174 def onCellClicked(self, grid, row, col): | |
175 if not self._xmlui_selectable: | |
176 return | |
177 self._xmlui_selected_row = row | |
178 try: | |
179 self._xmlui_select_cb(self) | |
180 except AttributeError: | |
181 print "WARNING: no select callback set" | |
182 | |
183 | |
184 def _xmluiAppend(self, widget): | |
185 self.setWidget(self.row, self.col, widget) | |
186 self.col += 1 | |
187 | |
188 def _xmluiAddRow(self, idx): | |
189 self._xmlui_rows_idx.insert(self.row, idx) | |
190 self.row += 1 | |
191 self.col = 0 | |
192 self.resizeRows(self.row) | |
193 | |
194 def _xmluiGetSelectedWidgets(self): | |
195 return [self.getWidget(self._xmlui_selected_row, col) for col in range(self.columns)] | |
196 | |
197 def _xmluiGetSelectedIndex(self): | |
198 try: | |
199 return self._xmlui_rows_idx[self._xmlui_selected_row] | |
200 except TypeError: | |
201 return None | |
202 | |
203 def _xmluiOnSelect(self, callback): | |
204 self._xmlui_select_cb = callback | |
205 | |
206 | |
207 class PairsContainer(xmlui.PairsContainer, Grid): | |
208 | |
209 def __init__(self, parent): | |
44 Grid.__init__(self, 0, 0) | 210 Grid.__init__(self, 0, 0) |
45 self.row = 0 | 211 self.row = 0 |
46 self.col = 0 | 212 self.col = 0 |
47 | 213 |
48 def append(self, widget): | 214 def _xmluiAppend(self, widget): |
49 if self.col == 0: | 215 if self.col == 0: |
50 self.resize(self.row+1, 2) | 216 self.resize(self.row+1, 2) |
51 self.setWidget(self.row, self.col, widget) | 217 self.setWidget(self.row, self.col, widget) |
52 self.col += 1 | 218 self.col += 1 |
53 if self.col == 2: | 219 if self.col == 2: |
54 self.row +=1 | 220 self.row +=1 |
55 self.col = 0 | 221 self.col = 0 |
56 | 222 |
57 class XMLUI(VerticalPanel): | 223 |
58 | 224 |
59 def __init__(self, host, xml_data, title = None, options = None, misc = None, close_cb = None): | 225 class TabsContainer(LiberviaContainer, xmlui.TabsContainer, TabPanel): |
60 print "XMLUI init" | 226 |
227 def __init__(self, parent): | |
228 TabPanel.__init__(self) | |
229 self.setStyleName('liberviaTabPanel') | |
230 | |
231 def _xmluiAddTab(self, label): | |
232 tab_panel = VerticalContainer(self) | |
233 self.add(tab_panel, label) | |
234 if len(self.getChildren()) == 1: | |
235 self.selectTab(0) | |
236 return tab_panel | |
237 | |
238 | |
239 class VerticalContainer(LiberviaContainer, xmlui.VerticalContainer, VerticalPanel): | |
240 __bases__ = (LiberviaContainer, xmlui.VerticalContainer, VerticalPanel) | |
241 | |
242 def __init__(self, parent): | |
61 VerticalPanel.__init__(self) | 243 VerticalPanel.__init__(self) |
244 | |
245 | |
246 class WidgetFactory(object): | |
247 # XXX: __getattr__ doesn't work here for an unknown reason | |
248 | |
249 def createVerticalContainer(self, *args, **kwargs): | |
250 instance = VerticalContainer(*args, **kwargs) | |
251 instance._xmlui_main = self._xmlui_main | |
252 return instance | |
253 | |
254 def createPairsContainer(self, *args, **kwargs): | |
255 instance = PairsContainer(*args, **kwargs) | |
256 instance._xmlui_main = self._xmlui_main | |
257 return instance | |
258 | |
259 def createTabsContainer(self, *args, **kwargs): | |
260 instance = TabsContainer(*args, **kwargs) | |
261 instance._xmlui_main = self._xmlui_main | |
262 return instance | |
263 | |
264 def createEmptyWidget(self, *args, **kwargs): | |
265 instance = EmptyWidget(*args, **kwargs) | |
266 instance._xmlui_main = self._xmlui_main | |
267 return instance | |
268 | |
269 def createTextWidget(self, *args, **kwargs): | |
270 instance = TextWidget(*args, **kwargs) | |
271 instance._xmlui_main = self._xmlui_main | |
272 return instance | |
273 | |
274 def createLabelWidget(self, *args, **kwargs): | |
275 instance = LabelWidget(*args, **kwargs) | |
276 instance._xmlui_main = self._xmlui_main | |
277 return instance | |
278 | |
279 def createJidWidget(self, *args, **kwargs): | |
280 instance = JidWidget(*args, **kwargs) | |
281 instance._xmlui_main = self._xmlui_main | |
282 return instance | |
283 | |
284 def createDividerWidget(self, *args, **kwargs): | |
285 instance = DividerWidget(*args, **kwargs) | |
286 instance._xmlui_main = self._xmlui_main | |
287 return instance | |
288 | |
289 def createStringWidget(self, *args, **kwargs): | |
290 instance = StringWidget(*args, **kwargs) | |
291 instance._xmlui_main = self._xmlui_main | |
292 return instance | |
293 | |
294 def createPasswordWidget(self, *args, **kwargs): | |
295 instance = PasswordWidget(*args, **kwargs) | |
296 instance._xmlui_main = self._xmlui_main | |
297 return instance | |
298 | |
299 def createTextBoxWidget(self, *args, **kwargs): | |
300 instance = TextBoxWidget(*args, **kwargs) | |
301 instance._xmlui_main = self._xmlui_main | |
302 return instance | |
303 | |
304 def createBoolWidget(self, *args, **kwargs): | |
305 instance = BoolWidget(*args, **kwargs) | |
306 instance._xmlui_main = self._xmlui_main | |
307 return instance | |
308 | |
309 def createButtonWidget(self, *args, **kwargs): | |
310 instance = ButtonWidget(*args, **kwargs) | |
311 instance._xmlui_main = self._xmlui_main | |
312 return instance | |
313 | |
314 def createListWidget(self, *args, **kwargs): | |
315 instance = ListWidget(*args, **kwargs) | |
316 instance._xmlui_main = self._xmlui_main | |
317 return instance | |
318 | |
319 | |
320 # def __getattr__(self, attr): | |
321 # if attr.startswith("create"): | |
322 # cls = globals()[attr[6:]] | |
323 # cls._xmlui_main = self._xmlui_main | |
324 # return cls | |
325 | |
326 | |
327 class XMLUI(xmlui.XMLUI, VerticalPanel): | |
328 widget_factory = WidgetFactory() | |
329 | |
330 def __init__(self, host, xml_data, title = None, flags = None): | |
331 self.widget_factory._xmlui_main = self | |
62 self.dom = NativeDOM() | 332 self.dom = NativeDOM() |
63 self.host = host | 333 dom_parse = lambda xml_data: self.dom.parseString(xml_data) |
64 self.title = title | 334 self._dest = "window" |
65 self.options = options or [] | 335 VerticalPanel.__init__(self) |
66 self.misc = misc or {} | |
67 self.close_cb = close_cb | |
68 self.__dest = "window" | |
69 self.ctrl_list = {} # usefull to access ctrl | |
70 self.constructUI(xml_data) | |
71 self.setSize('100%', '100%') | 336 self.setSize('100%', '100%') |
337 xmlui.XMLUI.__init__(self, host, xml_data, title, flags, dom_parse) | |
72 | 338 |
73 def setCloseCb(self, close_cb): | 339 def setCloseCb(self, close_cb): |
74 self.close_cb = close_cb | 340 self.close_cb = close_cb |
75 | 341 |
76 def close(self): | 342 def _xmluiClose(self): |
77 if self.close_cb: | 343 if self.close_cb: |
78 self.close_cb() | 344 self.close_cb() |
79 else: | 345 else: |
80 print "WARNING: no close method defined" | 346 print "WARNING: no close method defined" |
81 | 347 |
82 def __parseElems(self, node, parent): | |
83 """Parse elements inside a <layout> tags, and add them to the parent""" | |
84 for elem in node.childNodes: | |
85 if elem.nodeName != "elem": | |
86 raise Exception("Unmanaged tag [%s]" % (elem.nodeName)) | |
87 node_id = elem.getAttribute("node_id") | |
88 name = elem.getAttribute("name") | |
89 node_type = elem.getAttribute("type") | |
90 value = elem.getAttribute("value") if elem.hasAttribute('value') else u'' | |
91 if node_type=="empty": | |
92 ctrl = Label('') | |
93 elif node_type=="text": | |
94 try: | |
95 value = elem.childNodes[0].wholeText | |
96 except IndexError: | |
97 print ("WARNING: text node has no child !") | |
98 ctrl = Label(value) | |
99 elif node_type=="label": | |
100 ctrl = Label(value+": ") | |
101 elif node_type=="string": | |
102 ctrl = TextBox() | |
103 ctrl.setText(value) | |
104 self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl} | |
105 elif node_type=="password": | |
106 ctrl = PasswordTextBox() | |
107 ctrl.setText(value) | |
108 self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl} | |
109 elif node_type=="textbox": | |
110 ctrl = TextArea() | |
111 ctrl.setText(value) | |
112 self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl} | |
113 elif node_type=="bool": | |
114 ctrl = CheckBox() | |
115 ctrl.setChecked(value=="true") | |
116 self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl} | |
117 elif node_type=="list": | |
118 _options = [(option.getAttribute("label"), option.getAttribute("value")) for option in elem.getElementsByTagName("option")] | |
119 attr_map = {label: value for label, value in _options} | |
120 ctrl = ListBox() | |
121 ctrl.setMultipleSelect(elem.getAttribute("multi")=='yes') | |
122 for option in _options: | |
123 ctrl.addItem(option[0]) | |
124 ctrl.selectItem(value) | |
125 self.ctrl_list[name] = {'node_type':node_type, 'control':ctrl, 'attr_map': attr_map} | |
126 elif node_type=="button": | |
127 callback_id = elem.getAttribute("callback_id") | |
128 ctrl = Button(value, self.onButtonPress) | |
129 ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")]) | |
130 else: | |
131 print("FIXME FIXME FIXME: type [%s] is not implemented" % node_type) #FIXME ! | |
132 raise NotImplementedError | |
133 if self.node_type == 'param': | |
134 if isinstance(ctrl, TextBoxBase): | |
135 ctrl.addChangeListener(self.onParamChange) | |
136 elif isinstance(ctrl, CheckBox): | |
137 ctrl.addClickListener(self.onParamChange) | |
138 elif isinstance(ctrl, ListBox): | |
139 ctrl.addChangeListener(self.onParamChange) | |
140 ctrl._param_category = self._current_category | |
141 ctrl._param_name = name | |
142 parent.append(ctrl) | |
143 | |
144 def __parseChilds(self, current, elem, wanted = ['layout'], data = None): | |
145 """Recursively parse childNodes of an elemen | |
146 @param current: widget container with 'append' method | |
147 @param elem: element from which childs will be parsed | |
148 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant""" | |
149 for node in elem.childNodes: | |
150 if wanted and not node.nodeName in wanted: | |
151 raise InvalidXMLUI("ERROR: unexpected nodeName") | |
152 if node.nodeName == "layout": | |
153 node_type = node.getAttribute('type') | |
154 if node_type == "tabs": | |
155 tab_cont = TabPanel() | |
156 tab_cont.setStyleName('liberviaTabPanel') | |
157 tab_cont.setHeight('100%') | |
158 self.__parseChilds(current, node, ['category'], tab_cont) | |
159 current.append(tab_cont) | |
160 if isinstance(current, CellPanel): | |
161 current.setCellHeight(tab_cont, '100%') | |
162 if len(tab_cont.getChildren()) > 0: | |
163 tab_cont.selectTab(0) | |
164 elif node_type == "vertical": | |
165 self.__parseElems(node, current) | |
166 elif node_type == "pairs": | |
167 pairs = Pairs() | |
168 self.__parseElems(node, pairs) | |
169 current.append(pairs) | |
170 else: | |
171 print("WARNING: Unknown layout [%s], using default one" % (node_type,)) | |
172 self.__parseElems(node, current) | |
173 elif node.nodeName == "category": | |
174 name = node.getAttribute('name') | |
175 label = node.getAttribute('label') | |
176 if not name or not isinstance(data,TabPanel): | |
177 raise InvalidXMLUI | |
178 if self.node_type == 'param': | |
179 self._current_category = name #XXX: awful hack because params need category and we don't keep parent | |
180 tab_cont = data | |
181 tab_body = VerticalPanel() | |
182 tab_cont.add(tab_body, label or name) | |
183 self.__parseChilds(tab_body, node, ['layout']) | |
184 else: | |
185 message=_("Unknown tag") | |
186 raise NotImplementedError(message) | |
187 | |
188 def constructUI(self, xml_data): | 348 def constructUI(self, xml_data): |
189 | 349 super(XMLUI, self).constructUI(xml_data) |
190 cat_dom = self.dom.parseString(xml_data) | 350 self.add(self.main_cont) |
191 | 351 self.setCellHeight(self.main_cont, '100%') |
192 top=cat_dom.documentElement | 352 if self.type == 'form': |
193 self.node_type = top.getAttribute("type") | |
194 self.title = top.getAttribute("title") or self.title | |
195 self.session_id = top.getAttribute("session_id") or None | |
196 self.submit_id = top.getAttribute("submit") or None | |
197 if top.nodeName != "sat_xmlui" or not self.node_type in ['form', 'param', 'window']: | |
198 raise InvalidXMLUI | |
199 | |
200 if self.node_type == 'param': | |
201 self.param_changed = set() | |
202 | |
203 self.__parseChilds(self, cat_dom.documentElement) | |
204 | |
205 if self.node_type == 'form': | |
206 hpanel = HorizontalPanel() | 353 hpanel = HorizontalPanel() |
207 hpanel.add(Button('Submit',self.onFormSubmitted)) | 354 hpanel.add(Button('Submit',self.onFormSubmitted)) |
208 if not 'NO_CANCEL' in self.options: | 355 if not 'NO_CANCEL' in self.flags: |
209 hpanel.add(Button('Cancel',self.onFormCancelled)) | 356 hpanel.add(Button('Cancel',self.onFormCancelled)) |
210 self.add(hpanel) | 357 self.add(hpanel) |
211 elif self.node_type == 'param': | 358 elif self.type == 'param': |
212 assert(isinstance(self.children[0],TabPanel)) | 359 assert(isinstance(self.children[0][0],TabPanel)) |
213 hpanel = HorizontalPanel() | 360 hpanel = HorizontalPanel() |
214 hpanel.add(Button('Cancel', lambda ignore: self.close())) | 361 hpanel.add(Button('Cancel', lambda ignore: self._xmluiClose())) |
215 hpanel.add(Button('Save', self.onSaveParams)) | 362 hpanel.add(Button('Save', self.onSaveParams)) |
216 self.add(hpanel) | 363 self.add(hpanel) |
217 | 364 |
218 ##EVENTS## | 365 ##EVENTS## |
219 | 366 |
220 def onButtonPress(self, button): | 367 def onSaveParams(self, ignore=None): |
221 print "onButtonPress (%s)" % (button,) | 368 def postTreat(name, value, category): |
222 callback_id, fields = button.param_id | 369 self.host.bridge.call('setParam', None, name, value, category) |
223 for field in fields: | 370 xmlui.XMLUI.onSaveParams(self, ignore, postTreat) |
224 ctrl = self.ctrl_list[field] | |
225 if isinstance(ctrl['control'],ListBox): | |
226 data[field] = '\t'.join(ctrl['control'].getSelectedItemText()) | |
227 elif isinstance(ctrl['control'],CheckBox): | |
228 data[field] = "true" if ctrl['control'].isChecked() else "false" | |
229 else: | |
230 data[field] = ctrl['control'].getText() | |
231 | |
232 self.host.launchAction(callback_id, None) | |
233 | |
234 def onParamChange(self, widget): | |
235 """Called when type is param and a widget to save is modified""" | |
236 assert(self.node_type == "param") | |
237 print "onParamChange:", widget | |
238 self.param_changed.add(widget) | |
239 | |
240 def onFormSubmitted(self, button): | |
241 print "onFormSubmitted" | |
242 # FIXME: untested | |
243 print "FIXME FIXME FIXME: Form submitting not managed yet" | |
244 data = [] | |
245 for ctrl_name in self.ctrl_list: | |
246 escaped = u"%s%s" % (SAT_FORM_PREFIX, ctrl_name) | |
247 ctrl = self.ctrl_list[escaped] | |
248 if isinstance(ctrl['control'], ListBox): | |
249 data.append((escaped, '\t'.join([ctrl['attr_map'][label] for label in ctrl['control'].getSelectedItemText()]))) | |
250 elif isinstance(ctrl['control'], CheckBox): | |
251 data.append((escaped, "true" if ctrl['control'].isChecked() else "false")) | |
252 else: | |
253 data.append((escaped, ctrl['control'].getText())) | |
254 if 'action_back' in self.misc: #FIXME FIXME FIXME: WTF ! Must be cleaned | |
255 raise NotImplementedError | |
256 elif 'callback' in self.misc: | |
257 self.misc['callback'](data) | |
258 elif self.submit_id is not None: | |
259 data = dict(selected_values) | |
260 if self.session_id is not None: | |
261 data["session_id"] = self.session_id | |
262 self.host.launchAction(self.submit_id, data) | |
263 else: | |
264 print ("WARNING: The form data is not sent back, the type is not managed properly") | |
265 | |
266 self.close() | |
267 | |
268 def onFormCancelled(self, button): | |
269 self.close() | |
270 | |
271 def onSaveParams(self, button): | |
272 print "onSaveParams" | |
273 for ctrl in self.param_changed: | |
274 if isinstance(ctrl, CheckBox): | |
275 value = "true" if ctrl.isChecked() else "false" | |
276 elif isinstance(ctrl, ListBox): | |
277 value = '\t'.join(ctrl.getSelectedItemText()) | |
278 else: | |
279 value = ctrl.getText() | |
280 self.host.bridge.call('setParam', None, ctrl._param_name, value, ctrl._param_category) | |
281 self.close() |