comparison frontends/src/tools/xmlui.py @ 802:9007bb133009

core, frontends: XMLUI refactoring: - XMLUI now use objects with 2 main classes: widgets (button, label, etc), and container which contain widgets according to a layout - widgets and containers classes are found through introspection, thereby it's really easy to add a new one - there is still the AddWidgetName helper, for example AddText('jid', 'test@example.net') will add a StringWidget with name "jid" and default value "test@example.net" - container can be inside other containers. changeContainer change the first parent container
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:19:00 +0100
parents 46aa5ada61bf
children f100fd8d279f
comparison
equal deleted inserted replaced
801:02ee9ef95277 802:9007bb133009
86 86
87 87
88 class ListWidget(Widget): 88 class ListWidget(Widget):
89 """ A widget able to show/choose one or several strings in a list """ 89 """ A widget able to show/choose one or several strings in a list """
90 90
91
92 class AdvancedListWidget(Widget):
93 pass #TODO
94 91
95 class Container(Widget): 92 class Container(Widget):
96 """ Widget which can contain other ones with a specific layout """ 93 """ Widget which can contain other ones with a specific layout """
97 94
98 @classmethod 95 @classmethod
155 self.title = title or "" 152 self.title = title or ""
156 if flags is None: 153 if flags is None:
157 flags = [] 154 flags = []
158 self.flags = flags 155 self.flags = flags
159 self.ctrl_list = {} # usefull to access ctrl 156 self.ctrl_list = {} # usefull to access ctrl
157 self._main_cont = None
160 self.constructUI(xml_data) 158 self.constructUI(xml_data)
161 159
162 def _parseElems(self, node, parent, post_treat=None): 160 @property
163 """ Parse elements inside a <layout> tags, and add them to the parent 161 def main_cont(self):
164 @param node: current XMLUI node 162 return self._main_cont
165 @param parent: parent container 163
166 @param post_treat: frontend specific treatments do to on each element 164 @main_cont.setter
167 165 def main_cont(self, value):
168 """ 166 if self._main_cont is not None:
169 for elem in node.childNodes: 167 raise ValueError(_("XMLUI can have only one main container"))
170 if elem.nodeName != "elem": 168 self._main_cont = value
171 raise NotImplementedError(_('Unknown tag [%s]') % elem.nodeName) 169
172 id_ = elem.getAttribute("id") 170
173 name = elem.getAttribute("name") 171 def _parseChilds(self, parent, current_node, wanted = ('container',), data = None):
174 type_ = elem.getAttribute("type")
175 value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
176 if type_=="empty":
177 ctrl = self.widget_factory.createEmptyWidget(parent)
178 elif type_=="text":
179 try:
180 value = elem.childNodes[0].wholeText
181 except IndexError:
182 warning (_("text node has no child !"))
183 ctrl = self.widget_factory.createTextWidget(parent, value)
184 elif type_=="label":
185 ctrl = self.widget_factory.createTextWidget(parent, value+": ")
186 elif type_=="string":
187 ctrl = self.widget_factory.createStringWidget(parent, value)
188 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
189 elif type_=="password":
190 ctrl = self.widget_factory.createPasswordWidget(parent, value)
191 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
192 elif type_=="textbox":
193 ctrl = self.widget_factory.createTextBoxWidget(parent, value)
194 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
195 elif type_=="bool":
196 ctrl = self.widget_factory.createBoolWidget(parent, value=='true')
197 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
198 elif type_=="list":
199 style=[] if elem.getAttribute("multi")=='yes' else ['single']
200 _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in elem.getElementsByTagName("option")]
201 ctrl = self.widget_factory.createListWidget(parent, _options, style)
202 ctrl._xmluiSelectValue(elem.getAttribute("value"))
203 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
204 elif type_=="button":
205 callback_id = elem.getAttribute("callback")
206 ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress)
207 ctrl._xmlui_param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
208 elif type_=="advanced_list":
209 _options = [getText(txt_elt) for txt_elt in elem.getElementsByTagName("text")]
210 ctrl = self.widget_factory.createListWidget(parent, _options, ['can_select_none'])
211 ctrl._xmluiSelectValue(elem.getAttribute("value"))
212 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
213 else:
214 error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
215 raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
216
217 if self.type == 'param':
218 try:
219 ctrl._xmluiOnChange(self.onParamChange)
220 ctrl._param_category = self._current_category
221 ctrl._param_name = name.split(Const.SAT_PARAM_SEPARATOR)[1]
222 except AttributeError:
223 if not isinstance(ctrl, (EmptyWidget, TextWidget)):
224 warning(_("No change listener on [%s]" % ctrl))
225
226 if post_treat is not None:
227 post_treat(ctrl, id_, name, type_, value)
228 parent._xmluiAppend(ctrl)
229
230 def _parseChilds(self, current, elem, wanted = ['layout'], data = None):
231 """ Recursively parse childNodes of an elemen 172 """ Recursively parse childNodes of an elemen
232 @param current: widget container with '_xmluiAppend' method 173 @param parent: widget container with '_xmluiAppend' method
233 @param elem: element from which childs will be parsed 174 @param current_node: element from which childs will be parsed
234 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant 175 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant
235 @param data: additionnal data which are needed in some cases 176 @param data: additionnal data which are needed in some cases
236 177
237 """ 178 """
238 for node in elem.childNodes: 179 for node in current_node.childNodes:
239 if wanted and not node.nodeName in wanted: 180 if wanted and not node.nodeName in wanted:
240 raise InvalidXMLUI 181 raise InvalidXMLUI
241 if node.nodeName == "layout": 182
183 if node.nodeName == "container":
242 type_ = node.getAttribute('type') 184 type_ = node.getAttribute('type')
185 if parent is self and type_ != 'vertical':
186 # main container is not a VerticalContainer and we want one, so we create one to wrap it
187 parent = self.widget_factory.createVerticalContainer(self)
188 self.main_cont = parent
243 if type_ == "tabs": 189 if type_ == "tabs":
244 tab_cont = self.widget_factory.createTabsContainer(current) 190 cont = self.widget_factory.createTabsContainer(parent)
245 self._parseChilds(current, node, ['category'], tab_cont) 191 self._parseChilds(parent, node, ('tab',), cont)
246 current._xmluiAppend(tab_cont)
247 elif type_ == "vertical": 192 elif type_ == "vertical":
248 self._parseElems(node, current) 193 cont = self.widget_factory.createVerticalContainer(parent)
194 self._parseChilds(cont, node, ('widget', 'container'))
249 elif type_ == "pairs": 195 elif type_ == "pairs":
250 pairs = self.widget_factory.createPairsContainer(current) 196 cont = self.widget_factory.createPairsContainer(parent)
251 self._parseElems(node, pairs) 197 self._parseChilds(cont, node, ('widget', 'container'))
252 current._xmluiAppend(pairs)
253 else: 198 else:
254 warning(_("Unknown layout [%s], using default one") % type_) 199 warning(_("Unknown container [%s], using default one") % type_)
255 self._parseElems(node, current) 200 cont = self.widget_factory.createVerticalContainer(parent)
256 elif node.nodeName == "category": 201 self._parseChilds(cont, node, ('widget', 'container'))
202 try:
203 parent._xmluiAppend(cont)
204 except AttributeError:
205 if parent is self:
206 self.main_cont = cont
207 else:
208 raise Exception(_("Internal Error, container has not _xmluiAppend method"))
209
210 elif node.nodeName == "tab":
257 name = node.getAttribute('name') 211 name = node.getAttribute('name')
258 label = node.getAttribute('label') 212 label = node.getAttribute('label')
259 if not name or not isinstance(data, TabsContainer): 213 if not name or not isinstance(data, TabsContainer):
260 raise InvalidXMLUI 214 raise InvalidXMLUI
261 if self.type == 'param': 215 if self.type == 'param':
262 self._current_category = name #XXX: awful hack because params need category and we don't keep parent 216 self._current_category = name #XXX: awful hack because params need category and we don't keep parent
263 tab_cont = data 217 tab_cont = data
264 new_tab = tab_cont._xmluiAddTab(label or name) 218 new_tab = tab_cont._xmluiAddTab(label or name)
265 self._parseChilds(new_tab, node, ['layout']) 219 self._parseChilds(new_tab, node, ('widget', 'container'))
220
221 elif node.nodeName == "widget":
222 id_ = node.getAttribute("id")
223 name = node.getAttribute("name")
224 type_ = node.getAttribute("type")
225 value = node.getAttribute("value") if node.hasAttribute('value') else u''
226 if type_=="empty":
227 ctrl = self.widget_factory.createEmptyWidget(parent)
228 elif type_=="text":
229 try:
230 value = node.childNodes[0].wholeText
231 except IndexError:
232 warning (_("text node has no child !"))
233 ctrl = self.widget_factory.createTextWidget(parent, value)
234 elif type_=="label":
235 ctrl = self.widget_factory.createTextWidget(parent, value+": ")
236 elif type_=="string":
237 ctrl = self.widget_factory.createStringWidget(parent, value)
238 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
239 elif type_=="password":
240 ctrl = self.widget_factory.createPasswordWidget(parent, value)
241 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
242 elif type_=="textbox":
243 ctrl = self.widget_factory.createTextBoxWidget(parent, value)
244 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
245 elif type_=="bool":
246 ctrl = self.widget_factory.createBoolWidget(parent, value=='true')
247 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
248 elif type_=="list":
249 style=[] if node.getAttribute("multi")=='yes' else ['single']
250 _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")]
251 ctrl = self.widget_factory.createListWidget(parent, _options, style)
252 ctrl._xmluiSelectValue(node.getAttribute("value"))
253 self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
254 elif type_=="button":
255 callback_id = node.getAttribute("callback")
256 ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress)
257 ctrl._xmlui_param_id = (callback_id,[field.getAttribute('name') for field in node.getElementsByTagName("field_back")])
258 else:
259 error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_)
260 raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
261
262 if self.type == 'param':
263 try:
264 ctrl._xmluiOnChange(self.onParamChange)
265 ctrl._param_category = self._current_category
266 ctrl._param_name = name.split(Const.SAT_PARAM_SEPARATOR)[1]
267 except AttributeError:
268 if not isinstance(ctrl, (EmptyWidget, TextWidget)):
269 warning(_("No change listener on [%s]" % ctrl))
270
271 parent._xmluiAppend(ctrl)
272
266 else: 273 else:
267 raise NotImplementedError(_('Unknown tag')) 274 raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName)
268 275
269 def constructUI(self, xml_data, post_treat=None): 276 def constructUI(self, xml_data, post_treat=None):
270 """ Actually construct the UI 277 """ Actually construct the UI
271 @param xml_data: raw XMLUI 278 @param xml_data: raw XMLUI
272 @param post_treat: frontend specific treatments to do once the UI is constructed 279 @param post_treat: frontend specific treatments to do once the UI is constructed
273 @return: constructed widget 280 @return: constructed widget
274 """ 281 """
275 ret_wid = self.widget_factory.createVerticalContainer(self)
276
277 cat_dom = self.dom_parse(xml_data) 282 cat_dom = self.dom_parse(xml_data)
278 top=cat_dom.documentElement 283 top=cat_dom.documentElement
279 self.type = top.getAttribute("type") 284 self.type = top.getAttribute("type")
280 self.title = self.title or top.getAttribute("title") or u"" 285 self.title = self.title or top.getAttribute("title") or u""
281 self.session_id = top.getAttribute("session_id") or None 286 self.session_id = top.getAttribute("session_id") or None
284 raise InvalidXMLUI 289 raise InvalidXMLUI
285 290
286 if self.type == 'param': 291 if self.type == 'param':
287 self.param_changed = set() 292 self.param_changed = set()
288 293
289 self._parseChilds(ret_wid, cat_dom.documentElement) 294 self._parseChilds(self, cat_dom.documentElement)
290 295
291 if post_treat is not None: 296 if post_treat is not None:
292 ret_wid = post_treat(ret_wid) 297 post_treat()
293 298
294 self.dom_free(cat_dom) 299 self.dom_free(cat_dom)
295
296 return ret_wid
297
298 300
299 def _xmluiClose(self): 301 def _xmluiClose(self):
300 """ Close the window/popup/... where the constructeur XMLUI is 302 """ Close the window/popup/... where the constructeur XMLUI is
301 this method must be overrided 303 this method must be overrided
302 304