""" Accessible text display dialog - single point of truth for showing text to screen reader users Based on the implementation from Bifrost """ import time from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QTextEdit, QDialogButtonBox ) from PySide6.QtCore import Qt class AccessibleTextDialog(QDialog): """ Single reusable dialog for displaying text content accessibly to screen readers. Provides keyboard navigation, text selection, and proper focus management. """ # Class-level error debouncing to prevent dialog spam _lastErrorKey: str = "" _lastErrorTime: float = 0.0 _errorDebounceSeconds: float = 2.0 def __init__(self, title: str, content: str, dialogType: str = "info", parent=None): """ Initialize accessible text dialog Args: title: Window title content: Text content to display dialogType: "info", "error", "success", or "warning" - affects accessible name parent: Parent widget """ super().__init__(parent) self.setWindowTitle(title) self.setModal(True) # Size based on content length if len(content) > 500: self.setMinimumSize(600, 400) elif len(content) > 200: self.setMinimumSize(500, 300) else: self.setMinimumSize(400, 200) self.setupUi(content, dialogType) def setupUi(self, content: str, dialogType: str): """Setup the dialog UI with accessible text widget""" layout = QVBoxLayout(self) # Create accessible text edit widget self.textEdit = QTextEdit() self.textEdit.setPlainText(content) self.textEdit.setReadOnly(True) # Set accessible name based on dialog type accessibleNames = { "info": "Information Text", "error": "Error Details", "warning": "Warning Information", "success": "Success Information" } self.textEdit.setAccessibleName(accessibleNames.get(dialogType, "Dialog Text")) # Enable keyboard text selection and navigation self.textEdit.setTextInteractionFlags( Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse ) layout.addWidget(self.textEdit) # Button box buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) buttonBox.accepted.connect(self.accept) layout.addWidget(buttonBox) # Focus the text edit first so user can immediately read content self.textEdit.setFocus() @classmethod def showInfo(cls, title: str, content: str, parent=None): """Convenience method for info dialogs""" dialog = cls(title, content, "info", parent) dialog.exec() @classmethod def showError(cls, title: str, message: str, details: str = None, parent=None): """Convenience method for error dialogs with optional details. Includes debouncing to prevent the same error from spawning multiple dialogs in rapid succession. """ # Debounce: skip if same error within debounce window errorKey = f"{title}:{message}" currentTime = time.monotonic() if (errorKey == cls._lastErrorKey and currentTime - cls._lastErrorTime < cls._errorDebounceSeconds): return cls._lastErrorKey = errorKey cls._lastErrorTime = currentTime if details: content = f"{message}\n\nError Details:\n{details}" else: content = message dialog = cls(title, content, "error", parent) dialog.exec() @classmethod def showSuccess(cls, title: str, message: str, details: str = None, parent=None): """Convenience method for success dialogs with optional details""" if details: content = f"{message}\n\n{details}" else: content = message dialog = cls(title, content, "success", parent) dialog.exec() @classmethod def showWarning(cls, title: str, content: str, parent=None): """Convenience method for warning dialogs""" dialog = cls(title, content, "warning", parent) dialog.exec()