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
68 changes: 68 additions & 0 deletions autoptz/ui/widgets/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,23 @@ def _build_menus(self) -> None:
act.toggled.connect(lambda on, k=key: self._client.setOverlay(k, on))
overlays.addAction(act)

# Quick collapse/expand of the two side panels (mirrored on the status bar).
view.addSeparator()
self._act_toggle_left = QAction("Show Left Panel", self, checkable=True)
self._act_toggle_left.setShortcut("Ctrl+Alt+[")
self._act_toggle_left.setToolTip("Show or hide the left Properties panel.")
self._act_toggle_left.setStatusTip(self._act_toggle_left.toolTip())
self._act_toggle_left.toggled.connect(self._set_left_panel_visible)
view.addAction(self._act_toggle_left)
self._act_toggle_right = QAction("Show Right Panel", self, checkable=True)
self._act_toggle_right.setShortcut("Ctrl+Alt+]")
self._act_toggle_right.setToolTip(
"Show or hide the right Camera Info / People / Services panel."
)
self._act_toggle_right.setStatusTip(self._act_toggle_right.toolTip())
self._act_toggle_right.toggled.connect(self._set_right_panel_visible)
view.addAction(self._act_toggle_right)

# Panels (dock toggles) and Layouts are view concerns, so they live under
# View rather than as separate top-level menus.
view.addSeparator()
Expand Down Expand Up @@ -837,13 +854,22 @@ def _build_status_bar(self) -> None:
self._client,
logs_toggle=self._toggle_logs,
cameras_popup=self._open_cameras_menu,
left_toggle=self._set_left_panel_visible,
right_toggle=self._set_right_panel_visible,
)
self.statusBar().addPermanentWidget(self._status, 1)
self.statusBar().setSizeGripEnabled(False)
logs = self._docks.get("logs")
if logs is not None:
logs.visibilityChanged.connect(self._status.set_logs_visible)
self._status.set_logs_visible(logs.isVisible())
# Keep both the menu checkmarks and the status-bar buttons in sync with the
# side panels however they're toggled (menu, button, shortcut, or close box).
for key in ("properties", *self._RIGHT_PANEL_KEYS):
dock = self._docks.get(key)
if dock is not None:
dock.visibilityChanged.connect(lambda _v: self._sync_panel_toggles())
self._sync_panel_toggles()

def _toggle_logs(self, shown: bool | None = None) -> None:
dock = self._docks.get("logs")
Expand All @@ -856,6 +882,48 @@ def _toggle_logs(self, shown: bool | None = None) -> None:
else:
dock.hide()

# ── side-panel collapse ──────────────────────────────────────────────────────

#: The right-hand dock group is three tabbed panels toggled as one unit.
_RIGHT_PANEL_KEYS = ("camera_info", "people", "services")

def _set_left_panel_visible(self, shown: bool) -> None:
dock = self._docks.get("properties")
if dock is None:
return
if shown:
dock.show()
dock.raise_()
else:
dock.hide()

def _set_right_panel_visible(self, shown: bool) -> None:
docks = [d for k in self._RIGHT_PANEL_KEYS if (d := self._docks.get(k)) is not None]
for dock in docks:
dock.setVisible(shown)
if shown and docks:
docks[0].raise_()

def _sync_panel_toggles(self) -> None:
"""Keep the menu checkmarks and status-bar buttons in step with the docks."""
left = self._docks.get("properties")
left_on = bool(left is not None and left.isVisible())
right_on = any(
(d := self._docks.get(k)) is not None and d.isVisible() for k in self._RIGHT_PANEL_KEYS
)
for act, on in (
(getattr(self, "_act_toggle_left", None), left_on),
(getattr(self, "_act_toggle_right", None), right_on),
):
if act is not None:
blocked = act.blockSignals(True)
act.setChecked(on)
act.blockSignals(blocked)
status = getattr(self, "_status", None)
if status is not None:
status.set_left_visible(left_on)
status.set_right_visible(right_on)

# ── selection routing ──────────────────────────────────────────────────────

def _on_camera_selected(self, camera_id: str) -> None:
Expand Down
36 changes: 36 additions & 0 deletions autoptz/ui/widgets/status_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def __init__(
client: Any,
logs_toggle: Callable[[bool], None] | None = None,
cameras_popup: Callable[..., None] | None = None,
left_toggle: Callable[[bool], None] | None = None,
right_toggle: Callable[[bool], None] | None = None,
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
Expand Down Expand Up @@ -65,6 +67,26 @@ def __init__(
self._summary = QLabel("")
row.addWidget(self._summary)

# Quick collapse/expand of the side panels (also in View and on shortcuts).
self._left_btn: QPushButton | None = None
self._right_btn: QPushButton | None = None
if left_toggle is not None or right_toggle is not None:
row.addWidget(self._make_sep())
if left_toggle is not None:
self._left_btn = QPushButton("◧")
self._left_btn.setCheckable(True)
self._left_btn.setToolTip("Show or hide the left Properties panel.")
self._left_btn.toggled.connect(left_toggle)
row.addWidget(self._left_btn)
if right_toggle is not None:
self._right_btn = QPushButton("◨")
self._right_btn.setCheckable(True)
self._right_btn.setToolTip(
"Show or hide the right Camera Info / People / Services panel."
)
self._right_btn.toggled.connect(right_toggle)
row.addWidget(self._right_btn)

self._logs_btn: QPushButton | None = None
if logs_toggle is not None:
row.addWidget(self._make_sep())
Expand Down Expand Up @@ -116,6 +138,12 @@ def _on_logs_toggled(self, shown: bool) -> None:
if self._logs_toggle is not None:
self._logs_toggle(shown)

def set_left_visible(self, shown: bool) -> None:
_set_checked(self._left_btn, shown)

def set_right_visible(self, shown: bool) -> None:
_set_checked(self._right_btn, shown)

# ── refreshers ───────────────────────────────────────────────────────────────

def refresh(self) -> None:
Expand Down Expand Up @@ -202,3 +230,11 @@ def _safe(fn: Any, default: Any) -> Any:
return fn()
except Exception: # noqa: BLE001
return default


def _set_checked(btn: QPushButton | None, checked: bool) -> None:
"""Set a checkable button's state without re-emitting ``toggled``."""
if btn is not None:
was = btn.blockSignals(True)
btn.setChecked(checked)
btn.blockSignals(was)
Loading