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