Creating a Component Registry System
Registry mapping for configuration attributes/devices to UI components:
- Components are created only when relevant features exist in the configuration
- You can easily add new types of components by adding factory methods
- The UI updates dynamically when the configuration changes
- The code is more maintainable with clear separation of component creation logic
class ConfigController(QWidget):
 
    # ...existing signals...
 
    def __init__(self, cfg: 'ExperimentConfig'):
        super().__init__()
        self.config = cfg
        self.mmcores = cfg._cores
 
        # Initialize device-specific components registry
        self._component_registry = {
            # Maps device/feature -> (creation_method, layout_section)
            'led_control': (self._create_led_controls, 'buttons'),
            'camera_snap': (self._create_snap_control, 'buttons'),
            'psychopy': (self._create_psychopy_controls, 'buttons'),
            # Add more mappings as needed
        }
 
        # Initialize layouts dictionary to hold different layout sections
        self._layouts = {}
        self._setup_base_layout()
        self._setup_dynamic_components()
        self._refresh_config_table()
 2. Create Base Layout Method
def _setup_base_layout(self):
    """Create the base layout structure that will contain dynamic components."""
 
    main_layout = QVBoxLayout(self)
 
    self.setFixedWidth(500)
 
    # Directory selection section
    dir_layout = QHBoxLayout()
    self.directory_label = QLabel('Select Save Directory:')
    self.directory_line_edit = QLineEdit()
    self.directory_line_edit.setReadOnly(True)
    self.directory_button = QPushButton('Browse')
    dir_layout.addWidget(self.directory_label)
    dir_layout.addWidget(self.directory_line_edit)
    dir_layout.addWidget(self.directory_button)
    main_layout.addLayout(dir_layout)
 
    # JSON dropdown section
    json_layout = QHBoxLayout()
    self.json_dropdown_label = QLabel('Select JSON Config:')
    self.json_dropdown = QComboBox()
    json_layout.addWidget(self.json_dropdown_label)
    json_layout.addWidget(self.json_dropdown)
    main_layout.addLayout(json_layout)
 
    # Configuration table
    main_layout.addWidget(QLabel('Experiment Config:'))
    self.config_table = QTableWidget()
    self.config_table.setEditTriggers(QTableWidget.AllEditTriggers)
    self.config_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
    main_layout.addWidget(self.config_table)
 
    # Create a layout for buttons that will be added dynamically
    buttons_layout = QVBoxLayout()
    main_layout.addLayout(buttons_layout)
    self._layouts['buttons'] = buttons_layout
 
    # Always add record button
    self.record_button = QPushButton('Record')
    buttons_layout.addWidget(self.record_button)
    self.record_button.clicked.connect(self.record)
 
    # Connect base signals
    self.directory_button.clicked.connect(self._select_directory)
    self.json_dropdown.currentIndexChanged.connect(self._update_config)
    self.config_table.cellChanged.connect(self._on_table_edit)3. Create Component Factory Methods
 
def _create_led_controls(self):
    """Factory method to create LED control buttons."""
    widgets = []
 
    # Test LED button
    test_led_button = QPushButton("Test LED")
    test_led_button.clicked.connect(self._test_led)
    widgets.append(test_led_button)
 
    # Stop LED button
    stop_led_button = QPushButton("Stop LED")
    stop_led_button.clicked.connect(self._stop_led)
    widgets.append(stop_led_button)
 
    return widgets
 
def _create_snap_control(self):
    """Factory method to create camera snap control."""
 
    widgets = []
 
    # Snap image button
    snap_button = QPushButton("Snap Image")
 
    # Determine which camera to use based on configuration
    if len(self.mmcores) >= 1:
        camera = self._mmc1 if hasattr(self, '_mmc1') else self._mmc
        snap_button.clicked.connect(lambda: self._save_snapshot(camera.snap()))
    widgets.append(snap_button)
 
    return widgets
 
def _create_psychopy_controls(self):
    """Factory method to create PsychoPy related controls."""
    widgets = []
 
    # Add Note button (typically used during PsychoPy experiments)
    add_note_button = QPushButton("Add Note")
    add_note_button.clicked.connect(self._add_note)
    widgets.append(add_note_button)
 
    return widgets4. Setup Dynamic Components Method
 
def _setup_dynamic_components(self):
    """Dynamically create UI components based on config attributes."""
    # Check for LED control capability
    has_led_control = hasattr(self.config.hardware, 'Dhyana') and hasattr(self, '_mmc1')
    # Check for camera with snap capability
    has_camera_snap = len(self.mmcores) > 0
    # Check for PsychoPy capability
    has_psychopy = hasattr(self.config, 'start_on_trigger')
    # Dictionary of features and their availability
    available_features = {
        'led_control': has_led_control,
        'camera_snap': has_camera_snap,
        'psychopy': has_psychopy,
        # Add more feature checks as needed
    }
    # Create components for available features
    for feature, available in available_features.items():
        if available and feature in self._component_registry:
            creator_method, layout_section = self._component_registry[feature]
            widgets = creator_method()
            for widget in widgets:
                self._layouts[layout_section].addWidget(widget)5. Implement Update Method for Dynamic Reconfiguration
 
def update_dynamic_components(self):
    """Update dynamic components based on current configuration."""
 
    # Clear existing dynamic components
    for layout_name, layout in self._layouts.items():
        if layout_name != 'buttons':  # Don't clear the record button
            continue
 
        # Remove all widgets from the layout except the record button
        while layout.count() > 1:
            item = layout.takeAt(1)
            if item.widget():
                item.widget().deleteLater()
 
    # Re-setup dynamic components
    self._setup_dynamic_components()
 
## Usage in Configuration Update Method
def _update_config(self, index):
    """Update the experiment configuration from a new JSON file."""
 
    json_path_input = self.json_dropdown.currentText()
    if json_path_input and os.path.isfile(json_path_input):
        try:
            self.config.load_parameters(json_path_input)
            # Refresh the GUI table
            self._refresh_config_table()
            # Update dynamic components based on new config
            self.update_dynamic_components()
        except Exception as e:
            print(f"Trouble updating ExperimentConfig from AcquisitionEngine:\n{json_path_input}\nConfiguration not updated.")
            print(e)