Interface-module/boxes/CTkMessageBox.py
2025-03-18 13:17:29 +03:00

179 lines
8.5 KiB
Python
Raw 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
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()