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()