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 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: pass 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: 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: pass 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: pass 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: pass self.dismiss(selected)