Add configuration file selection and management window

- Implement ConfigSelectorWindow for advanced configuration file selection
- Add drag-and-drop support for configuration files
- Create file management features: add, edit, rename, and delete configs
- Enhance file selection process with a tree-view interface
- Improve configuration file handling with user-friendly interactions
This commit is contained in:
2025-02-19 22:25:28 +03:00
parent d3f832cdbb
commit ea432d2893

View File

@@ -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("<Double-1>", self.on_double_click)
self.tree.bind("<Button-3>", 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}")
# ==========================
# Основной запуск приложения
# ==========================