1 #!/usr/bin/env python 2 3 """ 4 PyQt-compatible resources for use with WebStack. 5 6 Copyright (C) 2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 import XSLForms.Prepare 24 import XSLForms.Resources.Common 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.Common.PyQtCommonResource): 32 33 """ 34 An XSLForms resource supporting PyQt-compatible Web applications for use 35 with WebStack. 36 """ 37 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 # Refresh status - avoiding multiple refresh calls. 70 71 self._refreshed = 0 72 73 # Resource methods. 74 75 def prepare_output(self, design_identifier): 76 77 """ 78 Prepare the output stylesheets using the given 'design_identifier' to 79 indicate which templates and stylesheets are to be employed in the 80 production of output from the resource. 81 82 The 'design_identifier' is used as a key to the 'design_resources' and 83 'template_resources' dictionary attributes. 84 85 Return the full path to the output stylesheet for use with 'send_output' 86 or 'get_result'. 87 """ 88 89 design_path = self.prepare_design(design_identifier) 90 template_filename, output_filename = self.template_resources[design_identifier] 91 output_path = os.path.abspath(os.path.join(self.resource_dir, output_filename)) 92 template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename)) 93 XSLForms.Prepare.ensure_qt_template(design_path, template_path) 94 XSLForms.Prepare.ensure_stylesheet(template_path, output_path) 95 return output_path 96 97 # PyQt compatibility methods. 98 99 def get_document(self, document_identifier): 100 return libxml2dom.parse(self.prepare_document(document_identifier)) 101 102 def prepare_widget(self, design_identifier, widget_identifier, parent=None): 103 fragment_name, widget_name = self.widget_resources[widget_identifier] 104 element = UINode(self.doc._node.ownerDocument.createElement(widget_name)) 105 106 # NOTE: Creating an element which may not be appropriate. 107 108 element._node.appendChild(self.doc._node.ownerDocument.createElement(widget_name + "_value")) 109 return element 110 111 def child(self, name): 112 return self.doc.child(name) 113 114 def sender(self): 115 return self._sender 116 117 # PyQt structural methods. 118 119 def form_init(self): 120 121 "Initialise a newly-created form." 122 123 raise NotImplementedError, "form_init" 124 125 def form_populate(self): 126 127 "Populate the values in a form." 128 129 raise NotImplementedError, "form_populate" 130 131 def form_refresh(self): 132 133 "Refresh the form." 134 135 raise NotImplementedError, "form_refresh" 136 137 def request_refresh(self, *args, **kw): 138 139 "Request a refresh of the form." 140 141 if not self._refreshed: 142 self._refreshed = 1 143 self.form_refresh(*args, **kw) 144 145 # Standard XSLFormsResource method, overridden to handle presentation. 146 147 def respond_to_form(self, trans, form): 148 149 """ 150 Respond to the request described by the given transaction 'trans', using 151 the given 'form' object to conveniently retrieve field (request 152 parameter) information and structured form information (as DOM-style XML 153 documents). 154 """ 155 156 self._refreshed = 0 157 158 # Ensure the presence of the template. 159 160 self.prepare_output(self.default_design) 161 162 # Remember the document since it is accessed independently elsewhere. 163 164 doc = form.get_document(self.default_design) 165 if doc is None: 166 doc = form.new_document(self.default_design) 167 doc = self._get_initialised_form(doc) 168 self.doc = UINode(doc.xpath("*")[0]) 169 self.form_init() 170 else: 171 doc = self._get_initialised_form(doc) 172 self.doc = UINode(doc.xpath("*")[0]) 173 174 self.form_populate() 175 176 # Updates happen here. 177 178 form.set_document(self.default_design, doc) 179 selectors = form.get_selectors() 180 connections = self.method_resources[self.default_design] 181 for selector_name, selector_values in selectors.items(): 182 if connections.has_key(selector_name): 183 slot = connections[selector_name] 184 if hasattr(self, slot): 185 186 # Initialise the sender. 187 188 for selector_value in selector_values: 189 # NOTE: Fake a special attribute to simulate the Qt widget hierarchy. 190 selector_value.setAttribute("_sender", "") 191 self._sender = UINode(selector_value.getAttributeNode("_sender")) 192 getattr(self, slot)() 193 194 # Consistency is ensured and filtering enforced. 195 196 self.request_refresh() 197 #print self.doc._node.toString("iso-8859-1") 198 199 # Output is produced. 200 201 trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding)) 202 design_xsl = self.prepare_output(self.default_design) 203 self.send_output(trans, [design_xsl], doc._node) 204 205 def _get_initialised_form(self, doc): 206 input_xsl = self.prepare_initialiser(self.default_design, init_enumerations=0) 207 return self.get_result([input_xsl], doc) 208 209 class UINode: 210 211 "A PyQt widget tree emulation node." 212 213 def __init__(self, node): 214 self._node = node 215 216 def add(self, node): 217 self._node.appendChild(node._node) 218 219 def child(self, name): 220 nodes = self._node.xpath(name) 221 if len(nodes) > 0: 222 return UINode(nodes[0]) 223 else: 224 return None 225 226 def children(self): 227 return [UINode(node) for node in self._node.childNodes] 228 229 def count(self): 230 return len(self._node.childNodes) 231 232 def currentText(self): 233 return self._node.getAttribute("value") 234 235 def currentItem(self): 236 found = self._node.xpath("*[@value=current()/@value]") 237 if found: 238 return int(found.xpath("count(preceding-sibling::*)")) 239 else: 240 return 0 241 242 def deleteLater(self): 243 pass 244 245 def insertItem(self, item, position=-1): 246 # NOTE: Names invented rather than being extracted from the schema. 247 new_element = self._node.ownerDocument.createElement(self._node.localName + "_enum") 248 new_element.setAttribute("value", item) 249 if position == -1: 250 self._node.appendChild(new_element) 251 else: 252 elements = self._node.xpath("*") 253 if position < len(elements) - 1: 254 self._node.insertBefore(new_element, elements[position]) 255 else: 256 self._node.appendChild(new_element) 257 258 def layout(self): 259 return self 260 261 def parent(self): 262 return UINode(self._node.parentNode) 263 264 def removeItem(self, item): 265 elements = self._node.xpath("*") 266 if item < len(elements): 267 self._node.removeChild(elements[item]) 268 269 def remove(self, item): 270 self._node.removeChild(item._node) 271 272 def setCurrentItem(self, index): 273 pass # NOTE: Not implemented yet! 274 275 def show(self): 276 pass 277 278 class Factory: 279 280 "A widget factory helper class." 281 282 def connect(self, widget, obj): 283 284 """ 285 Connection is done all at once by mapping field names to method names in 286 the resource object. 287 """ 288 289 pass 290 291 def find_widgets(self, widget, name): 292 293 """ 294 Find within the given 'widget' (a DOM node) the widget with the given 295 'name'. 296 """ 297 298 return [UINode(node) for node in widget.doc._node.getElementsByTagName(name)] 299 300 # vim: tabstop=4 expandtab shiftwidth=4