]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-66410: Do not stringify arguments of Tkinter callback (GH-98592)
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 7 May 2024 12:07:32 +0000 (15:07 +0300)
committerGitHub <noreply@github.com>
Tue, 7 May 2024 12:07:32 +0000 (12:07 +0000)
Callbacks registered in the tkinter module now take arguments as
various Python objects (int, float, bytes, tuple), not just str.
To restore the previous behavior set tkinter module global wantobject to 1
before creating the Tk object or call the wantobject() method of the Tk object
with argument 1.
Calling it with argument 2 restores the current default behavior.

Doc/whatsnew/3.13.rst
Lib/idlelib/redirector.py
Lib/test/test_tcl.py
Lib/tkinter/__init__.py
Misc/NEWS.d/next/Library/2022-10-24-12-05-19.gh-issue-66410.du4UKW.rst [new file with mode: 0644]
Modules/_tkinter.c
Modules/clinic/_tkinter.c.h

index b084e78889c81f94b67bb74016fa0c88a83b7987..daa8cf194204fa034aed96835122bc6a979296c7 100644 (file)
@@ -1859,6 +1859,16 @@ Changes in the Python API
   to :c:func:`PyUnstable_Code_GetFirstFree`.
   (Contributed by Bogdan Romanyuk in :gh:`115781`.)
 
+* Callbacks registered in the :mod:`tkinter` module now take arguments as
+  various Python objects (``int``, ``float``, ``bytes``, ``tuple``),
+  not just ``str``.
+  To restore the previous behavior set :mod:`!tkinter` module global
+  :data:`!wantobject` to ``1`` before creating the
+  :class:`!Tk` object or call the :meth:`!wantobject`
+  method of the :class:`!Tk` object with argument ``1``.
+  Calling it with argument ``2`` restores the current default behavior.
+  (Contributed by Serhiy Storchaka in :gh:`66410`.)
+
 
 Build Changes
 =============
