diff --git a/CHANGELOG.md b/CHANGELOG.md index d88e989..1312a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +ToDo: + - limit configuring attributes of overridden tk widgets + - remove bg and background support for CTk and CTkToplevel (to be done) + - optimize font attribute managing + - enforce font size in pixel + - enforce font to be tuple + ## Unreleased - 2022-10-2 ### Added - added .cget() method to all widgets and windows diff --git a/customtkinter/widgets/ctk_button.py b/customtkinter/widgets/ctk_button.py index 35a99e8..9b97ba2 100644 --- a/customtkinter/widgets/ctk_button.py +++ b/customtkinter/widgets/ctk_button.py @@ -240,9 +240,6 @@ class CTkButton(CTkBaseClass): padx=max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)), pady=(self._apply_widget_scaling(self._border_width), 2)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "text" in kwargs: self._text = kwargs.pop("text") diff --git a/customtkinter/widgets/ctk_checkbox.py b/customtkinter/widgets/ctk_checkbox.py index 11eb27b..ec2759f 100644 --- a/customtkinter/widgets/ctk_checkbox.py +++ b/customtkinter/widgets/ctk_checkbox.py @@ -177,9 +177,6 @@ class CTkCheckBox(CTkBaseClass): self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "text" in kwargs: self._text = kwargs.pop("text") diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index 6cb6f77..efb7b58 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -175,8 +175,10 @@ class CTkComboBox(CTkBaseClass): self._entry.configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode), fg=ThemeManager.single_color(self._text_color, self._appearance_mode), + disabledbackground=ThemeManager.single_color(self._fg_color, self._appearance_mode), disabledforeground=ThemeManager.single_color(self._text_color_disabled, self._appearance_mode), - disabledbackground=ThemeManager.single_color(self._fg_color, self._appearance_mode)) + highlightcolor=ThemeManager.single_color(self._fg_color, self._appearance_mode), + insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode)) if self._state == tkinter.DISABLED: self._canvas.itemconfig("dropdown_arrow", @@ -189,9 +191,6 @@ class CTkComboBox(CTkBaseClass): self._dropdown_menu.open(self.winfo_rootx(), self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "state" in kwargs: self._state = kwargs.pop("state") diff --git a/customtkinter/widgets/ctk_entry.py b/customtkinter/widgets/ctk_entry.py index 88f7d32..3ebac0e 100644 --- a/customtkinter/widgets/ctk_entry.py +++ b/customtkinter/widgets/ctk_entry.py @@ -6,7 +6,7 @@ from ..theme_manager import ThemeManager from ..draw_engine import DrawEngine from .widget_base_class import CTkBaseClass -from .widget_helper_functions import * +from .widget_helper_functions import pop_from_dict_by_set class CTkEntry(CTkBaseClass): @@ -15,13 +15,12 @@ class CTkEntry(CTkBaseClass): For detailed information check out the documentation. """ + _minimum_x_padding = 6 # minimum padding between tkinter entry and frame border + # attributes that are passed to and managed by the tkinter entry only: - _valid_tk_entry_attributes = {"exportselection", "font", "highlightbackground", - "highlightcolor", "insertbackground", "insertborderwidth", - "insertofftime", "insertontime", "insertwidth", - "justify", "selectbackground", "selectborderwidth", - "selectforeground", "show", "takefocus", "validate", - "validatecommand", "xscrollcommand"} + _valid_tk_entry_attributes = {"exportselection", "insertborderwidth", "insertofftime", + "insertontime", "insertwidth", "justify", "selectborderwidth", + "show", "takefocus", "validate", "validatecommand", "xscrollcommand"} def __init__(self, *args, width: int = 140, @@ -55,22 +54,24 @@ class CTkEntry(CTkBaseClass): self._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self._placeholder_text_color = ThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color - self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font self._border_color = ThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_color # shape self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self._border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width - # placeholder text + # text and state + self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font self._is_focused: bool = True self._placeholder_text = placeholder_text self._placeholder_text_active = False self._pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back - - # textvariable self._textvariable = textvariable self._state = state + self._textvariable_callback_name: str = "" + + if not (self._textvariable is None or self._textvariable is ""): + self._textvariable_callback_name = self._textvariable.trace_add("write", self._textvariable_callback) self._canvas = CTkCanvas(master=self, highlightthickness=0, @@ -86,10 +87,15 @@ class CTkEntry(CTkBaseClass): font=self._apply_font_scaling(self._font), state=self._state, textvariable=self._textvariable, - **filter_dict_by_set(kwargs, self._valid_tk_entry_attributes)) - self._entry.grid(column=0, row=0, sticky="nswe", - padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6), - pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) + **pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) + if self._corner_radius >= self._minimum_x_padding: + self._entry.grid(column=0, row=0, sticky="nswe", padx=min(self._apply_widget_scaling(self._corner_radius), self._current_height), + pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) + else: + self._entry.grid(column=0, row=0, sticky="nswe", padx=self._apply_widget_scaling(self._minimum_x_padding), + pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) + + self._check_kwargs_empty(kwargs, raise_error=True) super().bind('', self._update_dimensions_event) self._entry.bind('', self._entry_focus_out) @@ -98,6 +104,10 @@ class CTkEntry(CTkBaseClass): self._activate_placeholder() self._draw() + def _textvariable_callback(self, var_name, index, mode): + if self._textvariable.get() == "": + self._activate_placeholder() + def _set_scaling(self, *args, **kwargs): super()._set_scaling(*args, **kwargs) @@ -129,20 +139,20 @@ class CTkEntry(CTkBaseClass): fill=ThemeManager.single_color(self._fg_color, self._appearance_mode), outline=ThemeManager.single_color(self._fg_color, self._appearance_mode)) self._entry.configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode), - disabledbackground=ThemeManager.single_color(self._fg_color, self._appearance_mode), - highlightcolor=ThemeManager.single_color(self._fg_color, self._appearance_mode), fg=ThemeManager.single_color(self._text_color, self._appearance_mode), + disabledbackground=ThemeManager.single_color(self._fg_color, self._appearance_mode), disabledforeground=ThemeManager.single_color(self._text_color, self._appearance_mode), + highlightcolor=ThemeManager.single_color(self._fg_color, self._appearance_mode), insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode)) else: self._canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self._bg_color, self._appearance_mode), outline=ThemeManager.single_color(self._bg_color, self._appearance_mode)) self._entry.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode), - disabledbackground=ThemeManager.single_color(self._bg_color, self._appearance_mode), - highlightcolor=ThemeManager.single_color(self._bg_color, self._appearance_mode), fg=ThemeManager.single_color(self._text_color, self._appearance_mode), + disabledbackground=ThemeManager.single_color(self._bg_color, self._appearance_mode), disabledforeground=ThemeManager.single_color(self._text_color, self._appearance_mode), + highlightcolor=ThemeManager.single_color(self._bg_color, self._appearance_mode), insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode)) self._canvas.itemconfig("border_parts", @@ -152,12 +162,7 @@ class CTkEntry(CTkBaseClass): if self._placeholder_text_active: self._entry.config(fg=ThemeManager.single_color(self._placeholder_text_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): - self._entry.configure(**filter_dict_by_set(kwargs, self._valid_tk_entry_attributes)) - if "state" in kwargs: self._state = kwargs.pop("state") self._entry.configure(state=self._state) @@ -174,15 +179,16 @@ class CTkEntry(CTkBaseClass): self._border_color = kwargs.pop("border_color") require_redraw = True + if "border_width" in kwargs: + self._border_width = kwargs.pop("border_width") + require_redraw = True + if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") - - if self._corner_radius * 2 > self._current_height: - self._corner_radius = self._current_height / 2 - elif self._corner_radius * 2 > self._current_width: - self._corner_radius = self._current_width / 2 - - self._entry.grid(column=0, row=0, sticky="we", padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6)) + if self._corner_radius >= self._minimum_x_padding: + self._entry.grid(column=0, row=0, sticky="we", padx=min(self._apply_widget_scaling(self._corner_radius), self._current_height / 2)) + else: + self._entry.grid(column=0, row=0, sticky="we", padx=self._apply_widget_scaling(self._minimum_x_padding)) require_redraw = True if "width" in kwargs: @@ -213,11 +219,12 @@ class CTkEntry(CTkBaseClass): if "show" in kwargs: if self._placeholder_text_active: - self._pre_placeholder_arguments["show"] = kwargs.pop("show") + self._pre_placeholder_arguments["show"] = kwargs.pop("show") # remember show argument for when placeholder gets deactivated else: self._entry.configure(show=kwargs.pop("show")) - super().configure(require_redraw=require_redraw, **kwargs) + self._entry.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) # configure Tkinter.Entry + super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass def cget(self, attribute_name: str) -> any: if attribute_name == "corner_radius": @@ -238,22 +245,18 @@ class CTkEntry(CTkBaseClass): return self._textvariable elif attribute_name == "placeholder_text": return self._placeholder_text - elif attribute_name == "text_font": + elif attribute_name == "font": return self._font elif attribute_name == "state": return self._state - elif attribute_name in ["exportselection", "font", "highlightbackground", - "highlightcolor", "insertbackground", - "insertborderwidth", "insertofftime", "insertontime", - "insertwidth", "justify", "selectbackground", - "selectborderwidth", "selectforeground", "show", - "takefocus", "validate", "validatecommand", - "xscrollcommand"]: - return self._entry.cget(attribute_name) + elif attribute_name in self._valid_tk_entry_attributes: + return self._entry.cget(attribute_name) # cget of tkinter.Entry + else: + return super().cget(attribute_name) - def bind(self, *args, **kwargs): - self._entry.bind(*args, **kwargs) + def bind(self, sequence=None, command=None, add=None): + self._entry.bind(sequence, command, add) def _activate_placeholder(self): if self._entry.get() == "" and self._placeholder_text is not None and (self._textvariable is None or self._textvariable == ""): @@ -281,16 +284,16 @@ class CTkEntry(CTkBaseClass): self._deactivate_placeholder() self._is_focused = True - def delete(self, *args, **kwargs): - self._entry.delete(*args, **kwargs) + def delete(self, first_index, last_index=None): + self._entry.delete(first_index, last_index) if not self._is_focused and self._entry.get() == "": self._activate_placeholder() - def insert(self, *args, **kwargs): + def insert(self, index, string): self._deactivate_placeholder() - return self._entry.insert(*args, **kwargs) + return self._entry.insert(index, string) def get(self): if self._placeholder_text_active: @@ -299,7 +302,40 @@ class CTkEntry(CTkBaseClass): return self._entry.get() def focus(self): - self._entry.focus() + return self._entry.focus() def focus_force(self): - self._entry.focus_force() + return self._entry.focus_force() + + def index(self, index): + return self._entry.index(index) + + def icursor(self, index): + return self._entry.icursor(index) + + def select_adjust(self, index): + return self._entry.select_adjust(index) + + def select_from(self, index): + return self._entry.icursor(index) + + def select_clear(self): + return self._entry.select_clear() + + def select_present(self): + return self._entry.select_present() + + def select_range(self, start_index, end_index): + return self._entry.select_range(start_index, end_index) + + def select_to(self, index): + return self._entry.select_to(index) + + def xview(self, index): + return self._entry.xview(index) + + def xview_moveto(self, f): + return self._entry.xview_moveto(f) + + def xview_scroll(self, number, what): + return self._entry.xview_scroll(number, what) diff --git a/customtkinter/widgets/ctk_frame.py b/customtkinter/widgets/ctk_frame.py index 014d428..0fe4f3e 100644 --- a/customtkinter/widgets/ctk_frame.py +++ b/customtkinter/widgets/ctk_frame.py @@ -114,9 +114,6 @@ class CTkFrame(CTkBaseClass): self._canvas.tag_lower("inner_parts") self._canvas.tag_lower("border_parts") - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "fg_color" in kwargs: self._fg_color = kwargs.pop("fg_color") diff --git a/customtkinter/widgets/ctk_label.py b/customtkinter/widgets/ctk_label.py index cbad9d3..e9cf1c5 100644 --- a/customtkinter/widgets/ctk_label.py +++ b/customtkinter/widgets/ctk_label.py @@ -115,9 +115,6 @@ class CTkLabel(CTkBaseClass): self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "anchor" in kwargs: self._anchor = kwargs.pop("anchor") diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index da58acd..34f9aae 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -193,9 +193,6 @@ class CTkOptionMenu(CTkBaseClass): self._canvas.update_idletasks() - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "state" in kwargs: self._state = kwargs.pop("state") diff --git a/customtkinter/widgets/ctk_progressbar.py b/customtkinter/widgets/ctk_progressbar.py index 6a41af9..b59c112 100644 --- a/customtkinter/widgets/ctk_progressbar.py +++ b/customtkinter/widgets/ctk_progressbar.py @@ -152,9 +152,6 @@ class CTkProgressBar(CTkBaseClass): fill=ThemeManager.single_color(self._progress_color, self._appearance_mode), outline=ThemeManager.single_color(self._progress_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "fg_color" in kwargs: self._fg_color = kwargs.pop("fg_color") diff --git a/customtkinter/widgets/ctk_radiobutton.py b/customtkinter/widgets/ctk_radiobutton.py index 3768da2..00b8faa 100644 --- a/customtkinter/widgets/ctk_radiobutton.py +++ b/customtkinter/widgets/ctk_radiobutton.py @@ -158,9 +158,6 @@ class CTkRadioButton(CTkBaseClass): self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "text" in kwargs: self._text = kwargs.pop("text") diff --git a/customtkinter/widgets/ctk_scrollbar.py b/customtkinter/widgets/ctk_scrollbar.py index 65582a7..6b31e44 100644 --- a/customtkinter/widgets/ctk_scrollbar.py +++ b/customtkinter/widgets/ctk_scrollbar.py @@ -150,9 +150,6 @@ class CTkScrollbar(CTkBaseClass): self._canvas.update_idletasks() - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "fg_color" in kwargs: self._fg_color = kwargs.pop("fg_color") diff --git a/customtkinter/widgets/ctk_slider.py b/customtkinter/widgets/ctk_slider.py index 60913b3..186b81c 100644 --- a/customtkinter/widgets/ctk_slider.py +++ b/customtkinter/widgets/ctk_slider.py @@ -190,9 +190,6 @@ class CTkSlider(CTkBaseClass): fill=ThemeManager.single_color(self._button_color, self._appearance_mode), outline=ThemeManager.single_color(self._button_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "state" in kwargs: self._state = kwargs.pop("state") diff --git a/customtkinter/widgets/ctk_switch.py b/customtkinter/widgets/ctk_switch.py index 77dbf57..f76e929 100644 --- a/customtkinter/widgets/ctk_switch.py +++ b/customtkinter/widgets/ctk_switch.py @@ -207,9 +207,6 @@ class CTkSwitch(CTkBaseClass): self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "text" in kwargs: self._text = kwargs.pop("text") diff --git a/customtkinter/widgets/ctk_textbox.py b/customtkinter/widgets/ctk_textbox.py index bcd28a1..fae123c 100644 --- a/customtkinter/widgets/ctk_textbox.py +++ b/customtkinter/widgets/ctk_textbox.py @@ -118,9 +118,6 @@ class CTkTextbox(CTkBaseClass): self._canvas.tag_lower("inner_parts") self._canvas.tag_lower("border_parts") - def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) - def configure(self, require_redraw=False, **kwargs): if "fg_color" in kwargs: self._fg_color = kwargs.pop("fg_color") diff --git a/customtkinter/widgets/dropdown_menu.py b/customtkinter/widgets/dropdown_menu.py index 5a07970..47ca648 100644 --- a/customtkinter/widgets/dropdown_menu.py +++ b/customtkinter/widgets/dropdown_menu.py @@ -105,9 +105,6 @@ class DropdownMenu(tkinter.Menu): else: # Linux self.tk_popup(int(x), int(y)) - def config(self, **kwargs): - return self.configure(**kwargs) - def configure(self, **kwargs): if "fg_color" in kwargs: self._fg_color = kwargs.pop("fg_color") diff --git a/customtkinter/widgets/widget_base_class.py b/customtkinter/widgets/widget_base_class.py index fafa29a..f167013 100644 --- a/customtkinter/widgets/widget_base_class.py +++ b/customtkinter/widgets/widget_base_class.py @@ -15,7 +15,7 @@ from ..appearance_mode_tracker import AppearanceModeTracker from ..scaling_tracker import ScalingTracker from ..theme_manager import ThemeManager -from .widget_helper_functions import filter_dict_by_set +from .widget_helper_functions import pop_from_dict_by_set class CTkBaseClass(tkinter.Frame): @@ -23,7 +23,7 @@ class CTkBaseClass(tkinter.Frame): appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """ # attributes that are passed to and managed by the tkinter frame only: - _valid_tk_frame_attributes = {"cursor"} + _valid_tk_frame_attributes = {"cursor", "master"} def __init__(self, *args, width: int, @@ -32,7 +32,10 @@ class CTkBaseClass(tkinter.Frame): bg_color: Union[str, tuple] = None, **kwargs): - super().__init__(*args, width=width, height=height, **kwargs) # set desired size of underlying tkinter.Frame + super().__init__(*args, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes)) + + # check if kwargs is empty, if not raise error for unsupported arguments + self._check_kwargs_empty(kwargs, raise_error=True) # dimensions self._current_width = width # _current_width and _current_height in pixel, represent current size of the widget @@ -85,6 +88,18 @@ class CTkBaseClass(tkinter.Frame): self.master.config = new_configure self.master.configure = new_configure + @staticmethod + def _check_kwargs_empty(kwargs_dict, raise_error=False) -> bool: + """ returns True if kwargs are empty, False otherwise, raises error if not empty """ + + if len(kwargs_dict) > 0: + if raise_error: + raise ValueError(f"{list(kwargs_dict.keys())} are not supported arguments. Look at the documentation for supported arguments.") + else: + return True + else: + return False + def destroy(self): AppearanceModeTracker.remove(self._set_appearance_mode) super().destroy() @@ -127,10 +142,10 @@ class CTkBaseClass(tkinter.Frame): pass def config(self, *args, **kwargs): - return self.configure(*args, **kwargs) + raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.") def configure(self, require_redraw=False, **kwargs): - """ basic configure with _bg_color support, to be overridden """ + """ basic configure with bg_color support, calls configure of tkinter.Frame, calls draw() in the end """ if "bg_color" in kwargs: new_bg_color = kwargs.pop("bg_color") @@ -140,21 +155,28 @@ class CTkBaseClass(tkinter.Frame): self._bg_color = new_bg_color require_redraw = True - super().configure(**filter_dict_by_set(kwargs, self._valid_tk_frame_attributes)) + super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes)) # configure tkinter.Frame + + # if there are still items in the kwargs dict, raise ValueError + self._check_kwargs_empty(kwargs, raise_error=True) if require_redraw: self._draw() - def cget(self, key: str): - if key == "bg_color": + def cget(self, attribute_name: str): + """ basic cget with bg_color, width, height support, calls cget of tkinter.Frame """ + + if attribute_name == "bg_color": return self._bg_color - elif key == "width": + elif attribute_name == "width": return self._desired_width - elif key == "height": + elif attribute_name == "height": return self._desired_height - elif key in self._valid_tk_frame_attributes: - return super().cget(key) + elif attribute_name in self._valid_tk_frame_attributes: + return super().cget(attribute_name) # cget of tkinter.Frame + else: + raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.") def _update_dimensions_event(self, event): # only redraw if dimensions changed (for performance), independent of scaling diff --git a/customtkinter/widgets/widget_helper_functions.py b/customtkinter/widgets/widget_helper_functions.py index 85b813e..e2cf7fa 100644 --- a/customtkinter/widgets/widget_helper_functions.py +++ b/customtkinter/widgets/widget_helper_functions.py @@ -1,7 +1,7 @@ def filter_dict_by_set(dictionary: dict, valid_keys: set): - """ remove all key value pairs, where key is not in valid_keys set """ + """ create new dict with key value pairs of dictionary, where key is in valid_keys """ new_dictionary = {} for key, value in dictionary.items(): @@ -9,3 +9,14 @@ def filter_dict_by_set(dictionary: dict, valid_keys: set): new_dictionary[key] = value return new_dictionary + + +def pop_from_dict_by_set(dictionary: dict, valid_keys: set): + """ remove and create new dict with key value pairs of dictionary, where key is in valid_keys """ + new_dictionary = {} + + for key in list(dictionary.keys()): + if key in valid_keys: + new_dictionary[key] = dictionary.pop(key) + + return new_dictionary diff --git a/customtkinter/windows/ctk_input_dialog.py b/customtkinter/windows/ctk_input_dialog.py index af9f08d..ade590e 100644 --- a/customtkinter/windows/ctk_input_dialog.py +++ b/customtkinter/windows/ctk_input_dialog.py @@ -17,7 +17,7 @@ class CTkInputDialog: For detailed information check out the documentation. """ - def __init__(self, + def __init__(self, *args, fg_color: Union[str, Tuple[str, str]] = "default_theme", hover_color: Union[str, Tuple[str, str]] = "default_theme", border_color: Union[str, Tuple[str, str]] = "default_theme", @@ -27,7 +27,13 @@ class CTkInputDialog: text: str = "CTkDialog"): self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" - self.master = master + + if len(args) > 0 and master is None: + self.master = args[0] + elif master is not None: + self.master = master + else: + raise ValueError("master argument is missing") self._window_bg_color = ThemeManager.theme["color"]["window_bg_color"] self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color @@ -39,14 +45,15 @@ class CTkInputDialog: self._height: int = len(text.split("\n")) * 20 + 150 self._text = text - self._toplevel_window = CTkToplevel() + self._toplevel_window = CTkToplevel(self.master) self._toplevel_window.geometry(f"{280}x{self._height}") self._toplevel_window.minsize(280, self._height) self._toplevel_window.maxsize(280, self._height) self._toplevel_window.title(title) - self._toplevel_window.lift() self._toplevel_window.focus_force() - self._toplevel_window.grab_set() + self._toplevel_window.grab_set() # make other windows not clickable + self._toplevel_window.lift() # lift window on top + self._toplevel_window.attributes("-topmost", True) # stay on top self._toplevel_window.protocol("WM_DELETE_WINDOW", self._on_closing) self._toplevel_window.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background @@ -99,25 +106,17 @@ class CTkInputDialog: def _ok_event(self, event=None): self._user_input = self._entry.get() - self._running = False + self._toplevel_window.grab_release() + self._toplevel_window.destroy() def _on_closing(self): - self._running = False + self._toplevel_window.grab_release() + self._toplevel_window.destroy() def _cancel_event(self): - self._running = False + self._toplevel_window.grab_release() + self._toplevel_window.destroy() def get_input(self): - self._running = True - - while self._running: - try: - self._toplevel_window.update() - except Exception: - return self._user_input - finally: - time.sleep(0.01) - - time.sleep(0.05) - self._toplevel_window.destroy() + self.master.wait_window(self._toplevel_window) return self._user_input diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index 0055e97..0988c77 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -142,9 +142,9 @@ class CTk(tkinter.Tk): super().mainloop(*args, **kwargs) - def resizable(self, *args, **kwargs): - super().resizable(*args, **kwargs) - self._last_resizable_args = (args, kwargs) + def resizable(self, width: bool = None, height: bool = None): + super().resizable(width, height) + self._last_resizable_args = ([], {"width": width, "height": height}) if sys.platform.startswith("win"): if self._appearance_mode == 1: @@ -225,9 +225,6 @@ class CTk(tkinter.Tk): else: return value - def config(self, *args, **kwargs): - self.configure(*args, **kwargs) - def configure(self, *args, **kwargs): bg_changed = False diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index 55cc5ef..fdda6c6 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -161,9 +161,9 @@ class CTkToplevel(tkinter.Toplevel): self._iconify_called_after_windows_set_titlebar_color = True super().iconify() - def resizable(self, *args, **kwargs): - super().resizable(*args, **kwargs) - self._last_resizable_args = (args, kwargs) + def resizable(self, width: bool = None, height: bool = None): + super().resizable(width, height) + self._last_resizable_args = ([], {"width": width, "height": height}) if sys.platform.startswith("win"): if self._appearance_mode == 1: @@ -189,9 +189,6 @@ class CTkToplevel(tkinter.Toplevel): self._current_height = height super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height)) - def config(self, *args, **kwargs): - self.configure(*args, **kwargs) - def configure(self, *args, **kwargs): bg_changed = False diff --git a/test/manual_integration_tests/complex_example_new.py b/test/manual_integration_tests/complex_example_new.py index 1a9f307..7779477 100644 --- a/test/manual_integration_tests/complex_example_new.py +++ b/test/manual_integration_tests/complex_example_new.py @@ -13,6 +13,8 @@ class App(customtkinter.CTk): self.title("CustomTkinter complex_example.py") self.geometry(f"{920}x{500}") + self.minsize(700, 400) + self.maxsize(1200, 700) self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed # configure grid layout (4x4) @@ -126,7 +128,7 @@ class App(customtkinter.CTk): self.progressbar_1.start() def open_input_dialog(self): - dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog") + dialog = customtkinter.CTkInputDialog(master=self, text="Type in a number:", title="CTkInputDialog") print("CTkInputDialog:", dialog.get_input()) def change_appearance_mode(self, new_appearance_mode: str): @@ -139,10 +141,8 @@ class App(customtkinter.CTk): def sidebar_button_callback(self): print("sidebar_button click") - self.entry.delete(0, tkinter.END) - def on_closing(self, event=0): self.destroy() diff --git a/test/unit_tests/test_ctk.py b/test/unit_tests/test_ctk.py index ab0d801..5920909 100644 --- a/test/unit_tests/test_ctk.py +++ b/test/unit_tests/test_ctk.py @@ -76,14 +76,14 @@ class TestCTk(): def test_configure(self): print(" -> test_configure: ", end="") self.root_ctk.configure(bg="white") - assert self.root_ctk.fg_color == "white" + assert self.root_ctk.cget("fg_color") == "white" self.root_ctk.configure(background="red") - assert self.root_ctk.fg_color == "red" + assert self.root_ctk.cget("fg_color") == "red" assert self.root_ctk.cget("bg") == "red" self.root_ctk.config(fg_color=("green", "#FFFFFF")) - assert self.root_ctk.fg_color == ("green", "#FFFFFF") + assert self.root_ctk.cget("fg_color") == ("green", "#FFFFFF") print("successful") def test_appearance_mode(self): diff --git a/test/unit_tests/test_ctk_entry.py b/test/unit_tests/test_ctk_entry.py new file mode 100644 index 0000000..5c14c9a --- /dev/null +++ b/test/unit_tests/test_ctk_entry.py @@ -0,0 +1,55 @@ +import customtkinter +import time + +app = customtkinter.CTk() + +entry_1 = customtkinter.CTkEntry(app, width=100, height=25) +entry_1.pack(padx=20, pady=20) +entry_2 = customtkinter.CTkEntry(app, width=100, height=25) +entry_2.pack(padx=20, pady=20) + +txt_var = customtkinter.StringVar(value="test") + +entry_1.configure(width=300, + height=35, + corner_radius=1000, + border_width=4, + bg_color="green", + fg_color=("red", "yellow"), + border_color="blue", + text_color=("brown", "green"), + placeholder_text_color="blue", + textvariable=txt_var, + placeholder_text="new_placholder", + font=("Times New Roman", -8, "bold"), + state="normal", + insertborderwidth=5, + insertwidth=10, + justify="right", + show="+") + +assert entry_1.cget("width") == 300 +assert entry_1.cget("height") == 35 +assert entry_1.cget("corner_radius") == 1000 +assert entry_1.cget("border_width") == 4 +assert entry_1.cget("bg_color") == "green" +assert entry_1.cget("fg_color") == ("red", "yellow") +assert entry_1.cget("border_color") == "blue" +assert entry_1.cget("text_color") == ("brown", "green") +assert entry_1.cget("placeholder_text_color") == "blue" +assert entry_1.cget("textvariable") == txt_var +assert entry_1.cget("placeholder_text") == "new_placholder" +assert entry_1.cget("font") == ("Times New Roman", -8, "bold") +assert entry_1.cget("state") == "normal" +assert entry_1.cget("insertborderwidth") == 5 +assert entry_1.cget("insertwidth") == 10 +assert entry_1.cget("justify") == "right" +# assert entry_1.cget("show") == "+" # somehow does not work, maybe a tkinter bug? + +def test_textvariable(): + txt_var.set("test_2") + print(entry_1.get()) + assert entry_1.get() == "test_2" + +app.after(500, test_textvariable) +app.mainloop()