Skip to content

UI Services and Widgets

UI orchestration services and reusable widget containers.


ThemeService

ThemeService

Service responsible for loading and applying QSS themes.

Theme variables are declared without prefix in theme config files (e.g. main_surface) and referenced in QSS files using the standard CSS custom property notation var(--variable_name). The service resolves each reference to its palette value before applying the stylesheet.

apply_theme staticmethod

apply_theme(window: MainWindowProtocol) -> None

Load all QSS files from the themes directory and apply the merged stylesheet.

All .qss files found under <app_root>/bin/themes/ are loaded in alphabetical order and concatenated before palette variables are resolved. When that directory is absent or empty, the package's own resources/themes/ directory is used as fallback.

PARAMETER DESCRIPTION
window

The application window whose ui.styleSheet will be updated. Must expose window.ui.styleSheet.setStyleSheet.

TYPE: MainWindowProtocol

Source code in src/ezqt_app/services/ui/theme_service.py
@staticmethod
def apply_theme(window: MainWindowProtocol) -> None:
    """Load all QSS files from the themes directory and apply the merged stylesheet.

    All ``.qss`` files found under ``<app_root>/bin/themes/`` are loaded in
    alphabetical order and concatenated before palette variables are resolved.
    When that directory is absent or empty, the package's own
    ``resources/themes/`` directory is used as fallback.

    Args:
        window: The application window whose ``ui.styleSheet`` will be
            updated.  Must expose ``window.ui.styleSheet.setStyleSheet``.
    """
    settings_service = get_settings_service()
    config_service = get_config_service()

    theme_preset = settings_service.gui.THEME_PRESET
    theme_variant = settings_service.gui.THEME
    theme_config = config_service.load_config("theme")
    palette = theme_config.get("palette", {})
    colors: dict[str, str] = palette.get(theme_preset, {}).get(theme_variant, {})

    merged_style = ThemeService._load_themes_content()
    merged_style = ThemeService._resolve_variables(merged_style, colors)

    window.ui.style_sheet.setStyleSheet(f"{merged_style}\n")

get_available_themes staticmethod

get_available_themes() -> list[tuple[str, str]]

Return available theme options as (display_label, internal_value) pairs.

Reads palette keys from theme.config.yaml and generates one entry per preset × variant combination, e.g. ("Blue Gray - Dark", "blue_gray:dark").

RETURNS DESCRIPTION
list[tuple[str, str]]

List of (display_label, internal_value) tuples, one per theme variant.

Source code in src/ezqt_app/services/ui/theme_service.py
@staticmethod
def get_available_themes() -> list[tuple[str, str]]:
    """Return available theme options as ``(display_label, internal_value)`` pairs.

    Reads ``palette`` keys from ``theme.config.yaml`` and generates one entry
    per ``preset × variant`` combination, e.g.
    ``("Blue Gray - Dark", "blue_gray:dark")``.

    Returns:
        List of ``(display_label, internal_value)`` tuples, one per theme variant.
    """
    config_service = get_config_service()
    theme_config = config_service.load_config("theme")
    palette = theme_config.get("palette", {})

    options: list[tuple[str, str]] = []
    for preset_key, variants in palette.items():
        if not isinstance(variants, dict):
            continue
        display_preset = preset_key.replace("-", " ").title()
        for variant_key in variants:
            label = f"{display_preset} - {variant_key.title()}"
            options.append((label, f"{preset_key}:{variant_key}"))
    return options

MenuService

Service responsible for menu item selection state and style refresh.

select_menu staticmethod

select_menu(window: MainWindowProtocol, widget: str) -> None

Set active class on a menu button identified by its object name.

Source code in src/ezqt_app/services/ui/menu_service.py
@staticmethod
def select_menu(window: MainWindowProtocol, widget: str) -> None:
    """Set active class on a menu button identified by its object name."""
    for menu_widget in window.ui.menu_container.top_menu.findChildren(QToolButton):
        if menu_widget.objectName() == widget and isinstance(
            menu_widget, QToolButton
        ):
            menu_widget.setProperty("class", "active")
            MenuService.refresh_style(menu_widget)

deselect_menu staticmethod

deselect_menu(window: MainWindowProtocol, widget: str) -> None

Set inactive class on all menu buttons except the selected one.

Source code in src/ezqt_app/services/ui/menu_service.py
@staticmethod
def deselect_menu(window: MainWindowProtocol, widget: str) -> None:
    """Set inactive class on all menu buttons except the selected one."""
    for menu_widget in window.ui.menu_container.top_menu.findChildren(QToolButton):
        if menu_widget.objectName() != widget and isinstance(
            menu_widget, QToolButton
        ):
            menu_widget.setProperty("class", "inactive")
            MenuService.refresh_style(menu_widget)

refresh_style staticmethod

refresh_style(widget: QWidget) -> None

Re-apply widget style after dynamic property changes.

Source code in src/ezqt_app/services/ui/menu_service.py
@staticmethod
def refresh_style(widget: QWidget) -> None:
    """Re-apply widget style after dynamic property changes."""
    widget.style().unpolish(widget)
    widget.style().polish(widget)

PanelService

PanelService

Service responsible for menu and settings panel animations.

toggle_menu_panel staticmethod

toggle_menu_panel(window: MainWindowProtocol, enable: bool) -> None

Animate the left menu panel between shrink and extended widths.

Source code in src/ezqt_app/services/ui/panel_service.py
@staticmethod
def toggle_menu_panel(window: MainWindowProtocol, enable: bool) -> None:
    """Animate the left menu panel between shrink and extended widths."""
    if not enable:
        return

    settings_service = get_settings_service()
    width = window.ui.menu_container.width()
    max_extend = window.ui.menu_container.get_extended_width()
    standard = window.ui.menu_container.get_shrink_width()
    width_extended = max_extend if width == standard else standard

    window.menu_animation = QPropertyAnimation(
        window.ui.menu_container,  # type: ignore[arg-type]
        b"minimumWidth",
    )
    window.menu_animation.setDuration(settings_service.gui.TIME_ANIMATION)
    window.menu_animation.setStartValue(width)
    window.menu_animation.setEndValue(width_extended)
    window.menu_animation.setEasingCurve(QEasingCurve.Type.InOutQuart)
    window.menu_animation.start()

toggle_settings_panel staticmethod

toggle_settings_panel(window: MainWindowProtocol, enable: bool) -> None

Animate the right settings panel and synchronize theme toggle.

Source code in src/ezqt_app/services/ui/panel_service.py
@staticmethod
def toggle_settings_panel(window: MainWindowProtocol, enable: bool) -> None:
    """Animate the right settings panel and synchronize theme toggle."""
    if not enable:
        return

    settings_service = get_settings_service()
    width = window.ui.settings_panel.width()
    max_extend = settings_service.gui.SETTINGS_PANEL_WIDTH
    standard = 0
    is_opening = width == 0
    width_extended = max_extend if is_opening else standard

    window.ui.header_container.set_settings_panel_open(is_opening)

    window.settings_animation = QPropertyAnimation(
        window.ui.settings_panel,  # type: ignore[arg-type]
        b"minimumWidth",
    )
    window.settings_animation.setDuration(settings_service.gui.TIME_ANIMATION)
    window.settings_animation.setStartValue(width)
    window.settings_animation.setEndValue(width_extended)
    window.settings_animation.setEasingCurve(QEasingCurve.Type.InOutQuart)
    window.settings_animation.start()

    theme_toggle = window.ui.settings_panel.get_theme_selector()
    if theme_toggle and hasattr(theme_toggle, "set_value"):
        try:
            gui = settings_service.gui
            internal = f"{gui.THEME_PRESET}:{gui.THEME}"
            value_to_display = getattr(
                window.ui.settings_panel, "_theme_value_to_display", {}
            )
            display = value_to_display.get(internal, "")
            if display:
                theme_toggle.set_value(display)
        except Exception as error:
            warn_tech(
                code="ui.panel.theme_selector_sync_failed",
                message="Theme selector sync failed on panel open",
                error=error,
            )

