Button Panel Component

Reusable button panel with declarative configuration.

Module: pyqt_reactive.widgets.shared.button_panel

Overview

ButtonPanel provides a reusable button panel component that can be used by any widget without requiring inheritance. It uses a declarative BUTTON_CONFIGS format for specifying buttons.

This component was extracted from AbstractManagerWidget to allow widgets to use the same button panel pattern without inheriting from the full manager class.

Architecture

ButtonPanel uses a simple declarative configuration:

BUTTON_CONFIGS = [
    ("Refresh", "refresh", "Refresh the display"),
    ("Toggle", "toggle_layout", "Toggle between layouts"),
    ("Export", "export", "Export data"),
]

Each button configuration is a tuple of: - label: Button text (e.g., “Refresh”) - action_id: Identifier passed to callback (e.g., “refresh”) - tooltip: Tooltip text (e.g., “Refresh the display”)

Usage

Basic Usage

from pyqt_reactive.widgets.shared.button_panel import ButtonPanel
from pyqt_reactive.theming import StyleSheetGenerator

# Define button configurations
BUTTON_CONFIGS = [
    ("Refresh", "refresh", "Refresh the display"),
    ("Toggle", "toggle_layout", "Toggle between layouts"),
    ("Export", "export", "Export data"),
]

# Create button panel
panel = ButtonPanel(
    button_configs=BUTTON_CONFIGS,
    on_action=self.handle_button_action,
    style_generator=self.style_generator,
)

# Add panel to layout
layout.addWidget(panel)

Action Handler

The on_action callback receives the action_id from the clicked button:

def handle_button_action(self, action_id: str):
    """Handle button actions."""
    if action_id == "refresh":
        self.refresh_display()
    elif action_id == "toggle_layout":
        self.toggle_layout()
    elif action_id == "export":
        self.export_data()

Grid Layout

By default, buttons are laid out in a single horizontal row. You can specify a grid layout:

panel = ButtonPanel(
    button_configs=self.BUTTON_CONFIGS,
    on_action=self.handle_button_action,
    grid_columns=2,  # 2 columns
)

This creates a grid with the specified number of columns:

grid_columns

Layout

0

Single horizontal row

1

Single vertical column

2

2x2 grid

3

3x2 grid

4

4x2 grid

Integration with SystemMonitor

SystemMonitor uses ButtonPanel for its action buttons:

class SystemMonitorWidget(QWidget):
    """System Monitor Widget."""

    # Declarative button configuration
    BUTTON_CONFIGS = [
        ("Global Config", "global_config", "Open global configuration editor"),
        ("Log Viewer", "log_viewer", "Open log viewer window"),
        ("Custom Functions", "custom_functions", "Manage custom functions"),
        ("Test Plate", "test_plate", "Generate synthetic test plate"),
    ]

    BUTTON_GRID_COLUMNS = 0  # Single row

    def __init__(self, color_scheme=None, config=None, parent=None):
        super().__init__(parent)

        # Create button panel
        self.button_panel = ButtonPanel(
            button_configs=self.BUTTON_CONFIGS,
            style_generator=self.style_generator,
            grid_columns=self.BUTTON_GRID_COLUMNS,
        )

        # Connect actions
        self.button_panel.action_triggered.connect(self.handle_button_action)

    def handle_button_action(self, action_id: str):
        """Handle button panel actions."""
        if action_id == "global_config":
            self.show_global_config.emit()
        elif action_id == "log_viewer":
            self.show_log_viewer.emit()
        # ... etc

Styling

ButtonPanel integrates with StyleSheetGenerator for consistent styling:

from pyqt_reactive.theming import StyleSheetGenerator, ColorScheme

color_scheme = ColorScheme()
style_generator = StyleSheetGenerator(color_scheme)

panel = ButtonPanel(
    button_configs=self.BUTTON_CONFIGS,
    on_action=self.handle_button_action,
    style_generator=style_generator,  # Apply styles
)

Signals

action_triggered (pyqtSignal)

Emitted when a button is clicked. Provides the action_id:

panel.action_triggered.connect(lambda action_id: print(f"Action: {action_id}"))

Migration from AbstractManagerWidget

Before (AbstractManagerWidget):

class MyWidget(AbstractManagerWidget):
    """Widget with button panel."""

    BUTTON_CONFIGS = [
        ("Refresh", "refresh", "Refresh the display"),
    ]

    def __init__(self):
        super().__init__()
        # Button panel created automatically by AbstractManagerWidget

After (ButtonPanel):

class MyWidget(QWidget):
    """Widget with button panel."""

    def __init__(self):
        super().__init__()

        # Create button panel manually
        self.button_panel = ButtonPanel(
            button_configs=[
                ("Refresh", "refresh", "Refresh the display"),
            ],
            on_action=self.handle_button_action,
        )

    def handle_button_action(self, action_id: str):
        if action_id == "refresh":
            self.refresh()

Benefits

  • No inheritance required: Use with any widget class

  • Declarative configuration: Define buttons in a list

  • Flexible layout: Single row or grid layout

  • Consistent styling: Integrates with StyleSheetGenerator

  • Action-based: Simple callback interface with action IDs

See Also