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