Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ A scene is a group of frames in one roll shot in the same light. It has its own
<!-- panel:analysis -->
## 3. Analysis readout (always visible)

The readout sits above the Roll and Frame tabs and describes the current frame as you edit. Top to bottom:
The readout sits above the Roll and Frame tabs and describes the current frame as you edit. Each tab remembers whether the readout is collapsed. Top to bottom:

#### Photometric curve

Expand Down
49 changes: 34 additions & 15 deletions negpy/desktop/view/sidebar/right_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,17 @@ def wrap_scroll(widget: QWidget) -> QScrollArea:
repo = self.controller.session.repo
saved_sizes = repo.get_global_setting("analysis_splitter_sizes")
if isinstance(saved_sizes, list) and len(saved_sizes) == 2:
self.splitter.setSizes([int(s) for s in saved_sizes])
sizes = [int(s) for s in saved_sizes]
else:
self.splitter.setSizes(default_analysis_split(QApplication.primaryScreen()))
self.splitter.splitterMoved.connect(lambda *_: repo.save_global_setting("analysis_splitter_sizes", self.splitter.sizes()))
sizes = default_analysis_split(QApplication.primaryScreen())
self.splitter.setSizes(sizes)
self.splitter.splitterMoved.connect(self._on_analysis_splitter_moved)

# Collapsing the Analysis section hands its splitter space back to the tabs below and pins
# the header at the top, instead of leaving a large empty pane.
self._analysis_expanded_size = self.splitter.sizes()[0]
self._analysis_expanded_size = sizes[0]
self._analysis_split_shown = False
self.analysis_section.expanded_changed.connect(self._resize_splitter_for_analysis)
if not self.analysis_section.toggle_button.isChecked():
self._resize_splitter_for_analysis(False)

layout.addWidget(self.group_switcher)
layout.addWidget(self.splitter, 1)
Expand Down Expand Up @@ -267,9 +267,9 @@ def _build_frame_page(self, wrap_scroll) -> QWidget:
analysis_layout.addWidget(self.stats_widget, 0)

repo = self.controller.session.repo
self.analysis_section = make_section(
repo, "Analysis", "analysis", analysis_content, "fa5s.chart-bar", THEME.sidebar_expanded_defaults["analysis"]
)
# Roll and Frame each keep their own expanded state; _switch_group restores it.
self.analysis_section = make_section(repo, "Analysis", "analysis", analysis_content, "fa5s.chart-bar", persist=False)
self.analysis_section.expanded_changed.connect(lambda checked: repo.save_global_setting(self._analysis_expanded_setting(), checked))
self.clipping_btn = self.analysis_section.add_header_toggle("fa5s.adjust", "Clipping Overlay")
self.clipping_btn.setChecked(self.controller.state.clipping_overlay)
self.clipping_btn.toggled.connect(self.controller.toggle_clipping_overlay)
Expand Down Expand Up @@ -504,17 +504,32 @@ def show_analysis_help(self) -> None:

SectionHelpDialog("analysis", "Analysis", self, repo=self.controller.session.repo).exec()

def _analysis_expanded_setting(self) -> str:
return "section_expanded_analysis_roll" if self._group_keys[self._active_group] == "roll" else "section_expanded_analysis"

def showEvent(self, event) -> None:
super().showEvent(event)
# Before the first show the splitter has no real height, so a split set then is rescaled.
if not self._analysis_split_shown:
self._analysis_split_shown = True
QTimer.singleShot(0, lambda: self._resize_splitter_for_analysis(self.analysis_section.toggle_button.isChecked()))

def _on_analysis_splitter_moved(self, *_) -> None:
if not self.analysis_section.toggle_button.isChecked():
return
sizes = self.splitter.sizes()
self._analysis_expanded_size = sizes[0]
self.controller.session.repo.save_global_setting("analysis_splitter_sizes", sizes)

def _resize_splitter_for_analysis(self, expanded: bool) -> None:
"""Pin the collapsed Analysis header at the top: shrink pane 0 to the header and
give the rest to the tabs; restore the prior size when re-expanded."""
sizes = self.splitter.sizes()
total = sum(sizes)
if total <= 0:
give the rest to the tabs; restore the dragged size when re-expanded."""
total = sum(self.splitter.sizes())
if total <= 0 or not self._analysis_split_shown:
return
if expanded:
top = min(max(self._analysis_expanded_size, 120), max(120, total - 120))
else:
self._analysis_expanded_size = sizes[0]
top = max(1, self.analysis_section.sizeHint().height())
self.splitter.setSizes([top, max(0, total - top)])

Expand Down Expand Up @@ -677,7 +692,11 @@ def _switch_group(self, index: int) -> None:
self._active_group = index
self.controller.session.repo.save_global_setting("right_panel_group", index)
self.group_stack.setCurrentIndex(index)
self.analysis_section.setVisible(self._group_keys[index] in ("roll", "frame"))
analysis_shown = self._group_keys[index] in ("roll", "frame")
self.analysis_section.setVisible(analysis_shown)
if analysis_shown:
saved = self.controller.session.repo.get_global_setting(self._analysis_expanded_setting())
self.analysis_section.set_expanded(THEME.sidebar_expanded_defaults["analysis"] if saved is None else bool(saved))
for i, btn in enumerate(self._group_buttons):
btn.setChecked(i == index)
self.group_switcher.set_pinned(index)
Expand Down
6 changes: 4 additions & 2 deletions negpy/desktop/view/widgets/collapsible.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,15 +336,17 @@ def make_section(
default_expanded: bool = False,
background_widget: Optional[QWidget] = None,
collapsible: bool = True,
*,
persist: bool = True,
) -> CollapsibleSection:
"""The one way a sidebar builds a section: persisted under section_expanded_{key}, and the
ⓘ guide present exactly when docs/USER_GUIDE.md carries a `panel:{key}` marker. The help
dialog is parented to the section, so it centres on the window the section is in.
collapsible=False always expands and never reads or writes the persisted setting; repo=None
keeps the section but not its expanded state."""
or persist=False keeps the section but not its expanded state."""
from negpy.desktop.view.widgets.section_help_dialog import SectionHelpDialog, has_guide

persist = collapsible and repo is not None
persist = persist and collapsible and repo is not None
if collapsible:
setting = f"section_expanded_{key}"
persisted = repo.get_global_setting(setting) if persist else None
Expand Down
43 changes: 43 additions & 0 deletions tests/test_right_panel_wiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,49 @@ def test_analysis_shows_on_the_roll_and_frame_tabs_only():
panel.analysis_section.setVisible.assert_called_once_with(key in ("roll", "frame"))


def test_roll_and_frame_each_restore_their_own_analysis_expanded_state():
panel = _group_panel_stub()
panel._analysis_expanded_setting = lambda: RightPanel._analysis_expanded_setting(panel)
saved = {"section_expanded_analysis_roll": False, "section_expanded_analysis": True}
panel.controller.session.repo.get_global_setting.side_effect = saved.get

RightPanel._switch_group(panel, 0)
panel.analysis_section.set_expanded.assert_called_with(False)
RightPanel._switch_group(panel, 1)
panel.analysis_section.set_expanded.assert_called_with(True)

panel.analysis_section.reset_mock()
RightPanel._switch_group(panel, 2)
panel.analysis_section.set_expanded.assert_not_called()


def test_only_an_expanded_analysis_drag_sets_the_saved_height():
panel = MagicMock()
panel._analysis_expanded_size = 300
panel.splitter.sizes.return_value = [40, 860]
panel.analysis_section.toggle_button.isChecked.return_value = False

RightPanel._on_analysis_splitter_moved(panel)
assert panel._analysis_expanded_size == 300
panel.controller.session.repo.save_global_setting.assert_not_called()

panel.splitter.sizes.return_value = [500, 400]
panel.analysis_section.toggle_button.isChecked.return_value = True
RightPanel._on_analysis_splitter_moved(panel)
assert panel._analysis_expanded_size == 500
panel.controller.session.repo.save_global_setting.assert_called_once_with("analysis_splitter_sizes", [500, 400])


def test_analysis_split_waits_for_the_first_show():
panel = MagicMock()
panel.splitter.sizes.return_value = [189, 287]
panel._analysis_split_shown = False

RightPanel._resize_splitter_for_analysis(panel, True)

panel.splitter.setSizes.assert_not_called()


def test_switch_group_activates_the_chosen_scanner_only_on_the_scan_tab():
panel = _group_panel_stub(scan_index=5)

Expand Down
Loading