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., 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 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 trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding)) 212 design_xsl = self.prepare_output(self.default_design) 213 self.send_output(trans, [design_xsl], doc._node) 214 215 def _get_initialised_form(self, doc): 216 input_xsl = self.prepare_initialiser(self.default_design, init_enumerations=0) 217 return self.get_result([input_xsl], doc) 218 219 class UINode: 220 221 "A PyQt widget tree emulation node." 222 223 def __init__(self, node): 224 self._node = node 225 226 def add(self, node): 227 self._node.appendChild(node._node) 228 229 def child(self, name): 230 nodes = self._node.xpath(name) 231 if len(nodes) > 0: 232 return UINode(nodes[0]) 233 else: 234 return None 235 236 def children(self): 237 return [UINode(node) for node in self._node.childNodes] 238 239 def count(self): 240 return len(self._node.childNodes) 241 242 def currentText(self): 243 return self._node.getAttribute("value") 244 245 def currentItem(self): 246 found = self._node.xpath("*[@value=current()/@value]") 247 if found: 248 return int(found.xpath("count(preceding-sibling::*)")) 249 else: 250 return 0 251 252 def deleteLater(self): 253 pass 254 255 def insertItem(self, item, position=-1): 256 # NOTE: Names invented rather than being extracted from the schema. 257 new_element = self._node.ownerDocument.createElement(self._node.localName + "_enum") 258 new_element.setAttribute("value", item) 259 if position == -1: 260 self._node.appendChild(new_element) 261 else: 262 elements = self._node.xpath("*") 263 if position < len(elements) - 1: 264 self._node.insertBefore(new_element, elements[position]) 265 else: 266 self._node.appendChild(new_element) 267 268 def layout(self): 269 return self 270 271 def parent(self): 272 return UINode(self._node.parentNode) 273 274 def removeItem(self, item): 275 elements = self._node.xpath("*") 276 if item < len(elements): 277 self._node.removeChild(elements[item]) 278 279 def remove(self, item): 280 self._node.removeChild(item._node) 281 282 def setCurrentItem(self, index): 283 pass # NOTE: Not implemented yet! 284 285 def show(self): 286 pass 287 288 class Factory: 289 290 "A widget factory helper class." 291 292 def connect(self, widget, obj): 293 294 """ 295 Connection is done all at once by mapping field names to method names in 296 the resource object. 297 """ 298 299 pass 300 301 def find_widgets(self, widget, name): 302 303 """ 304 Find within the given 'widget' (a DOM node) the widget with the given 305 'name'. 306 """ 307 308 return [UINode(node) for node in widget.doc._node.getElementsByTagName(name)] 309 310 # vim: tabstop=4 expandtab shiftwidth=4