diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 55a0286..dd87dd2 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -218,16 +218,21 @@ class MainWindow(ctk.CTk): object's perspective); the FileSelector stores both the absolute path (for internal use) and the relative path (for saving to YAML). + + All attribute accesses are defensive — widgets may not exist + yet during construction (e.g. _port_var created in _build_serial). """ if not self._config: return - # Sync IP address + # Sync IP address (may not exist yet during _build_config_panel) if self._ip_string_var: self._config.zynq_ip = self._ip_string_var.get() # Sync Xilinx Kit path - self._config.xilinx_path = self._xilinx_path_var.get() - # Sync serial port - self._config.serial_port = self._port_var.get() + if hasattr(self, '_xilinx_path_var'): + self._config.xilinx_path = self._xilinx_path_var.get() + # Sync serial port (may not exist yet during _build_config_panel) + if hasattr(self, '_port_var'): + self._config.serial_port = self._port_var.get() # Sync file paths from FileSelectors files = self._get_selected_files() for attr, selector in [ @@ -245,7 +250,9 @@ class MainWindow(ctk.CTk): if abs_path: self._config._config_path # ensure _config_path is set setattr(self._config, attr, self._config._relative_path(abs_path)) - self._config.erase_all = self._erase_cb_var.get() + # Sync erase checkbox (may not exist yet) + if hasattr(self, '_erase_cb_var'): + self._config.erase_all = self._erase_cb_var.get() def _auto_save_config(self) -> None: """Auto-save config to disk after UI changes. @@ -586,6 +593,7 @@ class MainWindow(ctk.CTk): file_types=[("ELF files", "*.elf"), ("All files", "*")], callback=self._on_file_selected, ) + self._fsbl_selector._suppress_callback = True if self._config.fsbl_elf_path: resolved = self._config.resolve_path(self._config.fsbl_elf_path) self._fsbl_selector.set_relative_path(self._config.fsbl_elf_path) @@ -599,6 +607,7 @@ class MainWindow(ctk.CTk): file_types=[("BIT files", "*.bit"), ("All files", "*")], callback=self._on_file_selected, ) + self._bit_selector._suppress_callback = True if self._config.bootloader_bit_path: resolved = self._config.resolve_path(self._config.bootloader_bit_path) self._bit_selector.set_relative_path(self._config.bootloader_bit_path) @@ -612,6 +621,7 @@ class MainWindow(ctk.CTk): file_types=[("ELF files", "*.elf"), ("All files", "*")], callback=self._on_file_selected, ) + self._elf_selector._suppress_callback = True if self._config.bootloader_elf_path: resolved = self._config.resolve_path(self._config.bootloader_elf_path) self._elf_selector.set_relative_path(self._config.bootloader_elf_path) @@ -625,6 +635,7 @@ class MainWindow(ctk.CTk): file_types=[("BIN files", "*.bin"), ("All files", "*")], callback=self._on_file_selected, ) + self._bootloader_bin_selector._suppress_callback = True if self._config.bootloader_bin_path: resolved = self._config.resolve_path(self._config.bootloader_bin_path) self._bootloader_bin_selector.set_relative_path(self._config.bootloader_bin_path) @@ -660,12 +671,21 @@ class MainWindow(ctk.CTk): file_types=[("BIN files", "*.bin"), ("All files", "*")], callback=self._on_file_selected, ) + self._firmware_bin_selector._suppress_callback = True if self._config.firmware_bin_path: resolved = self._config.resolve_path(self._config.firmware_bin_path) self._firmware_bin_selector.set_relative_path(self._config.firmware_bin_path) self._firmware_bin_selector.set_path(str(resolved)) self._firmware_bin_selector.grid(row=8, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL) + # Enable callbacks now that all selectors are created + for sel in (self._fsbl_selector, self._bit_selector, self._elf_selector, + self._bootloader_bin_selector, self._firmware_bin_selector): + sel._suppress_callback = False + + # Now sync and save config (all selectors exist) + self._auto_save_config() + # Save button save_btn = ctk.CTkButton( panel, diff --git a/src/gui/widgets.py b/src/gui/widgets.py index 55da265..e40e428 100644 --- a/src/gui/widgets.py +++ b/src/gui/widgets.py @@ -528,6 +528,7 @@ class FileSelector(ctk.CTkFrame): self._full_path: str = "" # absolute path (internal) self._relative_path: str = "" # relative path (synced to Config) self._display_text = ctk.StringVar(value="") + self._suppress_callback: bool = False # True during construction self._create_widgets(label_text) @@ -590,7 +591,7 @@ class FileSelector(ctk.CTkFrame): """ self._full_path = path self._display_text.set(os.path.basename(path) if path else "") - if self._callback: + if self._callback and not self._suppress_callback: self._callback(path) def set_relative_path(self, path: str) -> None: @@ -606,7 +607,7 @@ class FileSelector(ctk.CTkFrame): if path: self._full_path = path # caller should resolve before calling self._display_text.set(os.path.basename(path) if path else "") - if self._callback: + if self._callback and not self._suppress_callback: self._callback(path) def get_path(self) -> str: