17db96d56Sopenharmony_ci"Zoom a window to maximum height."
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciimport re
47db96d56Sopenharmony_ciimport sys
57db96d56Sopenharmony_ciimport tkinter
67db96d56Sopenharmony_ci
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_ciclass WmInfoGatheringError(Exception):
97db96d56Sopenharmony_ci    pass
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ciclass ZoomHeight:
137db96d56Sopenharmony_ci    # Cached values for maximized window dimensions, one for each set
147db96d56Sopenharmony_ci    # of screen dimensions.
157db96d56Sopenharmony_ci    _max_height_and_y_coords = {}
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci    def __init__(self, editwin):
187db96d56Sopenharmony_ci        self.editwin = editwin
197db96d56Sopenharmony_ci        self.top = self.editwin.top
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ci    def zoom_height_event(self, event=None):
227db96d56Sopenharmony_ci        zoomed = self.zoom_height()
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_ci        if zoomed is None:
257db96d56Sopenharmony_ci            self.top.bell()
267db96d56Sopenharmony_ci        else:
277db96d56Sopenharmony_ci            menu_status = 'Restore' if zoomed else 'Zoom'
287db96d56Sopenharmony_ci            self.editwin.update_menu_label(menu='options', index='* Height',
297db96d56Sopenharmony_ci                                           label=f'{menu_status} Height')
307db96d56Sopenharmony_ci
317db96d56Sopenharmony_ci        return "break"
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ci    def zoom_height(self):
347db96d56Sopenharmony_ci        top = self.top
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_ci        width, height, x, y = get_window_geometry(top)
377db96d56Sopenharmony_ci
387db96d56Sopenharmony_ci        if top.wm_state() != 'normal':
397db96d56Sopenharmony_ci            # Can't zoom/restore window height for windows not in the 'normal'
407db96d56Sopenharmony_ci            # state, e.g. maximized and full-screen windows.
417db96d56Sopenharmony_ci            return None
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ci        try:
447db96d56Sopenharmony_ci            maxheight, maxy = self.get_max_height_and_y_coord()
457db96d56Sopenharmony_ci        except WmInfoGatheringError:
467db96d56Sopenharmony_ci            return None
477db96d56Sopenharmony_ci
487db96d56Sopenharmony_ci        if height != maxheight:
497db96d56Sopenharmony_ci            # Maximize the window's height.
507db96d56Sopenharmony_ci            set_window_geometry(top, (width, maxheight, x, maxy))
517db96d56Sopenharmony_ci            return True
527db96d56Sopenharmony_ci        else:
537db96d56Sopenharmony_ci            # Restore the window's height.
547db96d56Sopenharmony_ci            #
557db96d56Sopenharmony_ci            # .wm_geometry('') makes the window revert to the size requested
567db96d56Sopenharmony_ci            # by the widgets it contains.
577db96d56Sopenharmony_ci            top.wm_geometry('')
587db96d56Sopenharmony_ci            return False
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci    def get_max_height_and_y_coord(self):
617db96d56Sopenharmony_ci        top = self.top
627db96d56Sopenharmony_ci
637db96d56Sopenharmony_ci        screen_dimensions = (top.winfo_screenwidth(),
647db96d56Sopenharmony_ci                             top.winfo_screenheight())
657db96d56Sopenharmony_ci        if screen_dimensions not in self._max_height_and_y_coords:
667db96d56Sopenharmony_ci            orig_state = top.wm_state()
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ci            # Get window geometry info for maximized windows.
697db96d56Sopenharmony_ci            try:
707db96d56Sopenharmony_ci                top.wm_state('zoomed')
717db96d56Sopenharmony_ci            except tkinter.TclError:
727db96d56Sopenharmony_ci                # The 'zoomed' state is not supported by some esoteric WMs,
737db96d56Sopenharmony_ci                # such as Xvfb.
747db96d56Sopenharmony_ci                raise WmInfoGatheringError(
757db96d56Sopenharmony_ci                    'Failed getting geometry of maximized windows, because ' +
767db96d56Sopenharmony_ci                    'the "zoomed" window state is unavailable.')
777db96d56Sopenharmony_ci            top.update()
787db96d56Sopenharmony_ci            maxwidth, maxheight, maxx, maxy = get_window_geometry(top)
797db96d56Sopenharmony_ci            if sys.platform == 'win32':
807db96d56Sopenharmony_ci                # On Windows, the returned Y coordinate is the one before
817db96d56Sopenharmony_ci                # maximizing, so we use 0 which is correct unless a user puts
827db96d56Sopenharmony_ci                # their dock on the top of the screen (very rare).
837db96d56Sopenharmony_ci                maxy = 0
847db96d56Sopenharmony_ci            maxrooty = top.winfo_rooty()
857db96d56Sopenharmony_ci
867db96d56Sopenharmony_ci            # Get the "root y" coordinate for non-maximized windows with their
877db96d56Sopenharmony_ci            # y coordinate set to that of maximized windows.  This is needed
887db96d56Sopenharmony_ci            # to properly handle different title bar heights for non-maximized
897db96d56Sopenharmony_ci            # vs. maximized windows, as seen e.g. in Windows 10.
907db96d56Sopenharmony_ci            top.wm_state('normal')
917db96d56Sopenharmony_ci            top.update()
927db96d56Sopenharmony_ci            orig_geom = get_window_geometry(top)
937db96d56Sopenharmony_ci            max_y_geom = orig_geom[:3] + (maxy,)
947db96d56Sopenharmony_ci            set_window_geometry(top, max_y_geom)
957db96d56Sopenharmony_ci            top.update()
967db96d56Sopenharmony_ci            max_y_geom_rooty = top.winfo_rooty()
977db96d56Sopenharmony_ci
987db96d56Sopenharmony_ci            # Adjust the maximum window height to account for the different
997db96d56Sopenharmony_ci            # title bar heights of non-maximized vs. maximized windows.
1007db96d56Sopenharmony_ci            maxheight += maxrooty - max_y_geom_rooty
1017db96d56Sopenharmony_ci
1027db96d56Sopenharmony_ci            self._max_height_and_y_coords[screen_dimensions] = maxheight, maxy
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ci            set_window_geometry(top, orig_geom)
1057db96d56Sopenharmony_ci            top.wm_state(orig_state)
1067db96d56Sopenharmony_ci
1077db96d56Sopenharmony_ci        return self._max_height_and_y_coords[screen_dimensions]
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_cidef get_window_geometry(top):
1117db96d56Sopenharmony_ci    geom = top.wm_geometry()
1127db96d56Sopenharmony_ci    m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1137db96d56Sopenharmony_ci    return tuple(map(int, m.groups()))
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_ci
1167db96d56Sopenharmony_cidef set_window_geometry(top, geometry):
1177db96d56Sopenharmony_ci    top.wm_geometry("{:d}x{:d}+{:d}+{:d}".format(*geometry))
1187db96d56Sopenharmony_ci
1197db96d56Sopenharmony_ci
1207db96d56Sopenharmony_ciif __name__ == "__main__":
1217db96d56Sopenharmony_ci    from unittest import main
1227db96d56Sopenharmony_ci    main('idlelib.idle_test.test_zoomheight', verbosity=2, exit=False)
1237db96d56Sopenharmony_ci
1247db96d56Sopenharmony_ci    # Add htest?
125