From 0378328d45c171dbc0b62da6f8ef9ff7842a0fa5 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Sat, 13 Apr 2019 16:24:46 -0700 Subject: [PATCH] Merge pull request #1201 from RobinD42/fix-issue1198 Fix access to members of transient wx.VisualAttributes (cherry picked from commit d6324a0578a79f4414f5e0f2dabd4127776f3b92) --- CHANGES.rst | 5 ++++- etg/progdlg.py | 2 +- etg/window.py | 23 +++++++++++++++++++++++ etgtools/extractors.py | 3 +++ etgtools/sip_generator.py | 16 +++++++++++++++- etgtools/tweaker_tools.py | 24 ++++++++++++++++++------ unittests/test_window.py | 25 +++++++++++++++++++++++++ 7 files changed, 89 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f8c83f4b..287bd69a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -61,7 +61,10 @@ Changes in this release include the following: * Fix the use of the output parameter in HtmlWindow.OnOpeningURL the same way it was fixed in HtmlWindowInterface.OnHTMLOpeningURL. (#1068) - + +* Fixed a crashing bug when using a member of a transient wx.VisualAttributes + object. Also set the attributes to be read-only to simplify the fix. (#1198). + diff --git a/etg/progdlg.py b/etg/progdlg.py index c592e874..fe47f2d4 100644 --- a/etg/progdlg.py +++ b/etg/progdlg.py @@ -46,7 +46,6 @@ def run(): c = module.find('wxProgressDialog') - tools.fixWindowClass(c) # Copy methods from the generic to the native class. This is needed # because none of the methods are declared in the interface files, and @@ -59,6 +58,7 @@ def run(): not item.isDtor): c.addItem(copy.deepcopy(item)) + tools.fixWindowClass(c) #----------------------------------------------------------------- tools.doCommonTweaks(module) diff --git a/etg/window.py b/etg/window.py index a6b85e5e..7ee3e3b8 100644 --- a/etg/window.py +++ b/etg/window.py @@ -35,6 +35,29 @@ def run(): # Tweak the parsed meta objects in the module object as needed for # customizing the generated code and docstrings. + c = module.find('wxVisualAttributes') + assert isinstance(c, etgtools.ClassDef) + # Mark the stucture memebers as read-only, and make copies of the values + # when fetching them. This is to protect against cases where the + # VisualAttributes object is transient and may be GC'd while we still are + # using a reference to a C++ member value. + c.find('colBg').noSetter = True + c.find('colBg').getCode = """\ + wxColour* clr = new wxColour(sipCpp->colBg); + sipPy = wxPyConstructObject((void*)clr, "wxColour", true); + """ + c.find('colFg').noSetter = True + c.find('colFg').getCode = """\ + wxColour* clr = new wxColour(sipCpp->colFg); + sipPy = wxPyConstructObject((void*)clr, "wxColour", true); + """ + c.find('font').noSetter = True + c.find('font').getCode = """\ + wxFont* font = new wxFont(sipCpp->font); + sipPy = wxPyConstructObject((void*)font, "wxFont", true); + """ + + c = module.find('wxWindow') assert isinstance(c, etgtools.ClassDef) module.addGlobalStr('wxPanelNameStr', c) diff --git a/etgtools/extractors.py b/etgtools/extractors.py index 1302697c..7581b9dc 100644 --- a/etgtools/extractors.py +++ b/etgtools/extractors.py @@ -206,6 +206,7 @@ class VariableDef(BaseDef): self.definition = '' self.argsString = '' self.pyInt = False + self.noSetter = False self.__dict__.update(**kw) if element is not None: self.extract(element) @@ -247,6 +248,8 @@ class MemberVarDef(VariableDef): super(MemberVarDef, self).__init__() self.isStatic = False self.protection = 'public' + self.getCode = '' + self.setCode = '' self.__dict__.update(kw) if element is not None: self.extract(element) diff --git a/etgtools/sip_generator.py b/etgtools/sip_generator.py index be5e4aea..986f95f9 100644 --- a/etgtools/sip_generator.py +++ b/etgtools/sip_generator.py @@ -569,7 +569,19 @@ from .%s import * if memberVar.ignored: return stream.write('%s%s %s' % (indent, memberVar.type, memberVar.name)) - stream.write('%s;\n\n' % self.annotate(memberVar)) + stream.write(self.annotate(memberVar)) + if memberVar.getCode or memberVar.setCode: + stream.write('\n%s{\n' % (indent,)) + if memberVar.getCode: + stream.write('%s%%GetCode\n' % (indent)) + stream.write(nci(memberVar.getCode, len(indent)+4)) + stream.write('%s%%End\n' % (indent)) + if memberVar.setCode: + stream.write('%s%%SetCode\n' % (indent)) + stream.write(nci(memberVar.setCode, len(indent)+4)) + stream.write('%s%%End\n' % (indent)) + stream.write('%s}' % (indent,)) + stream.write(';\n\n') def generateProperty(self, prop, stream, indent): @@ -979,6 +991,8 @@ from .%s import * if isinstance(item, extractors.VariableDef): if item.pyInt: annotations.append('PyInt') + if item.noSetter: + annotations.append('NoSetter') if isinstance(item, extractors.TypedefDef): if item.noTypeName: diff --git a/etgtools/tweaker_tools.py b/etgtools/tweaker_tools.py index 5938312f..337ea971 100644 --- a/etgtools/tweaker_tools.py +++ b/etgtools/tweaker_tools.py @@ -261,17 +261,27 @@ def fixWindowClass(klass, hideVirtuals=True, ignoreProtected=True): removeVirtuals(klass) addWindowVirtuals(klass) - if not klass.findItem('GetClassDefaultAttributes'): - klass.addItem(extractors.WigCode("""\ - static wxVisualAttributes - GetClassDefaultAttributes(wxWindowVariant variant = wxWINDOW_VARIANT_NORMAL); - """)) - if not ignoreProtected: for item in klass.allItems(): if isinstance(item, extractors.MethodDef) and item.protection == 'protected': item.ignore(False) + fixDefaultAttributesMethods(klass) + + +def fixDefaultAttributesMethods(klass): + if not klass.findItem('GetClassDefaultAttributes'): + m = extractors.MethodDef( + type='wxVisualAttributes', name='GetClassDefaultAttributes', + isStatic=True, protection='public', + items=[extractors.ParamDef( + type='wxWindowVariant', name='variant', default='wxWINDOW_VARIANT_NORMAL')] + ) + klass.addItem(m) + + if klass.findItem('GetDefaultAttributes'): + klass.find('GetDefaultAttributes').mustHaveApp() + klass.find('GetClassDefaultAttributes').mustHaveApp() def fixTopLevelWindowClass(klass, hideVirtuals=True, ignoreProtected=True): @@ -308,6 +318,8 @@ def fixTopLevelWindowClass(klass, hideVirtuals=True, ignoreProtected=True): if isinstance(item, extractors.MethodDef) and item.protection == 'protected': item.ignore(False) + fixDefaultAttributesMethods(klass) + def fixSizerClass(klass): diff --git a/unittests/test_window.py b/unittests/test_window.py index 1f82132f..b8132b8e 100644 --- a/unittests/test_window.py +++ b/unittests/test_window.py @@ -137,6 +137,31 @@ class WindowTests(wtc.WidgetTestCase): wx.DLG_PNT + def test_vizattrs1(self): + w = wx.Window(self.frame, -1, (10,10), (50,50)) + a = w.GetClassDefaultAttributes() + assert isinstance(a.colBg, wx.Colour) + assert isinstance(a.colFg, wx.Colour) + assert isinstance(a.font, wx.Font) + + + def test_vizattrs2(self): + w = wx.Window(self.frame, -1, (10,10), (50,50)) + assert isinstance(w.GetClassDefaultAttributes().colBg, wx.Colour) + assert isinstance(w.GetClassDefaultAttributes().colFg, wx.Colour) + assert isinstance(w.GetClassDefaultAttributes().font, wx.Font) + + + def test_vizattrs3(self): + w = wx.Window(self.frame, -1, (10,10), (50,50)) + a = w.GetClassDefaultAttributes() + with self.assertRaises(AttributeError): + a.colBg = wx.Colour('blue') + with self.assertRaises(AttributeError): + a.colFg = wx.Colour('blue') + with self.assertRaises(AttributeError): + a.font = wx.NORMAL_FONT + #---------------------------------------------------------------------------