WindowService

WindowService

Service responsible for maximize/restore and window state flags.

maximize_restore staticmethod

maximize_restore(window: MainWindowProtocol) -> None

Toggle between maximized and restored window state.

Source code in src/ezqt_app/services/ui/window_service.py
@staticmethod
def maximize_restore(window: MainWindowProtocol) -> None:
    """Toggle between maximized and restored window state."""
    runtime_state_service = get_runtime_state_service()
    is_maximized = runtime_state_service.get_global_state()

    if not is_maximized:
        window.showMaximized()
        runtime_state_service.set_global_state(True)
        window.ui.app_margins_layout.setContentsMargins(0, 0, 0, 0)
        window.ui.header_container.maximize_restore_btn.setToolTip("Restore")
        window.ui.header_container.maximize_restore_btn.setIcon(
            QIcon(":/icons/icon_restore.png")
        )
        window.ui.bottom_bar.size_grip_spacer.hide()
        window.left_grip.hide()
        window.right_grip.hide()
        window.top_grip.hide()
        window.bottom_grip.hide()
        return

    runtime_state_service.set_global_state(False)
    window.showNormal()
    window.resize(window.width() + 1, window.height() + 1)
    window.ui.app_margins_layout.setContentsMargins(10, 10, 10, 10)
    window.ui.header_container.maximize_restore_btn.setToolTip("Maximize")
    window.ui.header_container.maximize_restore_btn.setIcon(
        QIcon(":/icons/icon_maximize.png")
    )
    window.ui.bottom_bar.size_grip_spacer.show()
    window.left_grip.show()
    window.right_grip.show()
    window.top_grip.show()
    window.bottom_grip.show()

get_status staticmethod

get_status() -> bool

Return the current maximize/restore status.

Source code in src/ezqt_app/services/ui/window_service.py
@staticmethod
def get_status() -> bool:
    """Return the current maximize/restore status."""
    return get_runtime_state_service().get_global_state()

set_status staticmethod

set_status(status: bool) -> None

Persist the current maximize/restore status.

Source code in src/ezqt_app/services/ui/window_service.py
@staticmethod
def set_status(status: bool) -> None:
    """Persist the current maximize/restore status."""
    get_runtime_state_service().set_global_state(status)

UiComponentFactory

UiComponentFactory

UiComponentFactory()

Bases: UiComponentFactoryProtocol

Factory that exposes predefined UI component specifications.

Initialize the factory.

Source code in src/ezqt_app/services/ui/component_factory.py
def __init__(self) -> None:
    """Initialize the factory."""
    self._initialized = False

initialize

initialize() -> None

Mark the factory as initialized (specs are statically defined).

Source code in src/ezqt_app/services/ui/component_factory.py
def initialize(self) -> None:
    """Mark the factory as initialized (specs are statically defined)."""
    self._initialized = True

get_font

get_font(name: str) -> FontSpec | None

Return a predefined font specification by name.

Source code in src/ezqt_app/services/ui/component_factory.py
def get_font(self, name: str) -> FontSpec | None:
    """Return a predefined font specification by name."""
    return FONT_SPECS.get(name)

get_size_policy

get_size_policy(name: str) -> SizePolicySpec | None

Return a predefined size policy specification by name.

Source code in src/ezqt_app/services/ui/component_factory.py
def get_size_policy(self, name: str) -> SizePolicySpec | None:
    """Return a predefined size policy specification by name."""
    return SIZE_POLICY_SPECS.get(name)

Core Widgets

EzApplication

EzApplication(*args: Any, **kwargs: Any)

Bases: QApplication

Extended main application with theme and UTF-8 encoding support.

This class inherits from QApplication and adds functionality for theme management and UTF-8 encoding.

Initialize the application with UTF-8 and high resolution support.

Parameters

args : Any Positional arguments passed to QApplication. *kwargs : Any Keyword arguments passed to QApplication.

Source code in src/ezqt_app/widgets/core/ez_app.py
def __init__(self, *args: Any, **kwargs: Any) -> None:
    """
    Initialize the application with UTF-8 and high resolution support.

    Parameters
    ----------
    *args : Any
        Positional arguments passed to QApplication.
    **kwargs : Any
        Keyword arguments passed to QApplication.
    """
    try:
        if QGuiApplication.instance() is None:
            QGuiApplication.setHighDpiScaleFactorRoundingPolicy(
                Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
            )
    except (ImportError, RuntimeError) as e:
        warn_tech(
            code="widgets.ez_app.high_dpi_init_config_failed",
            message="Could not configure High DPI in EzApplication.__init__",
            error=e,
        )

    existing_app = QApplication.instance()
    if existing_app and not isinstance(existing_app, EzApplication):
        raise RuntimeError(
            "Please destroy the QApplication singleton before creating a new EzApplication instance."
        )

    super().__init__(*args, **kwargs)

    with contextlib.suppress(locale.Error):
        locale.setlocale(locale.LC_ALL, "")

    os.environ["PYTHONIOENCODING"] = "utf-8"
    os.environ["QT_FONT_DPI"] = "96"

create_for_testing classmethod

create_for_testing(*args: Any, **kwargs: Any) -> EzApplication

Create an EzApplication instance for testing, bypassing singleton checks.

Source code in src/ezqt_app/widgets/core/ez_app.py
@classmethod
def create_for_testing(cls, *args: Any, **kwargs: Any) -> EzApplication:
    """
    Create an EzApplication instance for testing, bypassing singleton checks.
    """
    try:
        if QGuiApplication.instance() is None:
            QGuiApplication.setHighDpiScaleFactorRoundingPolicy(
                Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
            )
    except (ImportError, RuntimeError) as e:
        warn_tech(
            code="widgets.ez_app.high_dpi_test_config_failed",
            message=(
                "Could not configure High DPI in EzApplication.create_for_testing"
            ),
            error=e,
        )

    existing_app = QApplication.instance()
    if existing_app:
        if isinstance(existing_app, cls):
            return existing_app
        existing_app.quit()
        existing_app.deleteLater()
        time.sleep(0.2)

        if nested_app := QApplication.instance():
            nested_app.quit()
            nested_app.deleteLater()
            time.sleep(0.2)

    try:
        instance = cls(*args, **kwargs)
    except RuntimeError as e:
        if "QApplication singleton" in str(e):
            app = QApplication.instance()
            if app:
                app.quit()
                app.deleteLater()
                time.sleep(0.3)
            instance = cls(*args, **kwargs)
        else:
            raise

    with contextlib.suppress(locale.Error):
        locale.setlocale(locale.LC_ALL, "")

    os.environ["PYTHONIOENCODING"] = "utf-8"
    os.environ["QT_FONT_DPI"] = "96"

    return instance

Header

Header(app_name: str = '', description: str = '', parent: QWidget | None = None, *args: Any, **kwargs: Any)

Bases: QFrame

Application header with logo, name and control buttons.

This class provides a customizable header bar with the application logo, its name, description and window control buttons (minimize, maximize, close).

Initialize the application header.

PARAMETER DESCRIPTION
app_name

Application name (default: "").

TYPE: str DEFAULT: ''

description

Application description (default: "").

TYPE: str DEFAULT: ''

parent

The parent widget (default: None).

TYPE: QWidget | None DEFAULT: None

*args

Additional positional arguments.

TYPE: Any DEFAULT: ()

**kwargs

Additional keyword arguments.

TYPE: Any DEFAULT: {}

