"""PySide6 transcription display widget for showing real-time transcriptions.""" from PySide6.QtWidgets import QTextEdit from PySide6.QtGui import QFont, QTextCursor, QTextCharFormat, QColor from PySide6.QtCore import Qt, Slot from datetime import datetime class TranscriptionDisplay(QTextEdit): """Custom text widget for displaying transcriptions using PySide6.""" def __init__(self, parent=None, max_lines=100, show_timestamps=True, font_family="Courier", font_size=12): """ Initialize transcription display. Args: parent: Parent widget max_lines: Maximum number of lines to keep in display show_timestamps: Whether to show timestamps font_family: Font family name font_size: Font size in points """ super().__init__(parent) self.max_lines = max_lines self.show_timestamps = show_timestamps self.line_count = 0 self.font_family = font_family self.font_size = font_size # Track the current preview line for two-stage transcription self.preview_line_index = -1 # -1 means no active preview self.preview_block_number = -1 # Block number for the preview line # Configure text widget self.setReadOnly(True) self.setFont(QFont(font_family, font_size)) # Set dark theme styling self.setStyleSheet(""" QTextEdit { background-color: #2b2b2b; color: #ffffff; border: 1px solid #3d3d3d; border-radius: 5px; padding: 10px; } """) def _format_line(self, text: str, user_name: str, timestamp: datetime, is_preview: bool = False) -> str: """ Format a transcription line. Args: text: Transcription text user_name: User/speaker name timestamp: Timestamp of transcription is_preview: Whether this is a preview line Returns: Formatted line string """ line_parts = [] if self.show_timestamps: time_str = timestamp.strftime("%H:%M:%S") line_parts.append(f"[{time_str}]") if user_name and user_name.strip(): line_parts.append(f"{user_name}:") # Add preview indicator for visual distinction if is_preview: line_parts.append(f"[...] {text}") else: line_parts.append(text) return " ".join(line_parts) @Slot(str, str) def add_transcription(self, text: str, user_name: str = "", timestamp: datetime = None): """ Add a new transcription to the display. Args: text: Transcription text user_name: User/speaker name timestamp: Timestamp of transcription """ if timestamp is None: timestamp = datetime.now() line = self._format_line(text, user_name, timestamp, is_preview=False) # If there's an active preview, replace it instead of appending if self.preview_line_index >= 0: self._replace_preview_with_final(line) else: # Add to display normally self.append(line) self.line_count += 1 # Auto-scroll to bottom cursor = self.textCursor() cursor.movePosition(QTextCursor.End) self.setTextCursor(cursor) # Remove old lines if exceeding max if self.line_count > self.max_lines: self._remove_oldest_lines(self.line_count - self.max_lines) @Slot(str, str) def add_preview(self, text: str, user_name: str = "", timestamp: datetime = None): """ Add a preview transcription that will be replaced by the final transcription. Args: text: Preview transcription text user_name: User/speaker name timestamp: Timestamp of transcription """ if timestamp is None: timestamp = datetime.now() line = self._format_line(text, user_name, timestamp, is_preview=True) # If there's already a preview, replace it if self.preview_line_index >= 0: self._replace_preview_line(line) else: # Add new preview line cursor = self.textCursor() cursor.movePosition(QTextCursor.End) # Apply italic formatting for preview fmt = QTextCharFormat() fmt.setFontItalic(True) if self.line_count > 0: cursor.insertText("\n") cursor.insertText(line, fmt) self.preview_line_index = self.line_count self.preview_block_number = self.document().blockCount() - 1 self.line_count += 1 # Auto-scroll to bottom cursor = self.textCursor() cursor.movePosition(QTextCursor.End) self.setTextCursor(cursor) def _replace_preview_line(self, new_text: str): """Replace the current preview line with new preview text.""" if self.preview_block_number < 0: return doc = self.document() block = doc.findBlockByNumber(self.preview_block_number) if block.isValid(): cursor = QTextCursor(block) cursor.select(QTextCursor.BlockUnderCursor) # Apply italic formatting for preview fmt = QTextCharFormat() fmt.setFontItalic(True) cursor.removeSelectedText() cursor.insertText(new_text, fmt) def _replace_preview_with_final(self, final_text: str): """Replace the preview line with final transcription.""" if self.preview_block_number < 0: # No preview to replace, just add normally self.append(final_text) self.line_count += 1 self.preview_line_index = -1 self.preview_block_number = -1 return doc = self.document() block = doc.findBlockByNumber(self.preview_block_number) if block.isValid(): cursor = QTextCursor(block) cursor.select(QTextCursor.BlockUnderCursor) # Apply normal formatting for final text fmt = QTextCharFormat() fmt.setFontItalic(False) fmt.setForeground(QColor(255, 255, 255)) # White for final cursor.removeSelectedText() cursor.insertText(final_text, fmt) # Clear preview tracking self.preview_line_index = -1 self.preview_block_number = -1 def clear_preview(self): """Clear the current preview without adding a final transcription.""" if self.preview_block_number >= 0: doc = self.document() block = doc.findBlockByNumber(self.preview_block_number) if block.isValid(): cursor = QTextCursor(block) cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() cursor.deleteChar() # Remove newline self.line_count -= 1 self.preview_line_index = -1 self.preview_block_number = -1 def _remove_oldest_lines(self, num_lines: int): """ Remove oldest lines from the display. Args: num_lines: Number of lines to remove """ cursor = self.textCursor() cursor.movePosition(QTextCursor.Start) for _ in range(num_lines): cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() cursor.deleteChar() # Remove the newline self.line_count -= num_lines # Adjust preview tracking if lines were removed if self.preview_line_index >= 0: self.preview_line_index -= num_lines self.preview_block_number -= num_lines if self.preview_line_index < 0: self.preview_line_index = -1 self.preview_block_number = -1 def clear_all(self): """Clear all transcriptions.""" self.clear() self.line_count = 0 self.preview_line_index = -1 self.preview_block_number = -1 def get_all_text(self) -> str: """ Get all transcription text. Returns: All text in the display """ return self.toPlainText() def set_max_lines(self, max_lines: int): """Update maximum number of lines to keep.""" self.max_lines = max_lines # Trim if necessary if self.line_count > self.max_lines: self._remove_oldest_lines(self.line_count - self.max_lines) def set_show_timestamps(self, show: bool): """Update whether to show timestamps.""" self.show_timestamps = show def set_font(self, font_family: str, font_size: int): """ Update font settings. Args: font_family: Font family name font_size: Font size in points """ self.font_family = font_family self.font_size = font_size super().setFont(QFont(font_family, font_size)) def save_to_file(self, filepath: str) -> bool: """ Save transcriptions to a file. Args: filepath: Path to save file Returns: True if saved successfully """ try: with open(filepath, 'w') as f: f.write(self.toPlainText()) return True except Exception as e: print(f"Error saving transcriptions: {e}") return False