Compare commits
4 Commits
dcb5fa078a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f389c8d0e | |||
| cf62a0e750 | |||
| d0df8cce2d | |||
| 4c79770293 |
@@ -26,8 +26,8 @@ class CTkMessageBox(ctk.CTkToplevel):
|
|||||||
},
|
},
|
||||||
"error": {"Ok": {"output": True, "row": 0, "column": 0, "sticky": "center"}},
|
"error": {"Ok": {"output": True, "row": 0, "column": 0, "sticky": "center"}},
|
||||||
"yesno": {
|
"yesno": {
|
||||||
"Yes": {"output": True, "row": 0, "column": 0, "sticky": "e"},
|
"Yes": {"output": True, "row": 0, "width": 60, "padx": (0, 75)},
|
||||||
"No": {"output": False, "row": 0, "column": 1, "sticky": "w"}
|
"No": {"output": False, "row": 0, "width": 60, "padx": (0, 0)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@@ -53,6 +53,8 @@ class CTkMessageBox(ctk.CTkToplevel):
|
|||||||
buttons: Dict[str, Any] | str = 'auto'):
|
buttons: Dict[str, Any] | str = 'auto'):
|
||||||
|
|
||||||
super().__init__(fg_color=fg_color)
|
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._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._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color)
|
||||||
@@ -73,7 +75,7 @@ class CTkMessageBox(ctk.CTkToplevel):
|
|||||||
|
|
||||||
|
|
||||||
_text_slt = []
|
_text_slt = []
|
||||||
_x = 0
|
self._x = 0
|
||||||
for line in self._text.split('\n'):
|
for line in self._text.split('\n'):
|
||||||
if len(line) * 7 > 700:
|
if len(line) * 7 > 700:
|
||||||
wrapped_lines = wrap_text(line, max_message_line_width)
|
wrapped_lines = wrap_text(line, max_message_line_width)
|
||||||
@@ -81,56 +83,53 @@ class CTkMessageBox(ctk.CTkToplevel):
|
|||||||
else:
|
else:
|
||||||
_text_slt.append(line)
|
_text_slt.append(line)
|
||||||
|
|
||||||
_x = max(_x, max(len(w) * 7 for w in _text_slt))
|
self._x = max(self._x, max(len(w) * 7 for w in _text_slt))
|
||||||
_x += 20 # Add 20 pixels for padding on the left and right
|
self._x += 20 # Add 20 pixels for padding on the left and right
|
||||||
_x += 10 # Add 10 pixels for padding on the left and right
|
self._x += 10 # Add 10 pixels for padding on the left and right
|
||||||
|
|
||||||
_x = max(_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._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._text = "\n".join(_text_slt)
|
||||||
|
|
||||||
_y = 0
|
self._y = 0
|
||||||
_y += 24 + 10 + 5 # Add 24 pixels for the header (icon + text) and 15 pixels for padding
|
self._y += 24 + 10 + 5 # Add 24 pixels for the header (icon + text) and 15 pixels for padding
|
||||||
_y += len(_text_slt) * 12 # Calculate height based on the number of lines in self._text
|
self._y += len(_text_slt) * 12 # Calculate height based on the number of lines in self._text
|
||||||
_y += 15 + 10 # Add 25 pixels for padding between the header and the message
|
self._y += 15 + 10 # Add 25 pixels for padding between the header and the message
|
||||||
|
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
if self._header:
|
if self._header:
|
||||||
header = self._header(self, self.title(), disable_hide=True, disable_close=True)
|
header = self._header(self, self.title(), disable_hide=True, disable_close=True)
|
||||||
header.pack(fill="x", pady=(0, 0))
|
header.pack(fill="x", pady=(0, 0))
|
||||||
|
|
||||||
self.geometry(f"{_x}x{_y}")
|
self.geometry(f"{self._x}x{self._y}")
|
||||||
self.overrideredirect(True) # remove window decorations
|
self.overrideredirect(True) # remove window decorations
|
||||||
self.after(self._timeout, self.destroy) # close window after timeout
|
self.after(self._timeout, self._on_closing) # close window after timeout
|
||||||
self._buttons = {}
|
self._buttons = {}
|
||||||
else:
|
else:
|
||||||
_y += 30 + 10 + 12 # Add 30 pixels for the buttons
|
self._y += 30 + 10 + 12 # Add 30 pixels for the buttons
|
||||||
self.geometry(f"{_x}x{_y+12}") # Add 12 pixels to the height for the title bar
|
self.geometry(f"{self._x}x{self._y+12}") # Add 12 pixels to the height for the title bar
|
||||||
if buttons == "auto":
|
if buttons == "auto":
|
||||||
self._buttons = self.buttons_map.get(self._mode, {})
|
self._buttons = self.buttons_map.get(self._mode, {})
|
||||||
elif isinstance(buttons, str):
|
elif isinstance(buttons, str):
|
||||||
self._buttons = {buttons: {"output": True, "row": 0, "column": 0, "sticky": "center"}}
|
self._buttons = {buttons: {"output": True, "row": 0, "column": 0, "sticky": "center"}}
|
||||||
|
|
||||||
self.attributes("-topmost", True) # stay on top
|
self.attributes("-topmost", True) # stay on top
|
||||||
|
self._center()
|
||||||
self.transient(parent)
|
self.transient(parent)
|
||||||
self.title(title)
|
self.title(title)
|
||||||
self.lift() # lift window on top
|
|
||||||
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
|
||||||
self._create_widgets()
|
self._create_widgets()
|
||||||
self.resizable(False, False)
|
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||||
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):
|
def _on_closing(self):
|
||||||
|
self.withdraw()
|
||||||
self.grab_release()
|
self.grab_release()
|
||||||
if self.winfo_exists():
|
|
||||||
self.destroy()
|
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):
|
def _create_widgets(self):
|
||||||
# Иконки для типов сообщений
|
# Иконки для типов сообщений
|
||||||
icon_text = self.icon_map.get(self._mode, "?")
|
icon_text = self.icon_map.get(self._mode, "?")
|
||||||
@@ -166,6 +165,9 @@ class CTkMessageBox(ctk.CTkToplevel):
|
|||||||
self._on_closing()
|
self._on_closing()
|
||||||
|
|
||||||
def get_output(self):
|
def get_output(self):
|
||||||
|
self.lift()
|
||||||
|
self.grab_set()
|
||||||
|
self.deiconify()
|
||||||
self.master.wait_window(self)
|
self.master.wait_window(self)
|
||||||
if self._timeout > 0 and not self._input:
|
if self._timeout > 0 and not self._input:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ import customtkinter as ctk
|
|||||||
|
|
||||||
def darken_color_rgb(hex_color, amount=30):
|
def darken_color_rgb(hex_color, amount=30):
|
||||||
"""Затемняет цвет, вычитая значение из каждого компонента RGB"""
|
"""Затемняет цвет, вычитая значение из каждого компонента RGB"""
|
||||||
|
try:
|
||||||
hex_color = hex_color.lstrip("#")
|
hex_color = hex_color.lstrip("#")
|
||||||
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
# Уменьшаем компоненты, не давая им уйти в минус
|
# Уменьшаем компоненты, не давая им уйти в минус
|
||||||
r, g, b = max(0, r - amount), max(0, g - amount), max(0, b - amount)
|
r, g, b = max(0, r - amount), max(0, g - amount), max(0, b - amount)
|
||||||
|
except Exception as e:
|
||||||
|
print(e, hex_color)
|
||||||
|
raise e
|
||||||
return f"#{r:02X}{g:02X}{b:02X}"
|
return f"#{r:02X}{g:02X}{b:02X}"
|
||||||
|
|
||||||
class CTkTableFrame(ctk.CTkFrame):
|
class CTkTableFrame(ctk.CTkFrame):
|
||||||
@@ -44,6 +47,7 @@ class CTkTableFrame(ctk.CTkFrame):
|
|||||||
def _prepare_columns(self):
|
def _prepare_columns(self):
|
||||||
# Применяем шаблон значений по умолчанию
|
# Применяем шаблон значений по умолчанию
|
||||||
default_column = {"width": 0, "align": "left", "name": "N/A"}
|
default_column = {"width": 0, "align": "left", "name": "N/A"}
|
||||||
|
self._columns.clear()
|
||||||
for col in self.columns:
|
for col in self.columns:
|
||||||
if type(col) == dict:
|
if type(col) == dict:
|
||||||
if "width" not in col:
|
if "width" not in col:
|
||||||
@@ -65,16 +69,22 @@ class CTkTableFrame(ctk.CTkFrame):
|
|||||||
|
|
||||||
def _build_header(self):
|
def _build_header(self):
|
||||||
"""Создает заголовок таблицы."""
|
"""Создает заголовок таблицы."""
|
||||||
header_frame = ctk.CTkFrame(self, fg_color="gray30", corner_radius=10)
|
self.header_frame = ctk.CTkFrame(self, fg_color="gray30", corner_radius=10)
|
||||||
header_frame.pack(fill="x", padx=0, pady=1)
|
self.header_frame.pack(fill="x", padx=0, pady=1)
|
||||||
|
|
||||||
# Заголовки
|
# Заголовки
|
||||||
for col in self._columns:
|
for col in self._columns:
|
||||||
header_label = ctk.CTkLabel(
|
header_label = ctk.CTkLabel(
|
||||||
header_frame, text=col["name"], width=col["width"], anchor=col["align"], padx=5
|
self.header_frame, text=col["name"], width=col["width"], anchor=col["align"], padx=5
|
||||||
)
|
)
|
||||||
header_label.pack(side="left", padx=4, pady=3)
|
header_label.pack(side="left", padx=4, pady=3)
|
||||||
|
|
||||||
|
def _update_header(self):
|
||||||
|
"""Обновляет заголовок таблицы."""
|
||||||
|
for i, col in enumerate(self._columns):
|
||||||
|
header_label = self.header_frame.winfo_children()[i]
|
||||||
|
header_label.configure(width=col["width"], anchor=col["align"])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __row_enter(frame, e, color="gray40"):
|
def __row_enter(frame, e, color="gray40"):
|
||||||
frame.configure(fg_color=color)
|
frame.configure(fg_color=color)
|
||||||
@@ -111,6 +121,8 @@ class CTkTableFrame(ctk.CTkFrame):
|
|||||||
|
|
||||||
def create_table(self):
|
def create_table(self):
|
||||||
"""Создает таблицу с заголовками и данными, используя параметры из словаря."""
|
"""Создает таблицу с заголовками и данными, используя параметры из словаря."""
|
||||||
|
self._prepare_columns()
|
||||||
|
self._update_header()
|
||||||
self._rows = [None] * len(self._data)
|
self._rows = [None] * len(self._data)
|
||||||
self._rows_settings = [None] * len(self._data)
|
self._rows_settings = [None] * len(self._data)
|
||||||
loading = self.loading_frame.winfo_children()[0]
|
loading = self.loading_frame.winfo_children()[0]
|
||||||
@@ -166,6 +178,9 @@ class CTkTableFrame(ctk.CTkFrame):
|
|||||||
def edit(self, row_index, new_data=None, color=None, disable=False, **__):
|
def edit(self, row_index, new_data=None, color=None, disable=False, **__):
|
||||||
"""Редактирует строку по индексу."""
|
"""Редактирует строку по индексу."""
|
||||||
|
|
||||||
|
if row_index == -1:
|
||||||
|
row_index = len(self._data) - 1
|
||||||
|
|
||||||
def _disable_colors(widget):
|
def _disable_colors(widget):
|
||||||
widget.unbind("<Enter>")
|
widget.unbind("<Enter>")
|
||||||
widget.unbind("<Leave>")
|
widget.unbind("<Leave>")
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from . import fonts
|
from . import fonts
|
||||||
from .params import Strings, Icons
|
from .params import Strings, Icons
|
||||||
from .utils import base_path, get_file, wrap_text
|
from .utils import base_path, get_file, wrap_text, reg_ctrl_events
|
||||||
|
|||||||
@@ -32,3 +32,34 @@ def base_path():
|
|||||||
|
|
||||||
def get_file(filename):
|
def get_file(filename):
|
||||||
return base_path() / "resources" / filename
|
return base_path() / "resources" / filename
|
||||||
|
|
||||||
|
def on_ctrl_c(entry, event):
|
||||||
|
# Копируем текст из поля ввода в буфер обмена
|
||||||
|
text = entry.get() # Получаем текст из поля ввода
|
||||||
|
entry.clipboard_clear() # Очищаем буфер обмена
|
||||||
|
entry.clipboard_append(text) # Добавляем текст в буфер обмена
|
||||||
|
|
||||||
|
def on_ctrl_v(entry, event):
|
||||||
|
# Вставляем текст из буфера обмена в поле ввода
|
||||||
|
clipboard_text = entry.clipboard_get() # Получаем текст из буфера обмена
|
||||||
|
entry.insert("insert", clipboard_text) # Вставляем текст в позицию курсора
|
||||||
|
|
||||||
|
def on_ctrl_a(entry, event):
|
||||||
|
# Выделяем весь текст в поле ввода
|
||||||
|
entry.select_range(0, 'end') # Выделяем текст от начала до конца
|
||||||
|
|
||||||
|
def on_ctrl_left(entry, event):
|
||||||
|
# Перемещаем курсор на одно слово влево
|
||||||
|
entry.event_generate("<Left>") # Симулируем нажатие стрелки влево
|
||||||
|
|
||||||
|
def on_ctrl_right(entry, event):
|
||||||
|
# Перемещаем курсор на одно слово вправо
|
||||||
|
entry.event_generate("<Right>") # Симулируем нажатие стрелки вправо
|
||||||
|
|
||||||
|
def reg_ctrl_events(entry):
|
||||||
|
# Привязка горячих клавиш с использованием lambda
|
||||||
|
entry.bind("<Control-c>", lambda event: on_ctrl_c(entry, event)) # Копирование в буфер
|
||||||
|
entry.bind("<Control-v>", lambda event: on_ctrl_v(entry, event)) # Вставка из буфера
|
||||||
|
entry.bind("<Control-a>", lambda event: on_ctrl_a(entry, event)) # Выделение всего текста
|
||||||
|
entry.bind("<Control-Left>", lambda event: on_ctrl_left(entry, event)) # Перемещение курсора влево
|
||||||
|
entry.bind("<Control-Right>", lambda event: on_ctrl_right(entry, event)) # Перемещение курсора вправо
|
||||||
|
|||||||
Reference in New Issue
Block a user