Skip to content

Implement Settings Dialog and Application Settings Logic #38

@Sinitca-Aleksandr

Description

@Sinitca-Aleksandr

Enhanced specification for implementing the Application Settings Dialog that integrates with the centralized SettingsService (#100). The dialog shall provide a user-friendly interface for configuring dependency table visualization preferences, using Qt's Model/View architecture and standard configuration patterns.

Motivation

Users need a consistent, discoverable way to customize how dependency relationships are visualized in the BN Modeller. This dialog will:

  • Provide runtime configuration of the dependency selection table (dep_table)
  • Persist user preferences using the centralized SettingsService
  • Follow Qt best practices for maintainability and extensibility
  • Enable future expansion to other settings categories (visualization, analysis, etc.)

Technical Requirements

Dialog Architecture

  • Base Pattern: Adapt the QtMoko ConfigDialog pattern:
    • Left panel: Icon/list view for settings categories (expandable for future sections)
    • Right panel: QStackedWidget displaying category-specific configuration pages
    • Modal dialog with Accept/Reject/Apply buttons
  • Model/View Integration:
  • Qt Features:
    • Signals/slots for live preview: settingChanged → update dep_table colormap without dialog restart
    • QSettings::UserScope for storage location (handled by SettingsService) [[1]]
    • Thread-safe access patterns per Qt guidelines

Settings Dialog Structure

Page: "Dependency Table" (dep_table section)

Setting Key UI Control Type Default Description
depTable/colormap QComboBox (dropdown) str "viridis" Colormap for correlation strength visualization. Options: viridis, plasma, inferno, magma, coolwarm, RdYlGn

UI Implementation Notes:

# Example: Colormap selector binding
colormap_combo = QComboBox()
colormap_combo.addItems(COLORMAP_OPTIONS)
# Bind to SettingsService via mapper or direct signal connection
settings = SettingsService.instance()
current = settings.get_value("depTable/colormap", "viridis")
colormap_combo.setCurrentText(current)
colormap_combo.currentTextChanged.connect(
    lambda val: settings.set_value("depTable/colormap", val)
)

Extensibility Hooks

  • Design page registration system for future categories:
    class SettingsDialog(QDialog):
        def register_page(self, name: str, icon: QIcon, widget_factory: Callable):
            """Register a new settings category page"""
  • Support dynamic validation: e.g., colormap preview thumbnail updates on selection

Integration with SettingsService (#100)

  • Dialog reads initial values via SettingsService.get_value(key)
  • User changes emitted via SettingsService.set_value(key, value) → triggers settingChanged signal
  • Apply button: calls SettingsService.sync() to force disk write
  • Reset button: uses SettingsService.reset_to_defaults(group="depTable")

Acceptance Criteria

  • Dialog launches from main menu: Edit → Settings or toolbar gear icon
  • depTable/colormap selection persists across application restarts
  • Changing colormap updates dep_table visualization in real-time (via signal/slot)
  • Dialog follows platform-native styling (Qt style hints)
  • Unit tests cover:
    • Dialog initialization with existing settings
    • Signal emission on value change
    • Persistence verification (mock QSettings path)
  • Documentation:
    • User guide: "Customizing Dependency Table Appearance"
    • Developer guide: "Adding New Settings Pages"

Implementation Plan

Suggested File Structure

bn_modeller/
├── dialogs/
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── settings_dialog.py      # Main ConfigDialog adaptation
│   │   ├── pages/
│   │   │   ├── __init__.py
│   │   │   ├── dep_table_page.py   # Dependency table settings page
│   │   │   └── base_page.py        # Abstract page interface
│   │   └── widgets/
│   │       ├── colormap_selector.py # Reusable colormap dropdown widget
│   │       └── preview_label.py     # Optional: live colormap preview
│   └── __init__.py
├── utils/
│   └── settings_service.py          # From issue #100 (dependency)
└── widgets/
    └── dep_table.py                 # Existing: receives settingChanged signals

Key Code Snippets

# settings_dialog.py - Skeleton
class SettingsDialog(QDialog):
    settingApplied = Signal()  # Emitted on Apply/OK
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self._settings = SettingsService.instance()
        self._setup_ui()
        self._load_values()
        
    def _setup_ui(self):
        # Left: QListWidget for categories
        # Right: QStackedWidget for pages
        # Buttons: QDialogButtonBox with Apply/OK/Cancel/Reset
        pass
        
    def _load_values(self):
        # Populate widgets from SettingsService
        pass
        
    def accept(self):
        self._settings.sync()  # Force write to disk [[1]]
        self.settingApplied.emit()
        super().accept()

Testing Strategy

def test_colormap_persistence(qtbot, tmp_path):
    # Mock settings path
    monkeypatch.setenv("QT_SETTINGS_PATH", str(tmp_path))
    
    dialog = SettingsDialog()
    dialog.show()
    combo = dialog.findChild(QComboBox, "colormap_combo")
    
    # Change value
    qtbot.keyClicks(combo, "plasma")
    qtbot.mouseClick(dialog.button_box.button(QDialogButtonBox.Apply), Qt.LeftButton)
    
    # Verify persistence
    settings = SettingsService.instance()
    assert settings.get_value("depTable/colormap") == "plasma"

def test_live_update_signal(qtbot):
    dep_table = DependencyTable()  # Mock widget
    dialog = SettingsDialog()
    
    # Connect signal
    settings = SettingsService.instance()
    settings.settingChanged.connect(dep_table.update_colormap)
    
    with qtbot.waitSignal(settings.settingChanged):
        dialog._pages["dep_table"].colormap_combo.setCurrentText("coolwarm")
    
    assert dep_table.colormap == "coolwarm"

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status

    Available Items

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions