diff --git a/qtribu/gui/dck_qchat.py b/qtribu/gui/dck_qchat.py index 6a28409e..240efe98 100644 --- a/qtribu/gui/dck_qchat.py +++ b/qtribu/gui/dck_qchat.py @@ -11,23 +11,19 @@ from qgis.core import Qgis, QgsApplication from qgis.gui import QgisInterface, QgsDockWidget from qgis.PyQt import uic -from qgis.PyQt.QtCore import QPoint, Qt, QTime -from qgis.PyQt.QtGui import QBrush, QColor, QCursor, QIcon, QPixmap +from qgis.PyQt.QtCore import QPoint, Qt +from qgis.PyQt.QtGui import QCursor, QIcon from qgis.PyQt.QtWidgets import ( QAction, - QDialog, QFileDialog, - QLabel, QMenu, QMessageBox, QTreeWidgetItem, - QVBoxLayout, QWidget, ) from qtribu.__about__ import __title__ from qtribu.constants import ( - ADMIN_MESSAGES_AVATAR, ADMIN_MESSAGES_NICKNAME, CHEATCODE_10OCLOCK, CHEATCODE_DIZZY, @@ -36,6 +32,11 @@ CHEATCODES, QCHAT_NICKNAME_MINLENGTH, ) +from qtribu.gui.qchat_tree_widget_items import ( + QChatAdminTreeWidgetItem, + QChatImageTreeWidgetItem, + QChatTextTreeWidgetItem, +) from qtribu.logic.qchat_api_client import QChatApiClient from qtribu.logic.qchat_messages import ( QChatExiterMessage, @@ -426,15 +427,11 @@ def on_text_message_received(self, message: QChatTextMessage) -> None: if message.text in CHEATCODES: return + item = QChatTextTreeWidgetItem(self.twg_chat, message) + # check if message mentions current user words = message.text.split(" ") if f"@{self.settings.author_nickname}" in words or "@all" in words: - item = self.create_message_item( - message.author, - message.avatar, - message.text, - foreground_color=self.settings.qchat_color_mention, - ) if message.author != self.settings.author_nickname: self.log( message=self.tr( @@ -453,19 +450,7 @@ def on_text_message_received(self, message: QChatTextMessage) -> None: play_resource_sound( self.settings.qchat_ring_tone, self.settings.qchat_sound_volume ) - elif message.author == self.settings.author_nickname: - item = self.create_message_item( - message.author, - message.avatar, - message.text, - foreground_color=self.settings.qchat_color_self, - ) - else: - item = self.create_message_item( - message.author, - message.avatar, - message.text, - ) + self.twg_chat.addTopLevelItem(item) self.twg_chat.scrollToItem(item) @@ -473,20 +458,9 @@ def on_image_message_received(self, message: QChatImageMessage) -> None: """ Launched when an image message is received from the websocket """ - pixmap = QPixmap() - data = base64.b64decode(message.image_data) - pixmap.loadFromData(data) - item = self.create_image_item( - QTime.currentTime(), - message.author, - message.avatar, - pixmap, - ) - # set foreground color if sent by user - if message.author == self.settings.author_nickname: - for i in range(2): - item.setForeground(i, QBrush(QColor(self.settings.qchat_color_self))) + item = QChatImageTreeWidgetItem(self.twg_chat, message) self.twg_chat.addTopLevelItem(item) + self.twg_chat.scrollToItem(item) def on_nb_users_message_received(self, message: QChatNbUsersMessage) -> None: """ @@ -551,32 +525,15 @@ def on_message_clicked(self, item: QTreeWidgetItem, column: int) -> None: """ Action called when clicking on a chat message """ - author = item.text(1) - widget = self.twg_chat.itemWidget(item, 2) - # if there is a widget (QLabel) usually, - # it means that there is an image - # -> display the image in a new window - if column == 2 and widget: - pixmap = widget.pixmap() - dialog = QDialog(self) - dialog.setWindowTitle( - self.tr("QChat image sent by {author}").format(author=author) - ) - layout = QVBoxLayout() - label = QLabel() - label.setPixmap(pixmap) - layout.addWidget(label) - dialog.setLayout(layout) - dialog.setModal(True) - dialog.show() + item.on_click(column) def on_message_double_clicked(self, item: QTreeWidgetItem, column: int) -> None: """ Action called when double clicking on a chat message """ - author = item.text(1) + author = item.author # do nothing if double click on admin message - if author == ADMIN_MESSAGES_NICKNAME: + if author == ADMIN_MESSAGES_NICKNAME or author == self.settings.author_nickname: return text = self.lne_message.text() self.lne_message.setText(f"{text}@{author} ") @@ -600,26 +557,22 @@ def on_custom_context_menu_requested(self, point: QPoint) -> None: Action called when right clicking on a chat message """ item = self.twg_chat.itemAt(point) - author = item.text(1) - message = item.text(2) menu = QMenu(self.tr("QChat Menu"), self) - if ( - author != self.settings.author_nickname - and author != ADMIN_MESSAGES_NICKNAME - ): - # like message action + # like message action if possible + if item.can_be_liked: like_action = QAction( QgsApplication.getThemeIcon("mActionInOverview.svg"), self.tr("Like message"), ) like_action.triggered.connect( - partial(self.on_like_message, author, message) + partial(self.on_like_message, item.author, item.liked_message) ) menu.addAction(like_action) - # mention user action + # mention author action if possible + if item.can_be_mentioned: mention_action = QAction( QgsApplication.getThemeIcon("mMessageLogRead.svg"), self.tr("Mention user"), @@ -628,17 +581,15 @@ def on_custom_context_menu_requested(self, point: QPoint) -> None: partial(self.on_message_double_clicked, item, 2) ) menu.addAction(mention_action) - menu.addSeparator() - # copy message to clipboard action - copy_action = QAction( - QgsApplication.getThemeIcon("mActionEditCopy.svg"), - self.tr("Copy message to clipboard"), - ) - copy_action.triggered.connect( - partial(self.on_copy_message_to_clipboard, message) - ) - menu.addAction(copy_action) + # copy message to clipboard action if possible + if item.can_be_copied_to_clipboard: + copy_action = QAction( + QgsApplication.getThemeIcon("mActionEditCopy.svg"), + self.tr("Copy message to clipboard"), + ) + copy_action.triggered.connect(item.copy_to_clipboard) + menu.addAction(copy_action) # hide message action hide_action = QAction( @@ -650,12 +601,6 @@ def on_custom_context_menu_requested(self, point: QPoint) -> None: menu.exec(QCursor.pos()) - def on_copy_message_to_clipboard(self, message: str) -> None: - """ - Action called when copy to clipboard menu action is triggered - """ - QgsApplication.instance().clipboard().setText(message) - def on_hide_message(self, item: QTreeWidgetItem) -> None: """ Action called when hide message menu action is triggered @@ -770,62 +715,14 @@ def on_send_screenshot_button_clicked(self) -> None: ) self.qchat_ws.send_message(message) - def add_admin_message(self, message: str) -> None: + def add_admin_message(self, text: str) -> None: """ Adds an admin message to QTreeWidget chat """ - item = self.create_message_item( - ADMIN_MESSAGES_NICKNAME, - ADMIN_MESSAGES_AVATAR, - message, - foreground_color=self.settings.qchat_color_admin, - ) + item = QChatAdminTreeWidgetItem(self.twg_chat, text) self.twg_chat.addTopLevelItem(item) self.twg_chat.scrollToItem(item) - def create_message_item( - self, - author: str, - avatar: Optional[str], - message: str, - foreground_color: str = None, - background_color: str = None, - ) -> QTreeWidgetItem: - """ - Creates a QTreeWidgetItem for adding to QTreeWidget chat - Optionally with foreground / background colors given as hexa string - """ - item_data = [ - QTime.currentTime().toString(), - author, - message, - ] - item = QTreeWidgetItem(item_data) - if self.settings.qchat_show_avatars and avatar: - item.setIcon(1, QIcon(QgsApplication.iconPath(avatar))) - item.setToolTip(2, message) - if foreground_color: - for i in range(len(item_data)): - item.setForeground(i, QBrush(QColor(foreground_color))) - if background_color: - for i in range(len(item_data)): - item.setBackground(i, QBrush(QColor(background_color))) - return item - - def create_image_item( - self, time: QTime, author: str, avatar: Optional[str], pixmap: QPixmap - ) -> QTreeWidgetItem: - item = QTreeWidgetItem(self.twg_chat) - item.setText(0, time.toString()) - item.setText(1, author) - if self.settings.qchat_show_avatars: - item.setIcon(1, QIcon(QgsApplication.iconPath(avatar))) - label = QLabel(self.twg_chat) - label.setPixmap(pixmap) - item.treeWidget().setItemWidget(item, 2, label) - item.setSizeHint(2, pixmap.size()) - return item - def on_widget_closed(self) -> None: """ Action called when the widget is closed diff --git a/qtribu/gui/qchat_tree_widget_items.py b/qtribu/gui/qchat_tree_widget_items.py new file mode 100644 index 00000000..461bb651 --- /dev/null +++ b/qtribu/gui/qchat_tree_widget_items.py @@ -0,0 +1,188 @@ +import base64 +from typing import Optional + +from qgis.core import QgsApplication +from qgis.PyQt.QtCore import QTime +from qgis.PyQt.QtGui import QBrush, QColor, QIcon, QPixmap +from qgis.PyQt.QtWidgets import ( + QDialog, + QLabel, + QTreeWidget, + QTreeWidgetItem, + QVBoxLayout, +) + +from qtribu.constants import ADMIN_MESSAGES_AVATAR, ADMIN_MESSAGES_NICKNAME +from qtribu.logic.qchat_messages import QChatImageMessage, QChatTextMessage +from qtribu.toolbelt import PlgOptionsManager +from qtribu.toolbelt.preferences import PlgSettingsStructure + +TIME_COLUMN = 0 +AUTHOR_COLUM = 1 +MESSAGE_COLUMN = 2 + + +class QChatTreeWidgetItem(QTreeWidgetItem): + """ + Custom QTreeWidgetItem implementation for QChat + A QChatTreeWidgetItem should not be implemented + See inheriting classes for implementation + """ + + def __init__( + self, parent: QTreeWidget, time: QTime, author: str, avatar: Optional[str] + ): + super().__init__(parent) + self.plg_settings = PlgOptionsManager() + self.time = time + self.author = author + self.avatar = avatar + + @property + def settings(self) -> PlgSettingsStructure: + return self.plg_settings.get_plg_settings() + + def init_time_and_author(self) -> None: + self.setText(TIME_COLUMN, self.time.toString()) + self.setText(AUTHOR_COLUM, self.author) + if self.settings.qchat_show_avatars and self.avatar: + self.setIcon(AUTHOR_COLUM, QIcon(QgsApplication.iconPath(self.avatar))) + + def set_foreground_color(self, color: str) -> None: + fg_color = QBrush(QColor(color)) + self.setForeground(TIME_COLUMN, fg_color) + self.setForeground(AUTHOR_COLUM, fg_color) + self.setForeground(MESSAGE_COLUMN, fg_color) + + def on_click(self, column: int) -> None: + """ + Triggered when simple clicking on the item + Empty because this is the expected behaviour + :param column: column that has been clicked + """ + pass + + @property + def can_be_liked(self) -> bool: + """ + Returns if the item can be liked + """ + return self.author != self.settings.author_nickname + + @property + def liked_message(self) -> str: + """ + Returns the text message that was liked + """ + pass + + @property + def can_be_mentioned(self) -> bool: + """ + Returns if the item can be mentioned + """ + return self.author != self.settings.author_nickname + + @property + def can_be_copied_to_clipboard(self) -> bool: + """ + Returns if the item can be copied to clipboard + """ + return False + + def copy_to_clipboard(self) -> None: + """ + Performs action of copying message to clipboard + If the can_be_copied_to_clipboard is enabled ofc + """ + pass + + +class QChatAdminTreeWidgetItem(QChatTreeWidgetItem): + def __init__(self, parent: QTreeWidget, text: str): + super().__init__( + parent, QTime.currentTime(), ADMIN_MESSAGES_NICKNAME, ADMIN_MESSAGES_AVATAR + ) + self.text = text + self.init_time_and_author() + self.setText(MESSAGE_COLUMN, text) + self.setToolTip(MESSAGE_COLUMN, text) + self.set_foreground_color(self.settings.qchat_color_admin) + + @property + def can_be_liked(self) -> bool: + return False + + @property + def can_be_mentioned(self) -> bool: + return False + + +class QChatTextTreeWidgetItem(QChatTreeWidgetItem): + def __init__(self, parent: QTreeWidget, message: QChatTextMessage): + super().__init__(parent, QTime.currentTime(), message.author, message.avatar) + self.message = message + self.init_time_and_author() + self.setText(MESSAGE_COLUMN, message.text) + + # set foreground color if user is mentioned + words = message.text.split(" ") + if f"@{self.settings.author_nickname}" in words or "@all" in words: + self.set_foreground_color(self.settings.qchat_color_mention) + + # set foreground color if sent by user + if message.author == self.settings.author_nickname: + self.set_foreground_color(self.settings.qchat_color_self) + + @property + def liked_message(self) -> str: + return self.message.text + + @property + def can_be_copied_to_clipboard(self) -> bool: + return True + + def copy_to_clipboard(self) -> None: + QgsApplication.instance().clipboard().setPixmap(self.message.text) + + +class QChatImageTreeWidgetItem(QChatTreeWidgetItem): + def __init__(self, parent: QTreeWidget, message: QChatImageMessage): + super().__init__(parent, QTime.currentTime(), message.author, message.avatar) + self.message = message + self.init_time_and_author() + + # set foreground color if sent by user + if message.author == self.settings.author_nickname: + self.set_foreground_color(self.settings.qchat_color_self) + + self.pixmap = QPixmap() + data = base64.b64decode(message.image_data) + self.pixmap.loadFromData(data) + label = QLabel(self.parent()) + label.setPixmap(self.pixmap) + self.treeWidget().setItemWidget(self, MESSAGE_COLUMN, label) + self.setSizeHint(MESSAGE_COLUMN, self.pixmap.size()) + + def on_click(self, column: int) -> None: + if column == MESSAGE_COLUMN: + dialog = QDialog(self.treeWidget()) + dialog.setWindowTitle(f"QChat image {self.message.author}") + layout = QVBoxLayout() + label = QLabel() + label.setPixmap(self.pixmap) + layout.addWidget(label) + dialog.setLayout(layout) + dialog.setModal(True) + dialog.show() + + @property + def liked_message(self) -> str: + return "image" + + @property + def can_be_copied_to_clipboard(self) -> bool: + return True + + def copy_to_clipboard(self) -> None: + QgsApplication.instance().clipboard().setPixmap(self.pixmap)