Mercurial > libervia-backend
comparison frontends/src/wix/xmlui.py @ 796:46aa5ada61bf
core, frontends: XMLUI refactoring:
- now there is a base XMLUI class. Frontends inherits from this class, and add their specific widgets/containers/behaviour
- wix: param.py has been removed, as the same behaviour has been reimplemented through XMLUI
- removed "misc" argument in XMLUI.__init__
- severals things are broken (gateway, directory search), following patches will fix them
- core: elements names in xml_tools.dataForm2XMLUI now use a construction with category_name/param_name to avoid name conflicts (could happen with 2 parameters of the same name in differents categories).
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 04 Feb 2014 18:02:35 +0100 |
parents | bfabeedbf32e |
children | 9007bb133009 |
comparison
equal
deleted
inserted
replaced
795:6625558371db | 796:46aa5ada61bf |
---|---|
19 | 19 |
20 | 20 |
21 | 21 |
22 from sat.core.i18n import _ | 22 from sat.core.i18n import _ |
23 import wx | 23 import wx |
24 import pdb | |
25 from xml.dom import minidom | |
26 from logging import debug, info, warning, error | 24 from logging import debug, info, warning, error |
27 from sat.tools.jid import JID | 25 from sat.tools.jid import JID |
28 | 26 from sat_frontends.tools import xmlui |
29 SAT_FORM_PREFIX = "SAT_FORM_" | 27 |
30 | 28 |
31 | 29 class EventWidget(object): |
32 class XMLUI(wx.Frame): | 30 """ Used to manage change event of widgets """ |
33 """Create an user interface from a SàT xml""" | 31 |
34 | 32 def _xmluiOnChange(self, callback): |
35 def __init__(self, host, xml_data='', title="Form", options = None, misc = None): | 33 """ Call callback with widget as only argument """ |
36 if options is None: | 34 def change_cb(event): |
37 options = [] | 35 callback(self) |
38 style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in options else wx.DEFAULT_FRAME_STYLE #FIXME: Q&D tmp hack | 36 self.Bind(self._xmlui_change_event, change_cb) |
39 super(XMLUI, self).__init__(None, title=title, style=style) | 37 |
40 | 38 |
41 self.host = host | 39 class WixWidget(object): |
42 self.options = options | 40 _xmlui_proportion = 0 |
43 self.misc = misc or {} | 41 |
44 self.ctrl_list = {} # usefull to access ctrl | 42 |
45 | 43 class ValueWidget(WixWidget): |
44 def _xmluiGetValue(self): | |
45 return self.GetValue() | |
46 | |
47 | |
48 class EmptyWidget(WixWidget, xmlui.EmptyWidget, wx.Window): | |
49 | |
50 def __init__(self, parent): | |
51 wx.Window.__init__(self, parent, -1) | |
52 | |
53 | |
54 class TextWidget(WixWidget, xmlui.TextWidget, wx.StaticText): | |
55 | |
56 def __init__(self, parent, value): | |
57 wx.StaticText.__init__(self, parent, -1, value) | |
58 | |
59 | |
60 class StringWidget(EventWidget, ValueWidget, xmlui.StringWidget, wx.TextCtrl): | |
61 _xmlui_change_event = wx.EVT_TEXT | |
62 | |
63 def __init__(self, parent, value): | |
64 wx.TextCtrl.__init__(self, parent, -1, value) | |
65 self._xmlui_proportion = 1 | |
66 | |
67 | |
68 class PasswordWidget(EventWidget, ValueWidget, xmlui.PasswordWidget, wx.TextCtrl): | |
69 _xmlui_change_event = wx.EVT_TEXT | |
70 | |
71 def __init__(self, parent, value): | |
72 wx.TextCtrl.__init__(self, parent, -1, value, style=wx.TE_PASSWORD) | |
73 self._xmlui_proportion = 1 | |
74 | |
75 | |
76 class TextBoxWidget(EventWidget, ValueWidget, xmlui.TextBoxWidget, wx.TextCtrl): | |
77 _xmlui_change_event = wx.EVT_TEXT | |
78 | |
79 def __init__(self, parent, value): | |
80 wx.TextCtrl.__init__(self, parent, -1, value, style=wx.TE_MULTILINE) | |
81 self._xmlui_proportion = 1 | |
82 | |
83 | |
84 class BoolWidget(EventWidget, ValueWidget, xmlui.BoolWidget, wx.CheckBox): | |
85 _xmlui_change_event = wx.EVT_CHECKBOX | |
86 | |
87 def __init__(self, parent, state): | |
88 wx.CheckBox.__init__(self, parent, -1, "", style=wx.CHK_2STATE) | |
89 self.SetValue(state) | |
90 self._xmlui_proportion = 1 | |
91 | |
92 def _xmluiGetValue(self): | |
93 return "true" if self.GetValue() else "false" | |
94 | |
95 | |
96 class ButtonWidget(EventWidget, WixWidget, xmlui.ButtonWidget, wx.Button): | |
97 _xmlui_change_event = wx.EVT_BUTTON | |
98 | |
99 def __init__(self, parent, value, click_callback): | |
100 wx.Button.__init__(self, parent, -1, value) | |
101 self._xmlui_click_callback = click_callback | |
102 parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self) | |
103 | |
104 def _xmluiOnClick(self, event): | |
105 self._xmlui_click_callback(event.GetEventObject()) | |
106 event.Skip() | |
107 | |
108 class ListWidget(EventWidget, WixWidget, xmlui.ListWidget, wx.ListBox): | |
109 _xmlui_change_event = wx.EVT_LISTBOX | |
110 | |
111 def __init__(self, parent, options, flags): | |
112 styles = wx.LB_MULTIPLE if not 'single' in flags else wx.LB_SINGLE | |
113 wx.ListBox.__init__(self, parent, -1, choices=[option[1] for option in options], style=styles) | |
114 self._xmlui_attr_map = {label: value for value, label in options} | |
115 self._xmlui_proportion = 1 | |
116 | |
117 def _xmluiSelectValue(self, value): | |
118 try: | |
119 label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0] | |
120 except IndexError: | |
121 warning(_("Can't find value [%s] to select" % value)) | |
122 return | |
123 for idx in xrange(self.GetCount()): | |
124 self.SetSelection(idx, self.GetString(idx) == label) | |
125 | |
126 def _xmluiGetSelectedValues(self): | |
127 ret = [] | |
128 labels = [self.GetString(idx) for idx in self.GetSelections()] | |
129 for label in labels: | |
130 ret.append(self._xmlui_attr_map[label]) | |
131 return ret | |
132 | |
133 | |
134 class AdvancedListWidget(ListWidget): | |
135 #TODO | |
136 | |
137 def __init__(self, parent, options, flags): | |
138 super(ListWidget, self).__init__(parent, options, flags) | |
139 | |
140 | |
141 class WixContainer(object): | |
142 _xmlui_proportion = 1 | |
143 | |
144 def _xmluiAppend(self, widget): | |
145 self.sizer.Add(widget, self._xmlui_proportion, flag=wx.EXPAND) | |
146 | |
147 | |
148 class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel): | |
149 | |
150 def __init__(self, parent, weight_0='1', weight_1='1'): | |
151 wx.Panel.__init__(self, parent) | |
152 self.sizer = wx.FlexGridSizer(cols=2) | |
153 self.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs | |
154 self.SetSizer(self.sizer) | |
155 | |
156 | |
157 class TabsContainer(WixContainer, xmlui.TabsContainer, wx.Notebook): | |
158 | |
159 def __init__(self, parent): | |
160 wx.Notebook.__init__(self, parent, -1, style=wx.NB_LEFT if self._xmlui_main.type=='param' else 0) | |
161 | |
162 def _xmluiAddTab(self, label): | |
163 tab_panel = wx.Panel(self, -1) | |
164 tab_panel.sizer = wx.BoxSizer(wx.VERTICAL) | |
165 tab_panel.SetSizer(tab_panel.sizer) | |
166 self.AddPage(tab_panel, label) | |
167 VerticalContainer._xmluiAdapt(tab_panel) | |
168 return tab_panel | |
169 | |
170 | |
171 class VerticalContainer(WixContainer, xmlui.VerticalContainer, wx.Panel): | |
172 | |
173 def __init__(self, parent): | |
174 wx.Panel.__init__(self, parent) | |
46 self.sizer = wx.BoxSizer(wx.VERTICAL) | 175 self.sizer = wx.BoxSizer(wx.VERTICAL) |
47 self.SetSizer(self.sizer) | 176 self.SetSizer(self.sizer) |
48 self.SetAutoLayout(True) | 177 |
49 | 178 |
50 #events | 179 class WidgetFactory(object): |
51 if not 'NO_CANCEL' in self.options: | 180 |
181 def __getattr__(self, attr): | |
182 if attr.startswith("create"): | |
183 cls = globals()[attr[6:]] | |
184 cls._xmlui_main = self._xmlui_main | |
185 return cls | |
186 | |
187 | |
188 class XMLUI(xmlui.XMLUI, wx.Frame, WixContainer): | |
189 """Create an user interface from a SàT XML""" | |
190 widget_factory = WidgetFactory() | |
191 | |
192 def __init__(self, host, xml_data, title=None, flags = None,): | |
193 self.widget_factory._xmlui_main = self | |
194 xmlui.XMLUI.__init__(self, host, xml_data, title, flags) | |
195 | |
196 def constructUI(self, xml_data): | |
197 style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in self.flags else wx.DEFAULT_FRAME_STYLE | |
198 wx.Frame.__init__(self, None, style=style) | |
199 self.sizer = wx.BoxSizer(wx.VERTICAL) | |
200 self.SetSizer(self.sizer) | |
201 | |
202 def postTreat(ret_wid): | |
203 if self.title: | |
204 self.SetTitle(self.title) | |
205 | |
206 if self.type == 'form': | |
207 dialogButtons = wx.StdDialogButtonSizer() | |
208 submitButton = wx.Button(ret_wid,wx.ID_OK, label=_("Submit")) | |
209 dialogButtons.AddButton(submitButton) | |
210 ret_wid.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton) | |
211 if not 'NO_CANCEL' in self.flags: | |
212 cancelButton = wx.Button(ret_wid,wx.ID_CANCEL) | |
213 dialogButtons.AddButton(cancelButton) | |
214 ret_wid.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton) | |
215 dialogButtons.Realize() | |
216 ret_wid.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL) | |
217 | |
218 self._xmluiAppend(ret_wid) | |
219 self.sizer.Fit(self) | |
220 self.Show() | |
221 return ret_wid | |
222 | |
223 super(XMLUI, self).constructUI(xml_data, postTreat) | |
224 if not 'NO_CANCEL' in self.flags: | |
52 self.Bind(wx.EVT_CLOSE, self.onClose, self) | 225 self.Bind(wx.EVT_CLOSE, self.onClose, self) |
53 | |
54 self.MakeModal() | 226 self.MakeModal() |
55 | 227 |
56 self.constructUI(xml_data) | 228 def _xmluiClose(self): |
57 | 229 self.MakeModal(False) |
58 self.Show() | 230 self.Destroy() |
59 | |
60 def __parseElems(self, node, parent): | |
61 """Parse elements inside a <layout> tags, and add them to the parent sizer""" | |
62 for elem in node.childNodes: | |
63 if elem.nodeName != "elem": | |
64 message=_("Unmanaged tag") | |
65 error(message) | |
66 raise Exception(message) | |
67 _proportion = 0 | |
68 id = elem.getAttribute("id") | |
69 name = elem.getAttribute("name") | |
70 type = elem.getAttribute("type") | |
71 value = elem.getAttribute("value") if elem.hasAttribute('value') else u'' | |
72 if type=="empty": | |
73 ctrl = wx.Window(parent, -1) | |
74 elif type=="text": | |
75 try: | |
76 value = elem.childNodes[0].wholeText | |
77 except IndexError: | |
78 warning (_("text node has no child !")) | |
79 ctrl = wx.StaticText(parent, -1, value) | |
80 elif type=="label": | |
81 ctrl = wx.StaticText(parent, -1, value+": ") | |
82 elif type=="string": | |
83 ctrl = wx.TextCtrl(parent, -1, value) | |
84 self.ctrl_list[name] = ({'type':type, 'control':ctrl}) | |
85 _proportion = 1 | |
86 elif type=="password": | |
87 ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_PASSWORD) | |
88 self.ctrl_list[name] = ({'type':type, 'control':ctrl}) | |
89 _proportion = 1 | |
90 elif type=="textbox": | |
91 ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_MULTILINE) | |
92 self.ctrl_list[name] = ({'type':type, 'control':ctrl}) | |
93 _proportion = 1 | |
94 elif type=="bool": | |
95 ctrl = wx.CheckBox(panel, -1, "", style = wx.CHK_2STATE) | |
96 ctrl.SetValue(value=="true") | |
97 self.ctrl_list[name] = ({'type':type, 'control':ctrl}) | |
98 _proportion = 1 | |
99 elif type=="list": | |
100 style=wx.LB_MULTIPLE if elem.getAttribute("multi")=='yes' else wx.LB_SINGLE | |
101 _options = [(option.getAttribute("label"), option.getAttribute("value")) for option in elem.getElementsByTagName("option")] | |
102 attr_map = {label: value for label, value in _options} | |
103 ctrl = wx.ListBox(parent, -1, choices=[option[0] for option in _options], style=style) | |
104 self.ctrl_list[name] = ({'type':type, 'control':ctrl, 'attr_map': attr_map}) | |
105 _proportion = 1 | |
106 elif type=="button": | |
107 callback_id = elem.getAttribute("callback_id") | |
108 ctrl = wx.Button(parent, -1, value) | |
109 ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")]) | |
110 parent.Bind(wx.EVT_BUTTON, self.onButtonClicked, ctrl) | |
111 else: | |
112 error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME ! | |
113 raise NotImplementedError | |
114 parent.sizer.Add(ctrl, _proportion, flag=wx.EXPAND) | |
115 | |
116 def __parseChilds(self, parent, current_param, elem, wanted = ['layout']): | |
117 """Recursively parse childNodes of an elemen | |
118 @param parent: parent wx.Window | |
119 @param current_param: current wx.Window (often wx.Panel) or None if we must create one | |
120 @param elem: element from which childs will be parsed | |
121 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant""" | |
122 for node in elem.childNodes: | |
123 if wanted and not node.nodeName in wanted: | |
124 raise Exception("Invalid XMLUI") #TODO: make a custom exception | |
125 if node.nodeName == "layout": | |
126 _proportion = 0 | |
127 type = node.getAttribute('type') | |
128 if type == "tabs": | |
129 current = wx.Notebook(parent, -1, style=wx.NB_LEFT if self.type=='param' else 0) | |
130 self.__parseChilds(current, None, node, ['category']) | |
131 _proportion = 1 | |
132 else: | |
133 if current_param == None: | |
134 current = wx.Panel(parent, -1) | |
135 else: | |
136 current = current_param | |
137 if type == "vertical": | |
138 current.sizer = wx.BoxSizer(wx.VERTICAL) | |
139 elif type == "pairs": | |
140 current.sizer = wx.FlexGridSizer(cols=2) | |
141 current.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs | |
142 else: | |
143 warning(_("Unknown layout, using default one")) | |
144 current.sizer = wx.BoxSizer(wx.VERTICAL) | |
145 current.SetSizer(current.sizer) | |
146 self.__parseElems(node, current) | |
147 if parent: | |
148 parent.sizer.Add(current, _proportion, flag=wx.EXPAND) | |
149 elif node.nodeName == "category": | |
150 name = node.getAttribute('name') | |
151 label = node.getAttribute('label') | |
152 if not node.nodeName in wanted or not name or not isinstance(parent,wx.Notebook): | |
153 raise Exception("Invalid XMLUI") #TODO: make a custom exception | |
154 notebook = parent | |
155 tab_panel = wx.Panel(notebook, -1) | |
156 tab_panel.sizer = wx.BoxSizer(wx.VERTICAL) | |
157 tab_panel.SetSizer(tab_panel.sizer) | |
158 notebook.AddPage(tab_panel, label or name) | |
159 self.__parseChilds(tab_panel, None, node, ['layout']) | |
160 | |
161 else: | |
162 message=_("Unknown tag") | |
163 error(message) | |
164 raise Exception(message) #TODO: raise a custom exception here | |
165 | |
166 | |
167 def constructUI(self, xml_data): | |
168 panel=wx.Panel(self) | |
169 panel.sizer = wx.BoxSizer(wx.VERTICAL) | |
170 | |
171 cat_dom = minidom.parseString(xml_data.encode('utf-8')) | |
172 top= cat_dom.documentElement | |
173 self.type = top.getAttribute("type") | |
174 self.title = top .getAttribute("title") | |
175 self.session_id = top.getAttribute("session_id") or None | |
176 self.submit_id = top.getAttribute("submit") or None | |
177 if self.title: | |
178 self.SetTitle(self.title) | |
179 if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']: | |
180 raise Exception("Invalid XMLUI") #TODO: make a custom exception | |
181 | |
182 self.__parseChilds(panel, None, cat_dom.documentElement) | |
183 | |
184 if self.type == 'form': | |
185 dialogButtons = wx.StdDialogButtonSizer() | |
186 submitButton = wx.Button(panel,wx.ID_OK, label=_("Submit")) | |
187 dialogButtons.AddButton(submitButton) | |
188 panel.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton) | |
189 if not 'NO_CANCEL' in self.options: | |
190 cancelButton = wx.Button(panel,wx.ID_CANCEL) | |
191 dialogButtons.AddButton(cancelButton) | |
192 panel.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton) | |
193 dialogButtons.Realize() | |
194 panel.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL) | |
195 | |
196 panel.SetSizer(panel.sizer) | |
197 panel.SetAutoLayout(True) | |
198 panel.sizer.Fit(self) | |
199 self.sizer.Add(panel, 1, flag=wx.EXPAND) | |
200 cat_dom.unlink() | |
201 | 231 |
202 ###events | 232 ###events |
203 | 233 |
204 def onButtonClicked(self, event): | 234 def onParamChange(self, ctrl): |
205 """Called when a button is pushed""" | 235 super(XMLUI, self).onParamChange(ctrl) |
206 callback_id, fields = event.GetEventObject().param_id | 236 ### FIXME # Some hacks for better presentation, should be generic # FIXME ### |
207 for field in fields: | 237 if (ctrl._param_category, ctrl._param_name) == ('Connection', 'JabberID'): |
208 ctrl = self.ctrl_list[field] | 238 domain = JID(ctrl._xmluiGetValue()).domain |
209 if isinstance(ctrl['control'], wx.ListBox): | 239 for widget in (ctl['control'] for ctl in self.ctrl_list.values()): |
210 data[field] = '\t'.join([ctrl['control'].GetString(idx) for idx in ctrl['control'].GetSelections()]) | 240 if (widget._param_category, widget._param_name) == ('Connection', 'Server'): |
211 else: | 241 widget.SetValue(domain) |
212 data[field] = ctrl['control'].GetValue() | 242 break |
213 | |
214 self.host.launchAction(callback_id, None, profile_key = self.host.profile) | |
215 event.Skip() | |
216 | 243 |
217 def onFormSubmitted(self, event): | 244 def onFormSubmitted(self, event): |
218 """Called when submit button is clicked""" | 245 """Called when submit button is clicked""" |
219 debug(_("Submitting form")) | 246 button = event.GetEventObject() |
220 selected_values = [] | 247 super(XMLUI, self).onFormSubmitted(button) |
221 for ctrl_name in self.ctrl_list: | |
222 escaped = u"%s%s" % (SAT_FORM_PREFIX, ctrl_name) | |
223 ctrl = self.ctrl_list[ctrl_name] | |
224 if isinstance(ctrl['control'], wx.ListBox): | |
225 label = ctrl['control'].GetStringSelection() | |
226 value = ctrl['attr_map'][label] | |
227 selected_values.append((escaped, value)) | |
228 elif isinstance(ctrl['control'], wx.CheckBox): | |
229 selected_values.append((escaped, "true" if ctrl['control'].GetValue() else "false")) | |
230 else: | |
231 selected_values.append((escaped, ctrl['control'].GetValue())) | |
232 if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned | |
233 id = self.misc['action_back']("SUBMIT",self.misc['target'], selected_values) | |
234 self.host.current_action_ids.add(id) | |
235 elif self.misc.has_key('callback'): | |
236 self.misc['callback'](selected_values) | |
237 | |
238 elif self.submit_id is not None: | |
239 data = dict(selected_values) | |
240 if self.session_id is not None: | |
241 data["session_id"] = self.session_id | |
242 self.host.launchAction(self.submit_id, data, profile_key=self.host.profile) | |
243 else: | |
244 warning (_("The form data is not sent back, the type is not managed properly")) | |
245 self.MakeModal(False) | |
246 self.Destroy() | |
247 | |
248 def onFormCancelled(self, event): | |
249 """Called when cancel button is clicked""" | |
250 debug(_("Cancelling form")) | |
251 self.MakeModal(False) | |
252 self.Close() | |
253 | 248 |
254 def onClose(self, event): | 249 def onClose(self, event): |
255 """Close event: we have to send the form.""" | 250 """Close event: we have to send the form.""" |
256 debug(_("close")) | 251 debug(_("close")) |
257 self.MakeModal(False) | 252 if self.type == 'param': |
253 self.onSaveParams() | |
258 event.Skip() | 254 event.Skip() |
259 | 255 |