Refactor settings tab and add dedicated settings window
- Create a new `SettingsWindow` class for managing application settings - Simplify the settings tab in the main application - Add first-run detection and settings configuration prompt - Improve settings management with centralized save and load methods - Update menu to include settings option - Enhance user experience with more intuitive settings interface
This commit is contained in:
322
ComConfigCopy.py
322
ComConfigCopy.py
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
# Это программа для копирования конфигураций на коммутаторы
|
||||
# Программа использует библиотеку PySerial для работы с последовательными портами
|
||||
# Программа использует библиотеку PyQt5 для создания графического интерфейса
|
||||
@@ -411,21 +410,117 @@ def execute_commands_from_file(
|
||||
# Графический интерфейс (Tkinter)
|
||||
# ==========================
|
||||
|
||||
class SettingsWindow(tk.Toplevel):
|
||||
def __init__(self, parent, settings, callback=None):
|
||||
super().__init__(parent)
|
||||
self.title("Настройки")
|
||||
self.geometry("600x400")
|
||||
self.settings = settings
|
||||
self.callback = callback
|
||||
self.resizable(False, False)
|
||||
|
||||
# Создаем фрейм для настроек
|
||||
settings_frame = ttk.Frame(self, padding="10")
|
||||
settings_frame.pack(fill=BOTH, expand=True)
|
||||
|
||||
# COM порт
|
||||
ttk.Label(settings_frame, text="COM порт:").grid(row=0, column=0, sticky=W, pady=5)
|
||||
self.port_var = StringVar(value=settings.get("port", ""))
|
||||
self.port_combo = ttk.Combobox(settings_frame, textvariable=self.port_var)
|
||||
self.port_combo.grid(row=0, column=1, sticky=W, pady=5)
|
||||
ttk.Button(settings_frame, text="Обновить", command=self.update_ports).grid(row=0, column=2, padx=5)
|
||||
|
||||
# Скорость передачи
|
||||
ttk.Label(settings_frame, text="Скорость:").grid(row=1, column=0, sticky=W, pady=5)
|
||||
self.baudrate_var = StringVar(value=str(settings.get("baudrate", 9600)))
|
||||
baudrate_combo = ttk.Combobox(settings_frame, textvariable=self.baudrate_var,
|
||||
values=["9600", "19200", "38400", "57600", "115200"])
|
||||
baudrate_combo.grid(row=1, column=1, sticky=W, pady=5)
|
||||
|
||||
# Таймаут
|
||||
ttk.Label(settings_frame, text="Таймаут (сек):").grid(row=2, column=0, sticky=W, pady=5)
|
||||
self.timeout_var = StringVar(value=str(settings.get("timeout", 10)))
|
||||
timeout_entry = ttk.Entry(settings_frame, textvariable=self.timeout_var)
|
||||
timeout_entry.grid(row=2, column=1, sticky=W, pady=5)
|
||||
|
||||
# Режим копирования
|
||||
ttk.Label(settings_frame, text="Режим копирования:").grid(row=3, column=0, sticky=W, pady=5)
|
||||
self.copy_mode_var = StringVar(value=settings.get("copy_mode", "line"))
|
||||
copy_mode_frame = ttk.Frame(settings_frame)
|
||||
copy_mode_frame.grid(row=3, column=1, sticky=W, pady=5)
|
||||
ttk.Radiobutton(copy_mode_frame, text="Построчно", value="line",
|
||||
variable=self.copy_mode_var).pack(side=LEFT)
|
||||
ttk.Radiobutton(copy_mode_frame, text="Блоками", value="block",
|
||||
variable=self.copy_mode_var).pack(side=LEFT)
|
||||
|
||||
# Размер блока
|
||||
ttk.Label(settings_frame, text="Размер блока:").grid(row=4, column=0, sticky=W, pady=5)
|
||||
self.block_size_var = StringVar(value=str(settings.get("block_size", 15)))
|
||||
block_size_entry = ttk.Entry(settings_frame, textvariable=self.block_size_var)
|
||||
block_size_entry.grid(row=4, column=1, sticky=W, pady=5)
|
||||
|
||||
# Приглашение командной строки
|
||||
ttk.Label(settings_frame, text="Приглашение:").grid(row=5, column=0, sticky=W, pady=5)
|
||||
self.prompt_var = StringVar(value=settings.get("prompt", ">"))
|
||||
prompt_entry = ttk.Entry(settings_frame, textvariable=self.prompt_var)
|
||||
prompt_entry.grid(row=5, column=1, sticky=W, pady=5)
|
||||
|
||||
# Кнопки
|
||||
button_frame = ttk.Frame(settings_frame)
|
||||
button_frame.grid(row=6, column=0, columnspan=3, pady=20)
|
||||
ttk.Button(button_frame, text="Сохранить", command=self.save_settings).pack(side=LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="Отмена", command=self.destroy).pack(side=LEFT, padx=5)
|
||||
|
||||
self.update_ports()
|
||||
|
||||
# Центрируем окно
|
||||
self.center_window()
|
||||
|
||||
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()
|
||||
self.port_combo["values"] = ports
|
||||
if ports and not self.port_var.get():
|
||||
self.port_var.set(ports[0])
|
||||
|
||||
def save_settings(self):
|
||||
try:
|
||||
self.settings.update({
|
||||
"port": self.port_var.get(),
|
||||
"baudrate": int(self.baudrate_var.get()),
|
||||
"timeout": int(self.timeout_var.get()),
|
||||
"copy_mode": self.copy_mode_var.get(),
|
||||
"block_size": int(self.block_size_var.get()),
|
||||
"prompt": self.prompt_var.get()
|
||||
})
|
||||
settings_save(self.settings)
|
||||
if self.callback:
|
||||
self.callback()
|
||||
self.destroy()
|
||||
messagebox.showinfo("Успех", "Настройки успешно сохранены")
|
||||
except ValueError as e:
|
||||
messagebox.showerror("Ошибка", "Проверьте правильность введенных значений")
|
||||
|
||||
class SerialAppGUI(tk.Tk):
|
||||
def __init__(self, settings):
|
||||
super().__init__()
|
||||
self.title("Serial Device Manager")
|
||||
self.geometry("900x700")
|
||||
# Настройка ttk стилей
|
||||
self.style = ttk.Style(self)
|
||||
self.style.theme_use("clam")
|
||||
default_font = ("Segoe UI", 10)
|
||||
self.option_add("*Font", default_font)
|
||||
self.settings = settings
|
||||
self.connection = None
|
||||
# self.tftp_thread = None # TFTP сервер отключен
|
||||
|
||||
# Глобальные биндинги для копирования, вставки и вырезания
|
||||
# Глобальные биндинги
|
||||
self.bind_class("Text", "<Control-c>", lambda event: event.widget.event_generate("<<Copy>>"))
|
||||
self.bind_class("Text", "<Control-v>", lambda event: event.widget.event_generate("<<Paste>>"))
|
||||
self.bind_class("Text", "<Control-x>", lambda event: event.widget.event_generate("<<Cut>>"))
|
||||
@@ -435,134 +530,59 @@ class SerialAppGUI(tk.Tk):
|
||||
|
||||
self.create_menu()
|
||||
self.create_tabs()
|
||||
|
||||
|
||||
# Проверка первого запуска
|
||||
self.check_first_run()
|
||||
|
||||
def check_first_run(self):
|
||||
# Проверяем, существует ли папка Settings и файл settings.json
|
||||
if not os.path.exists("Settings") or not os.path.exists(SETTINGS_FILE):
|
||||
# Создаем папку Settings, если её нет
|
||||
os.makedirs("Settings", exist_ok=True)
|
||||
|
||||
response = messagebox.askyesno(
|
||||
"Первый запуск",
|
||||
"Это первый запуск программы. Хотите настроить параметры подключения сейчас?"
|
||||
)
|
||||
if response:
|
||||
self.open_settings()
|
||||
|
||||
def create_menu(self):
|
||||
menubar = tk.Menu(self)
|
||||
self.config(menu=menubar)
|
||||
|
||||
# Меню "Файл"
|
||||
file_menu = tk.Menu(menubar, tearoff=0)
|
||||
file_menu.add_command(label="Выход", command=self.quit)
|
||||
menubar.add_cascade(label="Файл", menu=file_menu)
|
||||
|
||||
file_menu.add_command(label="Настройки", command=self.open_settings)
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Выход", command=self.quit)
|
||||
|
||||
def open_settings(self):
|
||||
settings_window = SettingsWindow(self, self.settings, self.on_settings_changed)
|
||||
settings_window.transient(self)
|
||||
settings_window.grab_set()
|
||||
|
||||
def on_settings_changed(self):
|
||||
# Обновляем настройки в основном приложении
|
||||
self.settings = settings_load()
|
||||
|
||||
def create_tabs(self):
|
||||
notebook = ttk.Notebook(self)
|
||||
notebook.pack(fill=BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# Вкладка "Настройки"
|
||||
self.settings_frame = ttk.Frame(notebook, padding=10)
|
||||
notebook.add(self.settings_frame, text="Настройки")
|
||||
self.create_settings_tab(self.settings_frame)
|
||||
|
||||
# Вкладка "Интерактивный режим"
|
||||
self.interactive_frame = ttk.Frame(notebook, padding=10)
|
||||
notebook.add(self.interactive_frame, text="Интерактивный режим")
|
||||
self.create_interactive_tab(self.interactive_frame)
|
||||
|
||||
# Вкладка "Выполнить команды из файла"
|
||||
self.file_exec_frame = ttk.Frame(notebook, padding=10)
|
||||
notebook.add(self.file_exec_frame, text="Выполнить команды из файла")
|
||||
self.create_file_exec_tab(self.file_exec_frame)
|
||||
|
||||
# Вкладка "Редактор конфигурационного файла"
|
||||
self.config_editor_frame = ttk.Frame(notebook, padding=10)
|
||||
notebook.add(self.config_editor_frame, text="Редактор конфигурационного файла")
|
||||
self.create_config_editor_tab(self.config_editor_frame)
|
||||
|
||||
# # Вкладка "TFTP Сервер" (отключено)
|
||||
# self.tftp_frame = ttk.Frame(notebook, padding=10)
|
||||
# notebook.add(self.tftp_frame, text="TFTP Сервер")
|
||||
# self.create_tftp_tab(self.tftp_frame)
|
||||
|
||||
# -------------- Вкладка "Настройки" --------------
|
||||
def create_settings_tab(self, frame):
|
||||
# Используем grid с отступами
|
||||
ttk.Label(frame, text="COM-порт:").grid(row=0, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.port_var = StringVar(value=self.settings.get("port") or "")
|
||||
self.port_combo = ttk.Combobox(frame, textvariable=self.port_var, values=list_serial_ports(), width=20)
|
||||
self.port_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
ttk.Button(frame, text="Обновить", command=self.update_ports).grid(row=0, column=2, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Baudrate:").grid(row=1, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.baud_var = StringVar(value=str(self.settings.get("baudrate")))
|
||||
ttk.Entry(frame, textvariable=self.baud_var, width=10).grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Файл конфигурации:").grid(row=2, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.config_var = StringVar(value=self.settings.get("config_file") or "")
|
||||
ttk.Entry(frame, textvariable=self.config_var, width=40).grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
ttk.Button(frame, text="Выбрать", command=self.select_config_file).grid(row=2, column=2, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Логин:").grid(row=3, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.login_var = StringVar(value=self.settings.get("login") or "")
|
||||
ttk.Entry(frame, textvariable=self.login_var, width=20).grid(row=3, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Пароль:").grid(row=4, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.password_var = StringVar(value=self.settings.get("password") or "")
|
||||
ttk.Entry(frame, textvariable=self.password_var, show="*", width=20).grid(row=4, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Timeout (сек):").grid(row=5, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.timeout_var = StringVar(value=str(self.settings.get("timeout")))
|
||||
ttk.Entry(frame, textvariable=self.timeout_var, width=10).grid(row=5, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Режим копирования:").grid(row=6, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.copy_mode_var = StringVar(value=self.settings.get("copy_mode"))
|
||||
ttk.Radiobutton(frame, text="Построчно", variable=self.copy_mode_var, value="line").grid(row=6, column=1, sticky=tk.W, padx=5)
|
||||
ttk.Radiobutton(frame, text="Блочно", variable=self.copy_mode_var, value="block").grid(row=6, column=1, padx=100, sticky=tk.W)
|
||||
|
||||
ttk.Label(frame, text="Размер блока (строк):").grid(row=7, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.block_size_var = StringVar(value=str(self.settings.get("block_size")))
|
||||
ttk.Entry(frame, textvariable=self.block_size_var, width=10).grid(row=7, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
|
||||
ttk.Label(frame, text="Паттерн приглашения:").grid(row=8, column=0, sticky=tk.E, padx=5, pady=5)
|
||||
self.prompt_var = StringVar(value=self.settings.get("prompt"))
|
||||
ttk.Entry(frame, textvariable=self.prompt_var, width=20).grid(row=8, column=1, sticky=tk.W, padx=5, pady=5)
|
||||
|
||||
ttk.Button(frame, text="Сохранить настройки", command=self.save_settings).grid(row=9, column=0, padx=5, pady=10)
|
||||
ttk.Button(frame, text="Проверить соединение", command=self.check_connection).grid(row=9, column=1, padx=5, pady=10)
|
||||
|
||||
def update_ports(self):
|
||||
ports = list_serial_ports()
|
||||
self.port_combo['values'] = ports
|
||||
messagebox.showinfo("Информация", "Список портов обновлен.")
|
||||
|
||||
def select_config_file(self):
|
||||
filename = filedialog.askopenfilename(title="Выберите файл конфигурации", filetypes=[("Text files", "*.txt")])
|
||||
if filename:
|
||||
self.config_var.set(filename)
|
||||
|
||||
def save_settings(self):
|
||||
self.settings["port"] = self.port_var.get()
|
||||
try:
|
||||
self.settings["baudrate"] = int(self.baud_var.get())
|
||||
except ValueError:
|
||||
messagebox.showerror("Ошибка", "Некорректное значение baudrate!")
|
||||
return
|
||||
self.settings["config_file"] = self.config_var.get()
|
||||
self.settings["login"] = self.login_var.get()
|
||||
self.settings["password"] = self.password_var.get()
|
||||
try:
|
||||
self.settings["timeout"] = int(self.timeout_var.get())
|
||||
except ValueError:
|
||||
messagebox.showerror("Ошибка", "Некорректное значение timeout!")
|
||||
return
|
||||
self.settings["copy_mode"] = self.copy_mode_var.get()
|
||||
try:
|
||||
self.settings["block_size"] = int(self.block_size_var.get())
|
||||
except ValueError:
|
||||
messagebox.showerror("Ошибка", "Некорректное значение размера блока!")
|
||||
return
|
||||
self.settings["prompt"] = self.prompt_var.get()
|
||||
settings_save(self.settings)
|
||||
messagebox.showinfo("Информация", "Настройки сохранены.")
|
||||
|
||||
def check_connection(self):
|
||||
if not self.settings.get("port"):
|
||||
messagebox.showerror("Ошибка", "COM-порт не выбран!")
|
||||
return
|
||||
conn = create_connection(self.settings)
|
||||
if conn:
|
||||
messagebox.showinfo("Информация", "Соединение установлено успешно!")
|
||||
conn.close()
|
||||
else:
|
||||
messagebox.showerror("Ошибка", "Не удалось установить соединение.")
|
||||
self.notebook = ttk.Notebook(self)
|
||||
self.notebook.pack(fill=BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# Создаем вкладки
|
||||
interactive_frame = ttk.Frame(self.notebook)
|
||||
file_exec_frame = ttk.Frame(self.notebook)
|
||||
config_editor_frame = ttk.Frame(self.notebook)
|
||||
|
||||
self.notebook.add(interactive_frame, text="Интерактивный режим")
|
||||
self.notebook.add(file_exec_frame, text="Выполнение файла")
|
||||
self.notebook.add(config_editor_frame, text="Редактор конфигурации")
|
||||
|
||||
self.create_interactive_tab(interactive_frame)
|
||||
self.create_file_exec_tab(file_exec_frame)
|
||||
self.create_config_editor_tab(config_editor_frame)
|
||||
|
||||
# -------------- Вкладка "Интерактивный режим" --------------
|
||||
def create_interactive_tab(self, frame):
|
||||
@@ -760,56 +780,6 @@ class SerialAppGUI(tk.Tk):
|
||||
logging.error(f"Ошибка сохранения файла: {e}", exc_info=True)
|
||||
messagebox.showerror("Ошибка", f"Не удалось сохранить файл:\n{e}")
|
||||
|
||||
# -------------- Вкладка "TFTP Сервер" (отключено) --------------
|
||||
# def create_tftp_tab(self, frame):
|
||||
# ip_frame = ttk.Frame(frame)
|
||||
# ip_frame.pack(fill=X, pady=2)
|
||||
# ttk.Label(ip_frame, text="Слушать на IP:").pack(side=LEFT, padx=5)
|
||||
# self.tftp_listen_ip_var = StringVar(value="0.0.0.0")
|
||||
# ttk.Entry(ip_frame, textvariable=self.tftp_listen_ip_var, width=15).pack(side=LEFT, padx=5)
|
||||
#
|
||||
# port_frame = ttk.Frame(frame)
|
||||
# port_frame.pack(fill=X, pady=2)
|
||||
# ttk.Label(port_frame, text="Порт:").pack(side=LEFT, padx=5)
|
||||
# self.tftp_port_var = StringVar(value="6969")
|
||||
# ttk.Entry(port_frame, textvariable=self.tftp_port_var, width=10).pack(side=LEFT, padx=5)
|
||||
#
|
||||
# folder_frame = ttk.Frame(frame)
|
||||
# folder_frame.pack(fill=X, pady=2)
|
||||
# ttk.Label(folder_frame, text="Папка прошивок:").pack(side=LEFT, padx=5)
|
||||
# self.firmware_folder_var = StringVar(value="Firmware")
|
||||
# ttk.Label(folder_frame, textvariable=self.firmware_folder_var).pack(side=LEFT, padx=5)
|
||||
# ttk.Button(folder_frame, text="Открыть папку", command=lambda: os.startfile(os.path.abspath("Firmware"))).pack(side=LEFT, padx=5)
|
||||
#
|
||||
# button_frame = ttk.Frame(frame)
|
||||
# button_frame.pack(fill=X, pady=2)
|
||||
# ttk.Button(button_frame, text="Запустить TFTP сервер", command=self.start_tftp_server).pack(side=LEFT, padx=5)
|
||||
# ttk.Button(button_frame, text="Остановить TFTP сервер", command=self.stop_tftp_server).pack(side=LEFT, padx=5)
|
||||
#
|
||||
# self.tftp_console = tk.Text(frame, wrap="word", height=15)
|
||||
# self.tftp_console.pack(fill=BOTH, expand=True, padx=5, pady=5)
|
||||
#
|
||||
# def append_tftp_console(self, text):
|
||||
# self.tftp_console.insert(END, text)
|
||||
# self.tftp_console.see(END)
|
||||
|
||||
# def start_tftp_server(self):
|
||||
# listen_ip = self.tftp_listen_ip_var.get()
|
||||
# try:
|
||||
# port = int(self.tftp_port_var.get())
|
||||
# except ValueError:
|
||||
# messagebox.showerror("Ошибка", "Некорректное значение порта.")
|
||||
# return
|
||||
# self.tftp_thread = TFTPServerThread(listen_ip, port, self.append_tftp_console)
|
||||
# self.tftp_thread.start()
|
||||
|
||||
# def stop_tftp_server(self):
|
||||
# if self.tftp_thread:
|
||||
# self.tftp_thread.stop()
|
||||
# self.tftp_thread.join()
|
||||
# self.tftp_thread = None
|
||||
# self.append_tftp_console("TFTP сервер остановлен.\n")
|
||||
|
||||
# ==========================
|
||||
# Парсер аргументов (не используется)
|
||||
# ==========================
|
||||
|
||||
Reference in New Issue
Block a user