From 388b3d86bc91d254c49121601290a3d3a65d0598 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Wed, 19 Dec 2012 21:27:23 +0000 Subject: [PATCH] Add tests and support for creating custom XRC handlers and using the Python subclass factory. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@73224 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- etg/_xrc.py | 64 ++++++++++- unittests/test_xrc.py | 207 ++++++++++++++++++++++++++++++++++++ unittests/xrcfactorytest.py | 35 ++++++ 3 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 unittests/xrcfactorytest.py diff --git a/etg/_xrc.py b/etg/_xrc.py index 374c7b34..0dbfe712 100644 --- a/etg/_xrc.py +++ b/etg/_xrc.py @@ -56,15 +56,20 @@ def run(): module.addInclude(INCLUDES) module.addInitializerCode("""\ - wxXmlInitResourceModule(); + //wxXmlInitResourceModule(); + wxXmlResource::Get()->InitAllHandlers(); """) module.addHeaderCode('#include ') module.addHeaderCode('#include ') module.addHeaderCode('#include "wxpybuffer.h"') + + module.insertItem(0, etgtools.WigCode("""\ + // forward declarations + class wxAnimation; + """)) - #----------------------------------------------------------------- - + #----------------------------------------------------------------- c = module.find('wxXmlResource') assert isinstance(c, etgtools.ClassDef) @@ -116,11 +121,27 @@ def run(): """) c.find('AddHandler.handler').transfer = True - c.find('InsertHandler').transfer = True + c.find('InsertHandler.handler').transfer = True c.find('Set.res').transfer = True c.find('Set').transferBack = True + c.find('AddSubclassFactory.factory').transfer = True + #----------------------------------------------------------------- + c = module.find('wxXmlResourceHandler') + + # un-ignore all the protected methods + for item in c.allItems(): + if isinstance(item, etgtools.MethodDef): + item.ignore(False) + + # except for this (for now) + #c.find('GetAnimation').ignore() + + c.find('DoCreateResource').factory = True + + + #----------------------------------------------------------------- module.addPyFunction('EmptyXmlResource', '(flags=XRC_USE_LOCALE, domain="")', deprecated="Use :class:`xrc.XmlResource` instead", doc='A compatibility wrapper for the XmlResource(flags, domain) constructor', @@ -146,7 +167,40 @@ def run(): };""")) - + module.addPyCode("""\ + # Create a factory for handling the subclass property of XRC's + # object tag. This factory will search for the specified + # package.module.class and will try to instantiate it for XRC's + # use. The class must support instantiation with no parameters and + # delayed creation of the UI widget (aka 2-phase create). + + def _my_import(name): + try: + mod = __import__(name) + except ImportError: + import traceback + print traceback.format_exc() + raise + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + class XmlSubclassFactory_Python(XmlSubclassFactory): + def __init__(self): + XmlSubclassFactory.__init__(self) + + def Create(self, className): + assert className.find('.') != -1, "Module name must be specified!" + mname = className[:className.rfind('.')] + cname = className[className.rfind('.')+1:] + module = _my_import(mname) + klass = getattr(module, cname) + inst = klass() + return inst + + XmlResource.AddSubclassFactory(XmlSubclassFactory_Python()) + """) #----------------------------------------------------------------- tools.doCommonTweaks(module) diff --git a/unittests/test_xrc.py b/unittests/test_xrc.py index 97c8d247..9526d237 100644 --- a/unittests/test_xrc.py +++ b/unittests/test_xrc.py @@ -52,6 +52,213 @@ class xrc_Tests(wtc.WidgetTestCase): self.frame.SendSizeEvent() self.myYield() + #--------------------------------------------------------------------------- + # Tests for custom handlers + + # This test does not allow for 2-phase create or creating the instance of + # the resource before filling it with widgets or etc. See also the next + # test and try to keep the two of them in sync as much as possible. + def test_xrc5(self): + resource = ''' + + + 400,250 + This is a test + + + 200,100 + + + 10,10 + + + + ''' + + # this is the class that will be created for the resource + class MyCustomPanel(wx.Panel): + def __init__(self, parent, id, pos, size, style, name): + wx.Panel.__init__(self, parent, id, pos, size, style, name) + + # This is the little bit of customization that we do for this + # silly example. + self.Bind(wx.EVT_SIZE, self.OnSize) + t = wx.StaticText(self, -1, "MyCustomPanel") + f = t.GetFont() + f.SetWeight(wx.BOLD) + f.SetPointSize(f.GetPointSize()+2) + t.SetFont(f) + self.t = t + + def OnSize(self, evt): + sz = self.GetSize() + w, h = self.t.GetTextExtent(self.t.GetLabel()) + self.t.SetPosition(((sz.width-w)/2, (sz.height-h)/2)) + + + # this is the handler class that will create the resource item + class MyCustomPanelXmlHandler(xrc.XmlResourceHandler): + def __init__(self): + xrc.XmlResourceHandler.__init__(self) + # Specify the styles recognized by objects of this type + self.AddStyle("wxTAB_TRAVERSAL", wx.TAB_TRAVERSAL) + self.AddStyle("wxWS_EX_VALIDATE_RECURSIVELY", wx.WS_EX_VALIDATE_RECURSIVELY) + self.AddStyle("wxCLIP_CHILDREN", wx.CLIP_CHILDREN) + self.AddWindowStyles() + + def CanHandle(self, node): + return self.IsOfClass(node, "MyCustomPanel") + + def DoCreateResource(self): + # Ensure that the instance hasn't been created yet (since + # we're not using 2-phase create) + assert self.GetInstance() is None + + # Now create the object + panel = MyCustomPanel(self.GetParentAsWindow(), + self.GetID(), + self.GetPosition(), + self.GetSize(), + self.GetStyle("style", wx.TAB_TRAVERSAL), + self.GetName() + ) + self.SetupWindow(panel) + self.CreateChildren(panel) + return panel + + # now load it + xmlres = xrc.XmlResource() + xmlres.InsertHandler( MyCustomPanelXmlHandler() ) + success = xmlres.LoadFromString(resource) + + f = xmlres.LoadFrame(self.frame, 'MainFrame') + self.assertNotEqual(f, None) + f.Show() + self.myYield() + + panel = xrc.XRCCTRL(f, 'MyPanel') + self.assertNotEqual(panel, None) + self.assertTrue(isinstance(panel, MyCustomPanel)) + + + + # This test shows how to do basically the same as above while still + # allowing the instance to be created before loading the content. + + def test_xrc6(self): + resource = ''' + + + 400,250 + This is a test + + + 200,100 + + + 10,10 + + + + ''' + + # this is the class that will be created for the resource + class MyCustomPanel(wx.Panel): + def __init__(self): + wx.Panel.__init__(self) # create only the instance, not the widget + + def Create(self, parent, id, pos, size, style, name): + wx.Panel.Create(self, parent, id, pos, size, style, name) + self.Bind(wx.EVT_SIZE, self.OnSize) + t = wx.StaticText(self, -1, "MyCustomPanel") + f = t.GetFont() + f.SetWeight(wx.BOLD) + f.SetPointSize(f.GetPointSize()+2) + t.SetFont(f) + self.t = t + + def OnSize(self, evt): + sz = self.GetSize() + w, h = self.t.GetTextExtent(self.t.GetLabel()) + self.t.SetPosition(((sz.width-w)/2, (sz.height-h)/2)) + + + # this is the handler class that will create the resource item + class MyCustomPanelXmlHandler(xrc.XmlResourceHandler): + def __init__(self): + xrc.XmlResourceHandler.__init__(self) + # Specify the styles recognized by objects of this type + self.AddStyle("wxTAB_TRAVERSAL", wx.TAB_TRAVERSAL) + self.AddStyle("wxWS_EX_VALIDATE_RECURSIVELY", wx.WS_EX_VALIDATE_RECURSIVELY) + self.AddStyle("wxCLIP_CHILDREN", wx.CLIP_CHILDREN) + self.AddWindowStyles() + + def CanHandle(self, node): + return self.IsOfClass(node, "MyCustomPanel") + + def DoCreateResource(self): + panel = self.GetInstance() + if panel is None: + # if not, then create the instance (but not the window) + panel = MyCustomPanel() + + # Now create the UI object + panel.Create(self.GetParentAsWindow(), + self.GetID(), + self.GetPosition(), + self.GetSize(), + self.GetStyle("style", wx.TAB_TRAVERSAL), + self.GetName() + ) + self.SetupWindow(panel) + self.CreateChildren(panel) + return panel + + # now load it + xmlres = xrc.XmlResource() + xmlres.InsertHandler( MyCustomPanelXmlHandler() ) + success = xmlres.LoadFromString(resource) + + f = xmlres.LoadFrame(self.frame, 'MainFrame') + self.assertNotEqual(f, None) + f.Show() + self.myYield() + + panel = xrc.XRCCTRL(f, 'MyPanel') + self.assertNotEqual(panel, None) + self.assertTrue(isinstance(panel, MyCustomPanel)) + + + + #--------------------------------------------------------------------------- + # Tests for the Subclass Factory + def test_xrc7(self): + resource = ''' + + + + 200,100 + + + 10,10 + + + ''' + + + # now load it + xmlres = xrc.XmlResource() + success = xmlres.LoadFromString(resource) + + panel = xmlres.LoadPanel(self.frame, "MyPanel") + self.frame.SendSizeEvent() + self.myYield() + + self.assertNotEqual(panel, None) + import xrcfactorytest + self.assertTrue(isinstance(panel, xrcfactorytest.MyCustomPanel)) + + #--------------------------------------------------------------------------- diff --git a/unittests/xrcfactorytest.py b/unittests/xrcfactorytest.py new file mode 100644 index 00000000..e086e6ab --- /dev/null +++ b/unittests/xrcfactorytest.py @@ -0,0 +1,35 @@ +# This class is used by test_xrc7() in test_xrc.py +import wx + + +class MyCustomPanel(wx.Panel): + def __init__(self): + wx.Panel.__init__(self) + # the Create step is done by XRC. + self.Bind(wx.EVT_WINDOW_CREATE, self.OnCreate) + self.Bind(wx.EVT_SIZE, self.OnSize) + + + def OnCreate(self, evt): + # This is the little bit of customization that we do for this + # silly example. It could just as easily have been done in + # the resource. We do it in the EVT_WINDOW_CREATE handler + # because the window doesn't really exist yet in the __init__. + if self is evt.GetEventObject(): + t = wx.StaticText(self, -1, "MyCustomPanel") + f = t.GetFont() + f.SetWeight(wx.BOLD) + f.SetPointSize(f.GetPointSize()+2) + t.SetFont(f) + self.t = t + # On OSX the EVT_SIZE happens before EVT_WINDOW_CREATE !?! + # so give it another kick + wx.CallAfter(self.OnSize, None) + evt.Skip() + + def OnSize(self, evt): + if hasattr(self, 't'): + sz = self.GetSize() + w, h = self.t.GetTextExtent(self.t.GetLabel()) + self.t.SetPosition(((sz.width-w)/2, (sz.height-h)/2)) +