"""Session browser dialog — lists sessions, supports load and delete.""" import json import logging import gi gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Gtk, Adw, GObject from cht.stream.manager import list_sessions, delete_sessions log = logging.getLogger(__name__) class SessionDialog(Adw.Window): """Modal dialog listing sessions. Emits 'session-selected' with session id.""" __gsignals__ = { "session-selected": (GObject.SignalFlags.RUN_FIRST, None, (str,)), } def __init__(self, parent, **kwargs): super().__init__(transient_for=parent, modal=True, **kwargs) self.set_title("Load Session") self.set_default_size(500, 400) sessions = list_sessions() toolbar = Adw.ToolbarView() header = Adw.HeaderBar() select_all_btn = Gtk.CheckButton(label="All") header.pack_start(select_all_btn) delete_btn = Gtk.Button(label="Delete") delete_btn.add_css_class("destructive-action") header.pack_end(delete_btn) toolbar.add_top_bar(header) scroll = Gtk.ScrolledWindow() scroll.set_vexpand(True) listbox = Gtk.ListBox() listbox.set_selection_mode(Gtk.SelectionMode.NONE) listbox.add_css_class("boxed-list") checks: list[tuple[str, Gtk.CheckButton]] = [] for sid, sdir in sessions: idx = sdir / "frames" / "index.json" nframes = 0 try: nframes = len(json.loads(idx.read_text())) except Exception: pass nrec = len(list((sdir / "stream").glob("recording_*.mp4"))) check = Gtk.CheckButton() checks.append((sid, check)) row = Adw.ActionRow() row.set_title(sid) row.set_subtitle(f"{nframes} frames, {nrec} segments") row.set_activatable(True) row.add_prefix(check) def _on_row_activated(r, s=sid): self.close() self.emit("session-selected", s) row.connect("activated", _on_row_activated) listbox.append(row) def _on_select_all(btn): active = btn.get_active() for _, cb in checks: cb.set_active(active) select_all_btn.connect("toggled", _on_select_all) def _on_delete(btn): to_delete = [sid for sid, cb in checks if cb.get_active()] if not to_delete: return if self._current_session in to_delete: to_delete.remove(self._current_session) if to_delete: delete_sessions(to_delete) self.close() # Re-open with updated list new_dialog = SessionDialog(self.get_transient_for()) new_dialog._current_session = self._current_session # Forward the signal new_dialog.connect("session-selected", lambda d, s: self.emit("session-selected", s)) new_dialog.present() delete_btn.connect("clicked", _on_delete) scroll.set_child(listbox) toolbar.set_content(scroll) self.set_content(toolbar) self._current_session = None def set_current_session(self, session_id: str | None) -> None: """Set the active session id so it won't be deleted.""" self._current_session = session_id