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
|
from TFTPServer import TFTPServer
|
||||||
import socket
|
import socket
|
||||||
from update_checker import UpdateChecker
|
from update_checker import UpdateChecker
|
||||||
|
from send2trash import send2trash
|
||||||
|
|
||||||
# Версия программы
|
# Версия программы
|
||||||
VERSION = "1.0.2"
|
VERSION = "1.0.2"
|
||||||
@@ -1248,201 +1249,201 @@ class SerialAppGUI(tk.Tk):
|
|||||||
consecutive_errors = 0
|
consecutive_errors = 0
|
||||||
MAX_CONSECUTIVE_ERRORS = 3 # Максимальное количество последовательных ошибок
|
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):
|
def handle_no_response(cmd_or_block, is_block=False):
|
||||||
"""Обработка отсутствия ответа от устройства"""
|
"""Обработка отсутствия ответа от устройства"""
|
||||||
nonlocal consecutive_errors
|
nonlocal consecutive_errors
|
||||||
consecutive_errors += 1
|
consecutive_errors += 1
|
||||||
|
|
||||||
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
|
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
|
||||||
self.append_file_exec_text(
|
if not self.execution_stop: # Проверяем, не была ли выполнена остановка
|
||||||
f"[ERROR] Обнаружено {consecutive_errors} последовательных ошибок!\n"
|
self.append_file_exec_text(
|
||||||
"Возможно, устройство не отвечает или проблемы с соединением.\n"
|
f"[ERROR] Обнаружено {consecutive_errors} последовательных ошибок!\n"
|
||||||
)
|
"Возможно, устройство не отвечает или проблемы с соединением.\n"
|
||||||
# Автоматически ставим на паузу
|
)
|
||||||
self.execution_paused = True
|
# Автоматически ставим на паузу
|
||||||
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
self.execution_paused = True
|
||||||
# Показываем сообщение пользователю
|
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
||||||
self.after(0, lambda: messagebox.showerror(
|
# Показываем сообщение пользователю
|
||||||
"Устройство не отвечает",
|
self.after(0, lambda: messagebox.showerror(
|
||||||
f"Обнаружено {consecutive_errors} последовательных ошибок!\n\n"
|
"Устройство не отвечает",
|
||||||
"Возможные причины:\n"
|
f"Обнаружено {consecutive_errors} последовательных ошибок!\n\n"
|
||||||
"1. Устройство не отвечает на команды\n"
|
"Возможные причины:\n"
|
||||||
"2. Проблемы с соединением\n"
|
"1. Устройство не отвечает на команды\n"
|
||||||
"3. Неверный формат команд\n\n"
|
"2. Проблемы с соединением\n"
|
||||||
"Выполнение приостановлено.\n"
|
"3. Неверный формат команд\n\n"
|
||||||
"Проверьте подключение и состояние устройства,\n"
|
"Выполнение приостановлено.\n"
|
||||||
"затем нажмите 'Продолжить' или 'Остановить'."
|
"Проверьте подключение и состояние устройства,\n"
|
||||||
))
|
"затем нажмите 'Продолжить' или 'Остановить'."
|
||||||
|
))
|
||||||
return False
|
return False
|
||||||
return True
|
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":
|
try:
|
||||||
# Построчный режим
|
if copy_mode == "line":
|
||||||
while self.current_command_index < len(self.commands):
|
# Построчный режим
|
||||||
if self.execution_stop:
|
while self.current_command_index < len(self.commands):
|
||||||
break
|
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
|
|
||||||
|
|
||||||
self.current_command_index += 1
|
if not wait_before_next_command():
|
||||||
self.after(0, self.update_progress)
|
break
|
||||||
time.sleep(1) # Задержка между командами
|
|
||||||
|
# Проверяем соединение перед каждой командой
|
||||||
except Exception as e:
|
if not self.check_connection():
|
||||||
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n")
|
if self.execution_stop: # Если это остановка, прерываем выполнение
|
||||||
if not check_connection():
|
break
|
||||||
continue
|
continue
|
||||||
break
|
|
||||||
else:
|
cmd = self.commands[self.current_command_index]
|
||||||
# Блочный режим
|
try:
|
||||||
blocks = generate_command_blocks(self.commands, block_size)
|
success, response = send_command_and_process_response(
|
||||||
total_blocks = len(blocks)
|
self.connection,
|
||||||
current_block = 0
|
cmd,
|
||||||
|
self.settings.get("timeout", 10),
|
||||||
while current_block < total_blocks:
|
max_attempts=3,
|
||||||
if self.execution_stop:
|
log_callback=self.append_file_exec_text if not self.execution_stop else None,
|
||||||
break
|
login=self.settings.get("login"),
|
||||||
|
password=self.settings.get("password"),
|
||||||
if self.execution_paused:
|
is_gui=True
|
||||||
time.sleep(0.1)
|
)
|
||||||
continue
|
|
||||||
|
if self.execution_stop:
|
||||||
# Проверяем соединение перед каждым блоком
|
break
|
||||||
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
|
|
||||||
|
|
||||||
# Обрабатываем отсутствие ответа для блока
|
if not success:
|
||||||
if not success and not handle_no_response(block, True):
|
if not self.execution_stop:
|
||||||
continue
|
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
|
||||||
|
# Проверяем соединение после неудачной попытки
|
||||||
# Отправляем команды блока по отдельности
|
if not self.check_connection():
|
||||||
for cmd in block.splitlines():
|
if self.execution_stop:
|
||||||
if self.execution_stop:
|
|
||||||
break
|
|
||||||
if cmd.strip():
|
|
||||||
if not check_connection():
|
|
||||||
break
|
break
|
||||||
success, resp = send_command_and_process_response(
|
continue
|
||||||
self.connection,
|
# Обрабатываем отсутствие ответа
|
||||||
cmd,
|
if not handle_no_response(cmd):
|
||||||
self.settings.get("timeout", 10),
|
continue
|
||||||
max_attempts=3,
|
else:
|
||||||
log_callback=self.append_file_exec_text,
|
# Сбрасываем счетчик ошибок при успешном выполнении
|
||||||
login=self.settings.get("login"),
|
consecutive_errors = 0
|
||||||
password=self.settings.get("password"),
|
|
||||||
is_gui=True
|
self.current_command_index += 1
|
||||||
)
|
self.after(0, self.update_progress)
|
||||||
if not success:
|
|
||||||
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
|
except Exception as e:
|
||||||
if not check_connection():
|
if not self.execution_stop:
|
||||||
break
|
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n")
|
||||||
# Обрабатываем отсутствие ответа для отдельной команды
|
break
|
||||||
if not handle_no_response(cmd):
|
else:
|
||||||
break
|
# Блочный режим
|
||||||
else:
|
blocks = generate_command_blocks(self.commands, block_size)
|
||||||
# Сбрасываем счетчик ошибок при успешном выполнении
|
total_blocks = len(blocks)
|
||||||
consecutive_errors = 0
|
current_block = 0
|
||||||
else:
|
|
||||||
# Если блок выполнился успешно, выводим ответ и сбрасываем счетчик ошибок
|
while current_block < total_blocks:
|
||||||
consecutive_errors = 0
|
if self.execution_stop:
|
||||||
if response:
|
break
|
||||||
self.append_file_exec_text(f"Ответ устройства:\n{response}\n")
|
|
||||||
|
if not wait_before_next_command():
|
||||||
|
break
|
||||||
|
|
||||||
# Обновляем прогресс на основе количества выполненных блоков
|
# Проверяем соединение перед каждым блоком
|
||||||
current_block += 1
|
if not self.check_connection():
|
||||||
self.current_command_index = (current_block * 100) // total_blocks
|
if self.execution_stop:
|
||||||
self.after(0, self.update_progress)
|
break
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении блока команд: {str(e)}\n")
|
|
||||||
if not check_connection():
|
|
||||||
continue
|
continue
|
||||||
break
|
|
||||||
|
|
||||||
# Завершение выполнения
|
block = blocks[current_block]
|
||||||
self.after(0, self.execution_completed)
|
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):
|
def execution_completed(self):
|
||||||
"""Обработка завершения выполнения в главном потоке"""
|
"""Обработка завершения выполнения в главном потоке"""
|
||||||
@@ -1489,27 +1490,38 @@ class SerialAppGUI(tk.Tk):
|
|||||||
self.append_file_exec_text("[INFO] Выполнение возобновлено.\n")
|
self.append_file_exec_text("[INFO] Выполнение возобновлено.\n")
|
||||||
|
|
||||||
def stop_execution(self):
|
def stop_execution(self):
|
||||||
|
"""Остановка выполнения команд"""
|
||||||
|
# Устанавливаем флаг остановки
|
||||||
self.execution_stop = True
|
self.execution_stop = True
|
||||||
self.execution_paused = False
|
self.execution_paused = False
|
||||||
self.timer_running = 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:
|
if self.connection:
|
||||||
try:
|
try:
|
||||||
self.stop_port_monitoring()
|
self.stop_port_monitoring()
|
||||||
self.connection.close()
|
self.connection.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
self.connection = None
|
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.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):
|
def reset_execution_buttons(self):
|
||||||
self.start_button.config(state="normal")
|
self.start_button.config(state="normal")
|
||||||
self.pause_button.config(state="disabled", text="⏸ Пауза")
|
self.pause_button.config(state="disabled", text="⏸ Пауза")
|
||||||
@@ -1959,36 +1971,31 @@ 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):
|
def check_connection(self):
|
||||||
"""Обработка drop файла во вкладке выполнения команд"""
|
"""Проверка состояния соединения"""
|
||||||
try:
|
# Если выполнение остановлено пользователем, просто возвращаем False без сообщений
|
||||||
files = event.data.split()
|
if self.execution_stop:
|
||||||
if files:
|
return False
|
||||||
file = files[0] # Берем только первый файл
|
|
||||||
if file.lower().endswith('.txt'):
|
if not self.connection or not self.connection.is_open:
|
||||||
filename = os.path.basename(file)
|
# Если это не ручная остановка, показываем сообщение
|
||||||
new_path = os.path.join("Configs", filename)
|
if not self.execution_stop:
|
||||||
|
self.append_file_exec_text("[ERROR] Соединение потеряно!\n")
|
||||||
if os.path.exists(new_path):
|
# Автоматически ставим на паузу
|
||||||
if not messagebox.askyesno(
|
self.execution_paused = True
|
||||||
"Подтверждение",
|
self.after(0, lambda: self.pause_button.config(text="▶ Продолжить"))
|
||||||
f"Файл {filename} уже существует. Перезаписать?"
|
|
||||||
):
|
# Показываем сообщение только если это не ручная остановка
|
||||||
return
|
self.after(0, lambda: messagebox.showerror(
|
||||||
|
"Ошибка соединения",
|
||||||
# Копируем файл в папку Configs
|
"Соединение с устройством потеряно!\nВыполнение команд приостановлено.\n\n"
|
||||||
with open(file, 'r', encoding='utf-8') as source:
|
"Пожалуйста:\n"
|
||||||
content = source.read()
|
"1. Проверьте подключение\n"
|
||||||
with open(new_path, 'w', encoding='utf-8') as dest:
|
"2. Нажмите 'Продолжить' после восстановления соединения\n"
|
||||||
dest.write(content)
|
" или 'Остановить' для прекращения выполнения"
|
||||||
|
))
|
||||||
# Устанавливаем путь к файлу
|
return False
|
||||||
self.file_exec_var.set(new_path)
|
return True
|
||||||
messagebox.showinfo("Успех", "Файл конфигурации успешно добавлен")
|
|
||||||
else:
|
|
||||||
messagebox.showerror("Ошибка", "Поддерживаются только текстовые файлы (.txt)")
|
|
||||||
except Exception as e:
|
|
||||||
messagebox.showerror("Ошибка", f"Не удалось добавить файл: {str(e)}")
|
|
||||||
|
|
||||||
# Класс для окна выбора конфигурации
|
# Класс для окна выбора конфигурации
|
||||||
class ConfigSelectorWindow(tk.Toplevel):
|
class ConfigSelectorWindow(tk.Toplevel):
|
||||||
@@ -2041,12 +2048,20 @@ class ConfigSelectorWindow(tk.Toplevel):
|
|||||||
self.tree.column("#0", width=400, stretch=True)
|
self.tree.column("#0", width=400, stretch=True)
|
||||||
self.tree.heading("#0", text="Конфигурация")
|
self.tree.heading("#0", text="Конфигурация")
|
||||||
|
|
||||||
# Создаем контекстное меню
|
# Создаем контекстное меню для файлов
|
||||||
self.context_menu = tk.Menu(self, tearoff=0)
|
self.file_menu = tk.Menu(self, tearoff=0)
|
||||||
self.context_menu.add_command(label="Редактировать", command=self.edit_selected)
|
self.file_menu.add_command(label="✍️ Редактировать", command=self.edit_selected)
|
||||||
self.context_menu.add_command(label="Переименовать", command=self.rename_selected)
|
self.file_menu.add_command(label="📝 Переименовать", command=self.rename_selected)
|
||||||
self.context_menu.add_separator()
|
self.file_menu.add_command(label="📦 Переместить", command=self.move_selected)
|
||||||
self.context_menu.add_command(label="Удалить", command=self.delete_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)
|
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)
|
item = self.tree.identify('item', event.x, event.y)
|
||||||
if item:
|
if item:
|
||||||
self.tree.selection_set(item)
|
self.tree.selection_set(item)
|
||||||
if self.tree.item(item)['text'].startswith("📄"): # Только для файлов
|
item_text = self.tree.item(item)['text']
|
||||||
self.context_menu.post(event.x_root, event.y_root)
|
|
||||||
|
# Определяем, это файл или папка
|
||||||
|
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):
|
def get_selected_item(self):
|
||||||
"""Получение выбранного элемента"""
|
"""Получение выбранного элемента"""
|
||||||
@@ -2113,18 +2133,95 @@ class ConfigSelectorWindow(tk.Toplevel):
|
|||||||
self.edit_config(path)
|
self.edit_config(path)
|
||||||
|
|
||||||
def rename_selected(self):
|
def rename_selected(self):
|
||||||
"""Переименование выбранного файла"""
|
"""Переименование выбранного элемента"""
|
||||||
item_id = self.get_selected_item()
|
item_id = self.get_selected_item()
|
||||||
if item_id:
|
if item_id:
|
||||||
path = self.get_full_path(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):
|
def delete_selected(self):
|
||||||
"""Удаление выбранного файла"""
|
"""Удаление выбранного элемента"""
|
||||||
item_id = self.get_selected_item()
|
item_id = self.get_selected_item()
|
||||||
if item_id:
|
if item_id:
|
||||||
path = self.get_full_path(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):
|
def load_configs(self):
|
||||||
"""Загрузка конфигураций из папки Configs"""
|
"""Загрузка конфигураций из папки Configs"""
|
||||||
@@ -2237,35 +2334,6 @@ class ConfigSelectorWindow(tk.Toplevel):
|
|||||||
self.master.load_config_file()
|
self.master.load_config_file()
|
||||||
self.destroy()
|
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):
|
def delete_config(self, path):
|
||||||
"""Удаление конфигурации"""
|
"""Удаление конфигурации"""
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
@@ -2274,13 +2342,13 @@ class ConfigSelectorWindow(tk.Toplevel):
|
|||||||
# Запрашиваем подтверждение
|
# Запрашиваем подтверждение
|
||||||
if messagebox.askyesno(
|
if messagebox.askyesno(
|
||||||
"Подтверждение",
|
"Подтверждение",
|
||||||
f"Вы действительно хотите удалить файл {os.path.basename(path)}?"
|
f"Вы действительно хотите переместить файл {os.path.basename(path)} в корзину?"
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
os.remove(path)
|
send2trash(path)
|
||||||
self.load_configs() # Перезагружаем список
|
self.load_configs() # Перезагружаем список
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Ошибка", f"Не удалось удалить файл: {str(e)}")
|
messagebox.showerror("Ошибка", f"Не удалось переместить файл в корзину: {str(e)}")
|
||||||
|
|
||||||
def on_double_click(self, event):
|
def on_double_click(self, event):
|
||||||
"""Обработка двойного клика"""
|
"""Обработка двойного клика"""
|
||||||
@@ -2308,6 +2376,99 @@ class ConfigSelectorWindow(tk.Toplevel):
|
|||||||
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}")
|
||||||
|
|
||||||
|
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
|
tftpy>=0.8.0
|
||||||
pyserial>=3.5
|
pyserial>=3.5
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
packaging>=23.2
|
packaging>=23.2
|
||||||
|
send2trash>=1.8.0
|
||||||
Reference in New Issue
Block a user