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 element to simulate the Qt widget hierarchy. 190 # NOTE: We could instead have set the underlying annotations for 191 # NOTE: selector-field differently, but that would be more work. 192 # NOTE: An alternative which works in certain cases is a new 193 # NOTE: attribute whose node is retained. 194 _sender = self.doc._node.ownerDocument.createElement("_sender") 195 self._sender = UINode(selector_value.appendChild(_sender)) 196 getattr(self, slot)() 197 198 # Consistency is ensured and filtering enforced. 199 200 self.request_refresh() 201 #print self.doc._node.toString("iso-8859-1") 202 203 # Output is produced. 204 205 trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding)) 206 design_xsl = self.prepare_output(self.default_design) 207 self.send_output(trans, [design_xsl], doc._node) 208 209 def _get_initialised_form(self, doc): 210 input_xsl = self.prepare_initialiser(self.default_design, init_enumerations=0) 211 return self.get_result([input_xsl], doc) 212 213 class UINode: 214 215 "A PyQt widget tree emulation node." 216 217 def __init__(self, node): 218 self._node = node 219 220 def add(self, node): 221 self._node.appendChild(node._node) 222 223 def child(self, name): 224 nodes = self._node.xpath(name) 225 if len(nodes) > 0: 226 return UINode(nodes[0]) 227 else: 228 return None 229 230 def children(self): 231 return [UINode(node) for node in self._node.childNodes] 232 233 def count(self): 234 return len(self._node.childNodes) 235 236 def currentText(self): 237 return self._node.getAttribute("value") 238 239 def currentItem(self): 240 found = self._node.xpath("*[@value=current()/@value]") 241 if found: 242 return int(found.xpath("count(preceding-sibling::*)")) 243 else: 244 return 0 245 246 def deleteLater(self): 247 pass 248 249 def insertItem(self, item, position=-1): 250 # NOTE: Names invented rather than being extracted from the schema. 251 new_element = self._node.ownerDocument.createElement(self._node.localName + "_enum") 252 new_element.setAttribute("value", item) 253 if position == -1: 254 self._node.appendChild(new_element) 255 else: 256 elements = self._node.xpath("*") 257 if position < len(elements) - 1: 258 self._node.insertBefore(new_element, elements[position]) 259 else: 260 self._node.appendChild(new_element) 261 262 def layout(self): 263 return self 264 265 def parent(self): 266 return UINode(self._node.parentNode) 267 268 def removeItem(self, item): 269 elements = self._node.xpath("*") 270 if item < len(elements): 271 self._node.removeChild(elements[item]) 272 273 def remove(self, item): 274 self._node.removeChild(item._node) 275 276 def setCurrentItem(self, index): 277 pass # NOTE: Not implemented yet! 278 279 def show(self): 280 pass 281 282 class Factory: 283 284 "A widget factory helper class." 285 286 def connect(self, widget, obj): 287 288 """ 289 Connection is done all at once by mapping field names to method names in 290 the resource object. 291 """ 292 293 pass 294 295 def find_widgets(self, widget, name): 296 297 """ 298 Find within the given 'widget' (a DOM node) the widget with the given 299 'name'. 300 """ 301 302 return [UINode(node) for node in widget.doc._node.getElementsByTagName(name)] 303 304 # vim: tabstop=4 expandtab shiftwidth=4