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:
363
ComConfigCopy.py
363
ComConfigCopy.py
@@ -27,6 +27,7 @@ from tkinter import (
|
|||||||
filedialog,
|
filedialog,
|
||||||
messagebox,
|
messagebox,
|
||||||
simpledialog,
|
simpledialog,
|
||||||
|
Y,
|
||||||
)
|
)
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
@@ -567,13 +568,14 @@ class SettingsWindow(tk.Toplevel):
|
|||||||
|
|
||||||
# Центрирование окна
|
# Центрирование окна
|
||||||
def center_window(self):
|
def center_window(self):
|
||||||
|
"""Центрирование окна на экране"""
|
||||||
self.update_idletasks()
|
self.update_idletasks()
|
||||||
width = self.winfo_width()
|
width = self.winfo_width()
|
||||||
height = self.winfo_height()
|
height = self.winfo_height()
|
||||||
x = (self.winfo_screenwidth() // 2) - (width // 2)
|
x = (self.winfo_screenwidth() // 2) - (width // 2)
|
||||||
y = (self.winfo_screenheight() // 2) - (height // 2)
|
y = (self.winfo_screenheight() // 2) - (height // 2)
|
||||||
self.geometry(f"{width}x{height}+{x}+{y}")
|
self.geometry(f"{width}x{height}+{x}+{y}")
|
||||||
|
|
||||||
# Обновление списка доступных последовательных портов
|
# Обновление списка доступных последовательных портов
|
||||||
def update_ports(self):
|
def update_ports(self):
|
||||||
ports = list_serial_ports()
|
ports = list_serial_ports()
|
||||||
@@ -1131,7 +1133,8 @@ class SerialAppGUI(tk.Tk):
|
|||||||
file_frame.pack(fill=X, pady=5)
|
file_frame.pack(fill=X, pady=5)
|
||||||
ttk.Label(file_frame, text="Файл конфигурации:").pack(side=LEFT, padx=5)
|
ttk.Label(file_frame, text="Файл конфигурации:").pack(side=LEFT, padx=5)
|
||||||
self.file_exec_var = StringVar(value=self.settings.get("config_file") or "")
|
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)
|
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):
|
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):
|
def execute_file_commands(self):
|
||||||
@@ -1951,6 +1959,355 @@ class SerialAppGUI(tk.Tk):
|
|||||||
self.baudrate_label.config(text=f"Скорость: {baudrate}")
|
self.baudrate_label.config(text=f"Скорость: {baudrate}")
|
||||||
self.copy_mode_label.config(text=f"Режим: {copy_mode}")
|
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}")
|
||||||
|
|
||||||
# ==========================
|
# ==========================
|
||||||
# Основной запуск приложения
|
# Основной запуск приложения
|
||||||
# ==========================
|
# ==========================
|
||||||
|
|||||||
Reference in New Issue
Block a user