jpayne@68
|
1 "Zoom a window to maximum height."
|
jpayne@68
|
2
|
jpayne@68
|
3 import re
|
jpayne@68
|
4 import sys
|
jpayne@68
|
5 import tkinter
|
jpayne@68
|
6
|
jpayne@68
|
7
|
jpayne@68
|
8 class WmInfoGatheringError(Exception):
|
jpayne@68
|
9 pass
|
jpayne@68
|
10
|
jpayne@68
|
11
|
jpayne@68
|
12 class ZoomHeight:
|
jpayne@68
|
13 # Cached values for maximized window dimensions, one for each set
|
jpayne@68
|
14 # of screen dimensions.
|
jpayne@68
|
15 _max_height_and_y_coords = {}
|
jpayne@68
|
16
|
jpayne@68
|
17 def __init__(self, editwin):
|
jpayne@68
|
18 self.editwin = editwin
|
jpayne@68
|
19 self.top = self.editwin.top
|
jpayne@68
|
20
|
jpayne@68
|
21 def zoom_height_event(self, event=None):
|
jpayne@68
|
22 zoomed = self.zoom_height()
|
jpayne@68
|
23
|
jpayne@68
|
24 if zoomed is None:
|
jpayne@68
|
25 self.top.bell()
|
jpayne@68
|
26 else:
|
jpayne@68
|
27 menu_status = 'Restore' if zoomed else 'Zoom'
|
jpayne@68
|
28 self.editwin.update_menu_label(menu='options', index='* Height',
|
jpayne@68
|
29 label=f'{menu_status} Height')
|
jpayne@68
|
30
|
jpayne@68
|
31 return "break"
|
jpayne@68
|
32
|
jpayne@68
|
33 def zoom_height(self):
|
jpayne@68
|
34 top = self.top
|
jpayne@68
|
35
|
jpayne@68
|
36 width, height, x, y = get_window_geometry(top)
|
jpayne@68
|
37
|
jpayne@68
|
38 if top.wm_state() != 'normal':
|
jpayne@68
|
39 # Can't zoom/restore window height for windows not in the 'normal'
|
jpayne@68
|
40 # state, e.g. maximized and full-screen windows.
|
jpayne@68
|
41 return None
|
jpayne@68
|
42
|
jpayne@68
|
43 try:
|
jpayne@68
|
44 maxheight, maxy = self.get_max_height_and_y_coord()
|
jpayne@68
|
45 except WmInfoGatheringError:
|
jpayne@68
|
46 return None
|
jpayne@68
|
47
|
jpayne@68
|
48 if height != maxheight:
|
jpayne@68
|
49 # Maximize the window's height.
|
jpayne@68
|
50 set_window_geometry(top, (width, maxheight, x, maxy))
|
jpayne@68
|
51 return True
|
jpayne@68
|
52 else:
|
jpayne@68
|
53 # Restore the window's height.
|
jpayne@68
|
54 #
|
jpayne@68
|
55 # .wm_geometry('') makes the window revert to the size requested
|
jpayne@68
|
56 # by the widgets it contains.
|
jpayne@68
|
57 top.wm_geometry('')
|
jpayne@68
|
58 return False
|
jpayne@68
|
59
|
jpayne@68
|
60 def get_max_height_and_y_coord(self):
|
jpayne@68
|
61 top = self.top
|
jpayne@68
|
62
|
jpayne@68
|
63 screen_dimensions = (top.winfo_screenwidth(),
|
jpayne@68
|
64 top.winfo_screenheight())
|
jpayne@68
|
65 if screen_dimensions not in self._max_height_and_y_coords:
|
jpayne@68
|
66 orig_state = top.wm_state()
|
jpayne@68
|
67
|
jpayne@68
|
68 # Get window geometry info for maximized windows.
|
jpayne@68
|
69 try:
|
jpayne@68
|
70 top.wm_state('zoomed')
|
jpayne@68
|
71 except tkinter.TclError:
|
jpayne@68
|
72 # The 'zoomed' state is not supported by some esoteric WMs,
|
jpayne@68
|
73 # such as Xvfb.
|
jpayne@68
|
74 raise WmInfoGatheringError(
|
jpayne@68
|
75 'Failed getting geometry of maximized windows, because ' +
|
jpayne@68
|
76 'the "zoomed" window state is unavailable.')
|
jpayne@68
|
77 top.update()
|
jpayne@68
|
78 maxwidth, maxheight, maxx, maxy = get_window_geometry(top)
|
jpayne@68
|
79 if sys.platform == 'win32':
|
jpayne@68
|
80 # On Windows, the returned Y coordinate is the one before
|
jpayne@68
|
81 # maximizing, so we use 0 which is correct unless a user puts
|
jpayne@68
|
82 # their dock on the top of the screen (very rare).
|
jpayne@68
|
83 maxy = 0
|
jpayne@68
|
84 maxrooty = top.winfo_rooty()
|
jpayne@68
|
85
|
jpayne@68
|
86 # Get the "root y" coordinate for non-maximized windows with their
|
jpayne@68
|
87 # y coordinate set to that of maximized windows. This is needed
|
jpayne@68
|
88 # to properly handle different title bar heights for non-maximized
|
jpayne@68
|
89 # vs. maximized windows, as seen e.g. in Windows 10.
|
jpayne@68
|
90 top.wm_state('normal')
|
jpayne@68
|
91 top.update()
|
jpayne@68
|
92 orig_geom = get_window_geometry(top)
|
jpayne@68
|
93 max_y_geom = orig_geom[:3] + (maxy,)
|
jpayne@68
|
94 set_window_geometry(top, max_y_geom)
|
jpayne@68
|
95 top.update()
|
jpayne@68
|
96 max_y_geom_rooty = top.winfo_rooty()
|
jpayne@68
|
97
|
jpayne@68
|
98 # Adjust the maximum window height to account for the different
|
jpayne@68
|
99 # title bar heights of non-maximized vs. maximized windows.
|
jpayne@68
|
100 maxheight += maxrooty - max_y_geom_rooty
|
jpayne@68
|
101
|
jpayne@68
|
102 self._max_height_and_y_coords[screen_dimensions] = maxheight, maxy
|
jpayne@68
|
103
|
jpayne@68
|
104 set_window_geometry(top, orig_geom)
|
jpayne@68
|
105 top.wm_state(orig_state)
|
jpayne@68
|
106
|
jpayne@68
|
107 return self._max_height_and_y_coords[screen_dimensions]
|
jpayne@68
|
108
|
jpayne@68
|
109
|
jpayne@68
|
110 def get_window_geometry(top):
|
jpayne@68
|
111 geom = top.wm_geometry()
|
jpayne@68
|
112 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
|
jpayne@68
|
113 return tuple(map(int, m.groups()))
|
jpayne@68
|
114
|
jpayne@68
|
115
|
jpayne@68
|
116 def set_window_geometry(top, geometry):
|
jpayne@68
|
117 top.wm_geometry("{:d}x{:d}+{:d}+{:d}".format(*geometry))
|
jpayne@68
|
118
|
jpayne@68
|
119
|
jpayne@68
|
120 if __name__ == "__main__":
|
jpayne@68
|
121 from unittest import main
|
jpayne@68
|
122 main('idlelib.idle_test.test_zoomheight', verbosity=2, exit=False)
|
jpayne@68
|
123
|
jpayne@68
|
124 # Add htest?
|