Interface-module/boxes/CTkMessageBox.py
2025-03-27 13:13:25 +03:00

205 lines
9.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from typing import Optional, Union, Tuple, Dict, Any, Literal
import customtkinter as ctk
from customtkinter import CTkFont, ThemeManager
from ..utils import Strings, Icons, wrap_text
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, "width": 60, "padx": (0, 75)},
"No": {"output": False, "row": 0, "width": 60, "padx": (0, 0)}
}
}
def __init__(self,
title: str = "CTkDialog",
text: str = "CTkDialog",
mode: Literal["info", "warning", "error", "yesno"] = "info",
timeout: int = 0,
font: Optional[Union[tuple, CTkFont]] = None,
max_message_line_width: int = 500,
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.resizable(False, False)
self.withdraw()
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 = str(text)
self._timeout = timeout
self._font = font or ("Arial", 12)
self._input = None
_text_slt = []
self._x = 0
for line in self._text.split('\n'):
if len(line) * 7 > 700:
wrapped_lines = wrap_text(line, max_message_line_width)
_text_slt.extend(wrapped_lines)
else:
_text_slt.append(line)
self._x = max(self._x, max(len(w) * 7 for w in _text_slt))
self._x += 20 # Add 20 pixels for padding on the left and right
self._x += 10 # Add 10 pixels for padding on the left and right
self._x = max(self._x, (len(self.header_map[mode]) * 16) + 25 + 20) # Set width to the length of the title if it's longer than the message
self._text = "\n".join(_text_slt)
self._y = 0
self._y += 24 + 10 + 5 # Add 24 pixels for the header (icon + text) and 15 pixels for padding
self._y += len(_text_slt) * 12 # Calculate height based on the number of lines in self._text
self._y += 15 + 10 # Add 25 pixels for padding between the header and the message
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"{self._x}x{self._y}")
self.overrideredirect(True) # remove window decorations
self.after(self._timeout, self._on_closing) # close window after timeout
self._buttons = {}
else:
self._y += 30 + 10 + 12 # Add 30 pixels for the buttons
self.geometry(f"{self._x}x{self._y+12}") # Add 12 pixels to the height for the title bar
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._center()
self.transient(parent)
self.title(title)
self._create_widgets()
self.protocol("WM_DELETE_WINDOW", self._on_closing)
def _on_closing(self):
self.withdraw()
self.grab_release()
self.destroy()
def _center(self):
x = (self.winfo_screenwidth() - self._x) // 2
y = (self.winfo_screenheight() - self._y) // 2
self.geometry(f'{x}+{y}')
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, justify="left")
message_label.grid(row=1, column=0, padx=(15, 10), pady=5, rowspan=2, sticky="w")
for t, setts in self._buttons.items():
btn = ctk.CTkButton(main_frame, text=t, command=lambda b=setts['output']: self._on_button_click(b), width=setts.get("width", 10))
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=setts.get("padx", 10), pady=setts.get("pady", 10), sticky=setts.get("sticky", "e"))
def _on_button_click(self, button):
self._input = button
self.grab_release()
self._on_closing()
def get_output(self):
self.lift()
self.grab_set()
self.deiconify()
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()