1 #!/usr/bin/env python 2 3 """ 4 PyQt-compatible resources for use with WebStack. 5 6 Copyright (C) 2005, 2007 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library 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 GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 """ 22 23 import XSLForms.Prepare 24 import XSLForms.Resources.PyQtCommon 25 import XSLForms.Resources.WebResources 26 import WebStack.Generic 27 import os 28 import libxml2dom 29 30 class XSLFormsResource(XSLForms.Resources.WebResources.XSLFormsResource, 31 XSLForms.Resources.PyQtCommon.PyQtCommonResource): 32 33 """ 34 An XSLForms resource supporting PyQt-compatible Web applications for use 35 with WebStack. 36 """ 37 38 encoding = "utf-8" 39 widget_resources = {} 40 41 def __init__(self, design_identifier): 42 self.factory = Factory() 43 self.default_design = design_identifier 44 45 # NOTE: Filenames extended by string concatenation. 46 47 self.template_resources = {} 48 self.init_resources = {} 49 for design_identifier, design_name in self.design_resources.items(): 50 self.template_resources[design_identifier] = (design_name + "_template.xhtml", design_name + "_output.xsl") 51 self.init_resources[design_identifier] = (design_name + "_template.xhtml", design_name + "_input.xsl") 52 53 # Initialisation of connections - just a mapping from field names to 54 # methods in the Web version. 55 56 self.method_resources = {} 57 for design_identifier, design_name in self.design_resources.items(): 58 design_path = self.prepare_design(design_identifier) 59 design_doc = libxml2dom.parse(design_path) 60 connections = {} 61 for connection in design_doc.xpath("UI/connections/connection"): 62 receiver = "".join([n.nodeValue for n in connection.xpath("receiver/text()")]) 63 if receiver == design_identifier: 64 sender = "".join([n.nodeValue for n in connection.xpath("sender/text()")]) 65 slot = "".join([n.nodeValue for n in connection.xpath("slot/text()")]) 66 slot = slot.split("(")[0] 67 connections[sender] = slot 68 self.method_resources[design_identifier] = connections 69 70 # Initialisation of template fragments. 71 72 self.in_page_resources = {} 73 for widget_identifier, (design_name, fragment_id) in self.widget_resources.items(): 74 self.in_page_resources[widget_identifier] = (design_name + "_output.xsl", fragment_id) 75 76 # Refresh status - avoiding multiple refresh calls. 77 78 self._refreshed = 0 79 80 # Resource methods. 81 82 def prepare_output(self, design_identifier): 83 84 """ 85 Prepare the output stylesheets using the given 'design_identifier' to 86 indicate which templates and stylesheets are to be employed in the 87 production of output from the resource. 88 89 The 'design_identifier' is used as a key to the 'design_resources' and 90 'template_resources' dictionary attributes. 91 92 Return the full path to the output stylesheet for use with 'send_output' 93 or 'get_result'. 94 """ 95 96 design_path = self.prepare_design(design_identifier) 97 template_filename, output_filename = self.template_resources[design_identifier] 98 output_path = os.path.abspath(os.path.join(self.resource_dir, output_filename)) 99 template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename)) 100 XSLForms.Prepare.ensure_qt_template(design_path, template_path) 101 XSLForms.Prepare.ensure_stylesheet(template_path, output_path) 102 return output_path 103 104 # PyQt compatibility methods. 105 106 def get_document(self, document_identifier): 107 return libxml2dom.parse(self.prepare_document(document_identifier)) 108 109 def prepare_widget(self, design_identifier, widget_identifier, parent=None): 110 fragment_name, widget_name = self.widget_resources[widget_identifier] 111 element = UINode(self.doc._node.ownerDocument.createElement(widget_name)) 112 113 # NOTE: Creating an element which may not be appropriate. 114 115 element._node.appendChild(self.doc._node.ownerDocument.createElement(widget_name + "_value")) 116 return element 117 118 def child(self, name): 119 return self.doc.child(name) 120 121 def sender(self): 122 return self._sender 123 124 # PyQt structural methods. 125 126 def form_init(self): 127 128 "Initialise a newly-created form." 129 130 raise NotImplementedError, "form_init" 131 132 def form_populate(self): 133 134 "Populate the values in a form." 135 136 raise NotImplementedError, "form_populate" 137 138 def form_refresh(self): 139 140 "Refresh the form." 141 142 raise NotImplementedError, "form_refresh" 143 144 def request_refresh(self, *args, **kw): 145 146 "Request a refresh of the form." 147 148 if not self._refreshed: 149 self._refreshed = 1 150 self.form_refresh(*args, **kw) 151 152 # Standard XSLFormsResource method, overridden to handle presentation. 153 154 def respond_to_form(self, trans, form): 155 156 """ 157 Respond to the request described by the given transaction 'trans', using 158 the given 'form' object to conveniently retrieve field (request 159 parameter) information and structured form information (as DOM-style XML 160 documents). 161 """ 162 163 self._refreshed = 0 164 165 # Ensure the presence of the template. 166 167 self.prepare_output(self.default_design) 168 169 # Remember the document since it is accessed independently elsewhere. 170 171 doc = form.get_document(self.default_design) 172 if doc is None: 173 doc = form.new_document(self.default_design) 174 doc = self._get_initialised_form(doc) 175 self.doc = UINode(doc.xpath("*")[0]) 176 self.form_init() 177 else: 178 doc = self._get_initialised_form(doc) 179 self.doc = UINode(doc.xpath("*")[0]) 180 181 self.form_populate() 182 183 # Updates happen here. 184 185 form.set_document(self.default_design, doc) 186 selectors = form.get_selectors() 187 connections = self.method_resources[self.default_design] 188 for selector_name, selector_values in selectors.items(): 189 if connections.has_key(selector_name): 190 slot = connections[selector_name] 191 if hasattr(self, slot): 192 193 # Initialise the sender. 194 195 for selector_value in selector_values: 196 # NOTE: Fake a special element to simulate the Qt widget hierarchy. 197 # NOTE: We could instead have set the underlying annotations for 198 # NOTE: selector-field differently, but that would be more work. 199 # NOTE: An alternative which works in certain cases is a new 200 # NOTE: attribute whose node is retained. 201 _sender = self.doc._node.ownerDocument.createElement("_sender") 202 self._sender = UINode(selector_value.appendChild(_sender)) 203 getattr(self, slot)() 204 205 # Consistency is ensured and filtering enforced. 206 207 self.request_refresh() 208 #print self.doc._node.toString("iso-8859-1") 209 210 # Output is produced. 211 212 trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding)) 213 design_xsl = self.prepare_output(self.default_design) 214 self.send_output(trans, [design_xsl], doc._node) 215 216 def _get_initialised_form(self, doc): 217 input_xsl = self.prepare_initialiser(self.default_design, init_enumerations=0) 218 return self.get_result([input_xsl], doc) 219 220 class UINode: 221 222 "A PyQt widget tree emulation node." 223 224 def __init__(self, node): 225 self._node = node 226 227 def add(self, node): 228 self._node.appendChild(node._node) 229 230 def child(self, name): 231 nodes = self._node.xpath(name) 232 if len(nodes) > 0: 233 return UINode(nodes[0]) 234 else: 235 return None 236 237 def children(self): 238 return [UINode(node) for node in self._node.childNodes] 239 240 def count(self): 241 return len(self._node.childNodes) 242 243 def currentText(self): 244 return self._node.getAttribute("value") 245 246 def currentItem(self): 247 found = self._node.xpath("*[@value=current()/@value]") 248 if found: 249 return int(found.xpath("count(preceding-sibling::*)")) 250 else: 251 return 0 252 253 def deleteLater(self): 254 pass 255 256 def insertItem(self, item, position=-1): 257 # NOTE: Names invented rather than being extracted from the schema. 258 new_element = self._node.ownerDocument.createElement(self._node.localName + "_enum") 259 new_element.setAttribute("value", item) 260 if position == -1: 261 self._node.appendChild(new_element) 262 else: 263 elements = self._node.xpath("*") 264 if position < len(elements) - 1: 265 self._node.insertBefore(new_element, elements[position]) 266 else: 267 self._node.appendChild(new_element) 268 269 def layout(self): 270 return self 271 272 def parent(self): 273 return UINode(self._node.parentNode) 274 275 def removeItem(self, item): 276 elements = self._node.xpath("*") 277 if item < len(elements): 278 self._node.removeChild(elements[item]) 279 280 def remove(self, item): 281 self._node.removeChild(item._node) 282 283 def setCurrentItem(self, index): 284 pass # NOTE: Not implemented yet! 285 286 def show(self): 287 pass 288 289 class Factory: 290 291 "A widget factory helper class." 292 293 def connect(self, widget, obj): 294 295 """ 296 Connection is done all at once by mapping field names to method names in 297 the resource object. 298 """ 299 300 pass 301 302 def find_widgets(self, widget, name): 303 304 """ 305 Find within the given 'widget' (a DOM node) the widget with the given 306 'name'. 307 """ 308 309 return [UINode(node) for node in widget.doc._node.getElementsByTagName(name)] 310 311 # vim: tabstop=4 expandtab shiftwidth=4