Merge pull request #897 from RobinD42/add-windowidref

Add wx.WindowIDRef and wx.NewIdRef
This commit is contained in:
Robin Dunn
2018-06-24 21:36:52 -07:00
parent 3407c57980
commit b5bb587145
12 changed files with 170 additions and 19 deletions

View File

@@ -53,7 +53,7 @@ Pip: ``pip install wxPython==4.0.3``
Changes in this release include the following:
* Fixed a linking problem on macOS. The new waf added an explicit link to the
Python shared library which menat that it would try to load it at runtime,
Python shared library which meant that it would try to load it at runtime,
even if a different Python (such as Anaconda, EDM or Homebrew) was used to
import wxPython. This, of course, caused runtime errors. (#892)
@@ -63,6 +63,14 @@ Changes in this release include the following:
* Added Vagrant configuration for Fedora-28. Removed Fedora-23 (#884)
* Added wrappers for the wx.WindowIDRef class and added the wx.NewIdRef
function. These will make it possible to create reserved Window IDs using the
same mechanism which is used when passing wx.ID_ANY to a widget constructor.
The object returned by wx.NewIdRef will automatically convert to an int when
passing it to a window constructor, and can also be used as the source in a
Bind().
4.0.2 "Cute as a June bug!"
---------------------------

View File

@@ -290,6 +290,7 @@ def main(wxDir, args):
"--enable-optimise",
"--disable-debugreport",
"--enable-uiactionsim",
"--enable-autoidman",
]
if sys.platform.startswith("darwin"):

View File

@@ -24,7 +24,7 @@ class AppI18N(sc.SizedFrame):
# file menu
fileMenu = wx.Menu()
closeMenuItem = fileMenu.Append(wx.NewId(),
closeMenuItem = fileMenu.Append(wx.ID_ANY,
_(u"Close"),
_(u"Close the application"))
self.Bind(wx.EVT_MENU, self.onClose, closeMenuItem)
@@ -32,7 +32,7 @@ class AppI18N(sc.SizedFrame):
# edit menu
manageMenu = wx.Menu()
manageSomethingMenuItem = manageMenu.Append(wx.NewId(),
manageSomethingMenuItem = manageMenu.Append(wx.ID_ANY,
_(u"Edit something"),
_(u"Edit an entry of something"))
self.Bind(wx.EVT_MENU, self.doEditSomething, manageSomethingMenuItem)
@@ -41,7 +41,7 @@ class AppI18N(sc.SizedFrame):
# help menu
helpMenu = wx.Menu()
aboutMenuItem = helpMenu.Append(wx.NewId(),
aboutMenuItem = helpMenu.Append(wx.ID_ANY,
_(u"&About"),
_(u"About the program"))
self.Bind(wx.EVT_MENU, self.doAboutBox, aboutMenuItem)

View File

@@ -2378,6 +2378,7 @@
"NavigationKeyEventFlags":"wx.NavigationKeyEvent.",
"NewEventType":"wx.",
"NewId":"wx.",
"NewIdRef":"wx.",
"NonOwnedWindow":"wx.",
"Notebook":"wx.",
"NotebookNameStr":"wx.",
@@ -6674,6 +6675,7 @@
"WindowDestroyEvent":"wx.",
"WindowDisabler":"wx.",
"WindowID":"wx.",
"WindowIDRef":"wx.",
"WindowModalDialogEvent":"wx.",
"WindowVariant":"wx.",
"WithImages":"wx.",

View File

@@ -408,6 +408,6 @@ user-specified identifiers which must be always positive.
You can use ``wx.ID_HIGHEST`` to determine the number above which it is
safe to define your own identifiers. Or, you can use identifiers below
``wx.ID_LOWEST``. Finally, you can allocate identifiers dynamically
using :func:`wx.NewId` function too. If you use :func:`wx.NewId`
using :func:`wx.NewIdRef` function too. If you use :func:`wx.NewIdRef`
consistently in your application, you can be sure that your
identifiers don't conflict accidentally.

View File

@@ -26,7 +26,7 @@ used by the user code and also are sometimes used by wxPython
itself. These reserved identifiers are all in the range between
``wx.ID_LOWEST`` and ``wx.ID_HIGHEST`` and, accordingly, the user code
should avoid defining its own constants in this range (e.g. by using
:func:`wx.NewId` ()).
:func:`wx.NewIdRef` ()).
Refer to :ref:`the list of stock items <stock items>` for the subset
of standard IDs which are stock IDs as well.

View File

@@ -87,6 +87,15 @@ def run():
c.addPyMethod('__enter__', '(self)', 'return self')
c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'pass')
f = module.find('wxNewId')
f.clearDeprecated()
f.deprecated = """\
IDs generated by this function can possibly conflict with IDs used elsewhere in
the application code. It is recommended to instead use the ``wx.ID_ANY`` ID to
assign generated IDs for the controls menu items and etc. that you create in the
application. These IDs are guaranteed to not conflict with the other IDs that
are in use in the application. For those cases where you need to create an ID
that can be used more than once then please see :func:`wx.NewIdRef`.""".replace('\n', ' ')
for funcname in ['wxBell',
'wxBeginBusyCursor',
@@ -98,9 +107,10 @@ def run():
'wxGetKeyState',
'wxGetMouseState',
]:
c = module.find(funcname)
c.mustHaveApp()
f = module.find(funcname)
f.mustHaveApp()
#-----------------------------------------------------------------
tools.doCommonTweaks(module)

View File

@@ -9,6 +9,7 @@
import etgtools
import etgtools.tweaker_tools as tools
from etgtools import ClassDef, MethodDef, ParamDef
PACKAGE = "wx"
MODULE = "_core"
@@ -30,10 +31,82 @@ def run():
# Tweak the parsed meta objects in the module object as needed for
# customizing the generated code and docstrings.
c = module.find('wxIdManager')
assert isinstance(c, etgtools.ClassDef)
# no tweaks needed for this class
# wxWindowIDRef is not documented (and probably rightly so) but we're going
# to use it from Python anyway to help with preallocating IDs in a way that
# allows them to be reused and be also be protected from conflicts from
# other auto allocated IDs.
# First, add defintions of the existing C++ class and its elements
klass = ClassDef(name='wxWindowIDRef', bases = [],
briefDoc="""\
A wxWindowIDRef object wraps an ID value and marks it as being in-use until all references to that ID are gone.
""",
items = [
MethodDef(name='wxWindowIDRef', className='wxWindowIDRef', isCtor=True,
briefDoc='Default constructor',
overloads=[
MethodDef(name='wxWindowIDRef', className='wxWindowIDRef', isCtor=True,
briefDoc='Create reference from an ID',
items=[ ParamDef(type='int', name='id') ]),
MethodDef(name='wxWindowIDRef', className='wxWindowIDRef', isCtor=True,
briefDoc='Copy an ID reference',
items=[ ParamDef(type='const wxWindowIDRef&', name='idref') ]),
]),
MethodDef(name='~wxWindowIDRef', className='wxWindowIDRef', isDtor=True),
MethodDef(type='int', name='GetValue',
briefDoc='Get the ID value'),
])
# Now tweak it a bit
klass.addCppMethod('int', 'GetId', '()',
doc="Alias for GetValue allowing the IDRef to be passed as the source parameter to :meth:`wx.EvtHandler.Bind`.",
body="""\
return self->GetValue();
""")
klass.addCppMethod('int', '__int__', '()',
doc="Alias for GetValue allowing the IDRef to be passed as the WindowID parameter when creating widgets or etc.",
body="""\
return self->GetValue();
""")
klass.addPyMethod('__repr__', '(self)', 'return "WindowIDRef: {}".format(self.GetId())')
# and finish it up by adding it to the module
module.addItem(klass)
# Now, let's add a new Python function to the global scope that reserves an
# ID (or range) and returns a ref object for it.
module.addPyFunction('NewIdRef', '(count=1)',
doc="""\
Reserves a new Window ID (or range of WindowIDs) and returns a
:class:`wx.WindowIDRef` object (or list of them) that will help
manage the reservation of that ID.
This function is intended to be a drop-in replacement of the old
and deprecated :func:`wx.NewId` function, with the added benefit
that the ID should never conflict with an in-use ID or other IDs
generated by this function.
""",
body="""\
if count == 1:
return WindowIDRef(IdManager.ReserveId())
else:
start = IdManager.ReserveId(count)
IDRefs = []
for id in range(start, start+count):
IDRefs.append(WindowIDRef(id))
return IDRefs
""")
#-----------------------------------------------------------------
tools.doCommonTweaks(module)

View File

@@ -26,10 +26,6 @@ from sphinxtools.utilities import findDescendants
#---------------------------------------------------------------------------
# These classes simply hold various bits of information about the classes,
# methods, functions and other items in the C/C++ API being wrapped.
#
# NOTE: Currently very little is being done with the docstrings. They can
# either be reprocessed later by the document generator or we can do more
# tinkering with them here. It just depends on decisions not yet made...
#---------------------------------------------------------------------------
class BaseDef(object):
@@ -83,10 +79,21 @@ class BaseDef(object):
itemid = item.get('id')
if itemid and itemid.startswith('deprecated'):
self.deprecated = True
break
return
if self.deprecated:
break
def clearDeprecated(self):
"""
Remove the deprecation notice from the detailedDoc, if any, and reset
self.deprecated to False.
"""
self.deprecated = False
for para in self.detailedDoc:
for item in para.iter():
itemid = item.get('id')
if itemid and itemid.startswith('deprecated'):
self.detailedDoc.remove(para)
return
def ignore(self, val=True):

View File

@@ -1979,13 +1979,13 @@ class XMLDocString(object):
else:
raise Exception('Unhandled docstring kind for %s'%xml_item.__class__.__name__)
# Some of the Exctractors (xml item) will set deprecated themselves, in which case it is set as a
# Some of the Extractors (xml item) will set deprecated themselves, in which case it is set as a
# non-empty string. In such cases, this branch will insert a deprecated section into the xml tree
# so that the Node Tree (see classes above) will generate the deprecated tag on their own in self.RecurseXML
if hasattr(xml_item, 'deprecated') and xml_item.deprecated and isinstance(xml_item.deprecated, string_base):
element = et.Element('deprecated', kind='deprecated')
element.text = xml_item.deprecated
deprecated_section = Section(element, None, self.kind, self.is_overload, self.share_docstrings)
self.root.AddSection(deprecated_section)

View File

@@ -199,6 +199,10 @@ void wxPyCoreModuleInject(PyObject* moduleDict)
PyList_Append(PlatformInfo, obj);
Py_DECREF(obj);
#if wxUSE_AUTOID_MANAGEMENT
_AddInfoString("autoidman");
#endif
#undef _AddInfoString
PyObject* PlatformInfoTuple = PyList_AsTuple(PlatformInfo);

View File

@@ -1,18 +1,64 @@
import unittest
import wx
from unittests import wtc
#---------------------------------------------------------------------------
class IdManagerTest(unittest.TestCase):
class IdManagerTest(wtc.WidgetTestCase):
def test_idManager(self):
id = wx.IdManager.ReserveId(5)
self.assertTrue(id != wx.ID_NONE)
wx.IdManager.UnreserveId(id, 5)
def test_newIdRef01(self):
id = wx.NewIdRef()
assert isinstance(id, wx.WindowIDRef)
id.GetValue()
id.GetId()
id.__int__()
def test_newIdRef02(self):
refs = wx.NewIdRef(5)
assert len(refs) == 5
for ref in refs:
assert isinstance(ref, wx.WindowIDRef)
def test_newIdRef03(self):
"""Check that Auto ID Management is enabled (--enable-autoidman)"""
# This test is expected to fail if autoID mangagement is turned on
# because a reference to the ID is not being saved, so it will be
# unreserved when the first widget is destroyed.
id = wx.Window.NewControlId()
b = wx.Button(self.frame, id, 'button')
b.Destroy()
self.myYield()
with self.assertRaises(wx.wxAssertionError):
b = wx.Button(self.frame, id, 'button')
b.Destroy()
def test_newIdRef04(self):
"""Ensure that an ID can be used more than once"""
id = wx.NewIdRef() # but using this one should succeed
b = wx.Button(self.frame, id, 'button')
b.Destroy()
self.myYield()
b = wx.Button(self.frame, id, 'button')
b.Destroy()
#---------------------------------------------------------------------------