143
|
1 #!/usr/bin/python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 """ |
|
5 Libervia: a Salut à Toi frontend |
|
6 Copyright (C) 2011, 2012 Jérôme Poisson <goffi@goffi.org> |
|
7 |
|
8 This program is free software: you can redistribute it and/or modify |
|
9 it under the terms of the GNU Affero General Public License as published by |
|
10 the Free Software Foundation, either version 3 of the License, or |
|
11 (at your option) any later version. |
|
12 |
|
13 This program is distributed in the hope that it will be useful, |
|
14 but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
16 GNU Affero General Public License for more details. |
|
17 |
|
18 You should have received a copy of the GNU Affero General Public License |
|
19 along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
20 """ |
|
21 |
|
22 from pyjamas.ui.VerticalPanel import VerticalPanel |
|
23 from pyjamas.ui.HorizontalPanel import HorizontalPanel |
|
24 from pyjamas.ui.CellPanel import CellPanel |
|
25 from pyjamas.ui.TabPanel import TabPanel |
|
26 from pyjamas.ui.Grid import Grid |
|
27 from pyjamas.ui.Label import Label |
|
28 from pyjamas.ui.TextBoxBase import TextBoxBase |
|
29 from pyjamas.ui.TextBox import TextBox |
|
30 from pyjamas.ui.PasswordTextBox import PasswordTextBox |
|
31 from pyjamas.ui.TextArea import TextArea |
|
32 from pyjamas.ui.CheckBox import CheckBox |
|
33 from pyjamas.ui.ListBox import ListBox |
|
34 from pyjamas.ui.Button import Button |
|
35 from nativedom import NativeDOM |
|
36 |
|
37 |
|
38 class InvalidXMLUI(Exception): |
|
39 pass |
|
40 |
|
41 class Pairs(Grid): |
|
42 |
|
43 def __init__(self): |
|
44 Grid.__init__(self, 0, 0) |
|
45 self.row = 0 |
|
46 self.col = 0 |
|
47 |
|
48 def append(self, widget): |
|
49 if self.col == 0: |
|
50 self.resize(self.row+1, 2) |
|
51 self.setWidget(self.row, self.col, widget) |
|
52 self.col += 1 |
|
53 if self.col == 2: |
|
54 self.row +=1 |
|
55 self.col = 0 |
|
56 |
|
57 class XMLUI(VerticalPanel): |
|
58 |
|
59 def __init__(self, host, xml_data, title = None, options = None, misc = None, close_cb = None): |
|
60 print "XMLUI init" |
|
61 VerticalPanel.__init__(self) |
|
62 self.dom = NativeDOM() |
|
63 self.host = host |
|
64 self.title = title |
|
65 self.options = options or [] |
|
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%') |
|
72 |
|
73 def setCloseCb(self, close_cb): |
|
74 self.close_cb = close_cb |
|
75 |
|
76 def close(self): |
|
77 if self.close_cb: |
|
78 self.close_cb() |
|
79 else: |
|
80 print "WARNING: no close method defined" |
|
81 |
|
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 ctrl = ListBox() |
|
119 ctrl.setMultipleSelect(elem.getAttribute("multi")=='yes') |
|
120 for option in elem.getElementsByTagName("option"): |
|
121 ctrl.addItem(option.getAttribute("value")) |
|
122 self.ctrl_list[name] = ({'node_type':node_type, 'control':ctrl}) |
|
123 elif node_type=="button": |
|
124 callback_id = elem.getAttribute("callback_id") |
|
125 ctrl = Button(value, self.onButtonPress) |
|
126 ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")]) |
|
127 else: |
|
128 print("FIXME FIXME FIXME: type [%s] is not implemented" % node_type) #FIXME ! |
|
129 raise NotImplementedError |
|
130 if self.node_type == 'param': |
|
131 if isinstance(ctrl,TextBoxBase): |
|
132 ctrl.addChangeListener(self.onParamChange) |
|
133 elif isinstance(ctrl, CheckBox): |
|
134 ctrl.addClickListener(self.onParamChange) |
|
135 ctrl._param_category = self._current_category |
|
136 ctrl._param_name = name |
|
137 parent.append(ctrl) |
|
138 |
|
139 def __parseChilds(self, current, elem, wanted = ['layout'], data = None): |
|
140 """Recursively parse childNodes of an elemen |
|
141 @param current: widget container with 'append' method |
|
142 @param elem: element from which childs will be parsed |
|
143 @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant""" |
|
144 for node in elem.childNodes: |
|
145 if wanted and not node.nodeName in wanted: |
|
146 raise InvalidXMLUI("ERROR: unexpected nodeName") |
|
147 if node.nodeName == "layout": |
|
148 node_type = node.getAttribute('type') |
|
149 if node_type == "tabs": |
|
150 tab_cont = TabPanel() |
|
151 tab_cont.setStyleName('liberviaTabPanel') |
|
152 tab_cont.setHeight('100%') |
|
153 self.__parseChilds(current, node, ['category'], tab_cont) |
|
154 current.append(tab_cont) |
|
155 if isinstance(current, CellPanel): |
|
156 current.setCellHeight(tab_cont, '100%') |
|
157 elif node_type == "vertical": |
|
158 self.__parseElems(node, current) |
|
159 elif node_type == "pairs": |
|
160 pairs = Pairs() |
|
161 self.__parseElems(node, pairs) |
|
162 current.append(pairs) |
|
163 else: |
|
164 print("WARNING: Unknown layout [%s], using default one" % (node_type,)) |
|
165 self.__parseElems(node, current) |
|
166 elif node.nodeName == "category": |
|
167 name = node.getAttribute('name') |
|
168 label = node.getAttribute('label') |
|
169 if not name or not isinstance(data,TabPanel): |
|
170 raise InvalidXMLUI |
|
171 if self.node_type == 'param': |
|
172 self._current_category = name #XXX: awful hack because params need category and we don't keep parent |
|
173 tab_cont = data |
|
174 tab_body = VerticalPanel() |
|
175 tab_cont.add(tab_body, label or name) |
|
176 self.__parseChilds(tab_body, node, ['layout']) |
|
177 else: |
|
178 message=_("Unknown tag") |
|
179 raise NotImplementedError(message) |
|
180 |
|
181 def constructUI(self, xml_data): |
|
182 |
|
183 cat_dom = self.dom.parseString(xml_data) |
|
184 |
|
185 top=cat_dom.documentElement |
|
186 self.node_type = top.getAttribute("type") |
|
187 self.title = top.getAttribute("title") or self.title |
|
188 if top.nodeName != "sat_xmlui" or not self.node_type in ['form', 'param', 'window']: |
|
189 raise InvalidXMLUI |
|
190 |
|
191 if self.node_type == 'param': |
|
192 self.param_changed = set() |
|
193 |
|
194 self.__parseChilds(self, cat_dom.documentElement) |
|
195 |
|
196 if self.node_type == 'form': |
|
197 hpanel = HorizontalPanel() |
|
198 hpanel.add(Button('Submit',self.onFormSubmitted)) |
|
199 if not 'NO_CANCEL' in self.options: |
|
200 hpanel.add(Button('Cancel',self.onFormCancelled)) |
|
201 self.add(hpanel) |
|
202 elif self.node_type == 'param': |
|
203 assert(isinstance(self.children[0],TabPanel)) |
|
204 hpanel = HorizontalPanel() |
|
205 hpanel.add(Button('Cancel', lambda ignore: self.close())) |
|
206 hpanel.add(Button('Save', self.onSaveParams)) |
|
207 self.add(hpanel) |
|
208 |
|
209 ##EVENTS## |
|
210 |
|
211 def onButtonPress(self, button): |
|
212 print "onButtonPress (%s)" % (button,) |
|
213 callback_id, fields = button.param_id |
|
214 data = {"callback_id":callback_id} |
|
215 for field in fields: |
|
216 ctrl = self.ctrl_list[field] |
|
217 if isinstance(ctrl['control'],ListBox): |
|
218 data[field] = '\t'.join(ctrl['control'].getSelectedValues()) |
|
219 elif isinstance(ctrl['control'],CheckBox): |
|
220 data[field] = "true" if ctrl['control'].isChecked() else "false" |
|
221 else: |
|
222 data[field] = ctrl['control'].getText() |
|
223 |
|
224 self.host.bridge.call('launchAction', None, "button", data) |
|
225 self.host.current_action_ids.add(id) |
|
226 |
|
227 def onParamChange(self, widget): |
|
228 """Called when type is param and a widget to save is modified""" |
|
229 assert(self.node_type == "param") |
|
230 print "onParamChange:", widget |
|
231 self.param_changed.add(widget) |
|
232 |
|
233 def onFormSubmitted(self, button): |
|
234 print "onFormSubmitted" |
|
235 # FIXME: untested |
|
236 print "FIXME FIXME FIXME: Form submitting not managed yet" |
|
237 data = [] |
|
238 for ctrl_name in self.ctrl_list: |
|
239 ctrl = self.ctrl_list[ctrl_name] |
|
240 if isinstance(ctrl['control'], ListBox): |
|
241 data.append((ctrl_name, ctrl['control'].getValue())) |
|
242 elif isinstance(ctrl['control'], CheckBox): |
|
243 data.append((ctrl_name, "true" if ctrl['control'].isChecked() else "false")) |
|
244 else: |
|
245 data.append((ctrl_name, ctrl['control'].getText())) |
|
246 if 'action_back' in self.misc: #FIXME FIXME FIXME: WTF ! Must be cleaned |
|
247 raise NotImplementedError |
|
248 elif 'callback' in self.misc: |
|
249 self.misc['callback'](data) |
|
250 else: |
|
251 print ("WARNING: The form data is not sent back, the type is not managed properly") |
|
252 |
|
253 self.close() |
|
254 |
|
255 def onFormCancelled(self, button): |
|
256 self.close() |
|
257 |
|
258 def onSaveParams(self, button): |
|
259 print "onSaveParams" |
|
260 for ctrl in self.param_changed: |
|
261 if isinstance(ctrl, CheckBox): |
|
262 value = "true" if ctrl.isChecked() else "false" |
|
263 else: |
|
264 value = ctrl.getText() |
|
265 self.host.bridge.call('setParam', None, ctrl._param_name, value, ctrl._param_category) |
|
266 self.close() |