Mercurial > libervia-backend
comparison frontends/src/tools/xmlui.py @ 1106:e2e1e27a3680
frontends: XMLUI refactoring + dialogs:
- there are now XMLUIPanel and XMLUIDialog both inheriting from XMLUIBase
- following dialogs are managed:
- MessageDialog
- NoteDialog
- ConfirmDialog
- FileDialog
- XMLUI creation is now made using xmlui.create(...) instead of instanciating directly XMLUI
- classes must be registed in frontends
- "parent" attribute renamed to "_xmlui_parent" to avoid name conflicts with frontends toolkits
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 13 Aug 2014 14:48:49 +0200 |
parents | b3b7a2863060 |
children | 0a448c947038 |
comparison
equal
deleted
inserted
replaced
1105:018bdd687747 | 1106:e2e1e27a3680 |
---|---|
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from sat.core.log import getLogger | 21 from sat.core.log import getLogger |
22 log = getLogger(__name__) | 22 log = getLogger(__name__) |
23 from sat_frontends.constants import Const | 23 from sat_frontends.constants import Const as C |
24 from sat.core.exceptions import DataError | 24 from sat.core.exceptions import DataError |
25 | 25 |
26 | |
27 class_map = {} | |
28 CLASS_PANEL = 'panel' | |
29 CLASS_DIALOG = 'dialog' | |
26 | 30 |
27 class InvalidXMLUI(Exception): | 31 class InvalidXMLUI(Exception): |
28 pass | 32 pass |
29 | 33 |
34 | |
35 class ClassNotRegistedError(Exception): | |
36 pass | |
30 | 37 |
31 def getText(node): | 38 def getText(node): |
32 """Get child text nodes | 39 """Get child text nodes |
33 @param node: dom Node | 40 @param node: dom Node |
34 @return: joined unicode text of all nodes | 41 @return: joined unicode text of all nodes |
40 data.append(child.wholeText) | 47 data.append(child.wholeText) |
41 return u"".join(data) | 48 return u"".join(data) |
42 | 49 |
43 | 50 |
44 class Widget(object): | 51 class Widget(object): |
45 """ base Widget """ | 52 """base Widget""" |
46 pass | 53 pass |
47 | 54 |
48 | 55 |
49 class EmptyWidget(Widget): | 56 class EmptyWidget(Widget): |
50 """ Just a placeholder widget """ | 57 """Just a placeholder widget""" |
51 pass | 58 pass |
52 | 59 |
53 | 60 |
54 class TextWidget(Widget): | 61 class TextWidget(Widget): |
55 """ Non interactive text """ | 62 """Non interactive text""" |
56 pass | 63 pass |
57 | 64 |
58 | 65 |
59 class LabelWidget(Widget): | 66 class LabelWidget(Widget): |
60 """ Non interactive text """ | 67 """Non interactive text""" |
61 pass | 68 pass |
62 | 69 |
63 | 70 |
64 class JidWidget(Widget): | 71 class JidWidget(Widget): |
65 """ Jabber ID """ | 72 """Jabber ID""" |
66 pass | 73 pass |
67 | 74 |
68 | 75 |
69 class DividerWidget(Widget): | 76 class DividerWidget(Widget): |
70 """ Separator """ | 77 """Separator""" |
71 pass | 78 pass |
72 | 79 |
73 | 80 |
74 class StringWidget(Widget): | 81 class StringWidget(Widget): |
75 """ Input widget with require a string | 82 """Input widget wich require a string |
83 | |
76 often called Edit in toolkits | 84 often called Edit in toolkits |
77 | |
78 """ | 85 """ |
79 | 86 |
80 | 87 |
81 class PasswordWidget(Widget): | 88 class PasswordWidget(Widget): |
82 """ Input widget with require a masked string | 89 """Input widget with require a masked string""" |
83 | |
84 """ | |
85 | 90 |
86 | 91 |
87 class TextBoxWidget(Widget): | 92 class TextBoxWidget(Widget): |
88 """ Input widget with require a long, possibly multilines string | 93 """Input widget with require a long, possibly multilines string |
89 often called TextArea in toolkits | 94 often called TextArea in toolkits |
90 | |
91 """ | 95 """ |
92 | 96 |
93 | 97 |
94 class BoolWidget(Widget): | 98 class BoolWidget(Widget): |
95 """ Input widget with require a boolean value | 99 """Input widget with require a boolean value |
96 often called CheckBox in toolkits | 100 often called CheckBox in toolkits |
97 | |
98 """ | 101 """ |
99 | 102 |
100 | 103 |
101 class ButtonWidget(Widget): | 104 class ButtonWidget(Widget): |
102 """ A clickable widget """ | 105 """A clickable widget""" |
103 | 106 |
104 | 107 |
105 class ListWidget(Widget): | 108 class ListWidget(Widget): |
106 """ A widget able to show/choose one or several strings in a list """ | 109 """A widget able to show/choose one or several strings in a list""" |
107 | 110 |
108 | 111 |
109 class Container(Widget): | 112 class Container(Widget): |
110 """ Widget which can contain other ones with a specific layout """ | 113 """Widget which can contain other ones with a specific layout""" |
111 | 114 |
112 @classmethod | 115 @classmethod |
113 def _xmluiAdapt(cls, instance): | 116 def _xmluiAdapt(cls, instance): |
114 """ Make cls as instance.__class__ | 117 """Make cls as instance.__class__ |
118 | |
115 cls must inherit from original instance class | 119 cls must inherit from original instance class |
116 Usefull when you get a class from UI toolkit | 120 Usefull when you get a class from UI toolkit |
117 | |
118 """ | 121 """ |
119 assert instance.__class__ in cls.__bases__ | 122 assert instance.__class__ in cls.__bases__ |
120 instance.__class__ = type(cls.__name__, cls.__bases__, dict(cls.__dict__)) | 123 instance.__class__ = type(cls.__name__, cls.__bases__, dict(cls.__dict__)) |
121 | 124 |
122 | 125 |
123 class PairsContainer(Container): | 126 class PairsContainer(Container): |
124 """ Widgets are disposed in rows of two (usually label/input) """ | 127 """Widgets are disposed in rows of two (usually label/input) """ |
125 pass | |
126 | 128 |
127 | 129 |
128 class TabsContainer(Container): | 130 class TabsContainer(Container): |
129 """ A container which several other containers in tabs | 131 """A container which several other containers in tabs |
132 | |
130 Often called Notebook in toolkits | 133 Often called Notebook in toolkits |
131 | 134 """ |
132 """ | |
133 | |
134 | 135 |
135 class VerticalContainer(Container): | 136 class VerticalContainer(Container): |
136 """ Widgets are disposed vertically """ | 137 """Widgets are disposed vertically""" |
137 pass | |
138 | 138 |
139 | 139 |
140 class AdvancedListContainer(Container): | 140 class AdvancedListContainer(Container): |
141 """ Widgets are disposed in rows with advaned features """ | 141 """Widgets are disposed in rows with advaned features""" |
142 pass | 142 |
143 | 143 |
144 | 144 class Dialog(object): |
145 class XMLUI(object): | 145 """base dialog""" |
146 """ Base class to construct SàT XML User Interface | 146 |
147 def __init__(self, _xmlui_parent): | |
148 self._xmlui_parent = _xmlui_parent | |
149 | |
150 def _xmluiValidated(self, data=None): | |
151 if data is None: | |
152 data = {} | |
153 self._xmluiSetData(C.XMLUI_STATUS_VALIDATED, data) | |
154 self._xmluiSubmit(data) | |
155 self._xmluiClose() | |
156 | |
157 def _xmluiCancelled(self): | |
158 data = {C.XMLUI_DATA_CANCELLED: C.BOOL_TRUE} | |
159 self._xmluiSetData(C.XMLUI_STATUS_CANCELLED, data) | |
160 self._xmluiSubmit(data) | |
161 self._xmluiClose() | |
162 | |
163 def _xmluiSubmit(self, data): | |
164 self._xmlui_parent.submit(data) | |
165 | |
166 def _xmluiSetData(self, status, data): | |
167 pass | |
168 | |
169 | |
170 class MessageDialog(Dialog): | |
171 """Dialog with a OK/Cancel type configuration""" | |
172 | |
173 | |
174 class NoteDialog(Dialog): | |
175 """Dialog with a OK/Cancel type configuration""" | |
176 | |
177 | |
178 class ConfirmDialog(Dialog): | |
179 """Dialog with a OK/Cancel type configuration""" | |
180 | |
181 def _xmluiSetData(self, status, data): | |
182 if status == C.XMLUI_STATUS_VALIDATED: | |
183 data[C.XMLUI_DATA_ANSWER] = C.BOOL_TRUE | |
184 elif status == C.XMLUI_STATUS_CANCELLED: | |
185 data[C.XMLUI_DATA_ANSWER] = C.BOOL_FALSE | |
186 | |
187 | |
188 class FileDialog(Dialog): | |
189 """Dialog with a OK/Cancel type configuration""" | |
190 | |
191 | |
192 class XMLUIBase(object): | |
193 """Base class to construct SàT XML User Interface | |
194 | |
195 This class must not be instancied directly | |
196 """ | |
197 | |
198 def __init__(self, host, parsed_dom, title = None, flags = None): | |
199 """Initialise the XMLUI instance | |
200 | |
201 @param host: %(doc_host)s | |
202 @param parsed_dom: main parsed dom | |
203 @param title: force the title, or use XMLUI one if None | |
204 @param flags: list of string which can be: | |
205 - NO_CANCEL: the UI can't be cancelled | |
206 """ | |
207 self.host = host | |
208 top=parsed_dom.documentElement | |
209 self.session_id = top.getAttribute("session_id") or None | |
210 self.submit_id = top.getAttribute("submit") or None | |
211 self.title = title or top.getAttribute("title") or u"" | |
212 if flags is None: | |
213 flags = [] | |
214 self.flags = flags | |
215 | |
216 def _isAttrSet(self, name, node): | |
217 """Returnw widget boolean attribute status | |
218 | |
219 @param name: name of the attribute (e.g. "read_only") | |
220 @param node: Node instance | |
221 @return (bool): True if widget's attribute is set (C.BOOL_TRUE) | |
222 """ | |
223 read_only = node.getAttribute(name) or C.BOOL_FALSE | |
224 return read_only.lower().strip() == C.BOOL_TRUE | |
225 | |
226 def _getChildNode(self, node, name): | |
227 """Return the first child node with the given name | |
228 | |
229 @param node: Node instance | |
230 @param name: name of the wanted node | |
231 | |
232 @return: The found element or None | |
233 """ | |
234 for child in node.childNodes: | |
235 if child.nodeName == name: | |
236 return child | |
237 return None | |
238 | |
239 def submit(self, data): | |
240 if self.submit_id is None: | |
241 raise ValueError("Can't submit is self.submit_id is not set") | |
242 if "session_id" in data: | |
243 raise ValueError("session_id must no be used in data, it is automaticaly filled with self.session_id if present") | |
244 if self.session_id is not None: | |
245 data["session_id"] = self.session_id | |
246 self._xmluiLaunchAction(self.submit_id, data) | |
247 | |
248 def _xmluiLaunchAction(self, action_id, data): | |
249 self.host.launchAction(action_id, data, profile_key = self.host.profile) | |
250 | |
251 | |
252 class XMLUIPanel(XMLUIBase): | |
253 """XMLUI Panel | |
254 | |
147 New frontends can inherite this class to easily implement XMLUI | 255 New frontends can inherite this class to easily implement XMLUI |
148 @property widget_factory: factory to create frontend-specific widgets | 256 @property widget_factory: factory to create frontend-specific widgets |
149 @proporety dialog_factory: factory to create frontend-specific dialogs | 257 @proporety dialog_factory: factory to create frontend-specific dialogs |
150 | |
151 """ | 258 """ |
152 widget_factory = None | 259 widget_factory = None |
153 dialog_factory = None # TODO | 260 |
154 | 261 def __init__(self, host, parsed_dom, title = None, flags = None): |
155 def __init__(self, host, xml_data, title = None, flags = None, dom_parse=None, dom_free=None): | 262 super(XMLUIPanel, self).__init__(host, parsed_dom, title = None, flags = None) |
156 """ Initialise the XMLUI instance | |
157 @param host: %(doc_host)s | |
158 @param xml_data: the raw XML containing the UI | |
159 @param title: force the title, or use XMLUI one if None | |
160 @param flags: list of string which can be: | |
161 - NO_CANCEL: the UI can't be cancelled | |
162 @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one | |
163 @param dom_free: method used to free the parsed DOM | |
164 | |
165 """ | |
166 if dom_parse is None: | |
167 from xml.dom import minidom | |
168 self.dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8')) | |
169 self.dom_free = lambda cat_dom: cat_dom.unlink() | |
170 else: | |
171 self.dom_parse = dom_parse | |
172 self.dom_free = dom_free or (lambda cat_dom: None) | |
173 self.host = host | |
174 self.title = title or "" | |
175 if flags is None: | |
176 flags = [] | |
177 self.flags = flags | |
178 self.ctrl_list = {} # usefull to access ctrl | 263 self.ctrl_list = {} # usefull to access ctrl |
179 self._main_cont = None | 264 self._main_cont = None |
180 self.constructUI(xml_data) | 265 self.constructUI(parsed_dom) |
181 | 266 |
182 def escape(self, name): | 267 def escape(self, name): |
183 """ return escaped name for forms """ | 268 """Return escaped name for forms""" |
184 return u"%s%s" % (Const.SAT_FORM_PREFIX, name) | 269 return u"%s%s" % (C.SAT_FORM_PREFIX, name) |
185 | 270 |
186 @property | 271 @property |
187 def main_cont(self): | 272 def main_cont(self): |
188 return self._main_cont | 273 return self._main_cont |
189 | 274 |
191 def main_cont(self, value): | 276 def main_cont(self, value): |
192 if self._main_cont is not None: | 277 if self._main_cont is not None: |
193 raise ValueError(_("XMLUI can have only one main container")) | 278 raise ValueError(_("XMLUI can have only one main container")) |
194 self._main_cont = value | 279 self._main_cont = value |
195 | 280 |
196 def _isAttrSet(self, name, node): | 281 def _parseChilds(self, _xmlui_parent, current_node, wanted = ('container',), data = None): |
197 """Returnw widget boolean attribute status | |
198 | |
199 @param name: name of the attribute (e.g. "read_only") | |
200 @param node: Node instance | |
201 @return (bool): True if widget's attribute is set ("true") | |
202 """ | |
203 read_only = node.getAttribute(name) or "false" | |
204 return read_only.lower().strip() == "true" | |
205 | |
206 def _getChildNode(self, node, name): | |
207 """Return the first child node with the given name | |
208 | |
209 @param node: Node instance | |
210 @param name: name of the wanted node | |
211 | |
212 @return: The found element or None | |
213 """ | |
214 for child in node.childNodes: | |
215 if child.nodeName == name: | |
216 return child | |
217 return None | |
218 | |
219 def _parseChilds(self, parent, current_node, wanted = ('container',), data = None): | |
220 """Recursively parse childNodes of an elemen | 282 """Recursively parse childNodes of an elemen |
221 @param parent: widget container with '_xmluiAppend' method | 283 |
284 @param _xmlui_parent: widget container with '_xmluiAppend' method | |
222 @param current_node: element from which childs will be parsed | 285 @param current_node: element from which childs will be parsed |
223 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant | 286 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant |
224 @param data: additionnal data which are needed in some cases | 287 @param data: additionnal data which are needed in some cases |
225 | |
226 """ | 288 """ |
227 for node in current_node.childNodes: | 289 for node in current_node.childNodes: |
228 if wanted and not node.nodeName in wanted: | 290 if wanted and not node.nodeName in wanted: |
229 raise InvalidXMLUI('Unexpected node: [%s]' % node.nodeName) | 291 raise InvalidXMLUI('Unexpected node: [%s]' % node.nodeName) |
230 | 292 |
231 if node.nodeName == "container": | 293 if node.nodeName == "container": |
232 type_ = node.getAttribute('type') | 294 type_ = node.getAttribute('type') |
233 if parent is self and type_ != 'vertical': | 295 if _xmlui_parent is self and type_ != 'vertical': |
234 # main container is not a VerticalContainer and we want one, so we create one to wrap it | 296 # main container is not a VerticalContainer and we want one, so we create one to wrap it |
235 parent = self.widget_factory.createVerticalContainer(self) | 297 _xmlui_parent = self.widget_factory.createVerticalContainer(self) |
236 self.main_cont = parent | 298 self.main_cont = _xmlui_parent |
237 if type_ == "tabs": | 299 if type_ == "tabs": |
238 cont = self.widget_factory.createTabsContainer(parent) | 300 cont = self.widget_factory.createTabsContainer(_xmlui_parent) |
239 self._parseChilds(parent, node, ('tab',), cont) | 301 self._parseChilds(_xmlui_parent, node, ('tab',), cont) |
240 elif type_ == "vertical": | 302 elif type_ == "vertical": |
241 cont = self.widget_factory.createVerticalContainer(parent) | 303 cont = self.widget_factory.createVerticalContainer(_xmlui_parent) |
242 self._parseChilds(cont, node, ('widget', 'container')) | 304 self._parseChilds(cont, node, ('widget', 'container')) |
243 elif type_ == "pairs": | 305 elif type_ == "pairs": |
244 cont = self.widget_factory.createPairsContainer(parent) | 306 cont = self.widget_factory.createPairsContainer(_xmlui_parent) |
245 self._parseChilds(cont, node, ('widget', 'container')) | 307 self._parseChilds(cont, node, ('widget', 'container')) |
246 elif type_ == "advanced_list": | 308 elif type_ == "advanced_list": |
247 try: | 309 try: |
248 columns = int(node.getAttribute('columns')) | 310 columns = int(node.getAttribute('columns')) |
249 except (TypeError, ValueError): | 311 except (TypeError, ValueError): |
250 raise DataError("Invalid columns") | 312 raise DataError("Invalid columns") |
251 selectable = node.getAttribute('selectable') or 'no' | 313 selectable = node.getAttribute('selectable') or 'no' |
252 auto_index = node.getAttribute('auto_index') == 'true' | 314 auto_index = node.getAttribute('auto_index') == C.BOOL_TRUE |
253 data = {'index': 0} if auto_index else None | 315 data = {'index': 0} if auto_index else None |
254 cont = self.widget_factory.createAdvancedListContainer(parent, columns, selectable) | 316 cont = self.widget_factory.createAdvancedListContainer(_xmlui_parent, columns, selectable) |
255 callback_id = node.getAttribute("callback") or None | 317 callback_id = node.getAttribute("callback") or None |
256 if callback_id is not None: | 318 if callback_id is not None: |
257 if selectable == 'no': | 319 if selectable == 'no': |
258 raise ValueError("can't have selectable=='no' and callback_id at the same time") | 320 raise ValueError("can't have selectable=='no' and callback_id at the same time") |
259 cont._xmlui_callback_id = callback_id | 321 cont._xmlui_callback_id = callback_id |
260 cont._xmluiOnSelect(self.onAdvListSelect) | 322 cont._xmluiOnSelect(self.onAdvListSelect) |
261 | 323 |
262 self._parseChilds(cont, node, ('row',), data) | 324 self._parseChilds(cont, node, ('row',), data) |
263 else: | 325 else: |
264 log.warning(_("Unknown container [%s], using default one") % type_) | 326 log.warning(_("Unknown container [%s], using default one") % type_) |
265 cont = self.widget_factory.createVerticalContainer(parent) | 327 cont = self.widget_factory.createVerticalContainer(_xmlui_parent) |
266 self._parseChilds(cont, node, ('widget', 'container')) | 328 self._parseChilds(cont, node, ('widget', 'container')) |
267 try: | 329 try: |
268 parent._xmluiAppend(cont) | 330 _xmlui_parent._xmluiAppend(cont) |
269 except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError | 331 except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError |
270 if parent is self: | 332 if _xmlui_parent is self: |
271 self.main_cont = cont | 333 self.main_cont = cont |
272 else: | 334 else: |
273 raise Exception(_("Internal Error, container has not _xmluiAppend method")) | 335 raise Exception(_("Internal Error, container has not _xmluiAppend method")) |
274 | 336 |
275 elif node.nodeName == 'tab': | 337 elif node.nodeName == 'tab': |
287 try: | 349 try: |
288 index = str(data['index']) | 350 index = str(data['index']) |
289 data['index'] += 1 | 351 data['index'] += 1 |
290 except TypeError: | 352 except TypeError: |
291 index = node.getAttribute('index') or None | 353 index = node.getAttribute('index') or None |
292 parent._xmluiAddRow(index) | 354 _xmlui_parent._xmluiAddRow(index) |
293 self._parseChilds(parent, node, ('widget', 'container')) | 355 self._parseChilds(_xmlui_parent, node, ('widget', 'container')) |
294 | 356 |
295 elif node.nodeName == "widget": | 357 elif node.nodeName == "widget": |
296 id_ = node.getAttribute("id") | |
297 name = node.getAttribute("name") | 358 name = node.getAttribute("name") |
298 type_ = node.getAttribute("type") | 359 type_ = node.getAttribute("type") |
299 value_elt = self._getChildNode(node, "value") | 360 value_elt = self._getChildNode(node, "value") |
300 if value_elt is not None: | 361 if value_elt is not None: |
301 value = getText(value_elt) | 362 value = getText(value_elt) |
302 else: | 363 else: |
303 value = node.getAttribute("value") if node.hasAttribute('value') else u'' | 364 value = node.getAttribute("value") if node.hasAttribute('value') else u'' |
304 if type_=="empty": | 365 if type_=="empty": |
305 ctrl = self.widget_factory.createEmptyWidget(parent) | 366 ctrl = self.widget_factory.createEmptyWidget(_xmlui_parent) |
306 elif type_=="text": | 367 elif type_=="text": |
307 ctrl = self.widget_factory.createTextWidget(parent, value) | 368 ctrl = self.widget_factory.createTextWidget(_xmlui_parent, value) |
308 elif type_=="label": | 369 elif type_=="label": |
309 ctrl = self.widget_factory.createLabelWidget(parent, value) | 370 ctrl = self.widget_factory.createLabelWidget(_xmlui_parent, value) |
310 elif type_=="jid": | 371 elif type_=="jid": |
311 ctrl = self.widget_factory.createJidWidget(parent, value) | 372 ctrl = self.widget_factory.createJidWidget(_xmlui_parent, value) |
312 elif type_=="divider": | 373 elif type_=="divider": |
313 style = node.getAttribute("style") or 'line' | 374 style = node.getAttribute("style") or 'line' |
314 ctrl = self.widget_factory.createDividerWidget(parent, style) | 375 ctrl = self.widget_factory.createDividerWidget(_xmlui_parent, style) |
315 elif type_=="string": | 376 elif type_=="string": |
316 ctrl = self.widget_factory.createStringWidget(parent, value, self._isAttrSet("read_only", node)) | 377 ctrl = self.widget_factory.createStringWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) |
317 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) | 378 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) |
318 elif type_=="password": | 379 elif type_=="password": |
319 ctrl = self.widget_factory.createPasswordWidget(parent, value, self._isAttrSet("read_only", node)) | 380 ctrl = self.widget_factory.createPasswordWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) |
320 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) | 381 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) |
321 elif type_=="textbox": | 382 elif type_=="textbox": |
322 ctrl = self.widget_factory.createTextBoxWidget(parent, value, self._isAttrSet("read_only", node)) | 383 ctrl = self.widget_factory.createTextBoxWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) |
323 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) | 384 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) |
324 elif type_=="bool": | 385 elif type_=="bool": |
325 ctrl = self.widget_factory.createBoolWidget(parent, value=='true', self._isAttrSet("read_only", node)) | 386 ctrl = self.widget_factory.createBoolWidget(_xmlui_parent, value==C.BOOL_TRUE, self._isAttrSet("read_only", node)) |
326 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) | 387 self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) |
327 elif type_ == "list": | 388 elif type_ == "list": |
328 style = [] if node.getAttribute("multi") == 'yes' else ['single'] | 389 style = [] if node.getAttribute("multi") == 'yes' else ['single'] |
329 _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")] | 390 _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")] |
330 _selected = [option.getAttribute("value") for option in node.getElementsByTagName("option") if option.getAttribute('selected') == 'true'] | 391 _selected = [option.getAttribute("value") for option in node.getElementsByTagName("option") if option.getAttribute('selected') == C.BOOL_TRUE] |
331 ctrl = self.widget_factory.createListWidget(parent, _options, _selected, style) | 392 ctrl = self.widget_factory.createListWidget(_xmlui_parent, _options, _selected, style) |
332 self.ctrl_list[name] = ({'type': type_, 'control': ctrl}) | 393 self.ctrl_list[name] = ({'type': type_, 'control': ctrl}) |
333 elif type_=="button": | 394 elif type_=="button": |
334 callback_id = node.getAttribute("callback") | 395 callback_id = node.getAttribute("callback") |
335 ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress) | 396 ctrl = self.widget_factory.createButtonWidget(_xmlui_parent, value, self.onButtonPress) |
336 ctrl._xmlui_param_id = (callback_id, [field.getAttribute('name') for field in node.getElementsByTagName("field_back")]) | 397 ctrl._xmlui_param_id = (callback_id, [field.getAttribute('name') for field in node.getElementsByTagName("field_back")]) |
337 else: | 398 else: |
338 log.error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_) | 399 log.error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_) |
339 raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) | 400 raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) |
340 | 401 |
356 ctrl._xmluiOnClick(self.onChangeInternal) | 417 ctrl._xmluiOnClick(self.onChangeInternal) |
357 else: | 418 else: |
358 ctrl._xmluiOnChange(self.onChangeInternal) | 419 ctrl._xmluiOnChange(self.onChangeInternal) |
359 | 420 |
360 ctrl._xmlui_name = name | 421 ctrl._xmlui_name = name |
361 parent._xmluiAppend(ctrl) | 422 _xmlui_parent._xmluiAppend(ctrl) |
362 | 423 |
363 else: | 424 else: |
364 raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName) | 425 raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName) |
365 | 426 |
366 def constructUI(self, xml_data, post_treat=None): | 427 def constructUI(self, parsed_dom, post_treat=None): |
367 """ Actually construct the UI | 428 """Actually construct the UI |
368 @param xml_data: raw XMLUI | 429 |
430 @param parsed_dom: main parsed dom | |
369 @param post_treat: frontend specific treatments to do once the UI is constructed | 431 @param post_treat: frontend specific treatments to do once the UI is constructed |
370 @return: constructed widget | 432 @return: constructed widget |
371 """ | 433 """ |
372 cat_dom = self.dom_parse(xml_data) | 434 top=parsed_dom.documentElement |
373 top=cat_dom.documentElement | |
374 self.type = top.getAttribute("type") | 435 self.type = top.getAttribute("type") |
375 self.title = self.title or top.getAttribute("title") or u"" | |
376 self.session_id = top.getAttribute("session_id") or None | |
377 self.submit_id = top.getAttribute("submit") or None | |
378 if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window', 'popup']: | 436 if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window', 'popup']: |
379 raise InvalidXMLUI | 437 raise InvalidXMLUI |
380 | 438 |
381 if self.type == 'param': | 439 if self.type == 'param': |
382 self.param_changed = set() | 440 self.param_changed = set() |
383 | 441 |
384 self._parseChilds(self, cat_dom.documentElement) | 442 self._parseChilds(self, parsed_dom.documentElement) |
385 | 443 |
386 if post_treat is not None: | 444 if post_treat is not None: |
387 post_treat() | 445 post_treat() |
388 | 446 |
389 self.dom_free(cat_dom) | |
390 | |
391 def _xmluiClose(self): | 447 def _xmluiClose(self): |
392 """ Close the window/popup/... where the constructeur XMLUI is | 448 """Close the window/popup/... where the constructeur XMLUI is |
449 | |
393 this method must be overrided | 450 this method must be overrided |
394 | |
395 """ | 451 """ |
396 raise NotImplementedError | 452 raise NotImplementedError |
397 | |
398 def _xmluiLaunchAction(self, action_id, data): | |
399 self.host.launchAction(action_id, data, profile_key = self.host.profile) | |
400 | 453 |
401 def _xmluiSetParam(self, name, value, category): | 454 def _xmluiSetParam(self, name, value, category): |
402 self.host.bridge.setParam(name, value, category, profile_key=self.host.profile) | 455 self.host.bridge.setParam(name, value, category, profile_key=self.host.profile) |
403 | 456 |
404 ##EVENTS## | 457 ##EVENTS## |
405 | 458 |
406 def onParamChange(self, ctrl): | 459 def onParamChange(self, ctrl): |
407 """ Called when type is param and a widget to save is modified | 460 """Called when type is param and a widget to save is modified |
461 | |
408 @param ctrl: widget modified | 462 @param ctrl: widget modified |
409 | |
410 """ | 463 """ |
411 assert(self.type == "param") | 464 assert(self.type == "param") |
412 self.param_changed.add(ctrl) | 465 self.param_changed.add(ctrl) |
413 | 466 |
414 def onAdvListSelect(self, ctrl): | 467 def onAdvListSelect(self, ctrl): |
429 log.info(_("No callback_id found")) | 482 log.info(_("No callback_id found")) |
430 return | 483 return |
431 self._xmluiLaunchAction(callback_id, data) | 484 self._xmluiLaunchAction(callback_id, data) |
432 | 485 |
433 def onButtonPress(self, button): | 486 def onButtonPress(self, button): |
434 """ Called when an XMLUI button is clicked | 487 """Called when an XMLUI button is clicked |
488 | |
435 Launch the action associated to the button | 489 Launch the action associated to the button |
436 @param button: the button clicked | 490 @param button: the button clicked |
437 | |
438 """ | 491 """ |
439 callback_id, fields = button._xmlui_param_id | 492 callback_id, fields = button._xmlui_param_id |
440 if not callback_id: # the button is probably bound to an internal action | 493 if not callback_id: # the button is probably bound to an internal action |
441 return | 494 return |
442 data = {} | 495 data = {} |
448 else: | 501 else: |
449 data[escaped] = ctrl['control']._xmluiGetValue() | 502 data[escaped] = ctrl['control']._xmluiGetValue() |
450 self._xmluiLaunchAction(callback_id, data) | 503 self._xmluiLaunchAction(callback_id, data) |
451 | 504 |
452 def onChangeInternal(self, ctrl): | 505 def onChangeInternal(self, ctrl): |
453 """ Called when a widget that has been bound to an internal callback is changed. | 506 """Called when a widget that has been bound to an internal callback is changed. |
454 | 507 |
455 This is used to perform some UI actions without communicating with the backend. | 508 This is used to perform some UI actions without communicating with the backend. |
456 See sat.tools.xml_tools.Widget.setInternalCallback for more details. | 509 See sat.tools.xml_tools.Widget.setInternalCallback for more details. |
457 @param ctrl: widget modified | 510 @param ctrl: widget modified |
458 """ | 511 """ |
505 source = None | 558 source = None |
506 | 559 |
507 def getInternalCallbackData(self, action, node): | 560 def getInternalCallbackData(self, action, node): |
508 """Retrieve from node the data needed to perform given action. | 561 """Retrieve from node the data needed to perform given action. |
509 | 562 |
510 TODO: it would be better to not have a specific way to retrieve | |
511 data for each action, but instead to have a generic method to | |
512 extract any kind of data structure from the 'internal_data' element. | |
513 | |
514 @param action (string): a value from the one that can be passed to the | 563 @param action (string): a value from the one that can be passed to the |
515 'callback' parameter of sat.tools.xml_tools.Widget.setInternalCallback | 564 'callback' parameter of sat.tools.xml_tools.Widget.setInternalCallback |
516 @param node (DOM Element): the node of the widget that triggers the callback | 565 @param node (DOM Element): the node of the widget that triggers the callback |
517 """ | 566 """ |
567 # TODO: it would be better to not have a specific way to retrieve | |
568 # data for each action, but instead to have a generic method to | |
569 # extract any kind of data structure from the 'internal_data' element. | |
570 | |
518 try: # data is stored in the first 'internal_data' element of the node | 571 try: # data is stored in the first 'internal_data' element of the node |
519 data_elts = node.getElementsByTagName('internal_data')[0].childNodes | 572 data_elts = node.getElementsByTagName('internal_data')[0].childNodes |
520 except IndexError: | 573 except IndexError: |
521 return None | 574 return None |
522 data = {} | 575 data = {} |
527 for value_elt in elt.childNodes: | 580 for value_elt in elt.childNodes: |
528 data[jid_s].append(value_elt.getAttribute('name')) | 581 data[jid_s].append(value_elt.getAttribute('name')) |
529 return data | 582 return data |
530 | 583 |
531 def onFormSubmitted(self, ignore=None): | 584 def onFormSubmitted(self, ignore=None): |
532 """ An XMLUI form has been submited | 585 """An XMLUI form has been submited |
586 | |
533 call the submit action associated with this form | 587 call the submit action associated with this form |
534 | |
535 """ | 588 """ |
536 selected_values = [] | 589 selected_values = [] |
537 for ctrl_name in self.ctrl_list: | 590 for ctrl_name in self.ctrl_list: |
538 escaped = self.escape(ctrl_name) | 591 escaped = self.escape(ctrl_name) |
539 ctrl = self.ctrl_list[ctrl_name] | 592 ctrl = self.ctrl_list[ctrl_name] |
541 selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues()))) | 594 selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues()))) |
542 else: | 595 else: |
543 selected_values.append((escaped, ctrl['control']._xmluiGetValue())) | 596 selected_values.append((escaped, ctrl['control']._xmluiGetValue())) |
544 if self.submit_id is not None: | 597 if self.submit_id is not None: |
545 data = dict(selected_values) | 598 data = dict(selected_values) |
546 if self.session_id is not None: | 599 self.submit(data) |
547 data["session_id"] = self.session_id | |
548 self._xmluiLaunchAction(self.submit_id, data) | |
549 | |
550 else: | 600 else: |
551 log.warning(_("The form data is not sent back, the type is not managed properly")) | 601 log.warning(_("The form data is not sent back, the type is not managed properly")) |
552 self._xmluiClose() | 602 self._xmluiClose() |
553 | 603 |
554 def onFormCancelled(self, ignore=None): | 604 def onFormCancelled(self, ignore=None): |
555 """ Called when a form is cancelled """ | 605 """Called when a form is cancelled""" |
556 log.debug(_("Cancelling form")) | 606 log.debug(_("Cancelling form")) |
557 self._xmluiClose() | 607 self._xmluiClose() |
558 | 608 |
559 def onSaveParams(self, ignore=None): | 609 def onSaveParams(self, ignore=None): |
560 """ Params are saved, we send them to backend | 610 """Params are saved, we send them to backend |
611 | |
561 self.type must be param | 612 self.type must be param |
562 | |
563 """ | 613 """ |
564 assert(self.type == 'param') | 614 assert(self.type == 'param') |
565 for ctrl in self.param_changed: | 615 for ctrl in self.param_changed: |
566 if isinstance(ctrl, ListWidget): | 616 if isinstance(ctrl, ListWidget): |
567 value = u'\t'.join(ctrl._xmluiGetSelectedValues()) | 617 value = u'\t'.join(ctrl._xmluiGetSelectedValues()) |
568 else: | 618 else: |
569 value = ctrl._xmluiGetValue() | 619 value = ctrl._xmluiGetValue() |
570 param_name = ctrl._xmlui_name.split(Const.SAT_PARAM_SEPARATOR)[1] | 620 param_name = ctrl._xmlui_name.split(C.SAT_PARAM_SEPARATOR)[1] |
571 self._xmluiSetParam(param_name, value, ctrl._param_category) | 621 self._xmluiSetParam(param_name, value, ctrl._param_category) |
572 | 622 |
573 self._xmluiClose() | 623 self._xmluiClose() |
624 | |
625 def show(self, *args, **kwargs): | |
626 pass | |
627 | |
628 | |
629 class XMLUIDialog(XMLUIBase): | |
630 dialog_factory = None | |
631 | |
632 def __init__(self, host, parsed_dom, title = None, flags = None): | |
633 super(XMLUIDialog, self).__init__(host, parsed_dom, title = None, flags = None) | |
634 top=parsed_dom.documentElement | |
635 dlg_elt = self._getChildNode(top, "dialog") | |
636 if dlg_elt is None: | |
637 raise ValueError("Invalid XMLUI: no Dialog element found !") | |
638 dlg_type = dlg_elt.getAttribute("type") or C.XMLUI_DIALOG_MESSAGE | |
639 try: | |
640 mess_elt = self._getChildNode(dlg_elt, C.XMLUI_DATA_MESS) | |
641 message = getText(mess_elt) | |
642 except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError | |
643 message = "" | |
644 level = dlg_elt.getAttribute(C.XMLUI_DATA_LVL) or C.XMLUI_DATA_LVL_INFO | |
645 | |
646 if dlg_type == C.XMLUI_DIALOG_MESSAGE: | |
647 self.dlg = self.dialog_factory.createMessageDialog(self, self.title, message, level) | |
648 elif dlg_type == C.XMLUI_DIALOG_NOTE: | |
649 self.dlg = self.dialog_factory.createNoteDialog(self, self.title, message, level) | |
650 elif dlg_type == C.XMLUI_DIALOG_CONFIRM: | |
651 try: | |
652 buttons_elt = self._getChildNode(dlg_elt, "buttons") | |
653 buttons_set = buttons_elt.getAttribute("set") or C.XMLUI_DATA_BTNS_SET_DEFAULT | |
654 except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError | |
655 buttons_set = C.XMLUI_DATA_BTNS_SET_DEFAULT | |
656 self.dlg = self.dialog_factory.createConfirmDialog(self, self.title, message, level, buttons_set) | |
657 elif dlg_type == C.XMLUI_DIALOG_FILE: | |
658 try: | |
659 file_elt = self._getChildNode(dlg_elt, "file") | |
660 filetype = file_elt.getAttribute("type") or C.XMLUI_DATA_FILETYPE_DEFAULT | |
661 except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError | |
662 filetype = C.XMLUI_DATA_FILETYPE_DEFAULT | |
663 self.dlg = self.dialog_factory.createFileDialog(self, self.title, message, level, filetype) | |
664 else: | |
665 raise ValueError("Unknown dialog type [%s]" % dlg_type) | |
666 | |
667 def show(self): | |
668 self.dlg._xmluiShow() | |
669 | |
670 | |
671 def registerClass(type_, class_): | |
672 """Register the class to use with the factory | |
673 | |
674 @param type_: one of: | |
675 CLASS_PANEL: classical XMLUI interface | |
676 CLASS_DIALOG: XMLUI dialog | |
677 @param class_: the class to use to instanciate given type | |
678 """ | |
679 assert type_ in (CLASS_PANEL, CLASS_DIALOG) | |
680 class_map[type_] = class_ | |
681 | |
682 | |
683 def create(host, xml_data, title = None, flags = None, dom_parse=None, dom_free=None): | |
684 """ | |
685 @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one | |
686 @param dom_free: method used to free the parsed DOM | |
687 """ | |
688 if dom_parse is None: | |
689 from xml.dom import minidom | |
690 dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8')) | |
691 dom_free = lambda parsed_dom: parsed_dom.unlink() | |
692 else: | |
693 dom_parse = dom_parse | |
694 dom_free = dom_free or (lambda parsed_dom: None) | |
695 parsed_dom = dom_parse(xml_data) | |
696 top=parsed_dom.documentElement | |
697 ui_type = top.getAttribute("type") | |
698 try: | |
699 if ui_type != C.XMLUI_DIALOG: | |
700 cls = class_map[CLASS_PANEL] | |
701 else: | |
702 cls = class_map[CLASS_DIALOG] | |
703 except KeyError: | |
704 raise ClassNotRegistedError(_("You must register classes with registerClass before creating a XMLUI")) | |
705 | |
706 xmlui = cls(host, parsed_dom, title, flags) | |
707 dom_free(parsed_dom) | |
708 return xmlui |