"""Toplevel window for configuring Target and LRU connections. This module provides the `ConnectionSettingsWindow` dialog used by the main UI to configure Target and LRU communication settings. """ import tkinter as tk from tkinter import ttk, messagebox class ConnectionSettingsWindow(tk.Toplevel): """ A modal dialog window for configuring Target and LRU connection settings. This window allows users to specify connection parameters for different communication protocols like SFP, TFTP, and Serial. """ def __init__(self, master, config_manager, connection_config): """ Initialize the ConnectionSettingsWindow. Args: master (tk.Widget): The parent widget. config_manager (ConfigManager): The application's configuration manager. connection_config (dict): A dictionary containing the current connection settings. """ super().__init__(master) self.master_view = master self.config_manager = config_manager self.connection_config = connection_config self.title("Connection Settings") self.transient(master) self.grab_set() self.resizable(False, False) self._create_widgets() self._load_settings() self.protocol("WM_DELETE_WINDOW", self._on_cancel) # Center the window on the main view self.update_idletasks() try: main_x = master.winfo_rootx() main_y = master.winfo_rooty() main_w = master.winfo_width() main_h = master.winfo_height() except Exception: main_x = main_y = main_w = main_h = 0 try: win_w = self.winfo_reqwidth() win_h = self.winfo_reqheight() except Exception: win_w = self.winfo_width() win_h = self.winfo_height() x = main_x + (main_w // 2) - (win_w // 2) y = main_y + (main_h // 2) - (win_h // 2) self.geometry(f"{win_w}x{win_h}+{x}+{y}") def _load_settings(self): """ Populate the UI fields with the current connection settings. Reads values from `self.connection_config` and sets the corresponding Tkinter variables to update the widgets. """ # --- Load Target Settings --- target_cfg = self.connection_config.get("target", {}) target_sfp_cfg = target_cfg.get("sfp", {}) # Normalize connection type so it matches the radiobutton values used elsewhere t = (target_cfg.get("type", "SFP") or "").lower() if t == "serial": self.target_vars["conn_type"].set("Serial") else: self.target_vars["conn_type"].set(t.upper() or "SFP") self.target_vars["tftp_ip"].set( target_cfg.get("tftp", {}).get("ip", "127.0.0.1") ) self.target_vars["tftp_port"].set(target_cfg.get("tftp", {}).get("port", 69)) self.target_vars["serial_port"].set( target_cfg.get("serial", {}).get("port", "COM1") ) self.target_vars["serial_baud"].set( target_cfg.get("serial", {}).get("baudrate", 9600) ) self.target_vars["sfp_ip"].set(target_sfp_cfg.get("ip", "127.0.0.1")) self.target_vars["sfp_port"].set(target_sfp_cfg.get("port", 60003)) self.target_vars["sfp_local_port"].set(target_sfp_cfg.get("local_port", 60002)) self.target_vars["sfp_use_json"].set( target_sfp_cfg.get("use_json_protocol", False) ) self.target_vars["sfp_prediction_offset"].set( target_sfp_cfg.get("prediction_offset_ms", 0.0) ) # Select the correct notebook tab for target try: tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[ self.target_vars["conn_type"].get() ] self.target_vars["notebook"].select(tab_idx) except Exception: pass # --- Load LRU Settings --- lru_cfg = self.connection_config.get("lru", {}) lru_sfp_cfg = lru_cfg.get("sfp", {}) t = (lru_cfg.get("type", "SFP") or "").lower() if t == "serial": self.lru_vars["conn_type"].set("Serial") else: self.lru_vars["conn_type"].set(t.upper() or "SFP") self.lru_vars["tftp_ip"].set(lru_cfg.get("tftp", {}).get("ip", "127.0.0.1")) self.lru_vars["tftp_port"].set(lru_cfg.get("tftp", {}).get("port", 69)) self.lru_vars["serial_port"].set(lru_cfg.get("serial", {}).get("port", "COM1")) self.lru_vars["serial_baud"].set( lru_cfg.get("serial", {}).get("baudrate", 9600) ) self.lru_vars["sfp_ip"].set(lru_sfp_cfg.get("ip", "127.0.0.1")) self.lru_vars["sfp_port"].set(lru_sfp_cfg.get("port", 60003)) self.lru_vars["sfp_local_port"].set(lru_sfp_cfg.get("local_port", 60002)) self.lru_vars["sfp_use_json"].set(lru_sfp_cfg.get("use_json_protocol", False)) self.lru_vars["sfp_prediction_offset"].set( lru_sfp_cfg.get("prediction_offset_ms", 0.0) ) # Select the correct notebook tab for lru try: tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[ self.lru_vars["conn_type"].get() ] self.lru_vars["notebook"].select(tab_idx) except Exception: pass def _create_widgets(self): """ Create and layout all the widgets for the dialog. This includes separate panels for Target and LRU connections, and action buttons for saving or canceling. """ # Main content frame (scrolling not required here but keep expandable) main_frame = ttk.Frame(self, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) target_frame = ttk.LabelFrame(main_frame, text="Target Connection") target_frame.pack(fill=tk.X, expand=True, pady=5, padx=5) self.target_vars = self._create_connection_panel(target_frame) lru_frame = ttk.LabelFrame(main_frame, text="LRU Connection") lru_frame.pack(fill=tk.X, expand=True, pady=5, padx=5) self.lru_vars = self._create_connection_panel(lru_frame) # Keep action buttons fixed at the bottom of the dialog so they are # not pushed out of view by variable-height content in the main_frame. sep = ttk.Separator(self, orient=tk.HORIZONTAL) sep.pack(fill=tk.X, side=tk.BOTTOM, pady=(0, 5)) button_frame = ttk.Frame(self) button_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=8, padx=10) ttk.Button(button_frame, text="Save", command=self._on_save).pack( side=tk.RIGHT, padx=5 ) ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack( side=tk.RIGHT, padx=5 ) def _create_connection_panel(self, parent_frame): """ Create a panel for a single connection type (SFP/TFTP/Serial). This panel includes a dropdown to select the connection type and a notebook with tabs for each protocol's specific settings. Args: parent_frame (ttk.Frame): The parent frame to contain the panel. Returns: dict: A dictionary mapping variable names to their Tkinter variable objects. """ vars = {} # Top row: label + combobox to choose connection type type_row = ttk.Frame(parent_frame) type_row.pack(fill=tk.X, padx=5, pady=(5, 10)) vars["conn_type"] = tk.StringVar() ttk.Label(type_row, text="Type:").pack(side=tk.LEFT) type_combo = ttk.Combobox( type_row, textvariable=vars["conn_type"], state="readonly", values=["SFP", "TFTP", "Serial"], width=10, ) type_combo.pack(side=tk.LEFT, padx=6) # Notebook with a tab per connection type notebook = ttk.Notebook(parent_frame) notebook.pack(fill=tk.X, expand=False, padx=5, pady=2) vars["notebook"] = notebook # Frames for each tab sfp_frame = ttk.Frame(notebook, padding=5) tftp_frame = ttk.Frame(notebook, padding=5) serial_frame = ttk.Frame(notebook, padding=5) notebook.add(sfp_frame, text="SFP") notebook.add(tftp_frame, text="TFTP") notebook.add(serial_frame, text="Serial") def _on_tab_changed(event): idx = notebook.index(notebook.select()) vars["conn_type"].set({0: "SFP", 1: "TFTP", 2: "Serial"}.get(idx, "SFP")) def _on_type_changed(event=None): name = vars["conn_type"].get() idx = {"SFP": 0, "TFTP": 1, "Serial": 2}.get(name, 0) notebook.select(idx) notebook.bind("<>", _on_tab_changed) type_combo.bind("<>", _on_type_changed) # --- TFTP Widgets --- tftp_grid = tftp_frame tftp_grid.columnconfigure(1, weight=1) ttk.Label(tftp_grid, text="IP Address:").grid( row=0, column=0, sticky=tk.W, pady=2 ) vars["tftp_ip"] = tk.StringVar() ttk.Entry(tftp_grid, textvariable=vars["tftp_ip"]).grid( row=0, column=1, sticky=tk.EW, padx=5 ) ttk.Label(tftp_grid, text="Port:").grid(row=1, column=0, sticky=tk.W, pady=2) vars["tftp_port"] = tk.IntVar() ttk.Spinbox( tftp_grid, from_=1, to=65535, textvariable=vars["tftp_port"], width=7 ).grid(row=1, column=1, sticky=tk.W, padx=5) # --- Serial Widgets --- serial_grid = serial_frame serial_grid.columnconfigure(1, weight=1) ttk.Label(serial_grid, text="COM Port:").grid( row=0, column=0, sticky=tk.W, pady=2 ) vars["serial_port"] = tk.StringVar() ttk.Combobox( serial_grid, textvariable=vars["serial_port"], values=[f"COM{i}" for i in range(1, 17)], ).grid(row=0, column=1, sticky=tk.EW, padx=5) ttk.Label(serial_grid, text="Baud Rate:").grid( row=1, column=0, sticky=tk.W, pady=2 ) vars["serial_baud"] = tk.IntVar() ttk.Combobox( serial_grid, textvariable=vars["serial_baud"], values=[9600, 19200, 38400, 57600, 115200], width=7, ).grid(row=1, column=1, sticky=tk.W, padx=5) # --- SFP Widgets --- sfp_grid = sfp_frame sfp_grid.columnconfigure(1, weight=1) ttk.Label(sfp_grid, text="Server IP:").grid( row=0, column=0, sticky=tk.W, pady=2 ) vars["sfp_ip"] = tk.StringVar() ttk.Entry(sfp_grid, textvariable=vars["sfp_ip"]).grid( row=0, column=1, sticky=tk.EW, padx=5 ) ttk.Label(sfp_grid, text="Server Port:").grid( row=1, column=0, sticky=tk.W, pady=2 ) vars["sfp_port"] = tk.IntVar() ttk.Spinbox( sfp_grid, from_=1, to=65535, textvariable=vars["sfp_port"], width=7 ).grid(row=1, column=1, sticky=tk.W, padx=5) ttk.Label(sfp_grid, text="Local Port:").grid( row=2, column=0, sticky=tk.W, pady=2 ) vars["sfp_local_port"] = tk.IntVar() ttk.Spinbox( sfp_grid, from_=1, to=65535, textvariable=vars["sfp_local_port"], width=7 ).grid(row=2, column=1, sticky=tk.W, padx=5) # Prediction Offset widget on row 3 ttk.Label(sfp_grid, text="Prediction Offset (ms):").grid( row=3, column=0, sticky=tk.W, pady=2 ) vars["sfp_prediction_offset"] = tk.DoubleVar() ttk.Spinbox( sfp_grid, from_=0, to=1000, increment=1, textvariable=vars["sfp_prediction_offset"], width=7, ).grid(row=3, column=1, sticky=tk.W, padx=5) # JSON protocol checkbox moved to row 4 vars["sfp_use_json"] = tk.BooleanVar() ttk.Checkbutton( sfp_grid, text="Use JSON Protocol", variable=vars["sfp_use_json"] ).grid(row=4, column=0, columnspan=2, sticky=tk.W, pady=(5, 0)) # place test button to the right of the notebook area test_row = ttk.Frame(parent_frame) test_row.pack(fill=tk.X, padx=5, pady=(2, 8)) test_button = ttk.Button( test_row, text="Test Connection", command=lambda: self._test_connection(vars), ) test_button.pack(side=tk.RIGHT) return vars # Note: previous implementation used pack/grid switching per type. The UI # now uses a Notebook with tabs and a Combobox to select the active type. def _test_connection(self, vars): """ Perform a quick connection test for the selected protocol. Displays a messagebox with the success or failure result. Catches and displays any exceptions that occur during the test. Args: vars (dict): The dictionary of Tkinter variables for the connection panel being tested. """ conn_type = vars["conn_type"].get() result = False config = {} try: if conn_type == "TFTP": from target_simulator.core.tftp_communicator import TFTPCommunicator config = {"ip": vars["tftp_ip"].get(), "port": vars["tftp_port"].get()} result = TFTPCommunicator.test_connection(config) elif conn_type == "Serial": from target_simulator.core.serial_communicator import SerialCommunicator config = { "port": vars["serial_port"].get(), "baudrate": vars["serial_baud"].get(), } result = SerialCommunicator.test_connection(config) elif conn_type == "SFP": from target_simulator.core.sfp_communicator import SFPCommunicator config = {"local_port": vars["sfp_local_port"].get()} result = SFPCommunicator.test_connection(config) except Exception as e: messagebox.showerror("Test Error", f"An error occurred: {e}", parent=self) return if result: messagebox.showinfo( "Connection Test", f"{conn_type} connection test successful!", parent=self, ) else: messagebox.showerror( "Connection Test", f"{conn_type} connection test failed.", parent=self ) def _on_save(self): """ Save the connection settings and close the dialog. Collects all values from the UI widgets, updates the main application's configuration, and then destroys the window. """ new_config = { "target": { "type": self.target_vars["conn_type"].get().lower(), "tftp": { "ip": self.target_vars["tftp_ip"].get(), "port": self.target_vars["tftp_port"].get(), }, "serial": { "port": self.target_vars["serial_port"].get(), "baudrate": self.target_vars["serial_baud"].get(), }, "sfp": { "ip": self.target_vars["sfp_ip"].get(), "port": self.target_vars["sfp_port"].get(), "local_port": self.target_vars["sfp_local_port"].get(), "use_json_protocol": self.target_vars["sfp_use_json"].get(), "prediction_offset_ms": self.target_vars[ "sfp_prediction_offset" ].get(), }, }, "lru": { "type": self.lru_vars["conn_type"].get().lower(), "tftp": { "ip": self.lru_vars["tftp_ip"].get(), "port": self.lru_vars["tftp_port"].get(), }, "serial": { "port": self.lru_vars["serial_port"].get(), "baudrate": self.lru_vars["serial_baud"].get(), }, "sfp": { "ip": self.lru_vars["sfp_ip"].get(), "port": self.lru_vars["sfp_port"].get(), "local_port": self.lru_vars["sfp_local_port"].get(), "use_json_protocol": self.lru_vars["sfp_use_json"].get(), "prediction_offset_ms": self.lru_vars[ "sfp_prediction_offset" ].get(), }, }, } self.master_view.update_connection_settings(new_config) self.destroy() def _on_cancel(self): """ Close the dialog without saving any changes. """ self.destroy() # target_simulator/gui/connection_settings_window.py """ Toplevel window for configuring Target and LRU connections. """ import tkinter as tk from tkinter import ttk, messagebox class ConnectionSettingsWindow(tk.Toplevel): """A dialog for configuring connection settings. Inputs: - master: parent Tk widget - config_manager: ConfigManager instance for persistence - connection_config: dict with initial connection settings Side-effects: - creates a modal Toplevel window and writes settings via master_view.update_connection_settings on Save. """ def __init__(self, master, config_manager, connection_config): super().__init__(master) self.master_view = master self.config_manager = config_manager self.connection_config = connection_config self.title("Connection Settings") self.transient(master) self.grab_set() self.resizable(False, False) self._create_widgets() self._load_settings() self.protocol("WM_DELETE_WINDOW", self._on_cancel) # Center the window on the main view self.update_idletasks() main_x = master.winfo_rootx() main_y = master.winfo_rooty() main_w = master.winfo_width() main_h = master.winfo_height() # Use requested size so the dialog is large enough to show all widgets. # Some test doubles or minimal masters may not implement winfo_reqwidth/ # winfo_reqheight, so fall back to winfo_width/height when necessary. try: win_w = self.winfo_reqwidth() win_h = self.winfo_reqheight() except Exception: win_w = self.winfo_width() win_h = self.winfo_height() x = main_x + (main_w // 2) - (win_w // 2) y = main_y + (main_h // 2) - (win_h // 2) self.geometry(f"{win_w}x{win_h}+{x}+{y}") def _load_settings(self): """Populate UI fields from self.connection_config. Reads values from the provided connection_config and sets the associated Tk variables used by the widgets. """ # --- Load Target Settings --- target_cfg = self.connection_config.get("target", {}) target_sfp_cfg = target_cfg.get("sfp", {}) # Normalize connection type so it matches the radiobutton values used elsewhere t = (target_cfg.get("type", "SFP") or "").lower() if t == "serial": self.target_vars["conn_type"].set("Serial") else: self.target_vars["conn_type"].set(t.upper() or "SFP") self.target_vars["tftp_ip"].set( target_cfg.get("tftp", {}).get("ip", "127.0.0.1") ) self.target_vars["tftp_port"].set(target_cfg.get("tftp", {}).get("port", 69)) self.target_vars["serial_port"].set( target_cfg.get("serial", {}).get("port", "COM1") ) self.target_vars["serial_baud"].set( target_cfg.get("serial", {}).get("baudrate", 9600) ) self.target_vars["sfp_ip"].set(target_sfp_cfg.get("ip", "127.0.0.1")) self.target_vars["sfp_port"].set(target_sfp_cfg.get("port", 60003)) self.target_vars["sfp_local_port"].set(target_sfp_cfg.get("local_port", 60002)) self.target_vars["sfp_use_json"].set( target_sfp_cfg.get("use_json_protocol", False) ) self.target_vars["sfp_prediction_offset"].set( target_sfp_cfg.get("prediction_offset_ms", 0.0) ) # Select the correct notebook tab for target try: tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[ self.target_vars["conn_type"].get() ] self.target_vars["notebook"].select(tab_idx) except Exception: pass # --- Load LRU Settings --- lru_cfg = self.connection_config.get("lru", {}) lru_sfp_cfg = lru_cfg.get("sfp", {}) t = (lru_cfg.get("type", "SFP") or "").lower() if t == "serial": self.lru_vars["conn_type"].set("Serial") else: self.lru_vars["conn_type"].set(t.upper() or "SFP") self.lru_vars["tftp_ip"].set(lru_cfg.get("tftp", {}).get("ip", "127.0.0.1")) self.lru_vars["tftp_port"].set(lru_cfg.get("tftp", {}).get("port", 69)) self.lru_vars["serial_port"].set(lru_cfg.get("serial", {}).get("port", "COM1")) self.lru_vars["serial_baud"].set( lru_cfg.get("serial", {}).get("baudrate", 9600) ) self.lru_vars["sfp_ip"].set(lru_sfp_cfg.get("ip", "127.0.0.1")) self.lru_vars["sfp_port"].set(lru_sfp_cfg.get("port", 60003)) self.lru_vars["sfp_local_port"].set(lru_sfp_cfg.get("local_port", 60002)) self.lru_vars["sfp_use_json"].set(lru_sfp_cfg.get("use_json_protocol", False)) self.lru_vars["sfp_prediction_offset"].set( lru_sfp_cfg.get("prediction_offset_ms", 0.0) ) # Select the correct notebook tab for lru try: tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[ self.lru_vars["conn_type"].get() ] self.lru_vars["notebook"].select(tab_idx) except Exception: pass def _create_widgets(self): """Create and layout all widgets for the connection dialog. Returns nothing; stores Tk variables in self.target_vars and self.lru_vars for later readout. """ # Main content frame (scrolling not required here but keep expandable) main_frame = ttk.Frame(self, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) target_frame = ttk.LabelFrame(main_frame, text="Target Connection") target_frame.pack(fill=tk.X, expand=True, pady=5, padx=5) self.target_vars = self._create_connection_panel(target_frame) lru_frame = ttk.LabelFrame(main_frame, text="LRU Connection") lru_frame.pack(fill=tk.X, expand=True, pady=5, padx=5) self.lru_vars = self._create_connection_panel(lru_frame) # Keep action buttons fixed at the bottom of the dialog so they are # not pushed out of view by variable-height content in the main_frame. sep = ttk.Separator(self, orient=tk.HORIZONTAL) sep.pack(fill=tk.X, side=tk.BOTTOM, pady=(0, 5)) button_frame = ttk.Frame(self) button_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=8, padx=10) ttk.Button(button_frame, text="Save", command=self._on_save).pack( side=tk.RIGHT, padx=5 ) ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack( side=tk.RIGHT, padx=5 ) def _create_connection_panel(self, parent_frame): """Create the per-connection-type panel (SFP/TFTP/Serial). Inputs: - parent_frame: ttk.Frame to populate Returns: - dict of Tk variable objects used by the panel widgets """ vars = {} # Top row: label + combobox to choose connection type type_row = ttk.Frame(parent_frame) type_row.pack(fill=tk.X, padx=5, pady=(5, 10)) vars["conn_type"] = tk.StringVar() ttk.Label(type_row, text="Type:").pack(side=tk.LEFT) type_combo = ttk.Combobox( type_row, textvariable=vars["conn_type"], state="readonly", values=["SFP", "TFTP", "Serial"], width=10, ) type_combo.pack(side=tk.LEFT, padx=6) # Notebook with a tab per connection type notebook = ttk.Notebook(parent_frame) notebook.pack(fill=tk.X, expand=False, padx=5, pady=2) vars["notebook"] = notebook # Frames for each tab sfp_frame = ttk.Frame(notebook, padding=5) tftp_frame = ttk.Frame(notebook, padding=5) serial_frame = ttk.Frame(notebook, padding=5) notebook.add(sfp_frame, text="SFP") notebook.add(tftp_frame, text="TFTP") notebook.add(serial_frame, text="Serial") def _on_tab_changed(event): idx = notebook.index(notebook.select()) vars["conn_type"].set({0: "SFP", 1: "TFTP", 2: "Serial"}.get(idx, "SFP")) def _on_type_changed(event=None): name = vars["conn_type"].get() idx = {"SFP": 0, "TFTP": 1, "Serial": 2}.get(name, 0) notebook.select(idx) notebook.bind("<>", _on_tab_changed) type_combo.bind("<>", _on_type_changed) # --- TFTP Widgets --- tftp_grid = tftp_frame tftp_grid.columnconfigure(1, weight=1) ttk.Label(tftp_grid, text="IP Address:").grid( row=0, column=0, sticky=tk.W, pady=2 ) vars["tftp_ip"] = tk.StringVar() ttk.Entry(tftp_grid, textvariable=vars["tftp_ip"]).grid( row=0, column=1, sticky=tk.EW, padx=5 ) ttk.Label(tftp_grid, text="Port:").grid(row=1, column=0, sticky=tk.W, pady=2) vars["tftp_port"] = tk.IntVar() ttk.Spinbox( tftp_grid, from_=1, to=65535, textvariable=vars["tftp_port"], width=7 ).grid(row=1, column=1, sticky=tk.W, padx=5) # --- Serial Widgets --- serial_grid = serial_frame serial_grid.columnconfigure(1, weight=1) ttk.Label(serial_grid, text="COM Port:").grid( row=0, column=0, sticky=tk.W, pady=2 ) vars["serial_port"] = tk.StringVar() ttk.Combobox( serial_grid, textvariable=vars["serial_port"], values=[f"COM{i}" for i in range(1, 17)], ).grid(row=0, column=1, sticky=tk.EW, padx=5) ttk.Label(serial_grid, text="Baud Rate:").grid( row=1, column=0, sticky=tk.W, pady=2 ) vars["serial_baud"] = tk.IntVar() ttk.Combobox( serial_grid, textvariable=vars["serial_baud"], values=[9600, 19200, 38400, 57600, 115200], width=7, ).grid(row=1, column=1, sticky=tk.W, padx=5) # --- SFP Widgets --- sfp_grid = sfp_frame sfp_grid.columnconfigure(1, weight=1) ttk.Label(sfp_grid, text="Server IP:").grid( row=0, column=0, sticky=tk.W, pady=2 ) vars["sfp_ip"] = tk.StringVar() ttk.Entry(sfp_grid, textvariable=vars["sfp_ip"]).grid( row=0, column=1, sticky=tk.EW, padx=5 ) ttk.Label(sfp_grid, text="Server Port:").grid( row=1, column=0, sticky=tk.W, pady=2 ) vars["sfp_port"] = tk.IntVar() ttk.Spinbox( sfp_grid, from_=1, to=65535, textvariable=vars["sfp_port"], width=7 ).grid(row=1, column=1, sticky=tk.W, padx=5) ttk.Label(sfp_grid, text="Local Port:").grid( row=2, column=0, sticky=tk.W, pady=2 ) vars["sfp_local_port"] = tk.IntVar() ttk.Spinbox( sfp_grid, from_=1, to=65535, textvariable=vars["sfp_local_port"], width=7 ).grid(row=2, column=1, sticky=tk.W, padx=5) # Prediction Offset widget on row 3 ttk.Label(sfp_grid, text="Prediction Offset (ms):").grid( row=3, column=0, sticky=tk.W, pady=2 ) vars["sfp_prediction_offset"] = tk.DoubleVar() ttk.Spinbox( sfp_grid, from_=0, to=1000, increment=1, textvariable=vars["sfp_prediction_offset"], width=7, ).grid(row=3, column=1, sticky=tk.W, padx=5) # JSON protocol checkbox moved to row 4 vars["sfp_use_json"] = tk.BooleanVar() ttk.Checkbutton( sfp_grid, text="Use JSON Protocol", variable=vars["sfp_use_json"] ).grid(row=4, column=0, columnspan=2, sticky=tk.W, pady=(5, 0)) # place test button to the right of the notebook area test_row = ttk.Frame(parent_frame) test_row.pack(fill=tk.X, padx=5, pady=(2, 8)) test_button = ttk.Button( test_row, text="Test Connection", command=lambda: self._test_connection(vars), ) test_button.pack(side=tk.RIGHT) return vars # Note: previous implementation used pack/grid switching per type. The UI # now uses a Notebook with tabs and a Combobox to select the active type. def _test_connection(self, vars): """Perform a quick connection test for the selected type. Inputs: - vars: dict of Tk variables for a connection panel Returns: - None, but shows a messagebox with success/failure. """ conn_type = vars["conn_type"].get() result = False config = {} try: if conn_type == "TFTP": from target_simulator.core.tftp_communicator import TFTPCommunicator config = {"ip": vars["tftp_ip"].get(), "port": vars["tftp_port"].get()} result = TFTPCommunicator.test_connection(config) elif conn_type == "Serial": from target_simulator.core.serial_communicator import SerialCommunicator config = { "port": vars["serial_port"].get(), "baudrate": vars["serial_baud"].get(), } result = SerialCommunicator.test_connection(config) elif conn_type == "SFP": from target_simulator.core.sfp_communicator import SFPCommunicator config = {"local_port": vars["sfp_local_port"].get()} result = SFPCommunicator.test_connection(config) except Exception as e: messagebox.showerror("Test Error", f"An error occurred: {e}", parent=self) return if result: messagebox.showinfo( "Connection Test", f"{conn_type} connection test successful!", parent=self, ) else: messagebox.showerror( "Connection Test", f"{conn_type} connection test failed.", parent=self ) def _on_save(self): """Collect values from widgets and save them to master view. Builds the structured connection dict and calls master_view.update_connection_settings(new_config). """ new_config = { "target": { "type": self.target_vars["conn_type"].get().lower(), "tftp": { "ip": self.target_vars["tftp_ip"].get(), "port": self.target_vars["tftp_port"].get(), }, "serial": { "port": self.target_vars["serial_port"].get(), "baudrate": self.target_vars["serial_baud"].get(), }, "sfp": { "ip": self.target_vars["sfp_ip"].get(), "port": self.target_vars["sfp_port"].get(), "local_port": self.target_vars["sfp_local_port"].get(), "use_json_protocol": self.target_vars["sfp_use_json"].get(), "prediction_offset_ms": self.target_vars[ "sfp_prediction_offset" ].get(), }, }, "lru": { "type": self.lru_vars["conn_type"].get().lower(), "tftp": { "ip": self.lru_vars["tftp_ip"].get(), "port": self.lru_vars["tftp_port"].get(), }, "serial": { "port": self.lru_vars["serial_port"].get(), "baudrate": self.lru_vars["serial_baud"].get(), }, "sfp": { "ip": self.lru_vars["sfp_ip"].get(), "port": self.lru_vars["sfp_port"].get(), "local_port": self.lru_vars["sfp_local_port"].get(), "use_json_protocol": self.lru_vars["sfp_use_json"].get(), "prediction_offset_ms": self.lru_vars[ "sfp_prediction_offset" ].get(), }, }, } self.master_view.update_connection_settings(new_config) self.destroy() def _on_cancel(self): """Close the dialog without saving changes.""" self.destroy()