List Item Preview System
The list item preview system provides declarative, per-field styling for list items in manager widgets (PlateManager, PipelineEditor). It displays configuration previews with visual indicators for dirty state and signature differences.
Overview
List items in OpenHCS show rich previews of configuration state:
▶ MyPlate (W:8 | Seq:C,Z) ← Inline format
/path/to/plate
└─ wf:[3] | mat:ZARR | configs=[NAP, FIJI]
Visual semantics:
Underline = field differs from signature default (explicitly set)
Asterisk (*) = resolved value differs from saved (dirty/unsaved)
Name inherits styling if ANY field is dirty/sig-diff
Declarative Configuration
Each widget declares its format using ListItemFormat:
from openhcs.pyqt_gui.widgets.shared.abstract_manager_widget import ListItemFormat
class PlateManagerWidget(AbstractManagerWidget):
LIST_ITEM_FORMAT = ListItemFormat(
first_line=(), # Fields after name on line 1
preview_line=( # Fields on └─ preview line
'num_workers',
'vfs_config.materialization_backend',
'path_planning_config.well_filter',
),
detail_line_field='path', # Field for detail line
show_config_indicators=True, # Show NAP/FIJI/MAT configs
formatters={ # Custom formatters (optional)
'my_field': lambda v: f"custom:{v}" if v else None,
},
)
Field abbreviations are declared on config classes via @global_pipeline_config:
@global_pipeline_config(field_abbreviations={'well_filter': 'wf', 'output_dir_suffix': 'out'})
@dataclass(frozen=True)
class PathPlanningConfig:
well_filter: Optional[str] = None
output_dir_suffix: str = "_openhcs"
ListItemFormat Fields
Field |
Default |
Description |
|---|---|---|
|
|
Tuple of field paths shown after name on line 1 |
|
|
Tuple of field paths shown on └─ preview line |
|
|
Single field path for detail line (e.g., |
|
|
Whether to show config indicators (NAP, FIJI, MAT) |
|
|
Dict mapping field path to formatter function |
Visual Layout
Multiline Format
▶ ItemName (first_line_segments) ← Line 1: name + first_line fields
detail_line ← Line 2: e.g., path
└─ preview_segments | configs=[NAP, FIJI] ← Line 3: preview + configs
Inline Format
▶ ItemName (seg1 | seg2 | seg3) ← Single line with all segments
The format is determined by the StyledTextLayout.multiline flag, set automatically based on whether detail_line or preview_line are populated.
Field Types
Permanent vs Dynamic Fields
Type |
Source |
Behavior |
|---|---|---|
Declared |
|
Always shown (permanent) |
Config indicators |
|
Shown if config’s |
Sig-diff fields |
Auto-detected |
Dynamically added when value differs from signature default |
The auto-include logic (in _build_multiline_styled_text) automatically adds any field with a non-default value to the preview line, even if not explicitly declared.
Value Formatting
All field values are formatted by format_preview_value() in config_preview_formatters.py:
def format_preview_value(value: Any) -> Optional[str]:
if value is None:
return None
if isinstance(value, Enum):
return value.name if value.value else None
if isinstance(value, list):
if value and isinstance(value[0], Enum):
return ','.join(v.value for v in value)
return f'[{len(value)}]'
if callable(value) and not isinstance(value, type):
return getattr(value, '__name__', str(value))
return str(value)
Field abbreviations are declared on config classes via @global_pipeline_config(field_abbreviations=...):
@global_pipeline_config(field_abbreviations={'well_filter': 'wf', 'output_dir_suffix': 'out'})
@dataclass(frozen=True)
class PathPlanningConfig:
well_filter: Optional[str] = None
output_dir_suffix: str = "_openhcs"
Adding a New Preview Field
Add to ListItemFormat (in widget):
LIST_ITEM_FORMAT = ListItemFormat(
preview_line=(
'existing_field',
'my_new_config.some_field', # Add field path
),
formatters={
'some_field': lambda v: f"custom:{v}" if v else None, # Optional formatter
},
)
Add abbreviation (on config class):
@global_pipeline_config(field_abbreviations={'some_field': 'sf'})
@dataclass(frozen=True)
class MyNewConfig:
some_field: str = "default"
Adding a Config Indicator
Config indicators (NAP, FIJI, MAT) and field abbreviations are both declared via @global_pipeline_config:
from openhcs.config_framework import global_pipeline_config
@global_pipeline_config(
preview_label='NEW',
field_abbreviations={'some_setting': 'set'}
)
@dataclass
class MyNewConfig:
enabled: bool = False
some_setting: int = 10
The config indicator appears in configs=[...] when enabled=True.
Structured Rendering Architecture
The system uses structured StyledTextLayout objects instead of string parsing:
@dataclass(frozen=True)
class Segment:
"""A styled text segment with field path for dirty/sig-diff matching."""
text: str
field_path: Optional[str] = None # None=no styling, ''=root path
@dataclass
class StyledTextLayout:
"""Structured layout for styled text rendering."""
name: Segment
status_prefix: str = ""
first_line_segments: List[Segment] = field(default_factory=list)
detail_line: str = ""
preview_segments: List[Segment] = field(default_factory=list)
config_segments: List[Segment] = field(default_factory=list)
multiline: bool = False
The delegate iterates segments directly and applies styling per-segment without any string matching:
Build
StyledTextLayoutwith all segmentsStore layout in item data (
LAYOUT_ROLE)Delegate reads layout and renders segments with appropriate styling
No string parsing required
See Also
AbstractManagerWidget Architecture - AbstractManagerWidget base class
GUI Performance Patterns - Cross-window preview system
Configuration Framework - Config framework and lazy configs
Implementation References:
openhcs/pyqt_gui/widgets/shared/list_item_delegate.py- StyledTextLayout and renderingopenhcs/pyqt_gui/widgets/shared/abstract_manager_widget.py- ListItemFormat and build methodsopenhcs/pyqt_gui/widgets/config_preview_formatters.py- Centralized formatters