Source code in src/ezqt_app/widgets/core/header.py
def __init__(
    self,
    app_name: str = "",
    description: str = "",
    parent: QWidget | None = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """
    Initialize the application header.

    Args:
        app_name: Application name (default: "").
        description: Application description (default: "").
        parent: The parent widget (default: None).
        *args: Additional positional arguments.
        **kwargs: Additional keyword arguments.
    """
    super().__init__(parent, *args, **kwargs)
    self._buttons: list[QPushButton] = []
    self._icons: list[Any] = []

    # Store originals for retranslation
    self._app_name: str = app_name
    self._description: str = description

    # Widget properties
    self.setObjectName("header_container")
    self.setFixedHeight(50)
    self.setFrameShape(QFrame.Shape.NoFrame)
    self.setFrameShadow(QFrame.Shadow.Raised)

    # Size policy initialization
    if (
        hasattr(SizePolicy, "H_EXPANDING_V_PREFERRED")
        and SizePolicy.H_EXPANDING_V_PREFERRED is not None
    ):
        self.setSizePolicy(SizePolicy.H_EXPANDING_V_PREFERRED)
        SizePolicy.H_EXPANDING_V_PREFERRED.setHeightForWidth(
            self.sizePolicy().hasHeightForWidth()
        )
    else:
        default_policy = QSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
        )
        default_policy.setHorizontalStretch(0)
        default_policy.setVerticalStretch(0)
        self.setSizePolicy(default_policy)

    # Main layout
    self._layout = QHBoxLayout(self)
    self._layout.setSpacing(0)
    self._layout.setObjectName("header_layout")
    self._layout.setContentsMargins(0, 0, 10, 0)

    # Meta info section
    self._info_frame = QFrame(self)
    self._info_frame.setObjectName("info_frame")
    self._info_frame.setMinimumSize(QSize(0, 50))
    self._info_frame.setMaximumSize(QSize(16777215, 50))
    self._info_frame.setFrameShape(QFrame.Shape.NoFrame)
    self._info_frame.setFrameShadow(QFrame.Shadow.Raised)
    self._layout.addWidget(self._info_frame)

    # App logo
    self._logo_label = QLabel(self._info_frame)
    self._logo_label.setObjectName("app_logo")
    self._logo_label.setGeometry(QRect(10, 4, 40, 40))
    self._logo_label.setMinimumSize(QSize(40, 40))
    self._logo_label.setMaximumSize(QSize(40, 40))
    self._logo_label.setFrameShape(QFrame.Shape.NoFrame)
    self._logo_label.setFrameShadow(QFrame.Shadow.Raised)

    # App title
    self._title_label = QLabel(app_name, self._info_frame)
    self._title_label.setObjectName("app_title")
    self._title_label.setGeometry(QRect(65, 6, 160, 20))

    if hasattr(Fonts, "SEGOE_UI_12_SB") and Fonts.SEGOE_UI_12_SB is not None:
        self._title_label.setFont(Fonts.SEGOE_UI_12_SB)
    else:
        try:
            from PySide6.QtGui import QFont

            default_font = QFont()
            default_font.setFamily("Segoe UI")
            default_font.setPointSize(12)
            self._title_label.setFont(default_font)
        except ImportError:
            pass

    self._title_label.setAlignment(
        Qt.AlignmentFlag.AlignLeading
        | Qt.AlignmentFlag.AlignLeft
        | Qt.AlignmentFlag.AlignTop
    )

    # App subtitle
    self._subtitle_label = QLabel(description, self._info_frame)
    self._subtitle_label.setObjectName("app_subtitle")
    self._subtitle_label.setGeometry(QRect(65, 26, 16777215, 16))
    self._subtitle_label.setMaximumSize(QSize(16777215, 16))

    if hasattr(Fonts, "SEGOE_UI_8_REG") and Fonts.SEGOE_UI_8_REG is not None:
        self._subtitle_label.setFont(Fonts.SEGOE_UI_8_REG)
    else:
        try:
            from PySide6.QtGui import QFont

            default_font = QFont()
            default_font.setFamily("Segoe UI")
            default_font.setPointSize(8)
            self._subtitle_label.setFont(default_font)
        except ImportError:
            pass

    self._subtitle_label.setAlignment(
        Qt.AlignmentFlag.AlignLeading
        | Qt.AlignmentFlag.AlignLeft
        | Qt.AlignmentFlag.AlignTop
    )

    # Spacer
    self._spacer = QSpacerItem(
        20, 20, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred
    )
    self._layout.addItem(self._spacer)

    # Buttons frame
    self._buttons_frame = QFrame(self)
    self._buttons_frame.setObjectName("buttons_frame")
    self._buttons_frame.setMinimumSize(QSize(0, 28))
    self._buttons_frame.setFrameShape(QFrame.Shape.NoFrame)
    self._buttons_frame.setFrameShadow(QFrame.Shadow.Raised)
    self._layout.addWidget(self._buttons_frame, 0, Qt.AlignmentFlag.AlignRight)

    self._buttons_layout = QHBoxLayout(self._buttons_frame)
    self._buttons_layout.setSpacing(5)
    self._buttons_layout.setObjectName("buttons_layout")
    self._buttons_layout.setContentsMargins(0, 0, 0, 0)

    # Theme buttons
    from ezqt_widgets import ThemeIcon

    current_theme = get_settings_service().gui.THEME

    # Settings button
    self.settings_btn = QPushButton(self._buttons_frame)
    self._buttons.append(self.settings_btn)
    self.settings_btn.setObjectName("settings_btn")
    self.settings_btn.setMinimumSize(QSize(28, 28))
    self.settings_btn.setMaximumSize(QSize(28, 28))
    self.settings_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))

    icon_settings = ThemeIcon(Icons.icon_settings, theme=current_theme)
    self._icons.append(icon_settings)
    self.settings_btn.setIcon(icon_settings)
    self.settings_btn.setIconSize(QSize(20, 20))
    self._buttons_layout.addWidget(self.settings_btn)

    # Minimize button
    self.minimize_btn = QPushButton(self._buttons_frame)
    self._buttons.append(self.minimize_btn)
    self.minimize_btn.setObjectName("minimize_btn")
    self.minimize_btn.setMinimumSize(QSize(28, 28))
    self.minimize_btn.setMaximumSize(QSize(28, 28))
    self.minimize_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))

    icon_minimize = ThemeIcon(Icons.icon_minimize, theme=current_theme)
    self._icons.append(icon_minimize)
    self.minimize_btn.setIcon(icon_minimize)
    self.minimize_btn.setIconSize(QSize(20, 20))
    self._buttons_layout.addWidget(self.minimize_btn)

    # Maximize button
    self.maximize_restore_btn = QPushButton(self._buttons_frame)
    self._buttons.append(self.maximize_restore_btn)
    self.maximize_restore_btn.setObjectName("maximize_restore_btn")
    self.maximize_restore_btn.setMinimumSize(QSize(28, 28))
    self.maximize_restore_btn.setMaximumSize(QSize(28, 28))

    if hasattr(Fonts, "SEGOE_UI_10_REG") and Fonts.SEGOE_UI_10_REG is not None:
        self.maximize_restore_btn.setFont(Fonts.SEGOE_UI_10_REG)
    else:
        try:
            from PySide6.QtGui import QFont

            default_font = QFont()
            default_font.setFamily("Segoe UI")
            default_font.setPointSize(10)
            self.maximize_restore_btn.setFont(default_font)
        except ImportError:
            pass

    self.maximize_restore_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
    icon_maximize = ThemeIcon(Icons.icon_maximize, theme=current_theme)
    self._icons.append(icon_maximize)
    self.maximize_restore_btn.setIcon(icon_maximize)
    self.maximize_restore_btn.setIconSize(QSize(20, 20))
    self._buttons_layout.addWidget(self.maximize_restore_btn)

    # Close button
    self.close_btn = QPushButton(self._buttons_frame)
    self._buttons.append(self.close_btn)
    self.close_btn.setObjectName("close_btn")
    self.close_btn.setMinimumSize(QSize(28, 28))
    self.close_btn.setMaximumSize(QSize(28, 28))
    self.close_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))

    icon_close = ThemeIcon(Icons.icon_close, theme=current_theme)
    self._icons.append(icon_close)
    self.close_btn.setIcon(icon_close)
    self.close_btn.setIconSize(QSize(20, 20))
    self._buttons_layout.addWidget(self.close_btn)

    self.retranslate_ui()

