ConfigEditorWidget

A generic, reusable widget for editing configuration values with automatic UI generation based on field descriptors.

Features

  • Automatic UI Generation: Define fields declaratively, widget generates appropriate inputs

  • Multiple Field Types: String, int, float, bool, and choice (dropdown) fields

  • Grouping: Organize fields into collapsible groups

  • Dynamic Choices: Support for static or dynamic (callback-based) dropdown options

  • Change Callbacks: Execute custom logic when specific fields change

  • JSON View: Optional live JSON view of current configuration

  • File Operations: Built-in load/save with file dialogs supporting JSON, YAML, INI, and ENV formats

  • Signals: Emits signals when config is changed, loaded, or saved

Basic Usage

from qtframework.config import ConfigManager
from qtframework.widgets import ConfigEditorWidget, ConfigFieldDescriptor

# Create config manager
config = ConfigManager()

# Define fields
fields = [
    ConfigFieldDescriptor(
        key="app.name",
        label="Application Name",
        field_type="string",
        default="My App",
        group="Application"
    ),
    ConfigFieldDescriptor(
        key="ui.theme",
        label="Theme",
        field_type="choice",
        choices=["light", "dark", "monokai"],
        default="light",
        group="User Interface"
    ),
    ConfigFieldDescriptor(
        key="ui.font_scale",
        label="Font Scale (%)",
        field_type="int",
        default=100,
        min_value=50,
        max_value=200,
        group="User Interface"
    ),
]

# Create editor widget
editor = ConfigEditorWidget(
    config_manager=config,
    fields=fields,
    show_json_view=True,
    show_file_buttons=True
)

# Connect to signals
editor.config_changed.connect(lambda: print("Config changed!"))

Field Types

String Field

ConfigFieldDescriptor(
    key="app.name",
    label="Application Name",
    field_type="string",
    default="My App"
)

Integer Field

ConfigFieldDescriptor(
    key="performance.threads",
    label="Worker Threads",
    field_type="int",
    default=4,
    min_value=1,
    max_value=64
)

Float Field

ConfigFieldDescriptor(
    key="graphics.scale",
    label="UI Scale",
    field_type="float",
    default=1.0,
    min_value=0.5,
    max_value=2.0
)

Boolean Field

ConfigFieldDescriptor(
    key="app.debug",
    label="Debug Mode",
    field_type="bool",
    default=False
)

Choice Field (Static)

ConfigFieldDescriptor(
    key="ui.language",
    label="Language",
    field_type="choice",
    choices=["en_US", "es_ES", "fr_FR"],
    default="en_US"
)

Choice Field (Dynamic)

def get_themes():
    return theme_manager.list_themes()

ConfigFieldDescriptor(
    key="ui.theme",
    label="Theme",
    field_type="choice",
    choices_callback=get_themes,  # Dynamic choices
    default="light"
)

Change Callbacks

Execute custom logic when a field changes:

def on_theme_changed(new_theme: str):
    theme_manager.set_theme(new_theme)
    print(f"Theme changed to: {new_theme}")

ConfigFieldDescriptor(
    key="ui.theme",
    label="Theme",
    field_type="choice",
    choices=["light", "dark"],
    on_change=on_theme_changed  # Called when applied
)

Grouping Fields

Organize related fields into groups:

fields = [
    # Application group
    ConfigFieldDescriptor(
        key="app.name",
        label="Name",
        field_type="string",
        group="Application Settings"
    ),
    ConfigFieldDescriptor(
        key="app.version",
        label="Version",
        field_type="string",
        group="Application Settings"
    ),

    # UI group
    ConfigFieldDescriptor(
        key="ui.theme",
        label="Theme",
        field_type="choice",
        choices=["light", "dark"],
        group="User Interface"
    ),
]

Signals

# Config changed (after apply_changes)
editor.config_changed.connect(on_config_changed)

# Config loaded from file
editor.config_loaded.connect(lambda path: print(f"Loaded: {path}"))

# Config saved to file
editor.config_saved.connect(lambda path: print(f"Saved: {path}"))

Methods

refresh_values()

Reload field values from current config state:

editor.refresh_values()

apply_changes()

Apply widget values to config manager:

editor.apply_changes()

get_value(key)

Get current widget value (not yet applied):

current_theme = editor.get_value("ui.theme")

set_value(key, value)

Set widget value directly:

editor.set_value("ui.theme", "dark")

Complete Example

See examples/features/app/pages/config_editor.py for a complete working example that demonstrates:

  • Multiple field types

  • Dynamic choices from theme manager

  • Change callbacks for theme switching

  • Integration with application config manager

  • Grouped fields by category

Constructor Options

ConfigEditorWidget(
    config_manager: ConfigManager,  # Required: config manager instance
    fields: list[ConfigFieldDescriptor],  # Required: field definitions
    show_json_view: bool = True,  # Show JSON view section
    show_file_buttons: bool = True,  # Show load/save buttons
    parent: QWidget | None = None
)

Best Practices

  1. Define all fields upfront - Dynamic field addition requires UI rebuild

  2. Use groups - Organize related settings together

  3. Set appropriate min/max - Constrain numeric fields to valid ranges

  4. Use callbacks - Apply changes immediately when they affect app state

  5. Connect signals - React to config changes in other parts of your app

Comparison to Manual Implementation

Before (280 lines):

# Manually create each widget
self.app_name_edit = QLineEdit()
self.theme_combo = QComboBox()
# ... many more widgets
# Manually create layouts
# Manually wire up signals
# Manually implement refresh/apply logic

After (~50 lines):

fields = [
    ConfigFieldDescriptor(key="app.name", label="Name", field_type="string"),
    ConfigFieldDescriptor(key="ui.theme", label="Theme", field_type="choice",
                         choices_callback=get_themes),
]
editor = ConfigEditorWidget(config_manager, fields)

The generic widget reduces boilerplate by 80%+ while providing a consistent, themeable UI.