diff --git a/ComConfigCopy.py b/ComConfigCopy.py index b081d59..25ce503 100644 --- a/ComConfigCopy.py +++ b/ComConfigCopy.py @@ -27,6 +27,7 @@ from tkinter import ( filedialog, messagebox, simpledialog, + Y, ) from tkinter import ttk @@ -567,13 +568,14 @@ class SettingsWindow(tk.Toplevel): # Центрирование окна def center_window(self): + """Центрирование окна на экране""" self.update_idletasks() width = self.winfo_width() height = self.winfo_height() x = (self.winfo_screenwidth() // 2) - (width // 2) y = (self.winfo_screenheight() // 2) - (height // 2) self.geometry(f"{width}x{height}+{x}+{y}") - + # Обновление списка доступных последовательных портов def update_ports(self): ports = list_serial_ports() @@ -1131,7 +1133,8 @@ 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 "") - CustomEntry(file_frame, textvariable=self.file_exec_var, width=40).pack(side=LEFT, padx=5) + self.file_exec_entry = CustomEntry(file_frame, textvariable=self.file_exec_var, width=40) + self.file_exec_entry.pack(side=LEFT, padx=5) ttk.Button(file_frame, text="Выбрать", command=self.select_config_file_fileexec).pack(side=LEFT, padx=5) # Создаем фрейм для кнопок управления @@ -1187,7 +1190,12 @@ class SerialAppGUI(tk.Tk): # Выбор файла конфигурации для выполнения команд def select_config_file_fileexec(self): - select_config_file(self, self.file_exec_var) + def on_config_selected(path): + self.file_exec_var.set(path) + + # Создаем и показываем окно выбора конфигурации + config_selector = ConfigSelectorWindow(self, on_config_selected) + self.wait_window(config_selector) # Выполнение команд из файла def execute_file_commands(self): @@ -1951,6 +1959,355 @@ class SerialAppGUI(tk.Tk): self.baudrate_label.config(text=f"Скорость: {baudrate}") self.copy_mode_label.config(text=f"Режим: {copy_mode}") + def on_file_exec_drop(self, event): + """Обработка drop файла во вкладке выполнения команд""" + try: + files = event.data.split() + if files: + file = files[0] # Берем только первый файл + if file.lower().endswith('.txt'): + filename = os.path.basename(file) + new_path = os.path.join("Configs", filename) + + if os.path.exists(new_path): + if not messagebox.askyesno( + "Подтверждение", + f"Файл {filename} уже существует. Перезаписать?" + ): + return + + # Копируем файл в папку Configs + with open(file, 'r', encoding='utf-8') as source: + content = source.read() + with open(new_path, 'w', encoding='utf-8') as dest: + dest.write(content) + + # Устанавливаем путь к файлу + self.file_exec_var.set(new_path) + messagebox.showinfo("Успех", "Файл конфигурации успешно добавлен") + else: + messagebox.showerror("Ошибка", "Поддерживаются только текстовые файлы (.txt)") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось добавить файл: {str(e)}") + +# Класс для окна выбора конфигурации +class ConfigSelectorWindow(tk.Toplevel): + def __init__(self, parent, callback): + super().__init__(parent) + self.title("Выбор конфигурации") + self.geometry("600x400") + self.callback = callback + + # Создаем основной фрейм + main_frame = ttk.Frame(self, padding="10") + main_frame.pack(fill=BOTH, expand=True) + + # Создаем фрейм для кнопок управления + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=X, pady=5) + + # Кнопка "Добавить новую конфигурацию" + add_button = ttk.Button( + button_frame, + text="➕ Добавить новую конфигурацию", + command=self.add_new_config + ) + add_button.pack(side=LEFT, padx=5) + + # Кнопка "Создать папку" + create_folder_button = ttk.Button( + button_frame, + text="📁 Создать папку", + command=self.create_new_folder + ) + create_folder_button.pack(side=LEFT, padx=5) + + # Создаем фрейм для дерева и скроллбара + tree_frame = ttk.Frame(main_frame) + tree_frame.pack(fill=BOTH, expand=True, pady=5) + + # Создаем Treeview для отображения файлов и папок + self.tree = ttk.Treeview(tree_frame, selectmode="browse") + + # Добавляем скроллбар + scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) + scrollbar.pack(side=RIGHT, fill=Y) + + self.tree.pack(side=LEFT, fill=BOTH, expand=True) + self.tree.configure(yscrollcommand=scrollbar.set) + + # Настраиваем колонки + self.tree["columns"] = () # Убираем колонку действий + self.tree.column("#0", width=400, stretch=True) + self.tree.heading("#0", text="Конфигурация") + + # Создаем контекстное меню + self.context_menu = tk.Menu(self, tearoff=0) + self.context_menu.add_command(label="Редактировать", command=self.edit_selected) + self.context_menu.add_command(label="Переименовать", command=self.rename_selected) + self.context_menu.add_separator() + self.context_menu.add_command(label="Удалить", command=self.delete_selected) + + # Привязываем события + self.tree.bind("", self.on_double_click) + self.tree.bind("", self.show_context_menu) + + # Загружаем файлы и папки + self.load_configs() + + # Центрируем окно + self.center_window() + + # Делаем окно модальным + self.transient(parent) + self.grab_set() + + def create_new_folder(self): + """Создание новой папки""" + selected_item = self.get_selected_item() + parent_path = "Configs" + + if selected_item: + item_text = self.tree.item(selected_item)['text'] + if item_text.startswith("📁"): + parent_path = self.get_full_path(selected_item) + else: + parent_item = self.tree.parent(selected_item) + if parent_item: + parent_path = self.get_full_path(parent_item) + + folder_name = simpledialog.askstring( + "Создать папку", + "Введите имя новой папки:" + ) + + if folder_name: + try: + new_folder_path = os.path.join(parent_path, folder_name) + os.makedirs(new_folder_path, exist_ok=True) + self.load_configs() + messagebox.showinfo("Успех", "Папка успешно создана") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось создать папку: {str(e)}") + + def show_context_menu(self, event): + """Показ контекстного меню""" + item = self.tree.identify('item', event.x, event.y) + if item: + self.tree.selection_set(item) + if self.tree.item(item)['text'].startswith("📄"): # Только для файлов + self.context_menu.post(event.x_root, event.y_root) + + def get_selected_item(self): + """Получение выбранного элемента""" + selection = self.tree.selection() + if selection: + return selection[0] + return None + + def edit_selected(self): + """Редактирование выбранного файла""" + item_id = self.get_selected_item() + if item_id: + path = self.get_full_path(item_id) + self.edit_config(path) + + def rename_selected(self): + """Переименование выбранного файла""" + item_id = self.get_selected_item() + if item_id: + path = self.get_full_path(item_id) + self.rename_config(item_id, path) + + def delete_selected(self): + """Удаление выбранного файла""" + item_id = self.get_selected_item() + if item_id: + path = self.get_full_path(item_id) + self.delete_config(path) + + def load_configs(self): + """Загрузка конфигураций из папки Configs""" + # Очищаем дерево + for item in self.tree.get_children(): + self.tree.delete(item) + + # Функция для рекурсивного добавления файлов и папок + def add_directory(path, parent=""): + try: + # Сортируем содержимое: сначала папки, потом файлы + items = os.listdir(path) + directories = [] + files = [] + + for item in items: + full_path = os.path.join(path, item) + if os.path.isdir(full_path): + directories.append(item) + elif item.endswith('.txt'): + files.append(item) + + # Добавляем папки + for directory in sorted(directories): + full_path = os.path.join(path, directory) + folder_id = self.tree.insert( + parent, + "end", + text="📁 " + directory, + open=False + ) + add_directory(full_path, folder_id) + + # Добавляем файлы + for file in sorted(files): + self.tree.insert( + parent, + "end", + text="📄 " + file + ) + + except Exception as e: + messagebox.showerror("Ошибка", f"Ошибка при загрузке файлов: {str(e)}") + + # Загружаем файлы, начиная с корневой папки Configs + add_directory("Configs") + + def add_new_config(self): + """Добавление новой конфигурации через проводник Windows""" + file_path = filedialog.askopenfilename( + title="Выберите файл конфигурации", + filetypes=[("Текстовые файлы", "*.txt")], + initialdir=os.path.abspath(".") + ) + + if not file_path: # Если пользователь отменил выбор файла + return + + try: + # Создаем имя файла в папке Configs + filename = os.path.basename(file_path) + new_path = os.path.join("Configs", filename) + + # Если файл уже существует, спрашиваем о перезаписи + if os.path.exists(new_path): + if not messagebox.askyesno( + "Подтверждение", + f"Файл {filename} уже существует. Перезаписать?" + ): + return + + # Копируем файл + with open(file_path, 'r', encoding='utf-8') as source: + content = source.read() + with open(new_path, 'w', encoding='utf-8') as dest: + dest.write(content) + + self.load_configs() # Обновляем список + messagebox.showinfo("Успех", "Файл конфигурации успешно добавлен") + + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось добавить файл: {str(e)}") + + def get_full_path(self, item_id): + """Получение полного пути к файлу/папке""" + path_parts = [] + while item_id: + text = self.tree.item(item_id)['text'] + # Удаляем эмодзи из начала текста + if text.startswith("📁 "): + text = text[2:].strip() + elif text.startswith("📄 "): + text = text[2:].strip() + path_parts.insert(0, text) + item_id = self.tree.parent(item_id) + return os.path.join("Configs", *path_parts) + + def select_config(self, path): + """Выбор конфигурации""" + if os.path.isfile(path): + self.callback(path) + self.destroy() + + def edit_config(self, path): + """Редактирование конфигурации""" + if os.path.isfile(path): + # Переключаемся на вкладку редактора и загружаем файл + self.master.notebook.select(2) # Индекс вкладки редактора + self.master.editor_file_var.set(path) + self.master.load_config_file() + self.destroy() + + def rename_config(self, item_id, old_path): + """Переименование конфигурации""" + if not os.path.isfile(old_path): + return + + # Получаем текущее имя файла без эмодзи + current_name = self.tree.item(item_id)['text'] + if current_name.startswith("📄 "): + current_name = current_name[2:].strip() + + # Запрашиваем новое имя + new_name = simpledialog.askstring( + "Переименование", + "Введите новое имя файла:", + initialvalue=current_name + ) + + if new_name: + if not new_name.endswith('.txt'): + new_name += '.txt' + + new_path = os.path.join(os.path.dirname(old_path), new_name) + + try: + os.rename(old_path, new_path) + self.load_configs() # Перезагружаем список + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось переименовать файл: {str(e)}") + + def delete_config(self, path): + """Удаление конфигурации""" + if not os.path.isfile(path): + return + + # Запрашиваем подтверждение + if messagebox.askyesno( + "Подтверждение", + f"Вы действительно хотите удалить файл {os.path.basename(path)}?" + ): + try: + os.remove(path) + self.load_configs() # Перезагружаем список + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось удалить файл: {str(e)}") + + def on_double_click(self, event): + """Обработка двойного клика""" + item_id = self.tree.identify('item', event.x, event.y) + if not item_id: + return + + # Если это папка, разворачиваем/сворачиваем её + if not self.tree.item(item_id)['text'].startswith("📄"): # Если не файл + if self.tree.item(item_id)['open']: + self.tree.item(item_id, open=False) + else: + self.tree.item(item_id, open=True) + else: + # Если это файл, выбираем его + full_path = self.get_full_path(item_id) + self.select_config(full_path) + + def center_window(self): + """Центрирование окна на экране""" + self.update_idletasks() + width = self.winfo_width() + height = self.winfo_height() + x = (self.winfo_screenwidth() // 2) - (width // 2) + y = (self.winfo_screenheight() // 2) - (height // 2) + self.geometry(f"{width}x{height}+{x}+{y}") + # ========================== # Основной запуск приложения # ==========================