set_app_name

set_app_name(app_name: str) -> None

Set the application name in the header.

PARAMETER DESCRIPTION
app_name

The new application name.

TYPE: str

Source code in src/ezqt_app/widgets/core/header.py
def set_app_name(self, app_name: str) -> None:
    """
    Set the application name in the header.

    Args:
        app_name: The new application name.
    """
    self._app_name = app_name
    self._title_label.setText(QCoreApplication.translate("EzQt_App", app_name))

set_app_description

set_app_description(description: str) -> None

Set the application description in the header.

PARAMETER DESCRIPTION
description

The new application description.

TYPE: str

Source code in src/ezqt_app/widgets/core/header.py
def set_app_description(self, description: str) -> None:
    """
    Set the application description in the header.

    Args:
        description: The new application description.
    """
    self._description = description
    self._subtitle_label.setText(
        QCoreApplication.translate("EzQt_App", description)
    )

retranslate_ui

retranslate_ui() -> None

Apply current translations to all owned text labels and tooltips.

Source code in src/ezqt_app/widgets/core/header.py
def retranslate_ui(self) -> None:
    """Apply current translations to all owned text labels and tooltips."""
    self._title_label.setText(
        QCoreApplication.translate("EzQt_App", self._app_name)
    )
    self._subtitle_label.setText(
        QCoreApplication.translate("EzQt_App", self._description)
    )

    self.settings_btn.setToolTip(QCoreApplication.translate("EzQt_App", "Settings"))
    self.minimize_btn.setToolTip(QCoreApplication.translate("EzQt_App", "Minimize"))
    self.maximize_restore_btn.setToolTip(
        QCoreApplication.translate("EzQt_App", "Maximize")
    )
    self.close_btn.setToolTip(QCoreApplication.translate("EzQt_App", "Close"))

changeEvent

changeEvent(event: QEvent) -> None

Handle Qt change events, triggering UI retranslation on language change.

PARAMETER DESCRIPTION
event

The QEvent instance.

TYPE: QEvent

Source code in src/ezqt_app/widgets/core/header.py
def changeEvent(self, event: QEvent) -> None:
    """
    Handle Qt change events, triggering UI retranslation on language change.

    Args:
        event: The QEvent instance.
    """
    if event.type() == QEvent.Type.LanguageChange:
        self.retranslate_ui()
    super().changeEvent(event)
set_app_logo(logo: str | QPixmap, y_shrink: int = 0, y_offset: int = 0) -> None

Set the application logo in the header.

PARAMETER DESCRIPTION
logo

The logo to display (file path or QPixmap).

TYPE: str | QPixmap

y_shrink

Vertical reduction of the logo (default: 0).

TYPE: int DEFAULT: 0

y_offset

Vertical offset of the logo (default: 0).

TYPE: int DEFAULT: 0

Source code in src/ezqt_app/widgets/core/header.py
def set_app_logo(
    self, logo: str | QPixmap, y_shrink: int = 0, y_offset: int = 0
) -> None:
    """
    Set the application logo in the header.

    Args:
        logo: The logo to display (file path or QPixmap).
        y_shrink: Vertical reduction of the logo (default: 0).
        y_offset: Vertical offset of the logo (default: 0).
    """

    def offsetY(y_offset: int = 0, x_offset: int = 0) -> None:
        """Apply offset to logo."""
        current_rect = self._logo_label.geometry()
        new_rect = QRect(
            current_rect.x() + x_offset,
            current_rect.y() + y_offset,
            current_rect.width(),
            current_rect.height(),
        )
        self._logo_label.setGeometry(new_rect)

    # Process logo
    pixmap_logo = QPixmap(logo) if isinstance(logo, str) else logo
    if pixmap_logo.size() != self._logo_label.minimumSize():
        pixmap_logo = pixmap_logo.scaled(
            self._logo_label.minimumSize().shrunkBy(
                QMargins(0, y_shrink, 0, y_shrink)
            ),
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.SmoothTransformation,
        )

    self._logo_label.setPixmap(pixmap_logo)
    offsetY(y_offset, y_shrink)

set_settings_panel_open

set_settings_panel_open(is_open: bool) -> None

Update the open dynamic property on the settings button.

The property is used by QSS to apply an accent background when the settings panel is visible. Calling this method forces Qt to re-evaluate the style rules for the button immediately.

PARAMETER DESCRIPTION
is_open

True when the settings panel is opening, False when it is closing.

TYPE: bool

Source code in src/ezqt_app/widgets/core/header.py
def set_settings_panel_open(self, is_open: bool) -> None:
    """
    Update the ``open`` dynamic property on the settings button.

    The property is used by QSS to apply an accent background when the
    settings panel is visible.  Calling this method forces Qt to
    re-evaluate the style rules for the button immediately.

    Args:
        is_open: ``True`` when the settings panel is opening,
            ``False`` when it is closing.
    """
    self.settings_btn.setProperty("open", is_open)
    self.settings_btn.style().unpolish(self.settings_btn)
    self.settings_btn.style().polish(self.settings_btn)
    self.settings_btn.update()

update_all_theme_icons

update_all_theme_icons() -> None

Update all button icons according to current theme.

Source code in src/ezqt_app/widgets/core/header.py
def update_all_theme_icons(self) -> None:
    """Update all button icons according to current theme."""
    current_theme = get_settings_service().gui.THEME
    for i, btn in enumerate(self._buttons):
        icon = self._icons[i]
        setter = getattr(icon, "setTheme", None)
        if callable(setter):
            setter(current_theme)
        btn.setIcon(icon)

Menu

Menu(parent: QWidget | None = None, shrink_width: int = 60, extended_width: int = 240)

Bases: QFrame

Menu container with expansion/reduction support.

This class provides a menu container with a toggle button to expand or reduce the menu width. The menu contains an upper section for menu items and a lower section for the toggle button.

Initialize the menu container.

Parameters

parent : QWidget, optional The parent widget (default: None). shrink_width : int, optional Width when menu is shrunk (default: 60). extended_width : int, optional Width when menu is extended (default: 240).

