from typing import Optional, Union, Tuple, Dict, Any, Literal import customtkinter as ctk from customtkinter import CTkFont, ThemeManager from gui.utils import Strings, Icons class CTkMessageBox(ctk.CTkToplevel): header_map = { "info": Strings.info, "warning": Strings.warning, "error": Strings.error, "yesno": Strings.yesno } icon_map = { "info": Icons.info, "warning": Icons.warning, "error": Icons.error, "yesno": Icons.yesno } buttons_map = { "info": {"Ok": {"output": True, "row": 0, "column": 0, "sticky": "center"}}, "warning": { "Ok": {"output": True, "row": 0, "column": 0, "sticky": "e"}, "Cancel": {"output": False, "row": 0, "column": 1, "sticky": "w"} }, "error": {"Ok": {"output": True, "row": 0, "column": 0, "sticky": "center"}}, "yesno": { "Yes": {"output": True, "row": 0, "column": 0, "sticky": "e"}, "No": {"output": False, "row": 0, "column": 1, "sticky": "w"} } } def __init__(self, title: str = "CTkDialog", font: Optional[Union[tuple, CTkFont]] = None, text: str = "CTkDialog", mode: Literal["info", "warning", "error", "yesno"] = "info", timeout: int = 0, fg_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: Optional[Union[str, Tuple[str, str]]] = None, button_fg_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_text_color: Optional[Union[str, Tuple[str, str]]] = None, entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None, entry_border_color: Optional[Union[str, Tuple[str, str]]] = None, entry_text_color: Optional[Union[str, Tuple[str, str]]] = None, parent=None, header=None, buttons: Dict[str, Any] | str = 'auto'): super().__init__(fg_color=fg_color) self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color) self._button_fg_color = ThemeManager.theme["CTkButton"]["fg_color"] if button_fg_color is None else self._check_color_type(button_fg_color) self._button_hover_color = ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color) self._button_text_color = ThemeManager.theme["CTkButton"]["text_color"] if button_text_color is None else self._check_color_type(button_text_color) self._entry_fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if entry_fg_color is None else self._check_color_type(entry_fg_color) self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color) self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color) self._running: bool = False self._header = header self._mode = mode self._text = text self._timeout = timeout self._font = font or ("Arial", 12) self._input = None _y = len(self._text.split('\n')) * 7 # Calculate height based on the number of lines in self._text _y += 30 if timeout > 0: if self._header: header = self._header(self, self.title(), disable_hide=True, disable_close=True) header.pack(fill="x", pady=(0, 0)) # self.geometry(f"150x{_y}") self.overrideredirect(True) # remove window decorations self.after(self._timeout, self.destroy) # close window after timeout self._buttons = {} else: # self.geometry(f"150x{_y+12}") if buttons == "auto": self._buttons = self.buttons_map.get(self._mode, {}) elif isinstance(buttons, str): self._buttons = {buttons: {"output": True, "row": 0, "column": 0, "sticky": "center"}} self.attributes("-topmost", True) # stay on top self.transient(parent) self.title(title) self.lift() # lift window on top self.protocol("WM_DELETE_WINDOW", self._on_closing) self._create_widgets() self.resizable(False, False) self._set_on_pos() self.grab_set() # make other windows not clickable def _set_on_pos(self): self.update_idletasks() y = (self.winfo_screenheight() - self.winfo_reqheight()) // 8 self.geometry(f"+{y}+{y}") def _on_closing(self): self.grab_release() self.destroy() def _create_widgets(self): # Иконки для типов сообщений icon_text = self.icon_map.get(self._mode, "?") main_frame = ctk.CTkFrame(self) main_frame.focus_set() main_frame.pack(pady=(10, 10), padx=10, fill="x") # Верхняя часть с иконкой и текстом сообщения main_frame.grid_columnconfigure(0, weight=1) # Первый столбец main_frame.grid_columnconfigure(1, weight=2) # Второй столбец # Иконка и текст сообщения icon_label = ctk.CTkLabel(main_frame, text=icon_text, font=("Arial", 24)) icon_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 5), sticky="w") header_label = ctk.CTkLabel(main_frame, text=self.header_map.get(self._mode, "Unknown"), font=("Arial", 16)) header_label.grid(row=0, column=0, padx=(10+40, 10), pady=(12, 3), sticky="w", columnspan=2) message_label = ctk.CTkLabel(main_frame, text=self._text, font=self._font) message_label.grid(row=1, column=0, padx=(15, 10), pady=5, sticky="w", rowspan=2) for t, setts in self._buttons.items(): btn = ctk.CTkButton(main_frame, text=t, command=lambda b=setts['output']: self._on_button_click(b)) row = setts.get("row", 0) + 3 if setts.get("sticky") == "center": btn.grid(row=row, column=0, padx=10, pady=10, sticky="we", columnspan=2) else: btn.grid(row=row, column=setts.get("column", 0), padx=10, pady=10, sticky=setts.get("sticky", "e")) def _on_button_click(self, button): self._input = button self.grab_release() self.destroy() def get_output(self): self.master.wait_window(self) if self._timeout > 0 and not self._input: return return self._buttons.get(self._input, self._input) class messagebox: @staticmethod def showinfo(title: str | None, message: str, timeout: int = 0, parent=None, header=None): return CTkMessageBox(title=title, text=message, mode="info", timeout=timeout, parent=parent).get_output() @staticmethod def showwarning(title: str| None, message: str, timeout: int = 0, parent=None, header=None): return CTkMessageBox(title=title, text=message, mode="warning", timeout=timeout, parent=parent).get_output() @staticmethod def showerror(title: str | None, message: str, timeout: int = 0, parent=None, header=None): return CTkMessageBox(title=title, text=message, mode="error", timeout=timeout, parent=parent).get_output() @staticmethod def yesno(title: str | None, message: str, timeout: int = 0, parent=None, header=None): return CTkMessageBox(title=title, text=message, mode="yesno", timeout=timeout, parent=parent).get_output() # if __name__ == '__main__': # root = ctk.CTk() # root.geometry("200x150") # text = ctk.CTkLabel(root, text="Test the message box") # text.pack(pady=5) # info_button = ctk.CTkButton(root, text="Info", command=lambda: CTkMessageBox(title="Info", text="This is an info message", mode="info", timeout=1000)) # info_button.pack(pady=5) # warn_button = ctk.CTkButton(root, text="Warn", command=lambda: CTkMessageBox(title="Warning", text="This is a warning message", mode="warning")) # warn_button.pack(pady=5) # error_button = ctk.CTkButton(root, text="Error", command=lambda: CTkMessageBox(title="Error", text="This is an error message", mode="error")) # error_button.pack(pady=5) # root.mainloop()