Location: src/pyqt_reactive/widgets/shared/clickable_help_components.py
What was implemented:
- Created
ProvenanceButtonclass that inherits fromProvenanceNavigationMixin - Button always visible but only clickable when provenance exists
- Uses
_has_provenance()to check if field inherits from ancestor - Initial visibility set at creation based on provenance
- Dynamic updates in
field_change_dispatcher.pywhen any field changes
Code:
class ProvenanceButton(QPushButton, ProvenanceNavigationMixin):
def _on_click(self):
if self._has_provenance():
# Navigate to source
def enterEvent(self, event):
if self._has_provenance():
# Show pointer cursorWiring:
- Created in
widget_creation_config.pyfor enableable groupboxes - Added to title layout via
addEnableableWidgets() - Updated in
field_change_dispatcher.pyafter state changes
Location: src/pyqt_reactive/utils/styling_utils.py (NEW)
What was implemented:
- Created
update_reset_button_styling()function - Applies
*prefix when field is dirty - Applies
_underline when field differs from signature default - Uses font underline (not CSS) to preserve button styling
Code:
def update_reset_button_styling(button, state, field_id, field_name):
has_star, has_underline = get_field_indicators(state, field_id, field_name)
text = "Reset"
if has_star:
text = "*" + text
button.setText(text)
# Apply underline via font (preserves other styles)
font = button.font()
font.setUnderline(has_underline)
button.setFont(font)Wiring:
- Applied at creation in
widget_creation_config.py - Updated in
field_change_dispatcher.pyfor ALL reset buttons when any field changes
Location: src/pyqt_reactive/services/field_change_dispatcher.py
What was added:
# Update ProvenanceButton visibility
if groupbox and hasattr(groupbox, 'title_layout'):
for widget in groupbox.title_layout.findChildren(ProvenanceButton):
widget.setVisible(widget._has_provenance())
break
# Update reset button styling for ALL reset buttons
from pyqt_reactive.utils.styling_utils import update_reset_button_styling
for field_name, reset_button in source.reset_buttons.items():
update_reset_button_styling(reset_button, source.state, source.field_id, field_name)The user reports that both the provenance button (^) and reset button styling (* and _) are NOT syncing properly when field values change.
- Reset All button: When using the "Reset All" button at the groupbox or window level, individual field reset buttons don't update their * and _ styling
- Field labels work correctly: When resetting, the parameter labels (clickable to provenance) DO clear their _ and * indicators correctly
- Reset buttons should behave THE SAME as labels: The user wants the reset button styling to follow the exact same wiring/signals as the label styling
- Label styling updates correctly via
_update_label_styling()which is called after reset - Reset button styling is updated in field_change_dispatcher, but apparently not working for all reset scenarios
- Provenance button visibility is updated in field_change_dispatcher, but not syncing
-
When any field value changes (individual reset, reset all, manual edit):
- Label styling updates (* and _)
- Reset button styling updates (* and _)
- Provenance button visibility updates (show/hide)
-
All three should use the SAME signal/wiring mechanism
- Field value changes (user edit or reset)
state.update_parameter()is calledFieldChangeDispatcher.dispatch()is triggered- Dispatcher updates:
- Provenance button visibility
- Reset button styling
- Sibling refreshes
- Separately, label styling is updated via
_update_label_styling()called fromreset_parameter()
The reset button styling and provenance button updates are happening in the dispatcher, but they may not be triggered in all scenarios (particularly Reset All). The label styling works because it's explicitly called in reset_parameter() after the dispatch.
Ensure reset button styling and provenance button updates are triggered by the SAME mechanism that updates label styling. This likely means:
- Hook into the same signal/event that triggers
_update_label_styling() - OR ensure the field_change_dispatcher runs for ALL parameter changes including batch resets
- OR wire the updates directly into the reset_parameter flow
_update_label_styling(param_name)in parameter_form_manager.py - updates label * and _dispatch(event)in field_change_dispatcher.py - currently updates provenance and reset buttonsreset_parameter(param_name)in parameter_form_manager.py - resets field and triggers dispatchreset_all_parameters()in parameter_form_manager.py - resets all fields
state.dirty_fields- fields with unsaved changes (for *)state.signature_diff_fields- fields differing from signature (for _)state.get_provenance(dotted_path)- returns provenance info for navigation
Labels are updated via:
event = FieldChangeEvent(param_name, reset_value, self, is_reset=True)
FieldChangeDispatcher.instance().dispatch(event)
# Then immediately after:
self._update_label_styling(param_name)The reset button and provenance button should follow this same pattern.
- Verify field_change_dispatcher is being called for ALL reset scenarios (single and batch)
- Move reset button and provenance updates to same location as label updates, or ensure dispatcher runs
- Test both scenarios:
- Individual field reset via reset button
- Batch reset via "Reset All" button
- Verify provenance button shows/hides correctly in both scenarios
- Verify reset button shows * and _ correctly in both scenarios