Source code in src/ezqt_app/widgets/core/menu.py
def __init__(
    self,
    parent: QWidget | None = None,
    shrink_width: int = 60,
    extended_width: int = 240,
) -> None:
    """
    Initialize the menu container.

    Parameters
    ----------
    parent : QWidget, optional
        The parent widget (default: None).
    shrink_width : int, optional
        Width when menu is shrunk (default: 60).
    extended_width : int, optional
        Width when menu is extended (default: 240).
    """
    super().__init__(parent)

    # ////// STORE CONFIGURATION
    self._shrink_width = shrink_width
    self._extended_width = extended_width
    self.menus: dict[str, Any] = {}
    self._buttons: list[Any] = []
    self._icons: list[Any | None] = []

    # ////// SETUP WIDGET PROPERTIES
    self.setObjectName("menu_container")
    self.setMinimumSize(QSize(self._shrink_width, 0))
    self.setMaximumSize(QSize(self._shrink_width, 16777215))
    self.setFrameShape(QFrame.Shape.NoFrame)
    self.setFrameShadow(QFrame.Shadow.Raised)

    # ////// SETUP MAIN LAYOUT
    self._menu_layout = QVBoxLayout(self)
    self._menu_layout.setSpacing(0)
    self._menu_layout.setObjectName("menu_layout")
    self._menu_layout.setContentsMargins(0, 0, 0, 0)

    # ////// SETUP MAIN MENU FRAME
    self._main_menu_frame = QFrame(self)
    self._main_menu_frame.setObjectName("main_menu_frame")
    self._main_menu_frame.setFrameShape(QFrame.Shape.NoFrame)
    self._main_menu_frame.setFrameShadow(QFrame.Shadow.Raised)
    self._menu_layout.addWidget(self._main_menu_frame)

    # ////// SETUP MAIN MENU LAYOUT
    self._main_menu_layout = QVBoxLayout(self._main_menu_frame)
    self._main_menu_layout.setSpacing(0)
    self._main_menu_layout.setObjectName("main_menu_layout")
    self._main_menu_layout.setContentsMargins(0, 0, 0, 0)

    # ////// SETUP TOGGLE CONTAINER
    self._toggle_container = QFrame(self._main_menu_frame)
    self._toggle_container.setObjectName("toggle_container")
    self._toggle_container.setMaximumSize(QSize(16777215, 45))
    self._toggle_container.setFrameShape(QFrame.Shape.NoFrame)
    self._toggle_container.setFrameShadow(QFrame.Shadow.Raised)
    self._main_menu_layout.addWidget(self._toggle_container)

    # ////// SETUP TOGGLE LAYOUT
    self._toggle_layout = QVBoxLayout(self._toggle_container)
    self._toggle_layout.setSpacing(0)
    self._toggle_layout.setObjectName("toggle_layout")
    self._toggle_layout.setContentsMargins(0, 0, 0, 0)

    # ////// SETUP TOGGLE BUTTON
    # Lazy import to avoid circular imports
    from ezqt_widgets import ThemeIcon

    from ...widgets.extended.menu_button import MenuButton

    settings_service = get_settings_service()

    # Store the toggle button label so retranslate_ui() can re-apply it.
    self._toggle_text: str = "Hide"

    self.toggle_button = MenuButton(
        parent=self._toggle_container,
        icon=Icons.icon_menu,
        text=self._toggle_text,
        shrink_size=self._shrink_width,
        spacing=15,
        duration=settings_service.gui.TIME_ANIMATION,
    )
    self.toggle_button.setObjectName("toggle_button")
    _sp = SizePolicy.H_EXPANDING_V_FIXED
    if _sp is not None:
        _sp.setHeightForWidth(self.toggle_button.sizePolicy().hasHeightForWidth())
        self.toggle_button.setSizePolicy(_sp)
    self.toggle_button.setMinimumSize(QSize(0, 45))
    if Fonts.SEGOE_UI_10_REG is not None:
        self.toggle_button.setFont(Fonts.SEGOE_UI_10_REG)
    self.toggle_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
    self.toggle_button.setLayoutDirection(Qt.LayoutDirection.LeftToRight)

    icon_menu = ThemeIcon(Icons.icon_menu, theme=settings_service.gui.THEME)
    self._buttons.append(self.toggle_button)
    self._icons.append(icon_menu)
    # Connect to the toggle_state method
    self.toggle_button.clicked.connect(self.toggle_button.toggle_state)

    self._toggle_layout.addWidget(self.toggle_button)

    # ////// SETUP TOP MENU
    self.top_menu = QFrame(self._main_menu_frame)
    self.top_menu.setObjectName("top_menu")
    self.top_menu.setFrameShape(QFrame.Shape.NoFrame)
    self.top_menu.setFrameShadow(QFrame.Shadow.Raised)
    self._main_menu_layout.addWidget(self.top_menu, 0, Qt.AlignmentFlag.AlignTop)

    # ////// SETUP TOP MENU LAYOUT
    self._top_menu_layout = QVBoxLayout(self.top_menu)
    self._top_menu_layout.setSpacing(0)
    self._top_menu_layout.setObjectName("top_menu_layout")
    self._top_menu_layout.setContentsMargins(0, 0, 0, 0)

    # ////// SYNC INITIAL STATE
    self._sync_initial_state()

retranslate_ui

retranslate_ui() -> None

Apply current translations to all owned text labels and tooltips.

Source code in src/ezqt_app/widgets/core/menu.py
def retranslate_ui(self) -> None:
    """Apply current translations to all owned text labels and tooltips."""
    # The buttons are now autonomous and know how to retranslate themselves
    # as they store their original untranslated text.
    if hasattr(self.toggle_button, "retranslate_ui"):
        self.toggle_button.retranslate_ui()
        # Dynamic tooltip for the toggle button
        self.toggle_button.setToolTip(self._tr(self._toggle_text))

    for menu_btn in self.menus.values():
        if hasattr(menu_btn, "retranslate_ui"):
            menu_btn.retranslate_ui()
            # Dynamic tooltip: very useful when the menu is shrunk
            # menu_btn._original_text contains the key (e.g. "Home")
            original_text = getattr(menu_btn, "_original_text", "")
            if original_text:
                menu_btn.setToolTip(self._tr(original_text))

changeEvent

changeEvent(event: QEvent) -> None

Handle Qt change events, triggering UI retranslation on language change.

Source code in src/ezqt_app/widgets/core/menu.py
def changeEvent(self, event: QEvent) -> None:
    """Handle Qt change events, triggering UI retranslation on language change."""
    if event.type() == QEvent.Type.LanguageChange:
        self.retranslate_ui()
    super().changeEvent(event)

add_menu

add_menu(name: str, icon: QIcon | str | None = None)

Add a menu item to the container.

Source code in src/ezqt_app/widgets/core/menu.py
def add_menu(self, name: str, icon: QIcon | str | None = None):
    """Add a menu item to the container."""
    # Lazy import to avoid circular imports
    from ezqt_widgets import ThemeIcon

    from ...widgets.extended.menu_button import MenuButton

    current_theme = get_settings_service().gui.THEME

    menu = MenuButton(
        parent=self.top_menu,
        icon=icon,
        text=name,
        shrink_size=self._shrink_width,
        spacing=15,
        duration=get_settings_service().gui.TIME_ANIMATION,
    )
    menu.setObjectName(f"menu_{name}")
    menu.setProperty("class", "inactive")
    _sp = SizePolicy.H_EXPANDING_V_FIXED
    if _sp is not None:
        _sp.setHeightForWidth(menu.sizePolicy().hasHeightForWidth())
        menu.setSizePolicy(_sp)
    menu.setMinimumSize(QSize(0, 45))
    if Fonts.SEGOE_UI_10_REG is not None:
        menu.setFont(Fonts.SEGOE_UI_10_REG)
    menu.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
    menu.setLayoutDirection(Qt.LayoutDirection.LeftToRight)

    # ////// SETUP THEME ICON
    theme_icon = ThemeIcon(icon, theme=current_theme) if icon is not None else None
    self._buttons.append(menu)
    self._icons.append(theme_icon)

    # Connect to the toggle button to sync state
    self.toggle_button.stateChanged.connect(menu.set_state)

    self._top_menu_layout.addWidget(menu)
    self.menus[name] = menu

    return menu

update_all_theme_icons

update_all_theme_icons() -> None

Update theme icons for all buttons.

Source code in src/ezqt_app/widgets/core/menu.py
def update_all_theme_icons(self) -> None:
    """Update theme icons for all buttons."""
    current_theme = get_settings_service().gui.THEME
    for i, btn in enumerate(self._buttons):
        icon = self._icons[i]
        setter = getattr(icon, "setTheme", None)
        if callable(setter):
            setter(current_theme)
        updater = getattr(btn, "update_theme_icon", None)
        if icon is not None and callable(updater):
            updater(icon)

sync_all_menu_states

sync_all_menu_states(extended: bool) -> None

Sync all menu buttons to the given state.

Source code in src/ezqt_app/widgets/core/menu.py
def sync_all_menu_states(self, extended: bool) -> None:
    """Sync all menu buttons to the given state."""
    for btn in self._buttons:
        setter = getattr(btn, "set_state", None)
        if btn != self.toggle_button and callable(setter):
            setter(extended)

get_menu_state

get_menu_state() -> bool

Get the current menu state.

Source code in src/ezqt_app/widgets/core/menu.py
def get_menu_state(self) -> bool:
    """Get the current menu state."""
    if hasattr(self, "toggle_button"):
        return self.toggle_button.is_extended
    return False

get_shrink_width

get_shrink_width() -> int

Get the configured shrink width.

Source code in src/ezqt_app/widgets/core/menu.py
def get_shrink_width(self) -> int:
    """Get the configured shrink width."""
    return self._shrink_width

