class ConversationView(QFrame): """ View Displays an active conversation, including all previous messages and a text field for sending new messages to the recipient """ def __init__(self, parent: Optional[QWidget], client: Client, peer: Peer, friends_list: PeerList, conversation_list: PeerList): super().__init__(parent) self.setObjectName("conversation_view") self._peer = peer self._client = client self._friends_list = friends_list self._conversation_list = conversation_list self._conversation_model = self._client.conversation(self._peer) # Model containing messages self._layout_manager = QVBoxLayout(self) # Configure message list self._message_list = QListView() # View used to display conversation messages (the model) self._message_list.setWordWrap(True) self._message_list.setModel(self._conversation_model) # Set up custom delegate message_delegate = MessageItemDelegate(self._message_list) self._message_list.setItemDelegate(message_delegate) self._send_view = MessageSendView(self, self._conversation_model.peer().username() if self._conversation_model else None) self.setup_ui() def setup_ui(self): """ Builds UI for display """ self._layout_manager.setContentsMargins(0, 0, 0, 0) self._send_view.setContentsMargins(0, 0, 0, 0) self._message_list.setModel(self._conversation_model) # Connect model self._message_list.setLayoutMode(QListView.Batched) # Display as needed self._message_list.setBatchSize(10) # Number of messages to display self._message_list.setFlow(QListView.TopToBottom) # Display vertically self._message_list.setResizeMode(QListView.Adjust) # Items laid out every time view is resized # Layout widgets and views # self._layout_manager.addWidget(header) self._layout_manager.addWidget(self._message_list, 1) self._layout_manager.addWidget(self._send_view) # Connect to signals self._send_view.text_edit().keyPressEvent = self.send_view_did_change self._message_list.verticalScrollBar().rangeChanged.connect(self.scroll_to_message) # Listeners def send_view_did_change(self, event: QKeyEvent): """ As QPlainTextEdit doesn't natively support enter key response, this function prevents the user from typing the 'enter' key and sends a message on its press instead :param event: QKeyEvent raised by key press """ text_field = self._send_view.text_edit() if event.key() == Qt.Key_Return: message: str = text_field.toPlainText() if not message: # Don't want to allow sending of empty messages return # Clear message send view self._send_view.text_edit().clear() # Create message chat_msg = ChatMessage(message) # Send over network to peer self._client.send_chat(self._peer, chat_msg) else: QPlainTextEdit.keyPressEvent(text_field, event) def peer(self): """ Getter :return: this conversation's peer """ return self._peer @QtCore.pyqtSlot() def scroll_to_message(self): """ Event Listener connected to QListView's vertical scroll bar's range change Scrolls to a new message when view reflects a new message in the model """ self._message_list.scrollToBottom()
class PeerListView(QFrame): """" Abstract class, sets forth a basic list for viewing peers """ def __init__(self, parent: Optional[QWidget], search_placeholder: str, is_stateless: bool): super().__init__(parent) self.setProperty("class", "friends_list") self._layout_manager = QVBoxLayout(self) self._header_manager = QHBoxLayout() # Set up search bar self._search_bar = QLineEdit() self._search_bar.setPlaceholderText(search_placeholder) # Set up model self._peer_model = PeerList(self, is_stateless) self._peer_list_view = QListView(self) self._peer_list_view.setModel(self._peer_model) self._peer_list_view.setContextMenuPolicy(Qt.CustomContextMenu) # Connect events self._search_bar.returnPressed.connect(self._search_initiated) self._setup_ui() @QtCore.pyqtSlot() def _search_initiated(self): """ Slot connected to returnPressed signal, initiates a search """ print("Search!") def _setup_ui(self): """ Establishes UI for display """ # Set minimum width of search bar to length of placeholder text font_metrics = QFontMetrics(self._search_bar.font()) txt_width = font_metrics.width(self._search_bar.placeholderText()) self._search_bar.minimumSizeHint = QSize(txt_width, -1) self._search_bar.setFixedHeight(30) self._peer_list_view.setLayoutMode(QListView.Batched) # Display as needed self._peer_list_view.setBatchSize(10) # Number of messages to display self._peer_list_view.setFlow(QListView.TopToBottom) # Display vertically self._peer_list_view.setResizeMode(QListView.Adjust) # Items laid out every time view is resized # Set up header self._header_manager.addWidget(self._search_bar, 1) # Layout widgets self._layout_manager.addLayout(self._header_manager) self._layout_manager.addSpacing(10) self._layout_manager.addWidget(self._peer_list_view) self._layout_manager.setSpacing(0) def add_to_header(self, widget: QWidget): """ Adds the given widget to the header :param widget: Widget to be added """ self._header_manager.addSpacing(5) self._header_manager.addWidget(widget) def model(self) -> PeerList: """ :return: The peer list model """ return self._peer_model