201 lines
7.2 KiB
Python
201 lines
7.2 KiB
Python
"""Private message window for StormIRC."""
|
|
|
|
import logging
|
|
from PySide6.QtWidgets import (
|
|
QMainWindow, QWidget, QVBoxLayout, QTextEdit,
|
|
QPushButton, QHBoxLayout, QLabel, QPlainTextEdit
|
|
)
|
|
from PySide6.QtCore import Qt, Signal
|
|
from PySide6.QtGui import QAccessible
|
|
from src.ui.autocomplete_textedit import AutocompleteTextEdit
|
|
from src.config.settings import is_valid_text
|
|
from src.ui.ui_utils import (
|
|
format_message_with_timestamp,
|
|
apply_speech_settings,
|
|
DEFAULT_PM_WINDOW_WIDTH,
|
|
DEFAULT_PM_WINDOW_HEIGHT,
|
|
PM_MESSAGE_ENTRY_MAX_HEIGHT
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PMWindow(QMainWindow):
|
|
"""Private message window."""
|
|
|
|
message_send = Signal(str, str) # Signal(nickname, message)
|
|
window_closed = Signal(str) # Signal(nickname) when window closes
|
|
|
|
def __init__(self, nickname, irc_client, speech_manager, config_manager, server_name, parent=None):
|
|
super().__init__(parent)
|
|
self.nickname = nickname
|
|
self.irc_client = irc_client
|
|
self.speech_manager = speech_manager
|
|
self.config_manager = config_manager
|
|
self.server_name = server_name
|
|
self.message_buffer = []
|
|
|
|
# Set window title with format: "nickname - Private Message"
|
|
self.setWindowTitle(f"{nickname} - Private Message")
|
|
self.resize(DEFAULT_PM_WINDOW_WIDTH, DEFAULT_PM_WINDOW_HEIGHT)
|
|
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
"""Set up the user interface."""
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
|
|
layout = QVBoxLayout(central_widget)
|
|
|
|
# Header with nickname
|
|
header_label = QLabel(f"Private conversation with {self.nickname}")
|
|
header_label.setStyleSheet("font-weight: bold; padding: 5px;")
|
|
layout.addWidget(header_label)
|
|
|
|
# Chat display
|
|
self.chat_display = QTextEdit()
|
|
self.chat_display.setReadOnly(True)
|
|
# Enable cursor for screen reader navigation (same as main window)
|
|
self.chat_display.setTextInteractionFlags(
|
|
Qt.TextSelectableByMouse |
|
|
Qt.TextSelectableByKeyboard |
|
|
Qt.LinksAccessibleByMouse |
|
|
Qt.LinksAccessibleByKeyboard
|
|
)
|
|
self.chat_display.setFocusPolicy(Qt.StrongFocus)
|
|
self.chat_display.setAccessibleName(f"Private message conversation with {self.nickname}")
|
|
self.chat_display.setAccessibleDescription(
|
|
f"Chat history with {self.nickname}. Use arrow keys to navigate. Press Tab to move to message input."
|
|
)
|
|
layout.addWidget(self.chat_display)
|
|
|
|
# Message input area
|
|
input_layout = QVBoxLayout()
|
|
|
|
input_label = QLabel("Message:")
|
|
input_layout.addWidget(input_label)
|
|
|
|
# Use autocomplete text edit for nickname completion
|
|
self.message_entry = AutocompleteTextEdit()
|
|
self.message_entry.setAccessibleName(f"Message input for {self.nickname}")
|
|
self.message_entry.setAccessibleDescription(
|
|
f"Type your message to {self.nickname} here. Press Enter to send, Shift+Enter for new line."
|
|
)
|
|
self.message_entry.setPlaceholderText(f"Type message to {self.nickname}...")
|
|
self.message_entry.setMaximumHeight(PM_MESSAGE_ENTRY_MAX_HEIGHT)
|
|
|
|
# Autocomplete will be set up by parent when needed
|
|
|
|
# Connect Enter key handling
|
|
self.message_entry.installEventFilter(self)
|
|
input_layout.addWidget(self.message_entry)
|
|
|
|
# Send button
|
|
button_layout = QHBoxLayout()
|
|
self.send_button = QPushButton("Send")
|
|
self.send_button.setAccessibleName("Send message button")
|
|
self.send_button.clicked.connect(self.on_send_message)
|
|
button_layout.addWidget(self.send_button)
|
|
button_layout.addStretch()
|
|
|
|
input_layout.addLayout(button_layout)
|
|
layout.addLayout(input_layout)
|
|
|
|
# Set focus to message entry
|
|
self.message_entry.setFocus()
|
|
|
|
def eventFilter(self, obj, event):
|
|
"""Event filter to handle Enter key in message entry."""
|
|
from PySide6.QtCore import QEvent
|
|
|
|
if obj == self.message_entry and event.type() == QEvent.KeyPress:
|
|
# Enter without Shift sends message, Shift+Enter inserts newline
|
|
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
|
if not (event.modifiers() & Qt.ShiftModifier):
|
|
self.on_send_message()
|
|
return True # Event handled
|
|
return super().eventFilter(obj, event)
|
|
|
|
def keyPressEvent(self, event):
|
|
"""Handle key press events - Ctrl stops all speech, Escape closes window."""
|
|
if event.key() == Qt.Key_Control:
|
|
self.speech_manager.stop()
|
|
elif event.key() == Qt.Key_Escape:
|
|
self.close()
|
|
return
|
|
super().keyPressEvent(event)
|
|
|
|
def speak_message(self, text: str):
|
|
"""Speak message with PM-specific settings."""
|
|
accessibility = self.config_manager.get_accessibility_config()
|
|
|
|
# Check if speech is enabled and auto-read is on
|
|
if not accessibility.speech_enabled or not accessibility.auto_read_messages:
|
|
return
|
|
|
|
# Validate text has meaningful content
|
|
if not is_valid_text(text):
|
|
return
|
|
|
|
# Get PM-specific settings
|
|
channel_settings = self.config_manager.get_channel_speech_settings(self.server_name, self.nickname)
|
|
|
|
# Apply PM-specific settings using shared utility
|
|
apply_speech_settings(self.speech_manager, channel_settings, self.nickname)
|
|
|
|
# Speak the text
|
|
self.speech_manager.speak(text, priority="message", interrupt=False)
|
|
|
|
def on_send_message(self):
|
|
"""Handle send message."""
|
|
message = self.message_entry.toPlainText().strip()
|
|
if not message:
|
|
return
|
|
|
|
logger.debug(f"Sending PM to {self.nickname}: {message}")
|
|
|
|
# Emit signal to send message
|
|
self.message_send.emit(self.nickname, message)
|
|
|
|
# Display our message locally
|
|
self.add_message(self.irc_client.nickname, message)
|
|
|
|
# Clear input
|
|
self.message_entry.clear()
|
|
self.message_entry.setFocus()
|
|
|
|
def add_message(self, sender, message):
|
|
"""Add message to chat display."""
|
|
formatted_msg = f"<{sender}> {message}"
|
|
self.message_buffer.append(formatted_msg)
|
|
|
|
# Update display
|
|
self.chat_display.append(formatted_msg)
|
|
|
|
# Speak the message
|
|
is_own_message = (sender == self.irc_client.nickname)
|
|
if is_own_message:
|
|
# Check if "read my own messages" is enabled
|
|
accessibility = self.config_manager.get_accessibility_config()
|
|
if accessibility.speech_read_own_messages:
|
|
self.speak_message(formatted_msg)
|
|
else:
|
|
# Always speak incoming messages
|
|
self.speak_message(formatted_msg)
|
|
|
|
logger.debug(f"PM window [{self.nickname}]: Added message from {sender}")
|
|
|
|
def closeEvent(self, event):
|
|
"""Handle window close."""
|
|
logger.info(f"PM window closing for {self.nickname}")
|
|
self.window_closed.emit(self.nickname)
|
|
super().closeEvent(event)
|
|
|
|
def show_and_raise(self):
|
|
"""Show window and bring to front."""
|
|
self.show()
|
|
self.raise_()
|
|
self.activateWindow()
|
|
self.message_entry.setFocus()
|