get_extended_width

get_extended_width() -> int

Get the configured extended width.

Source code in src/ezqt_app/widgets/core/menu.py
def get_extended_width(self) -> int:
    """Get the configured extended width."""
    return self._extended_width

PageContainer

PageContainer(parent: QWidget | None = None)

Bases: QFrame

Page container with stacked widget management.

This class provides a container to manage multiple pages within a central receptacle using a QStackedWidget.

Initialize the page container.

Parameters

parent : QWidget, optional The parent widget (default: None).

Source code in src/ezqt_app/widgets/core/page_container.py
def __init__(self, parent: QWidget | None = None) -> None:
    """
    Initialize the page container.

    Parameters
    ----------
    parent : QWidget, optional
        The parent widget (default: None).
    """
    super().__init__(parent)
    self.pages: dict[str, QWidget] = {}

    # ////// SETUP WIDGET PROPERTIES
    self.setObjectName("pages_container")
    self.setFrameShape(QFrame.Shape.NoFrame)
    self.setFrameShadow(QFrame.Shadow.Raised)

    # ////// SETUP MAIN LAYOUT
    self._layout = QVBoxLayout(self)
    self._layout.setSpacing(0)
    self._layout.setObjectName("pages_container_layout")
    self._layout.setContentsMargins(10, 10, 10, 10)

    # ////// SETUP STACKED WIDGET
    self._stacked_widget = QStackedWidget(self)
    self._stacked_widget.setObjectName("pages_stacked_widget")
    self._layout.addWidget(self._stacked_widget)

add_page

add_page(name: str) -> QWidget

Add a new page to the container.

Parameters

name : str The name of the page to add.

Returns

QWidget The created page widget.

Source code in src/ezqt_app/widgets/core/page_container.py
def add_page(self, name: str) -> QWidget:
    """
    Add a new page to the container.

    Parameters
    ----------
    name : str
        The name of the page to add.

    Returns
    -------
    QWidget
        The created page widget.
    """
    page = QWidget()
    page.setObjectName(f"page_{name}")

    self._stacked_widget.addWidget(page)
    self.pages[name] = page

    return page

set_current_widget

set_current_widget(widget: QWidget) -> None

Set the current visible page.

Parameters

widget : QWidget The page widget to display.

Source code in src/ezqt_app/widgets/core/page_container.py
def set_current_widget(self, widget: QWidget) -> None:
    """
    Set the current visible page.

    Parameters
    ----------
    widget : QWidget
        The page widget to display.
    """
    self._stacked_widget.setCurrentWidget(widget)

get_stacked_widget

get_stacked_widget() -> QStackedWidget

Access the internal stacked widget.

Note: Use set_current_widget for standard navigation.

Source code in src/ezqt_app/widgets/core/page_container.py
def get_stacked_widget(self) -> QStackedWidget:
    """
    Access the internal stacked widget.

    Note: Use set_current_widget for standard navigation.
    """
    return self._stacked_widget

SettingsPanel

SettingsPanel(parent: QWidget | None = None, width: int = 240, load_from_yaml: bool = True)

Bases: QFrame

This class is used to create a settings panel. It contains a top border, a content settings frame and a theme settings container. The settings panel is used to display the settings.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def __init__(
    self,
    parent: QWidget | None = None,
    width: int = 240,
    load_from_yaml: bool = True,
) -> None:
    super().__init__(parent)
    self._widgets: list[QWidget] = []
    self._settings: dict[str, Any] = {}

    # Store configuration
    self._width = width

    self.setObjectName("settings_panel")
    self.setMinimumSize(QSize(0, 0))
    self.setMaximumSize(QSize(0, 16777215))
    self.setFrameShape(QFrame.Shape.NoFrame)
    self.setFrameShadow(QFrame.Shadow.Raised)

    # ////// SETUP MAIN LAYOUT
    self._layout = QVBoxLayout(self)
    self._layout.setSpacing(0)
    self._layout.setObjectName("settings_layout")
    self._layout.setContentsMargins(0, 0, 0, 0)

    # ////// SETUP TOP BORDER
    self._top_border = QFrame(self)
    self._top_border.setObjectName("settings_top_border")
    self._top_border.setMaximumSize(QSize(16777215, 3))
    self._top_border.setFrameShape(QFrame.Shape.NoFrame)
    self._top_border.setFrameShadow(QFrame.Shadow.Raised)
    self._layout.addWidget(self._top_border)

    # ////// SETUP SCROLL AREA
    self._scroll_area = QScrollArea(self)
    self._scroll_area.setObjectName("settings_scroll_area")
    self._scroll_area.setWidgetResizable(True)
    self._scroll_area.setHorizontalScrollBarPolicy(
        Qt.ScrollBarPolicy.ScrollBarAlwaysOff
    )
    self._scroll_area.setVerticalScrollBarPolicy(
        Qt.ScrollBarPolicy.ScrollBarAsNeeded
    )
    self._scroll_area.setFrameShape(QFrame.Shape.NoFrame)
    self._scroll_area.setFrameShadow(QFrame.Shadow.Raised)
    self._layout.addWidget(self._scroll_area)

    # ////// SETUP CONTENT WIDGET
    self._content_widget = QFrame()
    self._content_widget.setObjectName("content_widget")
    self._content_widget.setFrameShape(QFrame.Shape.NoFrame)
    self._content_widget.setFrameShadow(QFrame.Shadow.Raised)
    self._content_widget.setSizePolicy(
        QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
    )
    self._scroll_area.setWidget(self._content_widget)

    self._content_layout = QVBoxLayout(self._content_widget)
    self._content_layout.setObjectName("content_layout")
    self._content_layout.setSpacing(0)
    self._content_layout.setContentsMargins(0, 0, 0, 0)
    self._content_layout.setAlignment(Qt.AlignmentFlag.AlignTop)

    # ////// SETUP THEME SECTION
    self._theme_section_frame = QFrame(self._content_widget)
    self._theme_section_frame.setObjectName("theme_section_frame")
    self._theme_section_frame.setFrameShape(QFrame.Shape.NoFrame)
    self._theme_section_frame.setFrameShadow(QFrame.Shadow.Raised)
    self._content_layout.addWidget(
        self._theme_section_frame, 0, Qt.AlignmentFlag.AlignTop
    )

    self._theme_section_layout = QVBoxLayout(self._theme_section_frame)
    self._theme_section_layout.setSpacing(8)
    self._theme_section_layout.setObjectName("theme_section_layout")
    self._theme_section_layout.setContentsMargins(10, 10, 10, 10)

    # Build theme options from palette (display label → internal value mapping).
    from ...services.ui.theme_service import ThemeService

    _theme_options_data = ThemeService.get_available_themes()
    self._theme_options_map: dict[str, str] = dict(_theme_options_data)
    self._theme_value_to_display: dict[str, str] = {
        v: d for d, v in _theme_options_data
    }

    _gui = get_settings_service().gui
    _current_internal = f"{_gui.THEME_PRESET}:{_gui.THEME}"
    _current_display = self._theme_value_to_display.get(
        _current_internal,
        _theme_options_data[0][0] if _theme_options_data else "",
    )

    from ...widgets.extended.setting_widgets import SettingSelect

    self._theme_selector = SettingSelect(
        label="Active Theme",
        description="",
        options=[d for d, _ in _theme_options_data],
        default=_current_display,
    )
    self._theme_selector.setObjectName("theme_selector")
    self._theme_selector.setSizePolicy(
        QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
    )
    self._widgets.append(self._theme_selector)
    self._theme_selector.valueChanged.connect(
        lambda _key, display_val: self._on_theme_selector_changed(display_val)
    )
    self._theme_section_layout.addWidget(self._theme_selector)

    if load_from_yaml:
        self.load_settings_from_yaml()

    self.settingChanged.connect(self._on_setting_changed)

load_settings_from_yaml

load_settings_from_yaml() -> None

