From deebaa9163cc63b59b09518bc8efeffa213586e6 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Fri, 5 Aug 2022 20:38:05 +0200 Subject: [PATCH] enhanced geometry string parsing for CTk and CTkToplevel #345 #287 --- customtkinter/windows/ctk_tk.py | 63 ++++++++------- customtkinter/windows/ctk_toplevel.py | 78 +++++++++++-------- .../complex_example_new.py | 2 +- .../test_window_geometry.py | 35 +++++++++ 4 files changed, 118 insertions(+), 60 deletions(-) create mode 100644 test/manual_integration_tests/test_window_geometry.py diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index cc6270d..0ddbda6 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -142,41 +142,52 @@ class CTk(tkinter.Tk): def geometry(self, geometry_string: str = None): if geometry_string is not None: - print(self.apply_geometry_scaling(geometry_string), geometry_string) super().geometry(self.apply_geometry_scaling(geometry_string)) # update width and height attributes - numbers = list(map(int, re.split(r"[x+-]", geometry_string))) # split geometry string into list of numbers - self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max - self.current_height = max(self.min_height, min(numbers[1], self.max_height)) + width, height, x, y = self.parse_geometry_string(geometry_string) + if width is not None and height is not None: + self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max + self.current_height = max(self.min_height, min(height, self.max_height)) else: return self.reverse_geometry_scaling(super().geometry()) - def apply_geometry_scaling(self, geometry_string): - value_list = re.split(r"[x+-]", geometry_string) - separator_list = re.split(r"\d+", geometry_string) + @staticmethod + def parse_geometry_string(geometry_string: str) -> tuple: + # index: 1 2 3 4 5 6 + # regex group structure: ('x', '', '', '+-+-', '-', '-') + result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string) - if len(value_list) == 2: - scaled_width = str(round(int(value_list[0]) * self.window_scaling)) - scaled_height = str(round(int(value_list[1]) * self.window_scaling)) - return f"{scaled_width}x{scaled_height}" - elif len(value_list) == 4: - scaled_width = str(round(int(value_list[0]) * self.window_scaling)) - scaled_height = str(round(int(value_list[1]) * self.window_scaling)) - return f"{scaled_width}x{scaled_height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}" + width = int(result.group(2)) if result.group(2) is not None else None + height = int(result.group(3)) if result.group(3) is not None else None + x = int(result.group(5)) if result.group(5) is not None else None + y = int(result.group(6)) if result.group(6) is not None else None - def reverse_geometry_scaling(self, scaled_geometry_string): - value_list = re.split(r"[x+-]", scaled_geometry_string) - separator_list = re.split(r"\d+", scaled_geometry_string) + return width, height, x, y - if len(value_list) == 2: - width = str(round(int(value_list[0]) / self.window_scaling)) - height = str(round(int(value_list[1]) / self.window_scaling)) - return f"{width}x{height}" - elif len(value_list) == 4: - width = str(round(int(value_list[0]) / self.window_scaling)) - height = str(round(int(value_list[1]) / self.window_scaling)) - return f"{width}x{height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}" + def apply_geometry_scaling(self, geometry_string: str) -> str: + width, height, x, y = self.parse_geometry_string(geometry_string) + + if x is None and y is None: # no and in geometry_string + return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}" + + elif width is None and height is None: # no and in geometry_string + return f"+{x}+{y}" + + else: + return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}" + + def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str: + width, height, x, y = self.parse_geometry_string(scaled_geometry_string) + + if x is None and y is None: # no and in geometry_string + return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}" + + elif width is None and height is None: # no and in geometry_string + return f"+{x}+{y}" + + else: + return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}" def apply_window_scaling(self, value): if isinstance(value, (int, float)): diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index 3b7e9fd..b6bddf0 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -83,31 +83,54 @@ class CTkToplevel(tkinter.Toplevel): if self.max_width is not None or self.max_height is not None: super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height)) - def apply_geometry_scaling(self, geometry_string): - value_list = re.split(r"[x+-]", geometry_string) - separator_list = re.split(r"\d+", geometry_string) + def geometry(self, geometry_string: str = None): + if geometry_string is not None: + super().geometry(self.apply_geometry_scaling(geometry_string)) - if len(value_list) == 2: - scaled_width = str(round(int(value_list[0]) * self.window_scaling)) - scaled_height = str(round(int(value_list[1]) * self.window_scaling)) - return f"{scaled_width}x{scaled_height}" - elif len(value_list) == 4: - scaled_width = str(round(int(value_list[0]) * self.window_scaling)) - scaled_height = str(round(int(value_list[1]) * self.window_scaling)) - return f"{scaled_width}x{scaled_height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}" + # update width and height attributes + width, height, x, y = self.parse_geometry_string(geometry_string) + if width is not None and height is not None: + self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max + self.current_height = max(self.min_height, min(height, self.max_height)) + else: + return self.reverse_geometry_scaling(super().geometry()) - def reverse_geometry_scaling(self, scaled_geometry_string): - value_list = re.split(r"[x+-]", scaled_geometry_string) - separator_list = re.split(r"\d+", scaled_geometry_string) + @staticmethod + def parse_geometry_string(geometry_string: str) -> tuple: + # index: 1 2 3 4 5 6 + # regex group structure: ('x', '', '', '+-+-', '-', '-') + result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string) - if len(value_list) == 2: - width = str(round(int(value_list[0]) / self.window_scaling)) - height = str(round(int(value_list[1]) / self.window_scaling)) - return f"{width}x{height}" - elif len(value_list) == 4: - width = str(round(int(value_list[0]) / self.window_scaling)) - height = str(round(int(value_list[1]) / self.window_scaling)) - return f"{width}x{height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}" + width = int(result.group(2)) if result.group(2) is not None else None + height = int(result.group(3)) if result.group(3) is not None else None + x = int(result.group(5)) if result.group(5) is not None else None + y = int(result.group(6)) if result.group(6) is not None else None + + return width, height, x, y + + def apply_geometry_scaling(self, geometry_string: str) -> str: + width, height, x, y = self.parse_geometry_string(geometry_string) + + if x is None and y is None: # no and in geometry_string + return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}" + + elif width is None and height is None: # no and in geometry_string + return f"+{x}+{y}" + + else: + return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}" + + def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str: + width, height, x, y = self.parse_geometry_string(scaled_geometry_string) + + if x is None and y is None: # no and in geometry_string + return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}" + + elif width is None and height is None: # no and in geometry_string + return f"+{x}+{y}" + + else: + return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}" def apply_window_scaling(self, value): if isinstance(value, (int, float)): @@ -115,17 +138,6 @@ class CTkToplevel(tkinter.Toplevel): else: return value - def geometry(self, geometry_string: str = None): - if geometry_string is not None: - super().geometry(self.apply_geometry_scaling(geometry_string)) - - # update width and height attributes - numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers - self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max - self.current_height = max(self.min_height, min(numbers[1], self.max_height)) - else: - return self.reverse_geometry_scaling(super().geometry()) - def destroy(self): AppearanceModeTracker.remove(self.set_appearance_mode) ScalingTracker.remove_window(self.set_scaling, self) diff --git a/test/manual_integration_tests/complex_example_new.py b/test/manual_integration_tests/complex_example_new.py index 53d1854..2393aa9 100644 --- a/test/manual_integration_tests/complex_example_new.py +++ b/test/manual_integration_tests/complex_example_new.py @@ -12,7 +12,7 @@ class App(customtkinter.CTk): super().__init__() self.title("CustomTkinter complex_example.py") - self.geometry(f"{920}x{500}") + self.geometry(f"{920}x{500}-100-100") self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed # configure grid layout (4x4) diff --git a/test/manual_integration_tests/test_window_geometry.py b/test/manual_integration_tests/test_window_geometry.py new file mode 100644 index 0000000..d053785 --- /dev/null +++ b/test/manual_integration_tests/test_window_geometry.py @@ -0,0 +1,35 @@ +import customtkinter + +customtkinter.set_window_scaling(1.3) + +app = customtkinter.CTk() +app.geometry("300x300") +app.geometry("-100-100") +app.geometry("+-100+-100") +app.geometry("+100+100") +app.geometry("300x300-100-100") +app.geometry("300x300+-100+-100") +app.geometry("300x300+100+100") + +app.geometry("400x400") +app.geometry("+400+400") +app.update() +print(app.geometry()) +assert app.geometry() == "400x400+400+400" + +toplevel = customtkinter.CTkToplevel(app) +toplevel.geometry("300x300") +toplevel.geometry("-100-100") +toplevel.geometry("+-100+-100") +toplevel.geometry("+100+100") +toplevel.geometry("300x300-100-100") +toplevel.geometry("300x300+-100+-100") +toplevel.geometry("300x300+100+100") + +toplevel.geometry("300x300") +toplevel.geometry("+500+500") +toplevel.update() +print(toplevel.geometry()) +assert toplevel.geometry() == "300x300+500+500" + +app.mainloop()