Skip to content
Merged
115 changes: 97 additions & 18 deletions Lib/idlelib/zoomheight.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,121 @@

import re
import sys
import tkinter

from idlelib import macosx

class WmInfoGatheringError(Exception):
pass


class ZoomHeight:

def __init__(self, editwin):
self.editwin = editwin

# Cached values for maximized window dimensions, one for each set
# of screen dimensions.
self._max_height_and_y_coords = {}
Comment thread
terryjreedy marked this conversation as resolved.
Outdated

@property
Comment thread
terryjreedy marked this conversation as resolved.
Outdated
def top(self):
return self.editwin.top

def zoom_height_event(self, event=None):
top = self.editwin.top
zoomed = zoom_height(top)
menu_status = 'Restore' if zoomed else 'Zoom'
self.editwin.update_menu_label(menu='options', index='* Height',
label=f'{menu_status} Height')
zoomed = self.zoom_height()

if zoomed is not None:
menu_status = 'Restore' if zoomed else 'Zoom'
self.editwin.update_menu_label(menu='options', index='* Height',
label=f'{menu_status} Height')

return "break"

def zoom_height(self):
width, height, x, y = get_window_geometry(self.top)
Comment thread
terryjreedy marked this conversation as resolved.
Outdated

if self.top.wm_state() != 'normal':
# Can't zoom/restore window height for windows not in the 'normal'
# state, e.g. maximized and full-screen windows.
return None
Comment thread
terryjreedy marked this conversation as resolved.

try:
maxheight, maxy = self.get_max_height_and_y_coord()
except WmInfoGatheringError:
return None

if height != maxheight:
# Maximize the window's height.
set_window_geometry(self.top, (width, maxheight, x, maxy))
return True
else:
# Restore the window's height.
#
# .wm_geometry('') makes the window revert to the size requested
# by the widgets it contains.
self.top.wm_geometry('')
return False

def get_max_height_and_y_coord(self):
screen_dimensions = (self.top.winfo_screenwidth(),
self.top.winfo_screenheight())
if screen_dimensions not in self._max_height_and_y_coords:
orig_state = self.top.wm_state()

def zoom_height(top):
# Get window geometry info for maximized windows.
try:
self.top.wm_state('zoomed')
except tkinter.TclError:
# The 'zoomed' state is not supported by some esoteric WMs,
# such as Xvfb.
raise WmInfoGatheringError(
'Failed getting geometry of maximized windows, because ' +
'the "zoomed" window state is unavailable.')
self.top.update()
maxwidth, maxheight, maxx, maxy = get_window_geometry(self.top)
if sys.platform == 'win32':
# On Windows, the returned Y coordinate is the one before
# maximizing, so we use 0 which is correct unless a user puts
# their dock on the top of the screen (very rare).
maxy = 0
maxrooty = self.top.winfo_rooty()

# Get the "root y" coordinate for non-maximized windows with their
# y coordinate set to that of maximized windows. This is needed
# to properly handle different title bar heights for non-maximized
# vs. maximized windows, as seen e.g. in Windows 10.
self.top.wm_state('normal')
self.top.update()
orig_geom = get_window_geometry(self.top)
max_y_geom = orig_geom[:3] + (maxy,)
set_window_geometry(self.top, max_y_geom)
self.top.update()
max_y_geom_rooty = self.top.winfo_rooty()

# Adjust the maximum window height to account for the different
# title bar heights of non-maximized vs. maximized windows.
maxheight += maxrooty - max_y_geom_rooty

self._max_height_and_y_coords[screen_dimensions] = maxheight, maxy

set_window_geometry(self.top, orig_geom)
self.top.wm_state(orig_state)

return self._max_height_and_y_coords[screen_dimensions]


def get_window_geometry(top):
geom = top.wm_geometry()
m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
if not m:
top.bell()
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By PEP 8, this should be return None because of explicit return below. But unpacking None at the call site will crash IDLE anyway. So we might as well remove the check here.

https://www.tcl.tk/man/tcl8.6/TkCmd/tkvars.htm says
"The following variables are only guaranteed to exist in wish executables; the Tk library does not define them itself but many Tk environments do.
geometry
If set, contains the user-supplied geometry specification to use for the main Tk window. "

idlelib, including tests, is loaded with unguarded geometry calls, so IDLE and the test suite would fail on any tk without geometry. So there must be none on systems Python and IDLE currently run on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this part of the code is unchanged...

If the existing norm is not guarding against geometry() returning bad values, I'll remove the check.

width, height, x, y = map(int, m.groups())
newheight = top.winfo_screenheight()

# The constants below for Windows and Mac Aqua are visually determined
# to avoid taskbar or menubar and app icons.
newy, bot_y = ((0, 72) if sys.platform == 'win32' else
(22, 88) if macosx.isAquaTk() else
(0, 88) ) # Guess for anything else.
newheight = newheight - newy - bot_y
newgeom = '' if height >= newheight else f"{width}x{newheight}+{x}+{newy}"
top.wm_geometry(newgeom)
return newgeom != ""
return width, height, x, y

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just return map... and unpack where used.



def set_window_geometry(top, geometry):
top.wm_geometry("{:d}x{:d}{:+d}{:+d}".format(*geometry))


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix incorrect behavior of IDLE's "Zoom Height" command across platforms.