Load settings from YAML file.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def load_settings_from_yaml(self) -> None:
    """Load settings from YAML file."""
    try:
        from pathlib import Path

        import yaml

        possible_paths = [
            Path.cwd() / "bin" / "config" / "app.config.yaml",
            Path(__file__).parent.parent.parent
            / "resources"
            / "config"
            / "app.config.yaml",
        ]

        app_config = None
        for path in possible_paths:
            if path.exists():
                with open(path, encoding="utf-8") as f:
                    app_config = yaml.safe_load(f)
                break

        if app_config is None:
            warn_tech(
                code="widgets.settings_panel.app_config_yaml_not_found",
                message="Could not find app.config.yaml file",
            )
            return

        settings_config = app_config.get("settings_panel", {})

        for key, config in settings_config.items():
            if key == "theme":
                continue

            if config.get("enabled", True):
                widget = self.add_setting_from_config(key, config)
                default_value = config.get("default")
                if default_value is not None and hasattr(widget, "set_value"):
                    widget.set_value(default_value)  # type: ignore[union-attr]

    except Exception as e:
        warn_tech(
            code="widgets.settings_panel.load_yaml_failed",
            message="Error loading settings from YAML",
            error=e,
        )

add_setting_from_config

add_setting_from_config(key: str, config: dict) -> QWidget

Add a setting based on its YAML configuration.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_setting_from_config(self, key: str, config: dict) -> QWidget:
    """Add a setting based on its YAML configuration."""
    setting_type = config.get("type", "text")
    label = config.get("label", key)
    description = config.get("description", "")
    default_value = config.get("default")

    setting_container = QFrame(self._content_widget)
    setting_container.setObjectName(f"setting_container_{key}")
    setting_container.setFrameShape(QFrame.Shape.NoFrame)
    setting_container.setFrameShadow(QFrame.Shadow.Raised)

    container_layout = QVBoxLayout(setting_container)
    container_layout.setSpacing(8)
    container_layout.setObjectName(f"setting_container_layout_{key}")
    container_layout.setContentsMargins(10, 10, 10, 10)

    if setting_type == "toggle":
        widget = self._create_toggle_widget(
            label,
            description,
            bool(default_value) if default_value is not None else False,
            key,
        )
    elif setting_type == "select":
        options = config.get("options", [])
        widget = self._create_select_widget(
            label,
            description,
            options,
            str(default_value) if default_value is not None else "",
            key,
        )
    elif setting_type == "slider":
        min_val = config.get("min", 0)
        max_val = config.get("max", 100)
        unit = config.get("unit", "")
        widget = self._create_slider_widget(
            label,
            description,
            min_val,
            max_val,
            int(default_value) if default_value is not None else 0,
            unit,
            key,
        )
    elif setting_type == "checkbox":
        widget = self._create_checkbox_widget(
            label,
            description,
            bool(default_value) if default_value is not None else False,
            key,
        )
    else:
        widget = self._create_text_widget(
            label,
            description,
            str(default_value) if default_value is not None else "",
            key,
        )

    container_layout.addWidget(widget)
    self._content_layout.addWidget(setting_container)
    self._settings[key] = widget

    return widget

add_toggle_setting

add_toggle_setting(key: str, label: str, default: bool = False, description: str = '', enabled: bool = True)

Add a toggle setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_toggle_setting(
    self,
    key: str,
    label: str,
    default: bool = False,
    description: str = "",
    enabled: bool = True,  # noqa: ARG002
):
    """Add a toggle setting."""
    from ...widgets.extended.setting_widgets import SettingToggle

    widget = SettingToggle(label, description, default)
    widget.set_key(key)
    widget.valueChanged.connect(self._on_setting_changed)

    self._settings[key] = widget
    self.add_setting_widget(widget)
    return widget

add_select_setting

add_select_setting(key: str, label: str, options: list[str], default: str | None = None, description: str = '', enabled: bool = True)

Add a selection setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_select_setting(
    self,
    key: str,
    label: str,
    options: list[str],
    default: str | None = None,
    description: str = "",
    enabled: bool = True,  # noqa: ARG002
):
    """Add a selection setting."""
    from ...widgets.extended.setting_widgets import SettingSelect

    widget = SettingSelect(label, description, options, default)
    widget.set_key(key)
    widget.valueChanged.connect(self._on_setting_changed)

    self._settings[key] = widget
    self.add_setting_widget(widget)
    return widget

add_slider_setting

add_slider_setting(key: str, label: str, min_val: int, max_val: int, default: int, unit: str = '', description: str = '', enabled: bool = True)

Add a slider setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_slider_setting(
    self,
    key: str,
    label: str,
    min_val: int,
    max_val: int,
    default: int,
    unit: str = "",
    description: str = "",
    enabled: bool = True,  # noqa: ARG002
):
    """Add a slider setting."""
    from ...widgets.extended.setting_widgets import SettingSlider

    widget = SettingSlider(label, description, min_val, max_val, default, unit)
    widget.set_key(key)
    widget.valueChanged.connect(self._on_setting_changed)

    self._settings[key] = widget
    self.add_setting_widget(widget)
    return widget

add_text_setting

add_text_setting(key: str, label: str, default: str = '', description: str = '', enabled: bool = True)

Add a text setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_text_setting(
    self,
    key: str,
    label: str,
    default: str = "",
    description: str = "",
    enabled: bool = True,  # noqa: ARG002
):
    """Add a text setting."""
    from ...widgets.extended.setting_widgets import SettingText

    widget = SettingText(label, description, default)
    widget.set_key(key)
    widget.valueChanged.connect(self._on_setting_changed)

    self._settings[key] = widget
    self.add_setting_widget(widget)
    return widget

add_checkbox_setting

add_checkbox_setting(key: str, label: str, default: bool = False, description: str = '', enabled: bool = True)

Add a checkbox setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_checkbox_setting(
    self,
    key: str,
    label: str,
    default: bool = False,
    description: str = "",
    enabled: bool = True,  # noqa: ARG002
):
    """Add a checkbox setting."""
    from ...widgets.extended.setting_widgets import SettingCheckbox

    widget = SettingCheckbox(label, description, default)
    widget.set_key(key)
    widget.valueChanged.connect(self._on_setting_changed)

    self._settings[key] = widget
    self.add_setting_widget(widget)
    return widget

get_setting_value

get_setting_value(key: str) -> Any

Get the value of a setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def get_setting_value(self, key: str) -> Any:
    """Get the value of a setting."""
    if key in self._settings:
        return self._settings[key].get_value()
    return None

set_setting_value

set_setting_value(key: str, value: Any) -> None

Set the value of a setting.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def set_setting_value(self, key: str, value: Any) -> None:
    """Set the value of a setting."""
    if key in self._settings:
        self._settings[key].set_value(value)

get_all_settings

get_all_settings() -> dict[str, Any]

Get all settings and their values.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def get_all_settings(self) -> dict[str, Any]:
    """Get all settings and their values."""
    return {key: widget.get_value() for key, widget in self._settings.items()}

save_all_settings_to_yaml

save_all_settings_to_yaml() -> None

Stage all current setting values.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def save_all_settings_to_yaml(self) -> None:
    """Stage all current setting values."""
    from ...services.application.app_service import AppService

    for key, widget in self._settings.items():
        try:
            AppService.stage_config_value(
                [*self._settings_storage_prefix(), key, "default"],
                widget.get_value(),
            )
        except Exception as e:
            warn_tech(
                code="widgets.settings_panel.save_all_settings_failed",
                message=f"Could not save setting '{key}' to YAML",
                error=e,
            )

retranslate_ui

retranslate_ui() -> None

Apply current translations to all owned text labels.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def retranslate_ui(self) -> None:
    """Apply current translations to all owned text labels."""
    if hasattr(self, "_theme_selector") and hasattr(
        self._theme_selector, "retranslate_ui"
    ):
        self._theme_selector.retranslate_ui()
    for widget in self._settings.values():
        if hasattr(widget, "retranslate_ui"):
            widget.retranslate_ui()

