Improved configuration file management with advanced features
- Added send2trash library for safer file deletion - Implemented folder and file moving functionality in ConfigSelectorWindow - Created FolderSelectorDialog for moving files to a folder - Improved file and folder renaming with improved error handling - Added emoji icons for better visual representation of actions - Updated requirements.txt to include send2trash library - Removed drag-and-drop support. Postponed for future
This commit is contained in:
685
ComConfigCopy.py
685
ComConfigCopy.py
@@ -38,6 +38,7 @@ from about_window import AboutWindow
|
||||
from TFTPServer import TFTPServer
|
||||
import socket
|
||||
from update_checker import UpdateChecker
|
||||
from send2trash import send2trash
|
||||
|
||||
# Версия программы
|
||||
VERSION = "1.0.2"
|
||||
@@ -1248,201 +1249,201 @@ class SerialAppGUI(tk.Tk):
|
||||
consecutive_errors = 0
|
||||
MAX_CONSECUTIVE_ERRORS = 3 # Максимальное количество последовательных ошибок
|
||||
|
||||
def check_connection():
|
||||
"""Проверка состояния соединения"""
|
||||
# Если выполнение остановлено пользователем, просто возвращаем False без сообщений
|
||||
if self.execution_stop:
|
||||
return False
|
||||
|
||||
if not self.connection or not self.connection.is_open:
|
||||
# Если остановка произошла во время проверки, не показываем сообщение
|
||||
if self.execution_stop:
|
||||
return False
|
||||
|
||||
self.append_file_exec_text("[ERROR] Соединение потеряно!\n")
|
||||
# Автоматически ставим на паузу
|
||||
self.execution_paused = True
|
||||
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
||||
|
||||
# Показываем сообщение только если это не ручная остановка
|
||||
if not self.execution_stop:
|
||||
self.after(0, lambda: messagebox.showerror(
|
||||
"Ошибка соединения",
|
||||
"Соединение с устройством потеряно!\nВыполнение команд приостановлено.\n\n"
|
||||
"Пожалуйста:\n"
|
||||
"1. Проверьте подключение\n"
|
||||
"2. Нажмите 'Продолжить' после восстановления соединения\n"
|
||||
" или 'Остановить' для прекращения выполнения"
|
||||
))
|
||||
return False
|
||||
return True
|
||||
|
||||
def handle_no_response(cmd_or_block, is_block=False):
|
||||
"""Обработка отсутствия ответа от устройства"""
|
||||
nonlocal consecutive_errors
|
||||
consecutive_errors += 1
|
||||
|
||||
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
|
||||
self.append_file_exec_text(
|
||||
f"[ERROR] Обнаружено {consecutive_errors} последовательных ошибок!\n"
|
||||
"Возможно, устройство не отвечает или проблемы с соединением.\n"
|
||||
)
|
||||
# Автоматически ставим на паузу
|
||||
self.execution_paused = True
|
||||
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
||||
# Показываем сообщение пользователю
|
||||
self.after(0, lambda: messagebox.showerror(
|
||||
"Устройство не отвечает",
|
||||
f"Обнаружено {consecutive_errors} последовательных ошибок!\n\n"
|
||||
"Возможные причины:\n"
|
||||
"1. Устройство не отвечает на команды\n"
|
||||
"2. Проблемы с соединением\n"
|
||||
"3. Неверный формат команд\n\n"
|
||||
"Выполнение приостановлено.\n"
|
||||
"Проверьте подключение и состояние устройства,\n"
|
||||
"затем нажмите 'Продолжить' или 'Остановить'."
|
||||
))
|
||||
if not self.execution_stop: # Проверяем, не была ли выполнена остановка
|
||||
self.append_file_exec_text(
|
||||
f"[ERROR] Обнаружено {consecutive_errors} последовательных ошибок!\n"
|
||||
"Возможно, устройство не отвечает или проблемы с соединением.\n"
|
||||
)
|
||||
# Автоматически ставим на паузу
|
||||
self.execution_paused = True
|
||||
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
||||
# Показываем сообщение пользователю
|
||||
self.after(0, lambda: messagebox.showerror(
|
||||
"Устройство не отвечает",
|
||||
f"Обнаружено {consecutive_errors} последовательных ошибок!\n\n"
|
||||
"Возможные причины:\n"
|
||||
"1. Устройство не отвечает на команды\n"
|
||||
"2. Проблемы с соединением\n"
|
||||
"3. Неверный формат команд\n\n"
|
||||
"Выполнение приостановлено.\n"
|
||||
"Проверьте подключение и состояние устройства,\n"
|
||||
"затем нажмите 'Продолжить' или 'Остановить'."
|
||||
))
|
||||
return False
|
||||
return True
|
||||
|
||||
def wait_before_next_command():
|
||||
"""Ожидание перед следующей командой с учетом паузы"""
|
||||
while self.execution_paused and not self.execution_stop:
|
||||
time.sleep(0.1)
|
||||
if self.execution_stop:
|
||||
return False
|
||||
time.sleep(1) # Базовая задержка между командами
|
||||
return True
|
||||
|
||||
if copy_mode == "line":
|
||||
# Построчный режим
|
||||
while self.current_command_index < len(self.commands):
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
if self.execution_paused:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
# Проверяем соединение перед каждой командой
|
||||
if not check_connection():
|
||||
continue
|
||||
|
||||
cmd = self.commands[self.current_command_index]
|
||||
try:
|
||||
success, response = send_command_and_process_response(
|
||||
self.connection,
|
||||
cmd,
|
||||
self.settings.get("timeout", 10),
|
||||
max_attempts=3,
|
||||
log_callback=self.append_file_exec_text,
|
||||
login=self.settings.get("login"),
|
||||
password=self.settings.get("password"),
|
||||
is_gui=True
|
||||
)
|
||||
|
||||
if not success:
|
||||
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
|
||||
# Проверяем соединение после неудачной попытки
|
||||
if not check_connection():
|
||||
continue
|
||||
# Обрабатываем отсутствие ответа
|
||||
if not handle_no_response(cmd):
|
||||
continue
|
||||
else:
|
||||
# Сбрасываем счетчик ошибок при успешном выполнении
|
||||
consecutive_errors = 0
|
||||
try:
|
||||
if copy_mode == "line":
|
||||
# Построчный режим
|
||||
while self.current_command_index < len(self.commands):
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
self.current_command_index += 1
|
||||
self.after(0, self.update_progress)
|
||||
time.sleep(1) # Задержка между командами
|
||||
|
||||
except Exception as e:
|
||||
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n")
|
||||
if not check_connection():
|
||||
if not wait_before_next_command():
|
||||
break
|
||||
|
||||
# Проверяем соединение перед каждой командой
|
||||
if not self.check_connection():
|
||||
if self.execution_stop: # Если это остановка, прерываем выполнение
|
||||
break
|
||||
continue
|
||||
break
|
||||
else:
|
||||
# Блочный режим
|
||||
blocks = generate_command_blocks(self.commands, block_size)
|
||||
total_blocks = len(blocks)
|
||||
current_block = 0
|
||||
|
||||
while current_block < total_blocks:
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
if self.execution_paused:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
# Проверяем соединение перед каждым блоком
|
||||
if not check_connection():
|
||||
continue
|
||||
|
||||
block = blocks[current_block]
|
||||
try:
|
||||
# Выводим блок команд без [CMD] префикса
|
||||
self.append_file_exec_text(f"Выполнение блока команд:\n{block}\n")
|
||||
success, response = send_command_and_process_response(
|
||||
self.connection,
|
||||
block,
|
||||
self.settings.get("timeout", 10),
|
||||
max_attempts=3,
|
||||
log_callback=None, # Отключаем вывод для первой попытки
|
||||
login=self.settings.get("login"),
|
||||
password=self.settings.get("password"),
|
||||
is_gui=True
|
||||
)
|
||||
|
||||
if not success or (response and '^' in response):
|
||||
self.append_file_exec_text("[WARNING] Ошибка при выполнении блока команд. Отправляю команды по отдельности...\n")
|
||||
# Проверяем соединение перед отправкой отдельных команд
|
||||
if not check_connection():
|
||||
continue
|
||||
|
||||
cmd = self.commands[self.current_command_index]
|
||||
try:
|
||||
success, response = send_command_and_process_response(
|
||||
self.connection,
|
||||
cmd,
|
||||
self.settings.get("timeout", 10),
|
||||
max_attempts=3,
|
||||
log_callback=self.append_file_exec_text if not self.execution_stop else None,
|
||||
login=self.settings.get("login"),
|
||||
password=self.settings.get("password"),
|
||||
is_gui=True
|
||||
)
|
||||
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
# Обрабатываем отсутствие ответа для блока
|
||||
if not success and not handle_no_response(block, True):
|
||||
continue
|
||||
|
||||
# Отправляем команды блока по отдельности
|
||||
for cmd in block.splitlines():
|
||||
if self.execution_stop:
|
||||
break
|
||||
if cmd.strip():
|
||||
if not check_connection():
|
||||
if not success:
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
|
||||
# Проверяем соединение после неудачной попытки
|
||||
if not self.check_connection():
|
||||
if self.execution_stop:
|
||||
break
|
||||
success, resp = send_command_and_process_response(
|
||||
self.connection,
|
||||
cmd,
|
||||
self.settings.get("timeout", 10),
|
||||
max_attempts=3,
|
||||
log_callback=self.append_file_exec_text,
|
||||
login=self.settings.get("login"),
|
||||
password=self.settings.get("password"),
|
||||
is_gui=True
|
||||
)
|
||||
if not success:
|
||||
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
|
||||
if not check_connection():
|
||||
break
|
||||
# Обрабатываем отсутствие ответа для отдельной команды
|
||||
if not handle_no_response(cmd):
|
||||
break
|
||||
else:
|
||||
# Сбрасываем счетчик ошибок при успешном выполнении
|
||||
consecutive_errors = 0
|
||||
else:
|
||||
# Если блок выполнился успешно, выводим ответ и сбрасываем счетчик ошибок
|
||||
consecutive_errors = 0
|
||||
if response:
|
||||
self.append_file_exec_text(f"Ответ устройства:\n{response}\n")
|
||||
continue
|
||||
# Обрабатываем отсутствие ответа
|
||||
if not handle_no_response(cmd):
|
||||
continue
|
||||
else:
|
||||
# Сбрасываем счетчик ошибок при успешном выполнении
|
||||
consecutive_errors = 0
|
||||
|
||||
self.current_command_index += 1
|
||||
self.after(0, self.update_progress)
|
||||
|
||||
except Exception as e:
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n")
|
||||
break
|
||||
else:
|
||||
# Блочный режим
|
||||
blocks = generate_command_blocks(self.commands, block_size)
|
||||
total_blocks = len(blocks)
|
||||
current_block = 0
|
||||
|
||||
while current_block < total_blocks:
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
if not wait_before_next_command():
|
||||
break
|
||||
|
||||
# Обновляем прогресс на основе количества выполненных блоков
|
||||
current_block += 1
|
||||
self.current_command_index = (current_block * 100) // total_blocks
|
||||
self.after(0, self.update_progress)
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении блока команд: {str(e)}\n")
|
||||
if not check_connection():
|
||||
# Проверяем соединение перед каждым блоком
|
||||
if not self.check_connection():
|
||||
if self.execution_stop:
|
||||
break
|
||||
continue
|
||||
break
|
||||
|
||||
# Завершение выполнения
|
||||
self.after(0, self.execution_completed)
|
||||
block = blocks[current_block]
|
||||
try:
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text(f"Выполнение блока команд:\n{block}\n")
|
||||
success, response = send_command_and_process_response(
|
||||
self.connection,
|
||||
block,
|
||||
self.settings.get("timeout", 10),
|
||||
max_attempts=3,
|
||||
log_callback=None,
|
||||
login=self.settings.get("login"),
|
||||
password=self.settings.get("password"),
|
||||
is_gui=True
|
||||
)
|
||||
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
if not success or (response and '^' in response):
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text("[WARNING] Ошибка при выполнении блока команд. Отправляю команды по отдельности...\n")
|
||||
|
||||
# Проверяем соединение перед отправкой отдельных команд
|
||||
if not self.check_connection():
|
||||
if self.execution_stop:
|
||||
break
|
||||
continue
|
||||
|
||||
# Обрабатываем отсутствие ответа для блока
|
||||
if not success and not handle_no_response(block, True):
|
||||
continue
|
||||
|
||||
# Отправляем команды блока по отдельности
|
||||
for cmd in block.splitlines():
|
||||
if self.execution_stop:
|
||||
break
|
||||
if cmd.strip():
|
||||
if not self.check_connection():
|
||||
break
|
||||
success, resp = send_command_and_process_response(
|
||||
self.connection,
|
||||
cmd,
|
||||
self.settings.get("timeout", 10),
|
||||
max_attempts=3,
|
||||
log_callback=self.append_file_exec_text if not self.execution_stop else None,
|
||||
login=self.settings.get("login"),
|
||||
password=self.settings.get("password"),
|
||||
is_gui=True
|
||||
)
|
||||
|
||||
if self.execution_stop:
|
||||
break
|
||||
|
||||
if not success:
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
|
||||
if not self.check_connection():
|
||||
break
|
||||
if not handle_no_response(cmd):
|
||||
break
|
||||
else:
|
||||
consecutive_errors = 0
|
||||
|
||||
if not wait_before_next_command():
|
||||
break
|
||||
|
||||
# Если блок выполнился успешно
|
||||
if success and not (response and '^' in response):
|
||||
consecutive_errors = 0
|
||||
if response and not self.execution_stop:
|
||||
self.append_file_exec_text(f"Ответ устройства:\n{response}\n")
|
||||
|
||||
# Обновляем прогресс
|
||||
current_block += 1
|
||||
self.current_command_index = (current_block * 100) // total_blocks
|
||||
self.after(0, self.update_progress)
|
||||
|
||||
except Exception as e:
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении блока команд: {str(e)}\n")
|
||||
break
|
||||
finally:
|
||||
# Завершение выполнения
|
||||
self.after(0, self.execution_completed)
|
||||
|
||||
def execution_completed(self):
|
||||
"""Обработка завершения выполнения в главном потоке"""
|
||||
@@ -1489,27 +1490,38 @@ class SerialAppGUI(tk.Tk):
|
||||
self.append_file_exec_text("[INFO] Выполнение возобновлено.\n")
|
||||
|
||||
def stop_execution(self):
|
||||
"""Остановка выполнения команд"""
|
||||
# Устанавливаем флаг остановки
|
||||
self.execution_stop = True
|
||||
self.execution_paused = False
|
||||
self.timer_running = False
|
||||
|
||||
# Отключаемся от COM-порта
|
||||
# Очищаем очередь команд
|
||||
if hasattr(self, 'commands'):
|
||||
delattr(self, 'commands')
|
||||
|
||||
# Сбрасываем индекс текущей команды и прогресс
|
||||
self.current_command_index = 0
|
||||
self.progress_bar['value'] = 0
|
||||
self.update_progress()
|
||||
|
||||
# Отключаемся от COM-порта без вывода сообщений об ошибках
|
||||
if self.connection:
|
||||
try:
|
||||
self.stop_port_monitoring()
|
||||
self.connection.close()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
self.connection = None
|
||||
self.append_file_exec_text("[INFO] Соединение закрыто.\n")
|
||||
except Exception as e:
|
||||
self.append_file_exec_text(f"[ERROR] Ошибка при закрытии соединения: {str(e)}\n")
|
||||
|
||||
self.current_command_index = 0 # Сбрасываем индекс текущей команды
|
||||
self.progress_bar['value'] = 0 # Сбрасываем прогресс-бар
|
||||
self.update_progress() # Обновляем отображение прогресса
|
||||
# Выводим только одно сообщение об остановке
|
||||
self.append_file_exec_text("[INFO] Выполнение остановлено.\n")
|
||||
self.reset_execution_buttons() # Сбрасываем состояние кнопок
|
||||
self.update_status_bar() # Обновляем статус бар
|
||||
|
||||
# Сбрасываем состояние кнопок
|
||||
self.reset_execution_buttons()
|
||||
self.update_status_bar()
|
||||
|
||||
def reset_execution_buttons(self):
|
||||
self.start_button.config(state="normal")
|
||||
self.pause_button.config(state="disabled", text="⏸ Пауза")
|
||||
@@ -1959,36 +1971,31 @@ 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)}")
|
||||
def check_connection(self):
|
||||
"""Проверка состояния соединения"""
|
||||
# Если выполнение остановлено пользователем, просто возвращаем False без сообщений
|
||||
if self.execution_stop:
|
||||
return False
|
||||
|
||||
if not self.connection or not self.connection.is_open:
|
||||
# Если это не ручная остановка, показываем сообщение
|
||||
if not self.execution_stop:
|
||||
self.append_file_exec_text("[ERROR] Соединение потеряно!\n")
|
||||
# Автоматически ставим на паузу
|
||||
self.execution_paused = True
|
||||
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
||||
|
||||
# Показываем сообщение только если это не ручная остановка
|
||||
self.after(0, lambda: messagebox.showerror(
|
||||
"Ошибка соединения",
|
||||
"Соединение с устройством потеряно!\nВыполнение команд приостановлено.\n\n"
|
||||
"Пожалуйста:\n"
|
||||
"1. Проверьте подключение\n"
|
||||
"2. Нажмите 'Продолжить' после восстановления соединения\n"
|
||||
" или 'Остановить' для прекращения выполнения"
|
||||
))
|
||||
return False
|
||||
return True
|
||||
|
||||
# Класс для окна выбора конфигурации
|
||||
class ConfigSelectorWindow(tk.Toplevel):
|
||||
@@ -2041,12 +2048,20 @@ class ConfigSelectorWindow(tk.Toplevel):
|
||||
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.file_menu = tk.Menu(self, tearoff=0)
|
||||
self.file_menu.add_command(label="✍️ Редактировать", command=self.edit_selected)
|
||||
self.file_menu.add_command(label="📝 Переименовать", command=self.rename_selected)
|
||||
self.file_menu.add_command(label="📦 Переместить", command=self.move_selected)
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(label="🗑️ Удалить", command=self.delete_selected)
|
||||
|
||||
# Создаем контекстное меню для папок
|
||||
self.folder_menu = tk.Menu(self, tearoff=0)
|
||||
self.folder_menu.add_command(label="📝 Переименовать", command=self.rename_selected)
|
||||
self.folder_menu.add_command(label="📦 Переместить", command=self.move_selected)
|
||||
self.folder_menu.add_separator()
|
||||
self.folder_menu.add_command(label="🗑️ Удалить", command=self.delete_selected)
|
||||
|
||||
# Привязываем события
|
||||
self.tree.bind("<Double-1>", self.on_double_click)
|
||||
@@ -2095,8 +2110,13 @@ class ConfigSelectorWindow(tk.Toplevel):
|
||||
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)
|
||||
item_text = self.tree.item(item)['text']
|
||||
|
||||
# Определяем, это файл или папка
|
||||
if item_text.startswith("📁"): # Папка
|
||||
self.folder_menu.post(event.x_root, event.y_root)
|
||||
elif item_text.startswith("📄"): # Файл
|
||||
self.file_menu.post(event.x_root, event.y_root)
|
||||
|
||||
def get_selected_item(self):
|
||||
"""Получение выбранного элемента"""
|
||||
@@ -2113,18 +2133,95 @@ class ConfigSelectorWindow(tk.Toplevel):
|
||||
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)
|
||||
item_text = self.tree.item(item_id)['text']
|
||||
|
||||
# Определяем, это файл или папка
|
||||
is_folder = item_text.startswith("📁")
|
||||
current_name = item_text[2:].strip() # Убираем эмодзи
|
||||
|
||||
# Запрашиваем новое имя
|
||||
new_name = simpledialog.askstring(
|
||||
"Переименование",
|
||||
"Введите новое имя:" if is_folder else "Введите новое имя файла:",
|
||||
initialvalue=current_name
|
||||
)
|
||||
|
||||
if new_name:
|
||||
try:
|
||||
if not is_folder and not new_name.endswith('.txt'):
|
||||
new_name += '.txt'
|
||||
|
||||
new_path = os.path.join(os.path.dirname(path), new_name)
|
||||
|
||||
if os.path.exists(new_path):
|
||||
if not messagebox.askyesno(
|
||||
"Подтверждение",
|
||||
f"{'Папка' if is_folder else 'Файл'} с таким именем уже существует. Перезаписать?"
|
||||
):
|
||||
return
|
||||
|
||||
os.rename(path, new_path)
|
||||
self.load_configs() # Перезагружаем список
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось переименовать: {str(e)}")
|
||||
|
||||
def move_selected(self):
|
||||
"""Перемещение выбранного элемента"""
|
||||
item_id = self.get_selected_item()
|
||||
if item_id:
|
||||
source_path = self.get_full_path(item_id)
|
||||
item_text = self.tree.item(item_id)['text']
|
||||
is_folder = item_text.startswith("📁")
|
||||
|
||||
if os.path.exists(source_path):
|
||||
# Создаем окно выбора папки
|
||||
folder_selector = FolderSelectorDialog(self, "Configs")
|
||||
if folder_selector.result:
|
||||
target_folder = folder_selector.result
|
||||
try:
|
||||
# Получаем имя элемента из исходного пути
|
||||
name = os.path.basename(source_path)
|
||||
# Формируем путь назначения
|
||||
target_path = os.path.join(target_folder, name)
|
||||
|
||||
# Проверяем, существует ли элемент в целевой папке
|
||||
if os.path.exists(target_path):
|
||||
if not messagebox.askyesno(
|
||||
"Подтверждение",
|
||||
f"{'Папка' if is_folder else 'Файл'} {name} уже существует в целевой папке. Перезаписать?"
|
||||
):
|
||||
return
|
||||
|
||||
# Перемещаем элемент
|
||||
os.rename(source_path, target_path)
|
||||
self.load_configs() # Обновляем список
|
||||
messagebox.showinfo("Успех", f"{'Папка' if is_folder else 'Файл'} успешно перемещен(а)")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось переместить: {str(e)}")
|
||||
|
||||
def delete_selected(self):
|
||||
"""Удаление выбранного файла"""
|
||||
"""Удаление выбранного элемента"""
|
||||
item_id = self.get_selected_item()
|
||||
if item_id:
|
||||
path = self.get_full_path(item_id)
|
||||
self.delete_config(path)
|
||||
item_text = self.tree.item(item_id)['text']
|
||||
is_folder = item_text.startswith("📁")
|
||||
|
||||
if os.path.exists(path):
|
||||
# Запрашиваем подтверждение
|
||||
if messagebox.askyesno(
|
||||
"Подтверждение",
|
||||
f"Вы действительно хотите переместить {'папку' if is_folder else 'файл'} {os.path.basename(path)} в корзину?"
|
||||
):
|
||||
try:
|
||||
send2trash(path)
|
||||
self.load_configs() # Перезагружаем список
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось переместить в корзину: {str(e)}")
|
||||
|
||||
def load_configs(self):
|
||||
"""Загрузка конфигураций из папки Configs"""
|
||||
@@ -2237,35 +2334,6 @@ class ConfigSelectorWindow(tk.Toplevel):
|
||||
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):
|
||||
@@ -2274,13 +2342,13 @@ class ConfigSelectorWindow(tk.Toplevel):
|
||||
# Запрашиваем подтверждение
|
||||
if messagebox.askyesno(
|
||||
"Подтверждение",
|
||||
f"Вы действительно хотите удалить файл {os.path.basename(path)}?"
|
||||
f"Вы действительно хотите переместить файл {os.path.basename(path)} в корзину?"
|
||||
):
|
||||
try:
|
||||
os.remove(path)
|
||||
send2trash(path)
|
||||
self.load_configs() # Перезагружаем список
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось удалить файл: {str(e)}")
|
||||
messagebox.showerror("Ошибка", f"Не удалось переместить файл в корзину: {str(e)}")
|
||||
|
||||
def on_double_click(self, event):
|
||||
"""Обработка двойного клика"""
|
||||
@@ -2308,6 +2376,99 @@ class ConfigSelectorWindow(tk.Toplevel):
|
||||
y = (self.winfo_screenheight() // 2) - (height // 2)
|
||||
self.geometry(f"{width}x{height}+{x}+{y}")
|
||||
|
||||
class FolderSelectorDialog(tk.Toplevel):
|
||||
def __init__(self, parent, root_folder):
|
||||
super().__init__(parent)
|
||||
self.title("Выберите папку")
|
||||
self.geometry("500x400") # Увеличиваем начальный размер окна
|
||||
self.result = None
|
||||
|
||||
# Создаем основной фрейм с отступами
|
||||
main_frame = ttk.Frame(self, padding="10")
|
||||
main_frame.pack(fill=BOTH, expand=True)
|
||||
|
||||
# Добавляем метку с инструкцией
|
||||
ttk.Label(main_frame, text="Выберите папку назначения:").pack(anchor=W, pady=(0, 5))
|
||||
|
||||
# Создаем фрейм для дерева и скроллбара
|
||||
tree_frame = ttk.Frame(main_frame)
|
||||
tree_frame.pack(fill=BOTH, expand=True)
|
||||
|
||||
# Создаем и настраиваем дерево папок
|
||||
self.tree = ttk.Treeview(tree_frame, selectmode="browse")
|
||||
self.tree.pack(side=LEFT, fill=BOTH, expand=True)
|
||||
|
||||
# Добавляем скроллбар
|
||||
scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
|
||||
scrollbar.pack(side=RIGHT, fill=Y)
|
||||
self.tree.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
# Загружаем структуру папок
|
||||
self.load_folders(root_folder)
|
||||
|
||||
# Кнопки
|
||||
button_frame = ttk.Frame(main_frame)
|
||||
button_frame.pack(fill=X, pady=(10, 0))
|
||||
|
||||
# Кнопки справа
|
||||
ttk.Button(button_frame, text="❌ Отмена", command=self.on_cancel).pack(side=RIGHT, padx=(5, 0))
|
||||
ttk.Button(button_frame, text="✔️ Выбрать папку", command=self.on_select).pack(side=RIGHT)
|
||||
|
||||
# Центрируем окно
|
||||
self.center_window()
|
||||
|
||||
# Делаем окно модальным
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
# Устанавливаем минимальный размер окна
|
||||
self.minsize(400, 300) # Увеличиваем минимальный размер окна
|
||||
|
||||
parent.wait_window(self)
|
||||
|
||||
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 load_folders(self, root_folder):
|
||||
"""Загрузка структуры папок"""
|
||||
def add_folder(path, parent=""):
|
||||
try:
|
||||
items = os.listdir(path)
|
||||
for item in sorted(items):
|
||||
full_path = os.path.join(path, item)
|
||||
if os.path.isdir(full_path):
|
||||
folder_id = self.tree.insert(
|
||||
parent,
|
||||
"end",
|
||||
text="📁 " + item,
|
||||
values=(full_path,),
|
||||
open=False
|
||||
)
|
||||
add_folder(full_path, folder_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Добавляем корневую папку
|
||||
root_id = self.tree.insert("", "end", text="📁 Configs", values=(root_folder,), open=True)
|
||||
add_folder(root_folder, root_id)
|
||||
|
||||
def on_select(self):
|
||||
"""Обработка выбора папки"""
|
||||
selection = self.tree.selection()
|
||||
if selection:
|
||||
self.result = self.tree.item(selection[0])['values'][0]
|
||||
self.destroy()
|
||||
|
||||
def on_cancel(self):
|
||||
"""Отмена выбора"""
|
||||
self.destroy()
|
||||
|
||||
# ==========================
|
||||
# Основной запуск приложения
|
||||
# ==========================
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
tftpy>=0.8.0
|
||||
pyserial>=3.5
|
||||
requests>=2.31.0
|
||||
packaging>=23.2
|
||||
packaging>=23.2
|
||||
send2trash>=1.8.0
|
||||
Reference in New Issue
Block a user