from __future__ import annotations from typing import Any, Dict, List, Optional from textual.app import ComposeResult from textual.containers import Container, Horizontal, ScrollableContainer from textual.screen import ModalScreen from textual.widgets import Static, Button, Checkbox, ListView, ListItem from textual import work from rich.text import Text import logging logger = logging.getLogger(__name__) class MatrixRoomPicker(ModalScreen[List[str]]): """Modal that lists Matrix rooms and returns selected defaults.""" CSS = """ MatrixRoomPicker { align: center middle; background: rgba(0, 0, 0, 0.45); } #matrix-room-picker { width: 70%; height: 70%; background: $panel; border: thick $primary; padding: 1; } #matrix-room-picker-hint { text-style: italic; color: $text-muted; margin-bottom: 1; } #matrix-room-scroll { height: 1fr; border: solid $surface; padding: 1; margin-bottom: 1; } #matrix-room-list { padding: 0; } .matrix-room-row { border-bottom: solid $surface; padding: 1 0; align: left middle; } .matrix-room-meta { padding-left: 1; content-align: left middle; } #matrix-room-actions { height: 3; align: right middle; } #matrix-room-status { height: 3; text-style: bold; } """ def __init__( self, config: Dict[str, Any], *, existing: Optional[List[str]] = None, rooms: Optional[List[Dict[str, Any]]] = None, ) -> None: super().__init__() self.config = config self._prefetched_rooms = rooms self._existing_ids = {str(r).strip() for r in (existing or []) if str(r).strip()} self._checkbox_map: Dict[str, str] = {} self._rooms: List[Dict[str, Any]] = [] self._status_widget: Optional[Static] = None self._checklist: Optional[Vertical] = None self._save_button: Optional[Button] = None def compose(self) -> ComposeResult: with Container(id="matrix-room-picker"): yield Static("Matrix Default Rooms", classes="section-title") yield Static( "Choose rooms to keep in the sharing defaults and autocomplete.", id="matrix-room-picker-hint", ) with ScrollableContainer(id="matrix-room-scroll"): yield ListView(id="matrix-room-list") with Horizontal(id="matrix-room-actions"): yield Button("Cancel", variant="error", id="matrix-room-cancel") yield Button("Select All", id="matrix-room-select-all") yield Button("Clear All", id="matrix-room-clear") yield Button("Save defaults", variant="success", id="matrix-room-save") yield Static("Loading rooms...", id="matrix-room-status") def on_mount(self) -> None: self._status_widget = self.query_one("#matrix-room-status", Static) self._checklist = self.query_one("#matrix-room-list", ListView) self._save_button = self.query_one("#matrix-room-save", Button) if self._save_button: self._save_button.disabled = True if self._prefetched_rooms is not None: self._apply_room_results(self._prefetched_rooms, None) else: if self._status_widget: self._set_status("Loading rooms...") self._load_rooms_background() def on_list_view_selected(self, event: ListView.Selected) -> None: """Intercept ListView.Selected events and prevent them from bubbling up to parent components which may react (e.g., the main config modal). Selecting an item should not implicitly close the picker or change the outer editor state.""" try: # Stop propagation so parent handlers (ConfigModal) don't react. event.stop() except Exception: logger.exception("Failed to stop ListView.Selected event propagation") def _set_status(self, text: str) -> None: if self._status_widget: self._status_widget.update(text) def on_checkbox_changed(self, event: Checkbox.Changed) -> None: # Enable Save only when at least one checkbox is selected any_selected = False for checkbox_id in self._checkbox_map.keys(): try: cb = self.query_one(f"#{checkbox_id}", Checkbox) if cb.value: any_selected = True break except Exception: logger.exception("Error querying checkbox in MatrixRoomPicker; skipping") continue if self._save_button: self._save_button.disabled = not any_selected def _render_rooms(self, rooms: List[Dict[str, Any]]) -> None: if not self._checklist: return for child in list(self._checklist.children): child.remove() self._checkbox_map.clear() self._rooms = rooms if not rooms: self._set_status("Matrix returned no rooms.") if self._save_button: self._save_button.disabled = True return for idx, room in enumerate(rooms): room_id = str(room.get("room_id") or "").strip() name = str(room.get("name") or "").strip() checkbox_id = f"matrix-room-{idx}" # Prefer display name; otherwise fall back to room id label_text = name or room_id or "Matrix Room" checkbox = Checkbox( label_text, id=checkbox_id, value=bool(room_id and room_id in self._existing_ids), ) self._checkbox_map[checkbox_id] = room_id list_item = ListItem(checkbox, classes="matrix-room-row") self._checklist.mount(list_item) self._set_status("Loaded rooms. Select one or more and save.") if self._save_button: self._save_button.disabled = False @work(thread=True) def _load_rooms_background(self) -> None: try: from Provider.matrix import Matrix provider = Matrix(self.config) rooms = provider.list_rooms() self.app.call_from_thread(self._apply_room_results, rooms, None) except Exception as exc: self.app.call_from_thread(self._apply_room_results, [], str(exc)) def _apply_room_results(self, rooms: List[Dict[str, Any]], error: Optional[str]) -> None: if error: self._set_status(f"Failed to load Matrix rooms: {error}") if self._save_button: self._save_button.disabled = True return self._render_rooms(rooms) # Ensure save button is enabled only if at least one checkbox is selected any_selected = False for cbid in self._checkbox_map.keys(): try: cb = self.query_one(f"#{cbid}", Checkbox) if cb.value: any_selected = True break except Exception: continue if self._save_button: self._save_button.disabled = not any_selected def on_button_pressed(self, event: Button.Pressed) -> None: if event.button.id == "matrix-room-cancel": self.dismiss([]) elif event.button.id == "matrix-room-select-all": for checkbox_id in self._checkbox_map.keys(): try: cb = self.query_one(f"#{checkbox_id}", Checkbox) cb.value = True except Exception: logger.exception("Failed to set checkbox value in MatrixRoomPicker") if self._save_button: self._save_button.disabled = False elif event.button.id == "matrix-room-clear": for checkbox_id in self._checkbox_map.keys(): try: cb = self.query_one(f"#{checkbox_id}", Checkbox) cb.value = False except Exception: logger.exception("Failed to set checkbox value to False in MatrixRoomPicker") if self._save_button: self._save_button.disabled = True elif event.button.id == "matrix-room-save": selected: List[str] = [] for checkbox_id, room_id in self._checkbox_map.items(): try: cb = self.query_one(f"#{checkbox_id}", Checkbox) if cb.value and room_id: selected.append(room_id) except Exception: logger.exception("Failed to read checkbox state for '%s' while saving MatrixRoomPicker selection", checkbox_id) self.dismiss(selected)