Two problems from smoke-testing SlicerOpenLIFU#642.
1. Fixed header + scrollable page + fixed footer isn't working
The whole host module currently scrolls when the current page's content exceeds the viewport. That means the header (Save / Exit) and the footer (timeline + Back-to-Home) scroll away with the page content -- which defeats the point of both surfaces being persistent app chrome.
Desired layout: header always visible at top, footer always visible at bottom, page in between takes whatever vertical space is left, page scrolls internally if its content exceeds that space.
Root cause: the hostMainLayout in OpenLIFU.ui places pageStack (a QStackedWidget) between the header and footer. QStackedWidget's size hint follows its current widget -- if a page's content is tall, the stack is tall, the whole host widget is tall, and whatever scroll area wraps the host module (Slicer's default module-panel scroll area, or the custom app's chrome) ends up scrolling the WHOLE thing.
Fix
Two-part fix in host_widget.py:
- In
setup(): force the outer host widget + the pageStack itself to have Expanding vertical size policy so they claim all available vertical space in their parent viewport rather than growing past it.
- In
_embed_page_widget_into_stack: wrap each embedded page's uiWidget in a QScrollArea (with setWidgetResizable(True) + NoFrame shape) before adding to the pageStack. Result: when the page content exceeds the pageStack's allotted height, the QScrollArea inside the pageStack scrolls the content, and the header/footer stay pinned outside.
No changes to the .ui file needed -- the size policies + wrappers are cleaner as Python calls where a comment can explain the pattern.
2. Next button greyed out on Overview
The Next / previous-step gating in _refresh_timeline_state and onNextClicked reads WorkflowControls.can_proceed. For pages that DO register workflow controls (legacy pages did this in their setup), that gate works as designed. For pages that DON'T register (like the new info-only Planning Session Overview, and Target Selection which doesn't register either), workflow.workflow_controls.get(key) returns None, and the current code treats that as "can NOT proceed" -- so the Next button is disabled and reachable-page calculation short-circuits at the first unregistered page.
Info-only pages should always allow Next; Target Selection also has no gating logic worth blocking Next for. The right default is: missing controls means "proceed freely".
Fix: in _refresh_timeline_state, treat missing controls as can_proceed = True:
can_proceed = controls is None or bool(controls.can_proceed)
Same change in onNextClicked (the guard before advancing).
Pages that WANT gating can register WorkflowControls (as legacy pages did) and gate through can_proceed. Nothing changes for them.
Non-goals
- No changes to
TimelineWidget paint logic or geometry.
- No changes to
WorkflowControls; only the host's default treatment of "no controls registered".
- No .ui file changes.
Acceptance
- Load a Planning Session → Overview. Header (Save / Exit) at top, footer (timeline strip + Back-to-Home) at bottom. Both visible always.
- Toggle Slicer's data probe / developer tools -- the available vertical space changes, the page slot shrinks / grows to match; header + footer never move.
- Navigate to Target Selection. Add several targets so the page content exceeds the viewport. Only the page area scrolls; header + footer stay pinned.
- Next: Target Selection on Overview is enabled and clicking it navigates to Target Selection.
- Target Selection has no next timeline page yet, so Next is disabled (correct).
Relates to SlicerOpenLIFU#642, SlicerOpenLIFU#641.
Two problems from smoke-testing SlicerOpenLIFU#642.
1. Fixed header + scrollable page + fixed footer isn't working
The whole host module currently scrolls when the current page's content exceeds the viewport. That means the header (Save / Exit) and the footer (timeline + Back-to-Home) scroll away with the page content -- which defeats the point of both surfaces being persistent app chrome.
Desired layout: header always visible at top, footer always visible at bottom, page in between takes whatever vertical space is left, page scrolls internally if its content exceeds that space.
Root cause: the
hostMainLayoutinOpenLIFU.uiplacespageStack(a QStackedWidget) between the header and footer. QStackedWidget's size hint follows its current widget -- if a page's content is tall, the stack is tall, the whole host widget is tall, and whatever scroll area wraps the host module (Slicer's default module-panel scroll area, or the custom app's chrome) ends up scrolling the WHOLE thing.Fix
Two-part fix in
host_widget.py:setup(): force the outer host widget + the pageStack itself to haveExpandingvertical size policy so they claim all available vertical space in their parent viewport rather than growing past it._embed_page_widget_into_stack: wrap each embedded page'suiWidgetin aQScrollArea(withsetWidgetResizable(True)+NoFrameshape) before adding to the pageStack. Result: when the page content exceeds the pageStack's allotted height, the QScrollArea inside the pageStack scrolls the content, and the header/footer stay pinned outside.No changes to the
.uifile needed -- the size policies + wrappers are cleaner as Python calls where a comment can explain the pattern.2. Next button greyed out on Overview
The Next / previous-step gating in
_refresh_timeline_stateandonNextClickedreadsWorkflowControls.can_proceed. For pages that DO register workflow controls (legacy pages did this in theirsetup), that gate works as designed. For pages that DON'T register (like the new info-only Planning Session Overview, and Target Selection which doesn't register either),workflow.workflow_controls.get(key)returnsNone, and the current code treats that as "can NOT proceed" -- so the Next button is disabled and reachable-page calculation short-circuits at the first unregistered page.Info-only pages should always allow Next; Target Selection also has no gating logic worth blocking Next for. The right default is: missing controls means "proceed freely".
Fix: in
_refresh_timeline_state, treat missing controls ascan_proceed = True:Same change in
onNextClicked(the guard before advancing).Pages that WANT gating can register
WorkflowControls(as legacy pages did) and gate throughcan_proceed. Nothing changes for them.Non-goals
TimelineWidgetpaint logic or geometry.WorkflowControls; only the host's default treatment of "no controls registered".Acceptance
Relates to SlicerOpenLIFU#642, SlicerOpenLIFU#641.