From: Serhiy Storchaka Date: Mon, 22 Jun 2026 13:53:02 +0000 (+0300) Subject: gh-151881: Add tkinter Menu.postcascade, Misc.tk_scaling and tk_inactive (GH-151882) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6185dfb450a3a76d2451cf26066acaf0fded8bf2;p=thirdparty%2FPython%2Fcpython.git gh-151881: Add tkinter Menu.postcascade, Misc.tk_scaling and tk_inactive (GH-151882) Wrap three long-standing Tk commands that had no tkinter wrapper: * Menu.postcascade() posts the submenu of a cascade entry (Tk 8.5), complementing the existing post() and unpost() methods. * Misc.tk_scaling() queries or sets the scaling factor in pixels per point used to convert between physical units and pixels (Tk 8.4). * Misc.tk_inactive() returns the user idle time in milliseconds, and can reset that timer (Tk 8.5). Co-authored-by: Claude Opus 4.8 --- diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index a9659567eab5..95b4a088ef91 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -1954,6 +1954,26 @@ Base and mixin classes A true *boolean* value enables strict Motif compliance (for example, no color change when the mouse passes over a slider). Return the resulting setting. + + .. method:: tk_scaling(number=None, *, displayof=0) + + Query or set the scaling factor used by Tk to convert between physical + units (such as points, inches or millimeters) and pixels, expressed as + the number of pixels per point (where a point is 1/72 inch). + With no argument, return the current factor; otherwise set it to the + floating-point *number*. + + .. versionadded:: next + + .. method:: tk_inactive(reset=False, *, displayof=0) + + Return the number of milliseconds since the last time the user interacted + with the system, or ``-1`` if the windowing system does not support this. + If *reset* is true, reset the inactivity timer to zero instead and return + ``None``. + + .. versionadded:: next + .. method:: busy(**kw) :no-typesetting: @@ -4727,6 +4747,15 @@ Widget classes If the *postcommand* option has been specified, it is evaluated before the menu is posted. + .. method:: postcascade(index) + + Post the submenu associated with the cascade entry given by *index*, + unposting any previously posted submenu. + This has no effect if *index* does not name a cascade entry or if the + menu itself is not posted. + + .. versionadded:: next + .. method:: tk_popup(x, y, entry='') Post the menu as a popup at the root-window coordinates *x* and *y*. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index ec7329a3d852..1dc6a62cc16d 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -183,6 +183,12 @@ tkinter validation command. (Contributed by Serhiy Storchaka in :gh:`151878`.) +* Added the :meth:`tkinter.Menu.postcascade` method, and the + :meth:`~tkinter.Misc.tk_scaling` and :meth:`~tkinter.Misc.tk_inactive` + methods which respectively query or set the display scaling factor and + report the user idle time. + (Contributed by Serhiy Storchaka in :gh:`151881`.) + * Added new window-management methods :meth:`~tkinter.Misc.winfo_isdark` (dark mode detection), :meth:`~tkinter.Wm.wm_iconbadge` (application icon badge) and :meth:`~tkinter.Wm.wm_stackorder` (toplevel stacking order). diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 77bf84304e78..80dc163fc18d 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -463,6 +463,24 @@ class MiscTest(AbstractTkTest, unittest.TestCase): self.assertEqual(root['background'], '#ffe4c4') self.assertRaises(TypeError, root.tk_bisque, 'x') + def test_tk_scaling(self): + old = self.root.tk_scaling() + self.assertIsInstance(old, float) + self.assertGreater(old, 0) + self.addCleanup(self.root.tk_scaling, old) + # Setting the factor is reflected by a subsequent query. Tk may round + # it slightly when converting to and from its internal representation. + self.root.tk_scaling(2.0) + self.assertAlmostEqual(self.root.tk_scaling(), 2.0, delta=0.1) + + def test_tk_inactive(self): + ms = self.root.tk_inactive() + self.assertIsInstance(ms, int) + # A count of milliseconds, or -1 if the windowing system lacks support. + self.assertGreaterEqual(ms, -1) + # Resetting the timer returns None and does not raise. + self.assertIsNone(self.root.tk_inactive(reset=True)) + def test_wait_variable(self): var = tkinter.StringVar(self.root) self.assertEqual(self.root.waitvar, self.root.wait_variable) diff --git a/Lib/test/test_tkinter/test_widgets.py b/Lib/test/test_tkinter/test_widgets.py index c2117981208d..e388f0e8ed8f 100644 --- a/Lib/test/test_tkinter/test_widgets.py +++ b/Lib/test/test_tkinter/test_widgets.py @@ -2594,6 +2594,35 @@ class MenuTest(AbstractWidgetTest, unittest.TestCase): m.update() self.assertFalse(m.winfo_ismapped()) + def test_postcascade(self): + m = self.create(tearoff=False) + submenu = tkinter.Menu(m, tearoff=False) + submenu.add_command(label='Item') + m.add_cascade(label='Cascade', menu=submenu) + m.add_command(label='Plain') + # No effect (but no error) when the menu is not posted, when the index + # is not a cascade entry, or when given a label. + m.postcascade(0) + m.postcascade(1) + m.postcascade('Cascade') + + with self.subTest('posted menu'): + if m._windowingsystem != 'x11': + # Posting a menu is modal on Windows and uses a native, + # unmapped menu on Aqua, so it cannot be tested synchronously + # there. + self.skipTest('menu posting is not testable on this platform') + m.post(0, 0) + m.update() + m.postcascade('Cascade') + m.update() + self.assertTrue(submenu.winfo_ismapped()) + # A non-cascade index unposts the currently posted submenu. + m.postcascade(1) + m.update() + self.assertFalse(submenu.winfo_ismapped()) + m.unpost() + def check_entry_option(self, m, index, option, value, expected=None): if expected is None: expected = value diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index bf6a75875cfc..7fcc7d764da3 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -745,6 +745,32 @@ class Misc: self.tk.call(('tk_setPalette',) + _flatten(args) + _flatten(list(kw.items()))) + def tk_scaling(self, number=None, *, displayof=0): + """Query or set the scaling factor used by Tk to convert between + physical units and pixels. + + The scaling factor is the number of pixels per point on the display, + where a point is 1/72 inch. With no argument, return the current + factor; otherwise set it to the floating-point NUMBER.""" + args = ('tk', 'scaling') + self._displayof(displayof) + if number is not None: + self.tk.call(args + (number,)) + else: + return self.tk.getdouble(self.tk.call(args)) + + def tk_inactive(self, reset=False, *, displayof=0): + """Return the number of milliseconds since the last time the user + interacted with the system, or -1 if the windowing system does not + support this. + + If RESET is true, reset the inactivity timer to zero instead and + return None.""" + args = ('tk', 'inactive') + self._displayof(displayof) + if reset: + self.tk.call(args + ('reset',)) + else: + return self.tk.getint(self.tk.call(args)) + def wait_variable(self, name='PY_VAR'): """Wait until the variable is modified. @@ -3741,6 +3767,14 @@ class Menu(Widget): """Display a menu at position X,Y.""" self.tk.call(self._w, 'post', x, y) + def postcascade(self, index): + """Post the submenu of the cascade entry at INDEX, unposting any + previously posted submenu. + + Has no effect if INDEX does not name a cascade entry or if this menu + is not posted.""" + self.tk.call(self._w, 'postcascade', index) + def type(self, index): """Return the type of the menu item at INDEX.""" return self.tk.call(self._w, 'type', index) diff --git a/Misc/NEWS.d/next/Library/2026-06-22-01-39-38.gh-issue-151881.ShACSZ.rst b/Misc/NEWS.d/next/Library/2026-06-22-01-39-38.gh-issue-151881.ShACSZ.rst new file mode 100644 index 000000000000..9ae939438e82 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-22-01-39-38.gh-issue-151881.ShACSZ.rst @@ -0,0 +1,4 @@ +Add the :meth:`tkinter.Menu.postcascade` method and the +:meth:`!tkinter.Misc.tk_scaling` and :meth:`!tkinter.Misc.tk_inactive` +methods, wrapping the ``postcascade``, ``tk scaling`` and ``tk inactive`` +Tk commands.