index 08728956abd9008ab7a465a2fc06aa1dd85af65e..8e2ba68d3815bf04c58c1fee0f7c9db790eef0c1 100644 (file)
@@ -106,6 +106,7 @@ class WidgetRedirector:
         to *args to accomplish that.  For an example, see colorizer.py.
 
         '''
+        operation = str(operation)  # can be a Tcl_Obj
         m = self._operations.get(operation)
         try:
             if m:
index ebdb58f91d3d8afadc8b649e17a4cf9638a5c1ff..553d54329d7939c3f17169823564a19f86662b6f 100644 (file)
@@ -482,29 +482,36 @@ class TclTest(unittest.TestCase):
             return arg
         self.interp.createcommand('testfunc', testfunc)
         self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
-        def check(value, expected=None, *, eq=self.assertEqual):
-            if expected is None:
-                expected = value
+        def check(value, expected1=None, expected2=None, *, eq=self.assertEqual):
+            expected = value
+            if self.wantobjects >= 2:
+                if expected2 is not None:
+                    expected = expected2
+                expected_type = type(expected)
+            else:
+                if expected1 is not None:
+                    expected = expected1
+                expected_type = str
             nonlocal result
             result = None
             r = self.interp.call('testfunc', value)
-            self.assertIsInstance(result, str)
+            self.assertIsInstance(result, expected_type)
             eq(result, expected)
-            self.assertIsInstance(r, str)
+            self.assertIsInstance(r, expected_type)
             eq(r, expected)
         def float_eq(actual, expected):
             self.assertAlmostEqual(float(actual), expected,
                                    delta=abs(expected) * 1e-10)
 
-        check(True, '1')
-        check(False, '0')
+        check(True, '1', 1)
+        check(False, '0', 0)
         check('string')
         check('string\xbd')
         check('string\u20ac')
         check('string\U0001f4bb')
         if sys.platform != 'win32':
-            check('<\udce2\udc82\udcac>', '<\u20ac>')
-            check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>')
+            check('<\udce2\udc82\udcac>', '<\u20ac>', '<\u20ac>')
+            check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>', '<\U0001f4bb>')
         check('')
         check(b'string', 'string')
         check(b'string\xe2\x82\xac', 'string\xe2\x82\xac')
@@ -526,9 +533,13 @@ class TclTest(unittest.TestCase):
         check(float('inf'), eq=float_eq)
         check(-float('inf'), eq=float_eq)
         # XXX NaN representation can be not parsable by float()
-        check((), '')
-        check((1, (2,), (3, 4), '5 6', ()), '1 2 {3 4} {5 6} {}')
-        check([1, [2,], [3, 4], '5 6', []], '1 2 {3 4} {5 6} {}')
+        check((), '', '')
+        check((1, (2,), (3, 4), '5 6', ()),
+              '1 2 {3 4} {5 6} {}',
+              (1, (2,), (3, 4), '5 6', ''))
+        check([1, [2,], [3, 4], '5 6', []],
+              '1 2 {3 4} {5 6} {}',
+              (1, (2,), (3, 4), '5 6', ''))
 
     def test_splitlist(self):
         splitlist = self.interp.tk.splitlist
index dc6ee9a1b47225784e073505807ed6ee7c5ab0f9..daecf4eb2ea52201c684c69ad76c8bcc77a00707 100644 (file)
@@ -40,7 +40,7 @@ TclError = _tkinter.TclError
 from tkinter.constants import *
 import re
 
-wantobjects = 1
+wantobjects = 2
 _debug = False  # set to True to print executed Tcl/Tk commands
 
 TkVersion = float(_tkinter.TK_VERSION)
@@ -1762,7 +1762,10 @@ class Misc:
         try:
             e.type = EventType(T)
         except ValueError:
-            e.type = T
+            try:
+                e.type = EventType(str(T))  # can be int
+            except ValueError:
+                e.type = T
         try:
             e.widget = self._nametowidget(W)
         except KeyError:
diff --git a/Misc/NEWS.d/next/Library/2022-10-24-12-05-19.gh-issue-66410.du4UKW.rst b/Misc/NEWS.d/next/Library/2022-10-24-12-05-19.gh-issue-66410.du4UKW.rst
new file mode 100644 (file)
index 0000000..044fd18
--- /dev/null
@@ -0,0 +1,7 @@
+Callbacks registered in the :mod:`tkinter` module now take arguments as
+various Python objects (``int``, ``float``, ``bytes``, ``tuple``), not just
+``str``. To restore the previous behavior set :mod:`!tkinter` module global
+:data:`~tkinter.wantobject` to ``1`` before creating the
+:class:`~tkinter.Tk` object or call the :meth:`~tkinter.Tk.wantobject`
+method of the :class:`!Tk` object with argument ``1``. Calling it with
+argument ``2`` restores the current default behavior.
index fc8af244adf4ed4ebb028283d098a0bbefebabe8..c7e271faa4cf34e131817c23e0780151abff73b7 100644 (file)
@@ -2248,7 +2248,7 @@ _tkinter_tkapp_splitlist(TkappObject *self, PyObject *arg)
 
 /* Client data struct */
 typedef struct {
-    PyObject *self;
+    TkappObject *self;
     PyObject *func;
 } PythonCmd_ClientData;
 
@@ -2272,6 +2272,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
     PyObject *args, *res;
     int i;
     Tcl_Obj *obj_res;
+    int objargs = data->self->wantobjects >= 2;
 
     ENTER_PYTHON
 
@@ -2280,7 +2281,8 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
         return PythonCmd_Error(interp);
 
     for (i = 0; i < (objc - 1); i++) {
-        PyObject *s = unicodeFromTclObj(objv[i + 1]);
+        PyObject *s = objargs ? FromObj(data->self, objv[i + 1])
+                              : unicodeFromTclObj(objv[i + 1]);
         if (!s) {
             Py_DECREF(args);
             return PythonCmd_Error(interp);
@@ -2383,7 +2385,8 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
     data = PyMem_NEW(PythonCmd_ClientData, 1);
     if (!data)
         return PyErr_NoMemory();
-    data->self = Py_NewRef(self);
+    Py_INCREF(self);
+    data->self = self;
     data->func = Py_NewRef(func);
     if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
         Tcl_Condition cond = NULL;
@@ -2897,10 +2900,10 @@ Tkapp_WantObjects(PyObject *self, PyObject *args)
 {
 
     int wantobjects = -1;
-    if (!PyArg_ParseTuple(args, "|p:wantobjects", &wantobjects))
+    if (!PyArg_ParseTuple(args, "|i:wantobjects", &wantobjects))
         return NULL;
     if (wantobjects == -1)
-        return PyBool_FromLong(((TkappObject*)self)->wantobjects);
+        return PyLong_FromLong(((TkappObject*)self)->wantobjects);
     ((TkappObject*)self)->wantobjects = wantobjects;
 
     Py_RETURN_NONE;
@@ -3086,7 +3089,7 @@ _tkinter.create
     baseName: str = ""
     className: str = "Tk"
     interactive: bool = False
-    wantobjects: bool = False
+    wantobjects: int = 0
     wantTk: bool = True
         if false, then Tk_Init() doesn't get called
     sync: bool = False
@@ -3102,7 +3105,7 @@ _tkinter_create_impl(PyObject *module, const char *screenName,
                      const char *baseName, const char *className,
                      int interactive, int wantobjects, int wantTk, int sync,
                      const char *use)
-/*[clinic end generated code: output=e3315607648e6bb4 input=09afef9adea70a19]*/
+/*[clinic end generated code: output=e3315607648e6bb4 input=7e382ba431bed537]*/
 {
     /* XXX baseName is not used anymore;
      * try getting rid of it. */
index 192c49dba216e25d6007f49ee931a8074285596f..2b1ac954b4d5701e657e264f6371e1ce3aad6ccd 100644 (file)
@@ -676,7 +676,7 @@ PyDoc_STRVAR(_tkinter__flatten__doc__,
 
 PyDoc_STRVAR(_tkinter_create__doc__,
 "create($module, screenName=None, baseName=\'\', className=\'Tk\',\n"
-"       interactive=False, wantobjects=False, wantTk=True, sync=False,\n"
+"       interactive=False, wantobjects=0, wantTk=True, sync=False,\n"
 "       use=None, /)\n"
 "--\n"
 "\n"
@@ -777,8 +777,8 @@ _tkinter_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
     if (nargs < 5) {
         goto skip_optional;
     }
-    wantobjects = PyObject_IsTrue(args[4]);
-    if (wantobjects < 0) {
+    wantobjects = PyLong_AsInt(args[4]);
+    if (wantobjects == -1 && PyErr_Occurred()) {
         goto exit;
     }
     if (nargs < 6) {
@@ -888,4 +888,4 @@ exit:
 #ifndef _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
     #define _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
 #endif /* !defined(_TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF) */
-/*[clinic end generated code: output=86a515890d48a2ce input=a9049054013a1b77]*/
+/*[clinic end generated code: output=d90c1a9850c63249 input=a9049054013a1b77]*/