diff --git a/qtribu/constants.py b/qtribu/constants.py index 13c23ffa..42e0c03a 100644 --- a/qtribu/constants.py +++ b/qtribu/constants.py @@ -241,3 +241,4 @@ def local_path(self, base_path: Path = Path().home() / ".geotribu/cdn/") -> Path QCHAT_MESSAGE_TYPE_LIKE = "like" QCHAT_MESSAGE_TYPE_GEOJSON = "geojson" QCHAT_MESSAGE_TYPE_CRS = "crs" +QCHAT_MESSAGE_TYPE_BBOX = "bbox" diff --git a/qtribu/gui/dck_qchat.py b/qtribu/gui/dck_qchat.py index b246e165..5dfaed1d 100644 --- a/qtribu/gui/dck_qchat.py +++ b/qtribu/gui/dck_qchat.py @@ -31,6 +31,7 @@ CHEATCODE_IAMAROBOT, CHEATCODE_QGIS_PRO_LICENSE, CHEATCODES, + QCHAT_MESSAGE_TYPE_BBOX, QCHAT_MESSAGE_TYPE_CRS, QCHAT_MESSAGE_TYPE_GEOJSON, QCHAT_MESSAGE_TYPE_IMAGE, @@ -42,6 +43,7 @@ from qtribu.gui.qchat_tree_widget_items import ( MESSAGE_COLUMN, QChatAdminTreeWidgetItem, + QChatBboxTreeWidgetItem, QChatCrsTreeWidgetItem, QChatGeojsonTreeWidgetItem, QChatImageTreeWidgetItem, @@ -49,6 +51,7 @@ ) from qtribu.logic.qchat_api_client import QChatApiClient from qtribu.logic.qchat_messages import ( + QChatBboxMessage, QChatCrsMessage, QChatExiterMessage, QChatGeojsonMessage, @@ -169,6 +172,7 @@ def __init__( self.qchat_ws.like_message_received.connect(self.on_like_message_received) self.qchat_ws.geojson_message_received.connect(self.on_geojson_message_received) self.qchat_ws.crs_message_received.connect(self.on_crs_message_received) + self.qchat_ws.bbox_message_received.connect(self.on_bbox_message_received) # send message signal listener self.lne_message.returnPressed.connect(self.on_send_button_clicked) @@ -190,7 +194,7 @@ def __init__( ) # send extent message signal listener - self.btn_send_extent.pressed.connect(self.on_send_extent_button_clicked) + self.btn_send_extent.pressed.connect(self.on_send_bbox_button_clicked) self.btn_send_extent.setIcon( QIcon(QgsApplication.iconPath("mActionViewExtentInCanvas.svg")) ) @@ -576,6 +580,13 @@ def on_crs_message_received(self, message: QChatCrsMessage) -> None: item = QChatCrsTreeWidgetItem(self.twg_chat, message) self.add_tree_widget_item(item) + def on_bbox_message_received(self, message: QChatBboxMessage) -> None: + """ + Launched when a BBOX message is received from the websocket + """ + item = QChatBboxTreeWidgetItem(self.twg_chat, message, self.iface.mapCanvas()) + self.add_tree_widget_item(item) + # endregion def on_message_clicked(self, item: QTreeWidgetItem, column: int) -> None: @@ -637,6 +648,15 @@ def on_custom_context_menu_requested(self, point: QPoint) -> None: set_crs_action.triggered.connect(partial(item.on_click, MESSAGE_COLUMN)) menu.addAction(set_crs_action) + # if this is a bbox message + if type(item) is QChatBboxTreeWidgetItem: + set_bbox_action = QAction( + QgsApplication.getThemeIcon("mActionViewExtentInCanvas.svg"), + self.tr("Set current extent"), + ) + set_bbox_action.triggered.connect(partial(item.on_click, MESSAGE_COLUMN)) + menu.addAction(set_bbox_action) + # like message action if possible if item.can_be_liked: like_action = QAction( @@ -799,13 +819,24 @@ def on_send_screenshot_button_clicked(self) -> None: ) self.qchat_ws.send_message(message) - def on_send_extent_button_clicked(self) -> None: + def on_send_bbox_button_clicked(self) -> None: """ Action called when the Send extent button is clicked """ - QMessageBox.critical( - self, self.tr("Send extent"), self.tr("Not implemented yet") + crs = QgsProject.instance().crs() + rect = self.iface.mapCanvas().extent() + message = QChatBboxMessage( + type=QCHAT_MESSAGE_TYPE_BBOX, + author=self.settings.author_nickname, + avatar=self.settings.author_avatar, + crs_wkt=crs.toWkt(), + crs_authid=crs.authid(), + xmin=rect.xMinimum(), + xmax=rect.xMaximum(), + ymin=rect.yMinimum(), + ymax=rect.yMaximum(), ) + self.qchat_ws.send_message(message) def on_send_crs_button_clicked(self) -> None: """ diff --git a/qtribu/gui/dck_qchat.ui b/qtribu/gui/dck_qchat.ui index e62cdeeb..d60b4ffb 100644 --- a/qtribu/gui/dck_qchat.ui +++ b/qtribu/gui/dck_qchat.ui @@ -325,6 +325,9 @@ + + PointingHandCursor + Send Extent @@ -332,6 +335,9 @@ + + PointingHandCursor + Send CRS diff --git a/qtribu/gui/qchat_tree_widget_items.py b/qtribu/gui/qchat_tree_widget_items.py index 57f2a0c4..f6973751 100644 --- a/qtribu/gui/qchat_tree_widget_items.py +++ b/qtribu/gui/qchat_tree_widget_items.py @@ -7,9 +7,13 @@ from qgis.core import ( QgsApplication, QgsCoordinateReferenceSystem, + QgsCoordinateTransform, + QgsPointXY, QgsProject, + QgsRectangle, QgsVectorLayer, ) +from qgis.gui import QgsMapCanvas from qgis.PyQt.QtCore import QTime from qgis.PyQt.QtGui import QBrush, QColor, QIcon, QPixmap from qgis.PyQt.QtWidgets import ( @@ -22,6 +26,7 @@ from qtribu.constants import ADMIN_MESSAGES_AVATAR, ADMIN_MESSAGES_NICKNAME from qtribu.logic.qchat_messages import ( + QChatBboxMessage, QChatCrsMessage, QChatGeojsonMessage, QChatImageMessage, @@ -268,4 +273,49 @@ def can_be_copied_to_clipboard(self) -> bool: return True def copy_to_clipboard(self) -> None: - QgsApplication.instance().clipboard().setText(json.dumps(self.message.crs_wkt)) + QgsApplication.instance().clipboard().setText(self.message.crs_wkt) + + +class QChatBboxTreeWidgetItem(QChatTreeWidgetItem): + def __init__( + self, parent: QTreeWidget, message: QChatBboxMessage, canvas: QgsMapCanvas + ): + super().__init__(parent, QTime.currentTime(), message.author, message.avatar) + self.message = message + self.canvas = canvas + self.init_time_and_author() + self.setText(MESSAGE_COLUMN, self.liked_message) + self.setToolTip(MESSAGE_COLUMN, self.liked_message) + + # set foreground color if sent by user + if message.author == self.settings.author_nickname: + self.set_foreground_color(self.settings.qchat_color_self) + + def on_click(self, column: int) -> None: + if column == MESSAGE_COLUMN: + # set current canvas extent to the received one + project = QgsProject.instance() + tr = QgsCoordinateTransform( + QgsCoordinateReferenceSystem(self.message.crs_wkt), + project.crs(), + project, + ) + rect = QgsRectangle( + tr.transform(QgsPointXY(self.message.xmin, self.message.ymin)), + tr.transform(QgsPointXY(self.message.xmax, self.message.ymax)), + ) + self.canvas.setExtent(rect) + self.canvas.refresh() + + @property + def liked_message(self) -> str: + msg = f"[{self.message.xmin} {self.message.ymin}, {self.message.xmax} {self.message.ymax}]" + return f"" + + @property + def can_be_copied_to_clipboard(self) -> bool: + return True + + def copy_to_clipboard(self) -> None: + msg = f"[{self.message.xmin} {self.message.ymin}, {self.message.xmax} {self.message.ymax}]" + QgsApplication.instance().clipboard().setText(msg) diff --git a/qtribu/logic/qchat_messages.py b/qtribu/logic/qchat_messages.py index 979dbafb..4dde0415 100644 --- a/qtribu/logic/qchat_messages.py +++ b/qtribu/logic/qchat_messages.py @@ -64,3 +64,15 @@ class QChatCrsMessage(QChatMessage): avatar: Optional[str] crs_wkt: str crs_authid: str + + +@dataclass(init=True, frozen=True) +class QChatBboxMessage(QChatMessage): + author: str + avatar: Optional[str] + crs_wkt: str + crs_authid: str + xmin: float + xmax: float + ymin: float + ymax: float diff --git a/qtribu/logic/qchat_websocket.py b/qtribu/logic/qchat_websocket.py index eb38cc28..75d31837 100644 --- a/qtribu/logic/qchat_websocket.py +++ b/qtribu/logic/qchat_websocket.py @@ -7,6 +7,7 @@ from qgis.PyQt.QtCore import QObject, QUrl, pyqtSignal from qtribu.constants import ( + QCHAT_MESSAGE_TYPE_BBOX, QCHAT_MESSAGE_TYPE_CRS, QCHAT_MESSAGE_TYPE_EXITER, QCHAT_MESSAGE_TYPE_GEOJSON, @@ -18,6 +19,7 @@ QCHAT_MESSAGE_TYPE_UNCOMPLIANT, ) from qtribu.logic.qchat_messages import ( + QChatBboxMessage, QChatCrsMessage, QChatExiterMessage, QChatGeojsonMessage, @@ -72,6 +74,7 @@ def __init__(self): like_message_received = pyqtSignal(QChatLikeMessage) geojson_message_received = pyqtSignal(QChatGeojsonMessage) crs_message_received = pyqtSignal(QChatCrsMessage) + bbox_message_received = pyqtSignal(QChatBboxMessage) def open(self, qchat_instance_uri: str, room: str) -> None: """ @@ -136,3 +139,5 @@ def on_message_received(self, text: str) -> None: self.geojson_message_received.emit(QChatGeojsonMessage(**message)) elif msg_type == QCHAT_MESSAGE_TYPE_CRS: self.crs_message_received.emit(QChatCrsMessage(**message)) + elif msg_type == QCHAT_MESSAGE_TYPE_BBOX: + self.bbox_message_received.emit(QChatBboxMessage(**message))