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