fix: suppress FileSelector callbacks during construction
During _build_config_panel(), set_relative_path() triggered callbacks that called _get_selected_files() before all selectors were created, causing AttributeError. Fix: FileSelector now has _suppress_callback flag. Set True during construction, then False after all selectors are created. Finally call _auto_save_config() once at the end of the panel build.
This commit is contained in:
+22
-2
@@ -218,15 +218,20 @@ class MainWindow(ctk.CTk):
|
|||||||
object's perspective); the FileSelector stores both the
|
object's perspective); the FileSelector stores both the
|
||||||
absolute path (for internal use) and the relative path
|
absolute path (for internal use) and the relative path
|
||||||
(for saving to YAML).
|
(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:
|
if not self._config:
|
||||||
return
|
return
|
||||||
# Sync IP address
|
# Sync IP address (may not exist yet during _build_config_panel)
|
||||||
if self._ip_string_var:
|
if self._ip_string_var:
|
||||||
self._config.zynq_ip = self._ip_string_var.get()
|
self._config.zynq_ip = self._ip_string_var.get()
|
||||||
# Sync Xilinx Kit path
|
# Sync Xilinx Kit path
|
||||||
|
if hasattr(self, '_xilinx_path_var'):
|
||||||
self._config.xilinx_path = self._xilinx_path_var.get()
|
self._config.xilinx_path = self._xilinx_path_var.get()
|
||||||
# Sync serial port
|
# Sync serial port (may not exist yet during _build_config_panel)
|
||||||
|
if hasattr(self, '_port_var'):
|
||||||
self._config.serial_port = self._port_var.get()
|
self._config.serial_port = self._port_var.get()
|
||||||
# Sync file paths from FileSelectors
|
# Sync file paths from FileSelectors
|
||||||
files = self._get_selected_files()
|
files = self._get_selected_files()
|
||||||
@@ -245,6 +250,8 @@ class MainWindow(ctk.CTk):
|
|||||||
if abs_path:
|
if abs_path:
|
||||||
self._config._config_path # ensure _config_path is set
|
self._config._config_path # ensure _config_path is set
|
||||||
setattr(self._config, attr, self._config._relative_path(abs_path))
|
setattr(self._config, attr, self._config._relative_path(abs_path))
|
||||||
|
# Sync erase checkbox (may not exist yet)
|
||||||
|
if hasattr(self, '_erase_cb_var'):
|
||||||
self._config.erase_all = self._erase_cb_var.get()
|
self._config.erase_all = self._erase_cb_var.get()
|
||||||
|
|
||||||
def _auto_save_config(self) -> None:
|
def _auto_save_config(self) -> None:
|
||||||
@@ -586,6 +593,7 @@ class MainWindow(ctk.CTk):
|
|||||||
file_types=[("ELF files", "*.elf"), ("All files", "*")],
|
file_types=[("ELF files", "*.elf"), ("All files", "*")],
|
||||||
callback=self._on_file_selected,
|
callback=self._on_file_selected,
|
||||||
)
|
)
|
||||||
|
self._fsbl_selector._suppress_callback = True
|
||||||
if self._config.fsbl_elf_path:
|
if self._config.fsbl_elf_path:
|
||||||
resolved = self._config.resolve_path(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)
|
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", "*")],
|
file_types=[("BIT files", "*.bit"), ("All files", "*")],
|
||||||
callback=self._on_file_selected,
|
callback=self._on_file_selected,
|
||||||
)
|
)
|
||||||
|
self._bit_selector._suppress_callback = True
|
||||||
if self._config.bootloader_bit_path:
|
if self._config.bootloader_bit_path:
|
||||||
resolved = self._config.resolve_path(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)
|
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", "*")],
|
file_types=[("ELF files", "*.elf"), ("All files", "*")],
|
||||||
callback=self._on_file_selected,
|
callback=self._on_file_selected,
|
||||||
)
|
)
|
||||||
|
self._elf_selector._suppress_callback = True
|
||||||
if self._config.bootloader_elf_path:
|
if self._config.bootloader_elf_path:
|
||||||
resolved = self._config.resolve_path(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)
|
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", "*")],
|
file_types=[("BIN files", "*.bin"), ("All files", "*")],
|
||||||
callback=self._on_file_selected,
|
callback=self._on_file_selected,
|
||||||
)
|
)
|
||||||
|
self._bootloader_bin_selector._suppress_callback = True
|
||||||
if self._config.bootloader_bin_path:
|
if self._config.bootloader_bin_path:
|
||||||
resolved = self._config.resolve_path(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)
|
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", "*")],
|
file_types=[("BIN files", "*.bin"), ("All files", "*")],
|
||||||
callback=self._on_file_selected,
|
callback=self._on_file_selected,
|
||||||
)
|
)
|
||||||
|
self._firmware_bin_selector._suppress_callback = True
|
||||||
if self._config.firmware_bin_path:
|
if self._config.firmware_bin_path:
|
||||||
resolved = self._config.resolve_path(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_relative_path(self._config.firmware_bin_path)
|
||||||
self._firmware_bin_selector.set_path(str(resolved))
|
self._firmware_bin_selector.set_path(str(resolved))
|
||||||
self._firmware_bin_selector.grid(row=8, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
|
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 button
|
||||||
save_btn = ctk.CTkButton(
|
save_btn = ctk.CTkButton(
|
||||||
panel,
|
panel,
|
||||||
|
|||||||
+3
-2
@@ -528,6 +528,7 @@ class FileSelector(ctk.CTkFrame):
|
|||||||
self._full_path: str = "" # absolute path (internal)
|
self._full_path: str = "" # absolute path (internal)
|
||||||
self._relative_path: str = "" # relative path (synced to Config)
|
self._relative_path: str = "" # relative path (synced to Config)
|
||||||
self._display_text = ctk.StringVar(value="")
|
self._display_text = ctk.StringVar(value="")
|
||||||
|
self._suppress_callback: bool = False # True during construction
|
||||||
|
|
||||||
self._create_widgets(label_text)
|
self._create_widgets(label_text)
|
||||||
|
|
||||||
@@ -590,7 +591,7 @@ class FileSelector(ctk.CTkFrame):
|
|||||||
"""
|
"""
|
||||||
self._full_path = path
|
self._full_path = path
|
||||||
self._display_text.set(os.path.basename(path) if path else "")
|
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)
|
self._callback(path)
|
||||||
|
|
||||||
def set_relative_path(self, path: str) -> None:
|
def set_relative_path(self, path: str) -> None:
|
||||||
@@ -606,7 +607,7 @@ class FileSelector(ctk.CTkFrame):
|
|||||||
if path:
|
if path:
|
||||||
self._full_path = path # caller should resolve before calling
|
self._full_path = path # caller should resolve before calling
|
||||||
self._display_text.set(os.path.basename(path) if path else "")
|
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)
|
self._callback(path)
|
||||||
|
|
||||||
def get_path(self) -> str:
|
def get_path(self) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user