changeEvent

changeEvent(event: QEvent) -> None

Handle Qt change events.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def changeEvent(self, event: QEvent) -> None:
    """Handle Qt change events."""
    if event.type() == QEvent.Type.LanguageChange:
        self.retranslate_ui()
    super().changeEvent(event)

get_width

get_width() -> int

Get panel width.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def get_width(self) -> int:
    """Get panel width."""
    return self._width

set_width

set_width(width: int) -> None

Set panel width.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def set_width(self, width: int) -> None:
    """Set panel width."""
    self._width = width

get_theme_selector

get_theme_selector()

Get the theme selector if available.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def get_theme_selector(self):
    """Get the theme selector if available."""
    if hasattr(self, "_theme_selector"):
        return self._theme_selector
    return None

update_all_theme_icons

update_all_theme_icons() -> None

Update theme icons for all widgets that support it.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def update_all_theme_icons(self) -> None:
    """Update theme icons for all widgets that support it."""
    current_theme = get_settings_service().gui.THEME
    for widget in self._widgets:
        # New pattern: widgets exposing setTheme(theme) directly
        setter = getattr(widget, "setTheme", None)
        if callable(setter):
            setter(current_theme)
            continue
        # Legacy pattern: widgets exposing update_theme_icon()
        updater = getattr(widget, "update_theme_icon", None)
        if callable(updater):
            updater()

    self.style().unpolish(self)
    self.style().polish(self)

    for child in self.findChildren(QWidget):
        child.style().unpolish(child)
        child.style().polish(child)

add_setting_widget

add_setting_widget(widget: QWidget) -> None

Add a new setting widget to the settings panel.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_setting_widget(self, widget: QWidget) -> None:
    """Add a new setting widget to the settings panel."""
    setting_container = QFrame(self._content_widget)
    setting_container.setObjectName(f"setting_container_{widget.objectName()}")
    setting_container.setFrameShape(QFrame.Shape.NoFrame)
    setting_container.setFrameShadow(QFrame.Shadow.Raised)

    container_layout = QVBoxLayout(setting_container)
    container_layout.setSpacing(8)
    container_layout.setObjectName(
        f"setting_container_layout_{widget.objectName()}"
    )
    container_layout.setContentsMargins(10, 10, 10, 10)

    container_layout.addWidget(widget)
    self._content_layout.addWidget(setting_container)
    self._widgets.append(widget)

add_setting_section

add_setting_section(title: str = '') -> QFrame

Add a new settings section with optional title.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def add_setting_section(self, title: str = "") -> QFrame:
    """Add a new settings section with optional title."""
    section = QFrame(self._content_widget)
    section.setObjectName(f"settings_section_{title.replace(' ', '_').lower()}")
    section.setFrameShape(QFrame.Shape.NoFrame)
    section.setFrameShadow(QFrame.Shadow.Raised)

    section_layout = QVBoxLayout(section)
    section_layout.setSpacing(8)
    section_layout.setObjectName(
        f"settings_section_layout_{title.replace(' ', '_').lower()}"
    )
    section_layout.setContentsMargins(10, 10, 10, 10)

    if title:
        title_label = QLabel(title, section)
        title_label.setObjectName(
            f"settings_section_title_{title.replace(' ', '_').lower()}"
        )
        if Fonts.SEGOE_UI_10_REG is not None:
            title_label.setFont(Fonts.SEGOE_UI_10_REG)
        title_label.setAlignment(
            Qt.AlignmentFlag.AlignLeading
            | Qt.AlignmentFlag.AlignLeft
            | Qt.AlignmentFlag.AlignVCenter
        )
        section_layout.addWidget(title_label)

    self._content_layout.addWidget(section)
    return section

scroll_to_top

scroll_to_top() -> None

Scroll to top of settings panel.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def scroll_to_top(self) -> None:
    """Scroll to top of settings panel."""
    if hasattr(self, "_scroll_area"):
        self._scroll_area.verticalScrollBar().setValue(0)

scroll_to_bottom

scroll_to_bottom() -> None

Scroll to bottom of settings panel.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def scroll_to_bottom(self) -> None:
    """Scroll to bottom of settings panel."""
    if hasattr(self, "_scroll_area"):
        scrollbar = self._scroll_area.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())

scroll_to_widget

scroll_to_widget(widget: QWidget) -> None

Scroll to a specific widget in the settings panel.

Source code in src/ezqt_app/widgets/core/settings_panel.py
def scroll_to_widget(self, widget: QWidget) -> None:
    """Scroll to a specific widget in the settings panel."""
    if hasattr(self, "_scroll_area") and widget:
        widget_pos = widget.mapTo(self._content_widget, widget.rect().topLeft())
        self._scroll_area.verticalScrollBar().setValue(widget_pos.y())

Extended Widgets

setting_widgets

Setting widget components: toggle, select, slider, text, checkbox.

BaseSettingWidget

BaseSettingWidget(label: str, description: str = '')

Bases: QWidget

Base class for all setting widgets.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def __init__(self, label: str, description: str = ""):
    super().__init__()
    self._label_text = label
    self._description_text = description
    self._key = None
    self.setObjectName("base_setting_widget")

set_key

set_key(key: str)

Set the setting key.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def set_key(self, key: str):
    """Set the setting key."""
    self._key = key

retranslate_ui

retranslate_ui()

To be implemented by subclasses.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def retranslate_ui(self):
    """To be implemented by subclasses."""

SettingToggle

SettingToggle(label: str, description: str = '', default: bool = False)

Bases: BaseSettingWidget

Widget for toggle settings (on/off).

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def __init__(self, label: str, description: str = "", default: bool = False):
    super().__init__(label, description)
    self._value = default
    self.setObjectName("setting_toggle_container")
    self.setProperty("type", "setting_toggle")
    self._setup_ui()

retranslate_ui

retranslate_ui()

Update strings after language change.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def retranslate_ui(self):
    """Update strings after language change."""
    self._label_widget.setText(self._tr(self._label_text))
    if self._description_label:
        self._description_label.setText(self._tr(self._description_text))

SettingSelect

SettingSelect(label: str, description: str = '', options: list | None = None, default: str | None = None)

Bases: BaseSettingWidget

Widget for selection settings.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def __init__(
    self,
    label: str,
    description: str = "",
    options: list | None = None,
    default: str | None = None,
):
    super().__init__(label, description)
    self._options = options or []
    self._value = default or (options[0] if options else "")
    self.setObjectName("setting_select_container")
    self.setProperty("type", "setting_select")
    self._setup_ui()

SettingSlider

SettingSlider(label: str, description: str = '', min_val: int = 0, max_val: int = 100, default: int = 50, unit: str = '')

Bases: BaseSettingWidget

Widget for numeric settings with slider.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def __init__(
    self,
    label: str,
    description: str = "",
    min_val: int = 0,
    max_val: int = 100,
    default: int = 50,
    unit: str = "",
):
    super().__init__(label, description)
    self._min_val = min_val
    self._max_val = max_val
    self._value = default
    self._unit = unit
    self.setObjectName("setting_slider_container")
    self.setProperty("type", "setting_slider")
    self._setup_ui()

SettingText

SettingText(label: str, description: str = '', default: str = '')

Bases: BaseSettingWidget

Widget for text settings.

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def __init__(self, label: str, description: str = "", default: str = ""):
    super().__init__(label, description)
    self._value = default
    self.setObjectName("setting_text_container")
    self.setProperty("type", "setting_text")
    self._setup_ui()

SettingCheckbox

SettingCheckbox(label: str, description: str = '', default: bool = False)

Bases: BaseSettingWidget

Widget for checkbox settings (on/off).

Source code in src/ezqt_app/widgets/extended/setting_widgets.py
def __init__(self, label: str, description: str = "", default: bool = False):
    super().__init__(label, description)
    self._value = default
    self.setObjectName("setting_checkbox_container")
    self.setProperty("type", "setting_checkbox")
    self._setup_ui()