From 2e2dd9e7053f28b70880ecb431802e3f350b5eda Mon Sep 17 00:00:00 2001 From: Lowa Date: Sun, 16 Feb 2025 03:57:48 +0300 Subject: [PATCH] Add custom text and entry widgets with enhanced copy/paste functionality - Implement CustomText and CustomEntry classes with advanced text interaction features - Add context menu for text widgets with cut, copy, paste, and select all options - Support multiple keyboard shortcuts for text manipulation - Replace standard Tkinter Text and Entry widgets with custom implementations - Remove global text/entry widget bindings in favor of class-specific methods --- ComConfigCopy.py | 130 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 19 deletions(-) diff --git a/ComConfigCopy.py b/ComConfigCopy.py index 76ced5b..e5aded1 100644 --- a/ComConfigCopy.py +++ b/ComConfigCopy.py @@ -437,9 +437,109 @@ def execute_commands_from_file( logging.error(f"Ошибка при выполнении команд из файла: {e}", exc_info=True) # ========================== -# Графический интерфейс (Tkinter) +# Улучшенные текстовые виджеты # ========================== +class CustomText(tk.Text): + """Улучшенный текстовый виджет с расширенной функциональностью копирования/вставки""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.create_context_menu() + self.bind_shortcuts() + + def create_context_menu(self): + self.context_menu = tk.Menu(self, tearoff=0) + self.context_menu.add_command(label="Вырезать", command=self.cut) + self.context_menu.add_command(label="Копировать", command=self.copy) + self.context_menu.add_command(label="Вставить", command=self.paste) + self.context_menu.add_separator() + self.context_menu.add_command(label="Выделить всё", command=self.select_all) + + self.bind("", self.show_context_menu) + + def show_context_menu(self, event): + self.context_menu.post(event.x_root, event.y_root) + + def bind_shortcuts(self): + # Стандартные сочетания + self.bind("", lambda e: self.event_generate("<>")) + self.bind("", lambda e: self.event_generate("<>")) + self.bind("", lambda e: self.event_generate("<>")) + self.bind("", self.select_all) + + # Shift+Insert для вставки + self.bind("", lambda e: self.event_generate("<>")) + + # Ctrl+Insert для копирования + self.bind("", lambda e: self.event_generate("<>")) + + # Shift+Delete для вырезания + self.bind("", lambda e: self.event_generate("<>")) + + def cut(self): + self.event_generate("<>") + + def copy(self): + self.event_generate("<>") + + def paste(self): + self.event_generate("<>") + + def select_all(self, event=None): + self.tag_add(tk.SEL, "1.0", tk.END) + self.mark_set(tk.INSERT, "1.0") + self.see(tk.INSERT) + return "break" + +class CustomEntry(ttk.Entry): + """Улучшенное поле ввода с расширенной функциональностью копирования/вставки""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.create_context_menu() + self.bind_shortcuts() + + def create_context_menu(self): + self.context_menu = tk.Menu(self, tearoff=0) + self.context_menu.add_command(label="Вырезать", command=self.cut) + self.context_menu.add_command(label="Копировать", command=self.copy) + self.context_menu.add_command(label="Вставить", command=self.paste) + self.context_menu.add_separator() + self.context_menu.add_command(label="Выделить всё", command=self.select_all) + + self.bind("", self.show_context_menu) + + def show_context_menu(self, event): + self.context_menu.post(event.x_root, event.y_root) + + def bind_shortcuts(self): + # Стандартные сочетания + self.bind("", lambda e: self.event_generate("<>")) + self.bind("", lambda e: self.event_generate("<>")) + self.bind("", lambda e: self.event_generate("<>")) + self.bind("", self.select_all) + + # Shift+Insert для вставки + self.bind("", lambda e: self.event_generate("<>")) + + # Ctrl+Insert для копирования + self.bind("", lambda e: self.event_generate("<>")) + + # Shift+Delete для вырезания + self.bind("", lambda e: self.event_generate("<>")) + + def cut(self): + self.event_generate("<>") + + def copy(self): + self.event_generate("<>") + + def paste(self): + self.event_generate("<>") + + def select_all(self, event=None): + self.select_range(0, tk.END) + return "break" + class SettingsWindow(tk.Toplevel): def __init__(self, parent, settings, callback=None): super().__init__(parent) @@ -486,13 +586,13 @@ class SettingsWindow(tk.Toplevel): # Размер блока ttk.Label(settings_frame, text="Размер блока:").grid(row=4, column=0, sticky=W, pady=5) self.block_size_var = StringVar(value=str(settings.get("block_size", 15))) - block_size_entry = ttk.Entry(settings_frame, textvariable=self.block_size_var) + block_size_entry = CustomEntry(settings_frame, textvariable=self.block_size_var) block_size_entry.grid(row=4, column=1, sticky=W, pady=5) # Приглашение командной строки ttk.Label(settings_frame, text="Приглашение:").grid(row=5, column=0, sticky=W, pady=5) self.prompt_var = StringVar(value=settings.get("prompt", ">")) - prompt_entry = ttk.Entry(settings_frame, textvariable=self.prompt_var) + prompt_entry = CustomEntry(settings_frame, textvariable=self.prompt_var) prompt_entry.grid(row=5, column=1, sticky=W, pady=5) # Кнопки @@ -551,14 +651,6 @@ class SerialAppGUI(tk.Tk): self.connection = None self.tftp_server = None - # Глобальные биндинги - self.bind_class("Text", "", lambda event: event.widget.event_generate("<>")) - self.bind_class("Text", "", lambda event: event.widget.event_generate("<>")) - self.bind_class("Text", "", lambda event: event.widget.event_generate("<>")) - self.bind_class("Entry", "", lambda event: event.widget.event_generate("<>")) - self.bind_class("Entry", "", lambda event: event.widget.event_generate("<>")) - self.bind_class("Entry", "", lambda event: event.widget.event_generate("<>")) - self.create_menu() self.create_tabs() @@ -647,13 +739,13 @@ class SerialAppGUI(tk.Tk): ttk.Button(control_frame, text="Подключиться", command=self.connect_device).pack(side=LEFT, padx=5) ttk.Button(control_frame, text="Отключиться", command=self.disconnect_device).pack(side=LEFT, padx=5) - self.interactive_text = tk.Text(frame, wrap="word", height=20) + self.interactive_text = CustomText(frame, wrap="word", height=20) self.interactive_text.pack(fill=BOTH, expand=True, padx=5, pady=5) input_frame = ttk.Frame(frame) input_frame.pack(fill=X, pady=5) ttk.Label(input_frame, text="Команда:").pack(side=LEFT, padx=5) - self.command_entry = ttk.Entry(input_frame, width=50) + self.command_entry = CustomEntry(input_frame, width=50) self.command_entry.pack(side=LEFT, padx=5) ttk.Button(input_frame, text="Отправить", command=self.send_command).pack(side=LEFT, padx=5) @@ -744,10 +836,10 @@ class SerialAppGUI(tk.Tk): file_frame.pack(fill=X, pady=5) ttk.Label(file_frame, text="Файл конфигурации:").pack(side=LEFT, padx=5) self.file_exec_var = StringVar(value=self.settings.get("config_file") or "") - ttk.Entry(file_frame, textvariable=self.file_exec_var, width=40).pack(side=LEFT, padx=5) + CustomEntry(file_frame, textvariable=self.file_exec_var, width=40).pack(side=LEFT, padx=5) ttk.Button(file_frame, text="Выбрать", command=self.select_config_file_fileexec).pack(side=LEFT, padx=5) ttk.Button(frame, text="Выполнить команды", command=self.execute_file_commands).pack(pady=5) - self.file_exec_text = tk.Text(frame, wrap="word", height=15) + self.file_exec_text = CustomText(frame, wrap="word", height=15) self.file_exec_text.pack(fill=BOTH, expand=True, padx=5, pady=5) def select_config_file_fileexec(self): @@ -793,11 +885,11 @@ class SerialAppGUI(tk.Tk): top_frame.pack(fill=X, pady=5) ttk.Label(top_frame, text="Файл конфигурации:").pack(side=LEFT, padx=5) self.editor_file_var = StringVar(value=self.settings.get("config_file") or "") - ttk.Entry(top_frame, textvariable=self.editor_file_var, width=40).pack(side=LEFT, padx=5) + CustomEntry(top_frame, textvariable=self.editor_file_var, width=40).pack(side=LEFT, padx=5) ttk.Button(top_frame, text="Выбрать", command=self.select_config_file_editor).pack(side=LEFT, padx=5) ttk.Button(top_frame, text="Загрузить", command=self.load_config_file).pack(side=LEFT, padx=5) ttk.Button(top_frame, text="Сохранить", command=self.save_config_file).pack(side=LEFT, padx=5) - self.config_editor_text = tk.Text(frame, wrap="word") + self.config_editor_text = CustomText(frame, wrap="word") self.config_editor_text.pack(fill=BOTH, expand=True, padx=5, pady=5) def select_config_file_editor(self): @@ -868,7 +960,7 @@ class SerialAppGUI(tk.Tk): port_frame.pack(fill=X, padx=5, pady=2) ttk.Label(port_frame, text="Порт:").pack(side=LEFT, padx=5) self.tftp_port_var = StringVar(value="69") - self.tftp_port_entry = ttk.Entry(port_frame, textvariable=self.tftp_port_var) + self.tftp_port_entry = CustomEntry(port_frame, textvariable=self.tftp_port_var) self.tftp_port_entry.pack(fill=X, expand=True, padx=5) # Кнопки управления @@ -894,7 +986,7 @@ class SerialAppGUI(tk.Tk): log_frame = ttk.LabelFrame(tftp_frame, text="Лог сервера") log_frame.pack(fill=BOTH, expand=True, padx=5, pady=5) - self.tftp_log_text = tk.Text(log_frame, wrap=tk.WORD, height=10) + self.tftp_log_text = CustomText(log_frame, wrap=tk.WORD, height=10) self.tftp_log_text.pack(fill=BOTH, expand=True, padx=5, pady=5) # Добавляем скроллбар для лога