#----------------------------------------------------------------------
# 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
#
#              Massive rework by Eli Golovinsky
#
# RCS-ID:      $Id$
# Copyright:   (c) 2004 by Total Control Software, 2000 Vaclav Slavik
# Licence:     wxWindows license
#----------------------------------------------------------------------

"""
pywxrc -- Python XML resource compiler

Usage: python pywxrc.py -h
       python pywxrc.py <resource.xrc> [-e] [-o filename]
       
  -h, --help    show help message
  -e, --embed   embed resources in output file
  -o, --output  output filename, or - for stdout
"""

import sys, os, getopt, glob, re
import xml.dom.minidom as minidom
import wx
import wx.xrc

#----------------------------------------------------------------------

class PythonTemplates:
    FILE_HEADER = """\
# This file was automatically generated by pywxrc, do not edit by hand.

import wx
import wx.xrc as xrc

__res = None

def get_resources():
    \"\"\" This function provides access to the XML resources in this module.\"\"\"
    global __res
    if __res == None:
        __init_resources()
    return __res

"""

    CLASS_HEADER = """\
class %(windowName)sBase(wx.%(windowClass)s):
    def PreCreate(self):
        \"\"\" This function is called during the class's initialization.
        
        Override it for custom setup before the window is created usually to
        set additional window styles using SetWindowStyle() and SetExtraStyle().\"\"\"
        pass

    def __init__(self, parent):
        # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
        pre = wx.Pre%(windowClass)s()
        get_resources().LoadOn%(windowClass)s(pre, parent, "%(windowName)s")
        self.PreCreate()
        self.PostCreate(pre)

        # Define variables for the controls
"""

    CREATE_WIDGET_VAR = """\
        self.%(widgetName)s = xrc.XRCCTRL(self, \"%(widgetName)s\")
"""

    INIT_RESOURE_HEADER = """\
# -------------------------------------------------------------
# ------------------------ Resource data ----------------------
# -------------------------------------------------------------

def __init_resources():
"""

    LOAD_RES_FILE = """\
    global __res
    __res = xrc.XmlResource('%(resourceFilename)s')
"""

    FILE_AS_STRING = """\
    %(filename)s = '''\\
%(fileData)s'''


"""

    PREPARE_MEMFS = """\
    # Load all the strings as memory files
    
    wx.FileSystem.AddHandler(wx.MemoryFSHandler())
"""

    ADD_FILE_TO_MEMFS = """\
    wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s)
"""

    LOAD_RES_MEMFS = """\
    global __res
    __res = xrc.EmptyXmlResource()
    __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s')
"""

#----------------------------------------------------------------------

