From 2340d3bbf68952ac9ac78948ebfa3e2050697e0b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 19 Dec 2020 17:33:55 +0200 Subject: [PATCH 1/3] bpo-42685: Improve placing of simple query windows. * If parent is specified and mapped, the query widget is centered at the center of parent. Its position and size can be corrected so that it fits in the virtual root window. * Otherwise it is centered at the center of the screen. --- Lib/tkinter/simpledialog.py | 21 ++++++++++++++++--- .../2020-12-19-17-32-43.bpo-42685.kwZlwp.rst | 4 ++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-12-19-17-32-43.bpo-42685.kwZlwp.rst diff --git a/Lib/tkinter/simpledialog.py b/Lib/tkinter/simpledialog.py index b882d47c961bdb..2455c56c367a3b 100644 --- a/Lib/tkinter/simpledialog.py +++ b/Lib/tkinter/simpledialog.py @@ -157,9 +157,24 @@ def __init__(self, parent, title = None): self.protocol("WM_DELETE_WINDOW", self.cancel) - if parent is not None: - self.geometry("+%d+%d" % (parent.winfo_rootx()+50, - parent.winfo_rooty()+50)) + minwidth = self.winfo_reqwidth() + minheight = self.winfo_reqheight() + maxwidth = self.winfo_vrootwidth() + maxheight = self.winfo_vrootheight() + if parent is not None and parent.winfo_ismapped(): + x = parent.winfo_rootx() + (parent.winfo_width() - minwidth) // 2 + y = parent.winfo_rooty() + (parent.winfo_height() - minheight) // 2 + vrootx = self.winfo_vrootx() + vrooty = self.winfo_vrooty() + x = min(x, vrootx + maxwidth - minwidth) + x = max(x, vrootx) + y = min(y, vrooty + maxheight - minheight) + y = max(y, vrooty) + else: + x = (self.winfo_screenwidth() - minwidth) // 2 + y = (self.winfo_screenheight() - minheight) // 2 + self.wm_maxsize(maxwidth, maxheight) + self.geometry("+%d+%d" % (x, y)) self.deiconify() # become visible now diff --git a/Misc/NEWS.d/next/Library/2020-12-19-17-32-43.bpo-42685.kwZlwp.rst b/Misc/NEWS.d/next/Library/2020-12-19-17-32-43.bpo-42685.kwZlwp.rst new file mode 100644 index 00000000000000..068546a34c57ae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-19-17-32-43.bpo-42685.kwZlwp.rst @@ -0,0 +1,4 @@ +Improved placing of simple query windows in Tkinter (such as +:func:`tkinter.simpledialog.askinteger`). They are now centered at the +center of the parent window if it is specified and shown, otherwise at the +center of the screen. From 2e48accc7e9b10d286a27e945d120ed76ddbc1ec Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 24 Dec 2020 19:35:45 +0200 Subject: [PATCH 2/3] Reuse the code in SimpleDialog. --- Lib/idlelib/config_key.py | 12 +----- Lib/idlelib/configdialog.py | 1 + Lib/idlelib/debugger.py | 1 + Lib/idlelib/query.py | 12 +----- Lib/tkinter/simpledialog.py | 84 +++++++++++++++---------------------- 5 files changed, 40 insertions(+), 70 deletions(-) diff --git a/Lib/idlelib/config_key.py b/Lib/idlelib/config_key.py index 7510aa9f3d8786..c92229fe2d8994 100644 --- a/Lib/idlelib/config_key.py +++ b/Lib/idlelib/config_key.py @@ -4,6 +4,7 @@ from tkinter import Toplevel, Listbox, StringVar, TclError from tkinter.ttk import Frame, Button, Checkbutton, Entry, Label, Scrollbar from tkinter import messagebox +from tkinter.simpledialog import _place_window import string import sys @@ -80,17 +81,8 @@ def __init__(self, parent, title, action, current_key_sequences, self.modifier_vars.append(variable) self.advanced = False self.create_widgets() - self.update_idletasks() - self.geometry( - "+%d+%d" % ( - parent.winfo_rootx() + - (parent.winfo_width()/2 - self.winfo_reqwidth()/2), - parent.winfo_rooty() + - ((parent.winfo_height()/2 - self.winfo_reqheight()/2) - if not _htest else 150) - ) ) # Center dialog over parent (or below htest box). + _place_window(self, parent) if not _utest: - self.deiconify() # Geometry set, unhide. self.wait_window() def showerror(self, *args, **kwargs): diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index a84e1c5668f99f..9a5effdae5689b 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -21,6 +21,7 @@ import tkinter.colorchooser as tkColorChooser import tkinter.font as tkFont from tkinter import messagebox +from tkinter.simpledialog import _place_window from idlelib.config import idleConf, ConfigChanges from idlelib.config_key import GetKeysDialog diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index ccd03e46e16147..045448b2982822 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -7,6 +7,7 @@ from idlelib import macosx from idlelib.scrolledlist import ScrolledList from idlelib.window import ListedToplevel +from tkinter.simpledialog import _place_window class Idb(bdb.Bdb): diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py index 015fc7ade459db..96370313756942 100644 --- a/Lib/idlelib/query.py +++ b/Lib/idlelib/query.py @@ -28,6 +28,7 @@ from tkinter.ttk import Frame, Button, Entry, Label, Checkbutton from tkinter import filedialog from tkinter.font import Font +from tkinter.simpledialog import _place_window class Query(Toplevel): """Base class for getting verified answer from a user. @@ -74,19 +75,10 @@ def __init__(self, parent, title, message, *, text0='', used_names={}, self.bind("", self.ok) self.create_widgets() - self.update_idletasks() # Need here for winfo_reqwidth below. - self.geometry( # Center dialog over parent (or below htest box). - "+%d+%d" % ( - parent.winfo_rootx() + - (parent.winfo_width()/2 - self.winfo_reqwidth()/2), - parent.winfo_rooty() + - ((parent.winfo_height()/2 - self.winfo_reqheight()/2) - if not _htest else 150) - ) ) + _place_window(self, parent) self.resizable(height=False, width=False) if not _utest: - self.deiconify() # Unhide now that geometry set. self.wait_window() def create_widgets(self, ok_text='OK'): # Do not replace. diff --git a/Lib/tkinter/simpledialog.py b/Lib/tkinter/simpledialog.py index 2455c56c367a3b..638da87c8765d3 100644 --- a/Lib/tkinter/simpledialog.py +++ b/Lib/tkinter/simpledialog.py @@ -55,36 +55,8 @@ def __init__(self, master, b.config(relief=RIDGE, borderwidth=8) b.pack(side=LEFT, fill=BOTH, expand=1) self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window) - self._set_transient(master) - - def _set_transient(self, master, relx=0.5, rely=0.3): - widget = self.root - widget.withdraw() # Remain invisible while we figure out the geometry - widget.transient(master) - widget.update_idletasks() # Actualize geometry information - if master.winfo_ismapped(): - m_width = master.winfo_width() - m_height = master.winfo_height() - m_x = master.winfo_rootx() - m_y = master.winfo_rooty() - else: - m_width = master.winfo_screenwidth() - m_height = master.winfo_screenheight() - m_x = m_y = 0 - w_width = widget.winfo_reqwidth() - w_height = widget.winfo_reqheight() - x = m_x + (m_width - w_width) * relx - y = m_y + (m_height - w_height) * rely - if x+w_width > master.winfo_screenwidth(): - x = master.winfo_screenwidth() - w_width - elif x < 0: - x = 0 - if y+w_height > master.winfo_screenheight(): - y = master.winfo_screenheight() - w_height - elif y < 0: - y = 0 - widget.geometry("+%d+%d" % (x, y)) - widget.deiconify() # Become visible at the desired location + self.root.transient(master) + _place_window(self.root, master) def go(self): self.root.wait_visibility() @@ -157,26 +129,7 @@ def __init__(self, parent, title = None): self.protocol("WM_DELETE_WINDOW", self.cancel) - minwidth = self.winfo_reqwidth() - minheight = self.winfo_reqheight() - maxwidth = self.winfo_vrootwidth() - maxheight = self.winfo_vrootheight() - if parent is not None and parent.winfo_ismapped(): - x = parent.winfo_rootx() + (parent.winfo_width() - minwidth) // 2 - y = parent.winfo_rooty() + (parent.winfo_height() - minheight) // 2 - vrootx = self.winfo_vrootx() - vrooty = self.winfo_vrooty() - x = min(x, vrootx + maxwidth - minwidth) - x = max(x, vrootx) - y = min(y, vrooty + maxheight - minheight) - y = max(y, vrooty) - else: - x = (self.winfo_screenwidth() - minwidth) // 2 - y = (self.winfo_screenheight() - minheight) // 2 - self.wm_maxsize(maxwidth, maxheight) - self.geometry("+%d+%d" % (x, y)) - - self.deiconify() # become visible now + _place_window(self, parent) self.initial_focus.focus_set() @@ -266,6 +219,37 @@ def apply(self): pass # override +# Place a toplevel window at the center of parent or screen +# It is a Python implementation of ::tk::PlaceWindow. +def _place_window(w, parent=None): + w.wm_withdraw() # Remain invisible while we figure out the geometry + w.update_idletasks() # Actualize geometry information + + minwidth = w.winfo_reqwidth() + minheight = w.winfo_reqheight() + maxwidth = w.winfo_vrootwidth() + maxheight = w.winfo_vrootheight() + if parent is not None and parent.winfo_ismapped(): + x = parent.winfo_rootx() + (parent.winfo_width() - minwidth) // 2 + y = parent.winfo_rooty() + (parent.winfo_height() - minheight) // 2 + vrootx = w.winfo_vrootx() + vrooty = w.winfo_vrooty() + x = min(x, vrootx + maxwidth - minwidth) + x = max(x, vrootx) + y = min(y, vrooty + maxheight - minheight) + y = max(y, vrooty) + if w._windowingsystem == 'aqua': + # Avoid the native menu bar which sits on top of everything. + y = max(y, 22) + else: + x = (w.winfo_screenwidth() - minwidth) // 2 + y = (w.winfo_screenheight() - minheight) // 2 + + w.wm_maxsize(maxwidth, maxheight) + w.wm_geometry('+%d+%d' % (x, y)) + w.wm_deiconify() # Become visible at the desired location + + # -------------------------------------------------------------------- # convenience dialogues From f60bf90f35bc092f3ac7218deb77d19f914e7729 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 24 Dec 2020 19:41:29 +0200 Subject: [PATCH 3/3] Revert preliminary changes in IDLE. --- Lib/idlelib/config_key.py | 12 ++++++++++-- Lib/idlelib/configdialog.py | 1 - Lib/idlelib/debugger.py | 1 - Lib/idlelib/query.py | 12 ++++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Lib/idlelib/config_key.py b/Lib/idlelib/config_key.py index c92229fe2d8994..7510aa9f3d8786 100644 --- a/Lib/idlelib/config_key.py +++ b/Lib/idlelib/config_key.py @@ -4,7 +4,6 @@ from tkinter import Toplevel, Listbox, StringVar, TclError from tkinter.ttk import Frame, Button, Checkbutton, Entry, Label, Scrollbar from tkinter import messagebox -from tkinter.simpledialog import _place_window import string import sys @@ -81,8 +80,17 @@ def __init__(self, parent, title, action, current_key_sequences, self.modifier_vars.append(variable) self.advanced = False self.create_widgets() - _place_window(self, parent) + self.update_idletasks() + self.geometry( + "+%d+%d" % ( + parent.winfo_rootx() + + (parent.winfo_width()/2 - self.winfo_reqwidth()/2), + parent.winfo_rooty() + + ((parent.winfo_height()/2 - self.winfo_reqheight()/2) + if not _htest else 150) + ) ) # Center dialog over parent (or below htest box). if not _utest: + self.deiconify() # Geometry set, unhide. self.wait_window() def showerror(self, *args, **kwargs): diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 9a5effdae5689b..a84e1c5668f99f 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -21,7 +21,6 @@ import tkinter.colorchooser as tkColorChooser import tkinter.font as tkFont from tkinter import messagebox -from tkinter.simpledialog import _place_window from idlelib.config import idleConf, ConfigChanges from idlelib.config_key import GetKeysDialog diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 045448b2982822..ccd03e46e16147 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -7,7 +7,6 @@ from idlelib import macosx from idlelib.scrolledlist import ScrolledList from idlelib.window import ListedToplevel -from tkinter.simpledialog import _place_window class Idb(bdb.Bdb): diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py index 96370313756942..015fc7ade459db 100644 --- a/Lib/idlelib/query.py +++ b/Lib/idlelib/query.py @@ -28,7 +28,6 @@ from tkinter.ttk import Frame, Button, Entry, Label, Checkbutton from tkinter import filedialog from tkinter.font import Font -from tkinter.simpledialog import _place_window class Query(Toplevel): """Base class for getting verified answer from a user. @@ -75,10 +74,19 @@ def __init__(self, parent, title, message, *, text0='', used_names={}, self.bind("", self.ok) self.create_widgets() - _place_window(self, parent) + self.update_idletasks() # Need here for winfo_reqwidth below. + self.geometry( # Center dialog over parent (or below htest box). + "+%d+%d" % ( + parent.winfo_rootx() + + (parent.winfo_width()/2 - self.winfo_reqwidth()/2), + parent.winfo_rooty() + + ((parent.winfo_height()/2 - self.winfo_reqheight()/2) + if not _htest else 150) + ) ) self.resizable(height=False, width=False) if not _utest: + self.deiconify() # Unhide now that geometry set. self.wait_window() def create_widgets(self, ok_text='OK'): # Do not replace.