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,
|
||||
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}")
|
||||
|
||||
# ==========================
|
||||
# Основной запуск приложения
|
||||
# ==========================
|
||||
|
||||
Reference in New Issue
Block a user