# =========================================================================== # eXe # Copyright 2004-2005, University of Auckland # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # =========================================================================== """ The PropertiesPage is for user to enter or edit package's properties. It works with AJAX magic. It has 3 functions: 1. Translate the user interface of the xulpages on the fly (translate) 2. Fill in xul fields from engine object attributes when the page loads (fillInField) 3. Recieve user changes to those attributes (recieveFieldData) All fields are assumed to be string fields, except for a small list of attribute names found in 'PropertiesPage.booleanFieldNames' class attribute. To add any other type of field, create a similar list as this. There is more documentation in the headers of the files in xului/templates diretory about how to add fields etc. """ import logging from exe.webui.renderable import RenderableLivePage from nevow import loaders, inevow, stan from nevow.livepage import handler, js from exe.engine.path import Path, toUnicode import re log = logging.getLogger(__name__) class PropertiesPage(RenderableLivePage): """ The PropertiesPage is for user to enter or edit package's properties """ _templateFileName = 'properties.xul' name = 'properties' # List of field names that contain boolean values booleanFieldNames = ('pp_scolinks', 'pp_backgroundImgTile') # List of field names that contain image values imgFieldNames = ('pp_backgroundImg',) # Used for url encoding with unicode support reUni = re.compile('(%u)([0-9,A-F,a-f]{4})') reChr = re.compile('(%)([0-9,A-F,a-f]{2})') reRaw = re.compile('(.)') def __init__(self, parent): """ Initialize """ RenderableLivePage.__init__(self, parent) mainxul = Path(self.config.xulDir).joinpath('templates', 'properties.xul') self.docFactory = loaders.xmlfile(mainxul) self.client = None self.fieldsReceived = set() # Processing log.info("creating the properties page") def goingLive(self, ctx, client): """Called each time the page is served/refreshed""" inevow.IRequest(ctx).setHeader('content-type', 'application/vnd.mozilla.xul+xml') hndlr = handler(self.fillInField, identifier='fillInField') hndlr(ctx, client) # Stores it hndlr = handler(self.recieveFieldData, identifier='recieveFieldData') hndlr(ctx, client) # Stores it hndlr = handler(self.translate, identifier='translate') hndlr(ctx, client) # Stores it def __getattribute__(self, name): """ Provides auto field fill-ins """ if name.startswith('render_'): localName = name[7:] # Get rid of render # Name contains pp_abc to get package.abc or dc_abc to get dc.abc # pp means Project Properties # dc means Dublin Core # eo means Export Options if '_' in localName: obj, name = self.fieldId2obj(localName) return lambda ctx, data: ctx.tag(value=getattr(obj, localName)) return RenderableLivePage.__getattribute__(self, name) def fieldId2obj(self, fieldId): """ Takes a field id of the form xx_name and returns the object associated with xx and name. These can be used with getattr and setattr """ if '_' in fieldId: part, name = fieldId.split('_', 1) # Get the object if part == 'pp': obj = self.package if part == 'dc': obj = self.package.dublinCore if part == 'eo': obj = self.package.exportOptions if hasattr(obj, name): return obj, name raise ValueError("field id '%s' doesn't refer " "to a valid object attribute" % fieldId) def fillInField(self, client, fieldId): """ Makes the server fill in the value of a field on the client. n:render for people who don't have LivePage objects for xul overlays """ # Get the object obj, name = self.fieldId2obj(fieldId) value = getattr(obj, name) if fieldId in self.booleanFieldNames: client.sendScript(js( 'document.getElementById("%s").checked = %s' % \ (fieldId, str(value).lower()))) elif fieldId in self.imgFieldNames: path = "" if self.package.backgroundImg: path += "resources/%s" % self.package.backgroundImg.basename() client.sendScript(js( 'document.getElementById("%s").src = "%s"' % \ (fieldId, path))) else: # Remove enters encoded = '' for char in value: encoded += '%%u%04x' % ord(char[0]) client.sendScript(js( 'document.getElementById("%s").value = unescape("%s")' % \ (fieldId, encoded))) def recieveFieldData(self, client, fieldId, value, total, onDone=None): """ Called by client to give us a value from a certain field """ total = int(total) self.fieldsReceived.add(fieldId) obj, name = self.fieldId2obj(fieldId) # Decode the value decoded = '' toSearch = value def getMatch(): if toSearch and toSearch[0] == '%': match1 = self.reUni.search(toSearch) match2 = self.reChr.search(toSearch) if match1 and match2: if match1.start() < match2.start(): return match1 else: return match2 else: return match1 or match2 else: return self.reRaw.search(toSearch) match = getMatch() while match: num = match.groups()[-1] if len(num) > 1: decoded += unichr(int(num, 16)) else: decoded += num toSearch = toSearch[match.end():] match = getMatch() # Check the field type if fieldId in self.booleanFieldNames: setattr(obj, name, decoded[0].lower() == 't') elif fieldId in self.imgFieldNames: if not decoded.startswith("resources"): setattr(obj, name, toUnicode(decoded)) else: # Must be a string setattr(obj, name, toUnicode(decoded)) client.sendScript(js( 'document.getElementById("%s").style.color = "black"' % fieldId)) if len(self.fieldsReceived) == total: self.fieldsReceived = set() client.sendScript(js.alert( (u"%s" % _('Settings saved')).encode('utf8'))) if onDone: client.sendScript(js(onDone)) def translate(self, client, elementId, attribute, data): """ Translates a string from an element """ data = unicode(data, 'utf8') if data.strip() and data != 'undefined': newText = _(data) if newText != data and elementId != '': newText = newText.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') if elementId: if attribute.startswith('!contents!'): child = int(attribute[10:]) if child == 0: client.sendScript(js( "document.getElementById(\"%s\").firstChild.data = '%s';" % (elementId, newText.encode('utf-8')))) else: client.sendScript(js( "var snode=0; for (subnode in document.getElementById(\"%s\").childNodes) { if ((snode == %d) && (subnode.nodeName == \"#text\")) { subnode.data = \"%s\" }; snode = snode + 1; };" % (elementId, child, newText.encode('utf-8')))) else: client.sendScript(js( "document.getElementById(\"%s\").setAttribute('%s', '%s');" % (elementId, attribute, newText.encode('utf-8'))))