diff --git a/wxPython/scripts/CreateBatchFiles.py b/wxPython/scripts/CreateBatchFiles.py index 5496a7b290..6fb1d8780b 100644 --- a/wxPython/scripts/CreateBatchFiles.py +++ b/wxPython/scripts/CreateBatchFiles.py @@ -27,6 +27,7 @@ scripts = [ ("img2png", 0), ("pyalamode", 1), ("pyalacarte", 1), ("helpviewer", 1), + ("pywxrc", 0), ] template = """\ diff --git a/wxPython/scripts/pywxrc b/wxPython/scripts/pywxrc new file mode 100755 index 0000000000..066caa8e78 --- /dev/null +++ b/wxPython/scripts/pywxrc @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from wx.tools.pywxrc import main +main() + diff --git a/wxPython/wx/tools/pywxrc.py b/wxPython/wx/tools/pywxrc.py new file mode 100644 index 0000000000..178d45a6eb --- /dev/null +++ b/wxPython/wx/tools/pywxrc.py @@ -0,0 +1,711 @@ +#---------------------------------------------------------------------- +# Name: wx.tools.pywxrc +# Purpose: XML resource compiler +# +# Author: Robin Dunn +# Based on wxrc.cpp by Vaclav Slavik, Eduardo Marques +# Ported to Python in order to not require yet another +# binary in wxPython distributions +# +# RCS-ID: $Id$ +# Copyright: (c) 2004 by Total Control Software, 2000 Vaclav Slavik +# Licence: wxWindows license +#---------------------------------------------------------------------- + +""" +pywxrc -- XML resource compiler + +Usage: wxrc [-h] [-v] [-e] [-c] [-p] [-g] [-n ] [-o ] input file(s)... + -h, --help show help message + -v, --verbose be verbose + -e, --extra-cpp-code output C++ header file with XRC derived classes + -c, --cpp-code output C++ source rather than .xrs file + -p, --python-code output wxPython source rather than .rsc file + -g, --gettext output list of translatable strings (to stdout or file if -o used) + -n, --function str C++/Python function name (with -c or -p) [InitXmlResource] + -o, --output str output file [resource.xrs/cpp/py] +""" + +import sys, os, getopt, glob +import wx +import wx.xrc + + +#---------------------------------------------------------------------- + +class XRCWidgetData: + def __init__(self, vname, vclass): + self.name = vname + self.klass = vclass + def GetName(self): + return self.name + def GetClass(self): + return self.klass + + +#---------------------------------------------------------------------- + +class XRCWndClassData: + def __init__(self, className, parentClassName, node): + self.className = className + self.parentClassName = parentClassName + self.BrowseXmlNode(node.GetChildren()) + self.wdata = [] + + + def BrowseXmlNode(self, node): + while node: + if node.GetName() == "object" and node.HasProp("class") and node.HasProp("name"): + classVal = node.GetPropVal("class", "") + nameVal = node.GetPropVal("name", "") + self.wdata.append(XRCWidgetData(nameVal, classVal)) + children = node.GetChildren() + if children: + self.BrowseXmlNode(children) + node = node.GetNext() + + + def GetWidgetData(self): + return self.wdata + + + def IsRealClass(self, name): + if name in ['tool', 'unknown', 'notebookpage', 'separator', + 'sizeritem', 'wxMenuItem']: + return False + else: + return True + + + def GenerateHeaderCode(self, file): + file.write("class %s : public %s {\nprotected:\n" % (self.className, self.parentClassName)) + + for w in self.wdata: + if not self.IsRealClass(w.GetClass()): + continue + if not w.GetName(): + continue + file.write(" " + w.GetClass() + "* " + w.GetName() + ";\n") + + file.write("\nprivate:\n void InitWidgetsFromXRC(){\n", + + " wxXmlResource::Get()->LoadObject(this,NULL,\"" + + self.className + + "\",\"" + + self.parentClassName + + "\");\n"); + + for w in self.wdata: + if not self.IsRealClass(w.GetClass()): + continue + if not w.GetName(): + continue + file.write( " " + + w.GetName() + + " = XRCCTRL(*this,\"" + + w.GetName() + + "\"," + + w.GetClass() + + ");\n") + + file.write(" }\n") + file.write("public:\n" + + self.className + + "::" + + self.className + + "(){\n" + + " InitWidgetsFromXRC();\n" + + " }\n" + + "};\n") + + + +#---------------------------------------------------------------------- + + +class XmlResApp: + def __init__(self): + self.flagVerbose = False + self.flagCPP = False + self.flagH = False + self.flagPython = False + self.flagGettext = False + self.parOutput = "" + self.parFuncname = "InitXmlResource" + self.parFiles = [] + self.aXRCWndClassData = [] + + + #-------------------------------------------------- + def main(self, args): + try: + opts, args = getopt.getopt(args, "hvecpgn:o:", + "help verbose extra-cpp-code cpp-code python-code gettext function= output=".split()) + except getopt.GetoptError: + print __doc__ + sys.exit(1) + + for opt, val in opts: + if opt in ["-h", "--help"]: + print __doc__ + sys.exit(1) + + if opt in ["-v", "--verbose"]: + self.flagVerbose = True + + if opt in ["-e", "--extra-cpp-code"]: + self.flagH = True + + if opt in ["-c", "--cpp-code"]: + self.flagCPP = True + + if opt in ["-p", "--python-code"]: + self.flagPython = True + + if opt in ["-g", "--gettext"]: + self.flagGettext = True + + if opt in ["-n", "--function"]: + self.parFuncname = val + + if opt in ["-o", "--output"]: + self.parOutput = val + + if self.flagCPP + self.flagPython + self.flagGettext == 0: + print __doc__ + print "\nYou must specify one of -c, -p or -g!\n" + sys.exit(1) + + if self.flagCPP + self.flagPython + self.flagGettext > 1: + print __doc__ + print "\n-c, -p and -g are mutually exclusive, specify only 1!\n" + sys.exit(1) + + + if self.parOutput: + self.parOutput = os.path.normpath(self.parOutput) + self.parOutputPath = os.path.split(self.parOutput)[0] + else: + self.parOutputPath = "." + if self.flagCPP: + self.parOutput = "resource.cpp" + elif self.flagPython: + self.parOutput = "resource.py" + elif self.flagGettext: + self.parOutput = "" + else: + self.parOutput = "resource.xrs" + + if not args: + print __doc__ + sys.exit(1) + for arg in args: + self.parFiles += glob.glob(arg) + + self.retCode = 0 + if self.flagGettext: + self.OutputGettext() + else: + self.CompileRes() + + + + #-------------------------------------------------- + def CompileRes(self): + files = self.PrepareTempFiles() + try: + os.unlink(self.parOutput) + except OSError: + pass + + if not self.retCode: + if self.flagCPP: + self.MakePackageCPP(files) + if self.flagH: + self.GenCPPHeader() + + elif self.flagPython: + self.MakePackagePython(files) + + else: + self.MakePackageZIP(files) + + self.DeleteTempFiles(files) + + + #-------------------------------------------------- + def OutputGettext(self): + pass + + + #-------------------------------------------------- + def GetInternalFileName(self, name, flist): + name2 = name; + name2 = name2.replace(":", "_") + name2 = name2.replace("/", "_") + name2 = name2.replace("\\", "_") + name2 = name2.replace("*", "_") + name2 = name2.replace("?", "_") + + s = os.path.split(self.parOutput)[1] + "$" + name2 + + if os.path.exists(s) and s not in flist: + i = 0 + while True: + s = os.path.split(self.parOutput)[1] + ("$%s%03d" % (name2, i)) + if not os.path.exists(s) or s in flist: + break + return s; + + + #-------------------------------------------------- + def PrepareTempFiles(self): + flist = [] + for f in self.parFiles: + if self.flagVerbose: + print "processing %s..." % f + + doc = wx.xrc.EmptyXmlDocument() + + if not doc.Load(f): + print "Error parsing file", f + self.retCode = 1 + continue + + path, name = os.path.split(f) + name, ext = os.path.splitext(name) + + self.FindFilesInXML(doc.GetRoot(), flist, path) + if self.flagH: + node = doc.GetRoot().GetChildren() + while node: + if node.GetName() == "object" and node.HasProp("class") and node.HasProp("name"): + classVal = node.GetPropVal("class", "") + nameVal = node.GetPropVal("name", "") + self.aXRCWndClassData.append(XRCWidgetData(nameVal, classVal)) + node = node.GetNext() + internalName = self.GetInternalFileName(f, flist) + + doc.Save(os.path.join(self.parOutputPath, internalName)) + flist.append(internalName) + + return flist + + + #-------------------------------------------------- + # Does 'node' contain filename information at all? + def NodeContainsFilename(self, node): + # Any bitmaps: + if node.GetName() == "bitmap": + return True + + # URLs in wxHtmlWindow: + if node.GetName() == "url": + return True + + # wxBitmapButton: + parent = node.GetParent() + if parent != None and \ + parent.GetPropVal("class", "") == "wxBitmapButton" and \ + (node.GetName() == "focus" or node.etName() == "disabled" or + node.GetName() == "selected"): + return True + + # wxBitmap or wxIcon toplevel resources: + if node.GetName() == "object": + klass = node.GetPropVal("class", "") + if klass == "wxBitmap" or klass == "wxIcon": + return True + + return False + + #-------------------------------------------------- + # find all files mentioned in structure, e.g. filename + def FindFilesInXML(self, node, flist, inputPath): + # Is 'node' XML node element? + if node is None: return + if node.GetType() != wx.xrc.XML_ELEMENT_NODE: return + + containsFilename = self.NodeContainsFilename(node); + + n = node.GetChildren() + while n: + if (containsFilename and + (n.GetType() == wx.xrc.XML_TEXT_NODE or + n.GetType() == wx.xrc.XML_CDATA_SECTION_NODE)): + + if os.path.isabs(n.GetContent()) or inputPath == "": + fullname = n.GetContent() + else: + fullname = os.path.join(inputPath, n.GetContent()) + + if self.flagVerbose: + print "adding %s..." % fullname + + filename = self.GetInternalFileName(n.GetContent(), flist) + n.SetContent(filename) + + if filename not in flist: + flist.append(filename) + + inp = open(fullname) + out = open(os.path.join(self.parOutputPath, filename), "w") + out.write(inp.read()) + + # subnodes: + if n.GetType() == wx.xrc.XML_ELEMENT_NODE: + self.FindFilesInXML(n, flist, inputPath); + + n = n.GetNext() + + + + #-------------------------------------------------- + def DeleteTempFiles(self, flist): + for f in flist: + os.unlink(os.path.join(self.parOutputPath, f)) + + + #-------------------------------------------------- + def MakePackageZIP(self, flist): + files = " ".join(flist) + + if self.flagVerbose: + print "compressing %s..." % self.parOutput + + cwd = os.getcwd() + os.chdir(self.parOutputPath) + cmd = "zip -9 -j " + if not self.flagVerbose: + cmd += "-q " + cmd += self.parOutput + " " + files + + from distutils.spawn import spawn + try: + spawn(cmd.split()) + success = True + except: + success = False + + os.chdir(cwd) + + if not success: + print "Unable to execute zip program. Make sure it is in the path." + print "You can download it at http://www.cdrom.com/pub/infozip/" + self.retCode = 1 + + + #-------------------------------------------------- + def FileToCppArray(self, filename, num): + output = [] + buffer = open(filename, "rb").read() + lng = len(buffer) + + output.append("static size_t xml_res_size_%d = %d;\n" % (num, lng)) + output.append("static unsigned char xml_res_file_%d[] = {\n" % num) + # we cannot use string literals because MSVC is dumb wannabe compiler + # with arbitrary limitation to 2048 strings :( + + linelng = 0 + for i in xrange(lng): + tmp = "%i" % ord(buffer[i]) + if i != 0: output.append(',') + if linelng > 70: + linelng = 0 + output.append("\n") + + output.append(tmp) + linelng += len(tmp)+1 + + output.append("};\n\n") + + return "".join(output) + + + + #-------------------------------------------------- + def MakePackageCPP(self, flist): + file = open(self.parOutput, "wt") + + if self.flagVerbose: + print "creating C++ source file %s..." % self.parOutput + + file.write("""\ +// +// This file was automatically generated by wxrc, do not edit by hand. +// + +#include + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include +#endif + +#include +#include +#include +#include + +""") + + num = 0 + for f in flist: + file.write(self.FileToCppArray(os.path.join(self.parOutputPath, f), num)) + num += 1 + + + file.write("void " + self.parFuncname + "()\n") + file.write("""\ +{ + + // Check for memory FS. If not present, load the handler: + { + wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/dummy_file\"), wxT(\"dummy one\")); + wxFileSystem fsys; + wxFSFile *f = fsys.OpenFile(wxT(\"memory:XRC_resource/dummy_file\")); + wxMemoryFSHandler::RemoveFile(wxT(\"XRC_resource/dummy_file\")); + if (f) delete f; + else wxFileSystem::AddHandler(new wxMemoryFSHandler); + } +"""); + + for i in range(len(flist)): + file.write(" wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/" + flist[i]) + file.write("\"), xml_res_file_%i, xml_res_size_%i);\n" %(i, i)) + + + for i in range(len(self.parFiles)): + file.write(" wxXmlResource::Get()->Load(wxT(\"memory:XRC_resource/" + + self.GetInternalFileName(self.parFiles[i], flist) + + "\"));\n") + + file.write("}\n") + + + #-------------------------------------------------- + def GenCPPHeader(self): + path, name = os.path.split(self.parOutput) + name, ext = os.path.splitext(name) + heaFileName = name+'.h' + + file = open(heaFileName, "wt") + file.write("""\ +// +// This file was automatically generated by wxrc, do not edit by hand. +// +"""); + file.write("#ifndef __" + name + "_h__\n") + file.write("#define __" + name + "_h__\n") + + for data in self.aXRCWndClassData: + data.GenerateHeaderCode(file) + + file.write("\nvoid \n" + self.parFuncname + "();\n#endif\n") + + + #-------------------------------------------------- + def FileToPythonArray(self, filename, num): + output = [] + buffer = open(filename, "rb").read() + lng = len(buffer) + + output.append(" xml_res_file_%d = '''\\\n" % num) + + linelng = 0 + for i in xrange(lng): + s = buffer[i] + c = ord(s) + if s == '\n': + tmp = s + linelng = 0 + elif c < 32 or c > 127 or s == "'": + tmp = "\\x%02x" % c + elif s == "\\": + tmp = "\\\\" + else: + tmp = s + + if linelng > 70: + linelng = 0 + output.append("\\\n") + + output.append(tmp) + linelng += len(tmp) + + output.append("'''\n\n") + + return "".join(output) + + #-------------------------------------------------- + def MakePackagePython(self, flist): + file = open(self.parOutput, "wt") + + if self.flagVerbose: + print "creating Python source file %s..." % self.parOutput + + file.write("""\ +# +# This file was automatically generated by wxrc, do not edit by hand. +# + +import wx +import wx.xrc + +""") + file.write("def " + self.parFuncname + "():\n") + + num = 0 + for f in flist: + file.write(self.FileToPythonArray(os.path.join(self.parOutputPath, f), num)) + num += 1 + + file.write(""" + + # check if the memory filesystem handler has been loaded yet, and load it if not + wx.MemoryFSHandler.AddFile('XRC_resource/dummy_file', 'dummy value') + fsys = wx.FileSystem() + f = fsys.OpenFile('memory:XRC_resource/dummy_file') + wx.MemoryFSHandler.RemoveFile('XRC_resource/dummy_file') + if f is not None: + f.Destroy() + else: + wx.FileSystem.AddHandler(wx.MemoryFSHandler()) + + # load all the strings as memory files and load into XmlRes +""") + + for i in range(len(flist)): + file.write(" wx.MemoryFSHandler.AddFile('XRC_resource/" + flist[i] + + "', xml_res_file_%i)\n" % i) + + for pf in self.parFiles: + file.write(" wx.xrc.XmlResource.Get().Load('memory:XRC_resource/" + + self.GetInternalFileName(pf, flist) + "')\n") + + + #-------------------------------------------------- + def OutputGettext(self): + strings = self.FindStrings() + + if not self.parOutput: + out = sys.stdout + else: + out = open(self.parOutput, "wt") + + for st in strings: + out.write("_(\"%s\")\n" % st) + + + + #-------------------------------------------------- + def FindStrings(self): + strings = [] + for pf in self.parFiles: + if self.flagVerbose: + print "processing %s..." % pf + + doc = wx.xrc.EmptyXmlDocument() + if not doc.Load(pf): + print "Error parsing file", pf + self.retCode = 1 + continue + + strings += self.FindStringsInNode(doc.GetRoot()) + + return strings + + + #-------------------------------------------------- + def ConvertText(self, st): + st2 = "" + dt = list(st) + + skipNext = False + for i in range(len(dt)): + if skipNext: + skipNext = False + continue + + if dt[i] == '_': + if dt[i+1] == '_': + st2 += '_' + skipNext = True + else: + st2 += '&' + elif dt[i] == '\n': + st2 += '\\n' + elif dt[i] == '\t': + st2 += '\\t' + elif dt[i] == '\r': + st2 += '\\r' + elif dt[i] == '\\': + if dt[i+1] not in ['n', 't', 'r']: + st2 += '\\\\' + else: + st2 += '\\' + elif dt[i] == '"': + st2 += '\\"' + else: + st2 += dt[i] + + return st2 + + + + #-------------------------------------------------- + def FindStringsInNode(self, parent): + def is_number(st): + try: + i = int(st) + return True + except ValueError: + return False + + strings = [] + if parent is None: + return strings; + child = parent.GetChildren() + + while child: + if ((parent.GetType() == wx.xrc.XML_ELEMENT_NODE) and + # parent is an element, i.e. has subnodes... + (child.GetType() == wx.xrc.XML_TEXT_NODE or + child.GetType() == wx.xrc.XML_CDATA_SECTION_NODE) and + # ...it is textnode... + ( + parent.GetName() == "label" or + (parent.GetName() == "value" and + not is_number(child.GetContent())) or + parent.GetName() == "help" or + parent.GetName() == "longhelp" or + parent.GetName() == "tooltip" or + parent.GetName() == "htmlcode" or + parent.GetName() == "title" or + parent.GetName() == "item" + )): + # ...and known to contain translatable string + if (not self.flagGettext or + parent.GetPropVal("translate", "1") != "0"): + + strings.append(self.ConvertText(child.GetContent())) + + # subnodes: + if child.GetType() == wx.xrc.XML_ELEMENT_NODE: + strings += self.FindStringsInNode(child) + + child = child.GetNext() + + return strings + +#--------------------------------------------------------------------------- + +def main(): + XmlResApp().main(sys.argv[1:]) + + +if __name__ == "__main__": + main() +