class XmlResourceCompiler:
    
    templates = PythonTemplates()

    """This class generates Python code from XML resource files (XRC)."""

    def MakePythonModule(self, resourceFilename, outputFilename, embedResources=False):
        if outputFilename == "-":
            outputFile = sys.stdout
        else:
            try:
                outputFile = open(outputFilename, "wt")
            except IOError:
                raise IOError("Can't write output to '%s'" % outputFilename)

        resourceDocument = minidom.parse(resourceFilename)
        print >>outputFile, self.templates.FILE_HEADER
        print >>outputFile, self.GenerateClasses(resourceDocument)
        
        if embedResources:
            print >>outputFile, self.GenerateInitResourcesEmbedded(resourceFilename, resourceDocument)
        else:
            print >>outputFile, self.GenerateInitResourcesFile(resourceFilename, resourceDocument)

    #-------------------------------------------------------------------

    def GenerateClasses(self, resourceDocument):
        outputList = []
        
        resource = resourceDocument.firstChild
        topWindows = [e for e in resource.childNodes
                      if e.nodeType == e.ELEMENT_NODE and e.tagName == "object"]
        
        # Generate a class for each top-window object (Frame, Panel, Dialog, etc.)
        for topWindow in topWindows:
            windowClass = topWindow.getAttribute("class")
            windowClass = re.sub("^wx", "", windowClass)
            windowName = topWindow.getAttribute("name")
            outputList.append(self.templates.CLASS_HEADER % locals())
            
            # Generate a variable for each control, and standard event handlers
            # for standard controls.
            for widget in topWindow.getElementsByTagName("object"):
                widgetClass = widget.getAttribute("class")
                widgetClass = re.sub("^wx", "", widgetClass)
                widgetName = widget.getAttribute("name")
                if (widgetName != "" and widgetClass != "" and 
                    widgetClass not in 
                        ['tool', 'unknown', 'notebookpage', 
                         'separator', 'sizeritem', 'MenuItem']):
                    outputList.append(self.templates.CREATE_WIDGET_VAR % locals())
            outputList.append('\n\n')
                    
        return "".join(outputList)

    #-------------------------------------------------------------------

    def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument):
        outputList = []

        outputList.append(self.templates.INIT_RESOURE_HEADER)

        files = []

        resourcePath = os.path.split(resourceFilename)[0]
        memoryPath = self.GetMemoryFilename(os.path.splitext(os.path.split(resourceFilename)[1])[0])
        resourceFilename = self.GetMemoryFilename(os.path.split(resourceFilename)[1])
        
        self.ReplaceFilenamesInXRC(resourceDocument.firstChild, files, resourcePath)
        
        filename = resourceFilename
        fileData = resourceDocument.toxml()
        outputList.append(self.templates.FILE_AS_STRING % locals())

        for f in files:
            filename = self.GetMemoryFilename(f)
            fileData = self.FileToString(os.path.join(resourcePath, f))
            outputList.append(self.templates.FILE_AS_STRING % locals())

        outputList.append(self.templates.PREPARE_MEMFS % locals())
        
        for f in [resourceFilename] + files:
            filename = self.GetMemoryFilename(f)
            outputList.append(self.templates.ADD_FILE_TO_MEMFS % locals())
   
        outputList.append(self.templates.LOAD_RES_MEMFS % locals())
        
        return "".join(outputList)
        
    #-------------------------------------------------------------------

    def GenerateInitResourcesFile(self, resourceFilename, resourceDocument):
        outputList = []
        outputList.append(self.templates.INIT_RESOURE_HEADER)
        outputList.append(self.templates.LOAD_RES_FILE % locals())
        return "".join(outputList)

    #-------------------------------------------------------------------

    def GetMemoryFilename(self, filename):
        # Remove special chars from the filename
        return re.sub(r"[^A-Za-z0-9_]", "_", filename)

    #-------------------------------------------------------------------

    def FileToString(self, filename):
        outputList = []
        
        buffer = open(filename, "rb").read()
        fileLen = len(buffer)

        linelng = 0
        for i in xrange(fileLen):
            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
                outputList.append("\\\n")
            
            outputList.append(tmp)
            linelng += len(tmp)
            
        return "".join(outputList)
            
    #-------------------------------------------------------------------

    def NodeContainsFilename(self, node):
        """ Does 'node' contain filename information at all? """

        # Any bitmaps:
        if node.nodeName == "bitmap":
            return True

        if node.nodeName == "icon":
            return True

        # URLs in wxHtmlWindow:
        if node.nodeName == "url":
            return True

        # wxBitmapButton:
        parent = node.parentNode
        if parent.__class__ != minidom.Document and \
           parent.getAttribute("class") == "wxBitmapButton" and \
           (node.nodeName == "focus" or node.nodeName == "disabled" or
            node.nodeName == "selected"):
            return True

        # wxBitmap or wxIcon toplevel resources:
        if node.nodeName == "object":
            klass = node.getAttribute("class")
            if klass == "wxBitmap" or klass == "wxIcon":
                return True

        return False

    #-------------------------------------------------------------------

    def ReplaceFilenamesInXRC(self, node, files, resourcePath):
        """ Finds all files mentioned in resource file, e.g. <bitmap>filename</bitmap> 
        and replaces them with the memory filenames.
        
        Fills a list of the filenames found."""
        
        # Is 'node' XML node element?
        if node is None: return
        if node.nodeType != minidom.Document.ELEMENT_NODE: return

        containsFilename = self.NodeContainsFilename(node);

        for n in node.childNodes:

            if (containsFilename and
                (n.nodeType == minidom.Document.TEXT_NODE or
                 n.nodeType == minidom.Document.CDATA_SECTION_NODE)):
                
                filename = n.nodeValue
                memoryFilename = self.GetMemoryFilename(filename)
                n.nodeValue = memoryFilename

                if filename not in files:
                    files.append(filename)

            # Recurse into children
            if n.nodeType == minidom.Document.ELEMENT_NODE:
                self.ReplaceFilenamesInXRC(n, files, resourcePath);

#---------------------------------------------------------------------------

def main(args):
    resourceFilename = ""
    outputFilename = ""
    embedResources = False

    try:
        opts, args = getopt.gnu_getopt(args, "heo:", "help embed output=".split())
    except getopt.GetoptError:
        print __doc__
        sys.exit(1)

    # If there is no input file argument, show help and exit
    if args:
        resourceFilename = args[0]
    else:
        print __doc__
        sys.exit(1)

    # Parse options and arguments
    for opt, val in opts:
        if opt in ["-h", "--help"]:
            print __doc__
            sys.exit(1)

        if opt in ["-o", "--output"]:
            outputFilename = val
            
        if opt in ["-e", "--embed"]:
            embedResources = True

    if outputFilename is None or outputFilename == "":
        outputFilename = os.path.splitext(resourceFilename)[0] + "_xrc.py"

    comp = XmlResourceCompiler()
    
    try:
        comp.MakePythonModule(resourceFilename, outputFilename, embedResources)
    except IOError, e:
        print >>sys.stderr, "%s." % str(e)
    else:
        if outputFilename != "-":
            print >>sys.stderr, "Resources written to %s." % outputFilename

if __name__ == "__main__":
    main(sys.argv[1:])

