GraphicsView Scene postEvent focusProxy does not pass events from event filter to QWebEngineView Widget

Viewed 172

I am currently learning how to write a GUI in PyQt5 that enable user to draw lines and rectangles in a QGrapchisView Scene and sets a QWebEngineView Widget containing a folium map as background.

Issue: In order to synchronize QGrapchisView Scene and folium map pan and zoom, I use a Event Filter that passes GraphicsScene events to QWebEngineView Widget.

The QEvent.GraphicsSceneWheel event is passed as expected, but the QEvent.GraphicsSceneMouseRelease, QEvent.GraphicsSceneMousePress and QEvent.GraphicsSceneMouseMove are not pass to the QWebEngineView Widget.

Expected behavior: Selected GraphicsScene events are pass to the QWebEngineView Widget, and it enables to synchronize pan and zoom for both Widgets.

What I have tried so far:

import folium
import sys
from PyQt5 import QtGui, QtCore, QtWidgets, QtWebEngineWidgets
import io

class eventFilterClass(QtCore.QObject):
    def __init__(self, sender, receiver, gv):
        super(eventFilterClass, self).__init__()
        self.gv = gv
        self.m_sender = sender
        self.m_receiver = receiver
        self.m_sender.installEventFilter(self)

    def eventFilter(self, obj, event):
        if self.m_sender is obj:
            if event.type() == QtCore.QEvent.GraphicsSceneMousePress:
                if event.button() == QtCore.Qt.MiddleButton:
                    new_event = QtGui.QMouseEvent(int(2), self.gv.mapFromScene(event.scenePos()),
                                                  self.gv.mapFromScene(event.scenePos()), event.screenPos(), event.button(),
                                                  event.buttons(), event.modifiers(), event.source())
                    QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)
                    return True

            elif event.type() == QtCore.QEvent.GraphicsSceneMouseMove:
                if event.buttons() == QtCore.Qt.NoButton:
                    new_event = QtGui.QMouseEvent(int(5), self.gv.mapFromScene(event.scenePos()),self.gv.mapFromScene(event.scenePos()), event.screenPos(), event.button(), event.buttons(), event.modifiers(), event.source())
                    QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)
                    return True

            elif event.type() == QtCore.QEvent.GraphicsSceneMouseRelease:
                print('Release!')
                if event.button() == QtCore.Qt.MiddleButton:
                    new_event = QtGui.QMouseEvent(int(3), self.gv.mapFromScene(event.scenePos()),
                                                  self.gv.mapFromScene(event.scenePos()), event.screenPos(), event.button(),
                                                  event.buttons(), event.modifiers(), event.source())
                    QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)

            elif event.type() == QtCore.QEvent.GraphicsSceneWheel:
                new_event = QtGui.QWheelEvent(self.gv.mapFromScene(event.scenePos()), event.screenPos(), QtCore.QPoint(), QtCore.QPoint(0, event.delta()),event.buttons(), event.modifiers(), int(0), False, int(0))
                QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)
                return True
        return False

class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent, scene=None):
        super(GraphicsView, self).__init__(scene, parent)
        "VARIABLES INICIALES"
        self.pos_init_class = None
        self.scale_factor = 1.25
        "ACTIVAR TRACKING DE POSICION DE MOUSE"
        self.setMouseTracking(True)
        "REMOVER BARRAS DE SCROLL"
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        "ASIGNAR ANCLA PARA HACER ZOOM SOBRE EL MISMO PUNTO"
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

    def wheelEvent(self, event):
        if event.angleDelta().y() > 0:
            self.scale(self.scale_factor, self.scale_factor)
        else:
            self.scale(1 / self.scale_factor, 1 / self.scale_factor)
        super(GraphicsView, self).wheelEvent(event)

    def mousePressEvent(self, event):
        pos = self.mapToScene(event.pos())
        "PAN"
        if event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = pos
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ClosedHandCursor)
            super(GraphicsView, self).mousePressEvent(event)
            return
        super(GraphicsView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        pos = self.mapToScene(event.pos())
        "PAN"
        if self.pos_init_class:
            delta = self.pos_init_class - self.mapToScene(event.pos())
            r = self.mapToScene(self.viewport().rect()).boundingRect()
            self.setSceneRect(r.translated(delta))
            return
        super(GraphicsView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        "PAN"
        if self.pos_init_class and event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = None
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
        super(GraphicsView, self).mouseReleaseEvent(event)

class Ui_MainWindow(object):
    def __init__(self):
        super(Ui_MainWindow, self).__init__()

    def zoom_ext(self):
        "Zoom extent"
        x_range, y_range, h_range, w_range = self.graphicsView.scene().itemsBoundingRect().getRect()
        rect_scene = QtCore.QRectF(x_range, y_range, h_range, w_range)
        self.graphicsView.setSceneRect(rect_scene)
        unity = self.graphicsView.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
        self.graphicsView.scale(1 / unity.width(), 1 / unity.height())
        viewrect = self.graphicsView.viewport().rect()
        scenerect = self.graphicsView.transform().mapRect(rect_scene)
        factor = min(viewrect.width() / scenerect.width(), viewrect.height() / scenerect.height())
        self.graphicsView.scale(factor, factor)

    def draw_lines(self):
        cancha = QtGui.QPolygonF([
            QtCore.QPointF(721383.8266, -9678885.4514),
            QtCore.QPointF(721404.5488, -9678961.6564),
            QtCore.QPointF(721453.4389, -9678948.7816),
            QtCore.QPointF(721433.20288, -9678871.92070)])
        self.graphicsView.scene().addPolygon(cancha, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine))
        caseta = QtGui.QPolygonF([
            QtCore.QPointF(721455.8594, -9678925.4517),
            QtCore.QPointF(721492.5411, -9678915.2403),
            QtCore.QPointF(721498.3404, -9678937.9702),
            QtCore.QPointF(721461.7904, -9678947.5050)])
        self.graphicsView.scene().addPolygon(caseta, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine))

    def setupUi(self, MainWindow):
        "CENTRAL WIDGET"
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        MainWindow.setCentralWidget(self.centralwidget)
        MainWindow.resize(1000, 1000)

        "WEB MAP"
        self.w = QtWebEngineWidgets.QWebEngineView(self.centralwidget)
        self.w.setGeometry(QtCore.QRect(0, 0, 1000, 1000))

        "QGRAPCHISVIEW SCENE"
        self.graphicsView = GraphicsView(parent=self.centralwidget)
        self.scene = QtWidgets.QGraphicsScene(parent=self.graphicsView)
        self.graphicsView.setScene(self.scene)
        self.graphicsView.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
        self.graphicsView.setStyleSheet("background:transparent;")

        "Draw LINES"
        self.draw_lines()
        "ZOOM EXT"
        self.zoom_ext()

        "AGREAGR MAPA"
        self.m = folium.Map(tiles='http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}', attr='Google Satellite', zoomSnap=0.0, wheelDebounceTime=-1,
                            wheelPxPerZoomLevel=105, prefer_canvas=True, control_scale=True, max_zoom=100000,
                            zoomControl=False)
        sw, ne = (-2.903683906544795, -79.00835706455769), (-2.9026234455284583, -79.00729860799157)
        self.m.fit_bounds([sw, ne], padding=(0, 0))
        data = io.BytesIO()
        self.m.save(data, close_file=False)
        self.w.setHtml(data.getvalue().decode())

        "APLICAR FILTER"
        self.efc = eventFilterClass(sender=self.graphicsView.scene(), receiver=self.w, gv=self.graphicsView)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Question:

Is there any other way of accomplishing this?

1 Answers

It appears that the OP tries to create the events by taking custom parameters unnecessarily. In my solution I propose to just copy the parameters:

import io
import sys

from PyQt5 import QtGui, QtCore, QtWidgets, QtWebEngineWidgets

import folium


class ReplyEvents(QtCore.QObject):
    def __init__(self, sender, receiver, parent=None):
        super().__init__(parent)
        self._sender = sender
        self._receiver = receiver
        self._sender.installEventFilter(self)

    def eventFilter(self, obj, event):
        if obj is self._sender:
            new_event = None
            if event.type() in (
                QtCore.QEvent.MouseButtonPress,
                QtCore.QEvent.MouseButtonRelease,
                QtCore.QEvent.MouseButtonDblClick,
                QtCore.QEvent.MouseMove,
            ):
                new_event = QtGui.QMouseEvent(
                    event.type(),
                    event.pos(),
                    event.button(),
                    event.buttons(),
                    event.modifiers(),
                )

            elif event.type() == QtCore.QEvent.Wheel:
                new_event = QtGui.QWheelEvent(
                    event.pos(),
                    event.globalPosition(),
                    event.pixelDelta(),
                    event.angleDelta(),
                    event.buttons(),
                    event.modifiers(),
                    event.phase(),
                    event.inverted(),
                )
            if new_event:
                QtCore.QCoreApplication.postEvent(self._receiver, new_event)
        return super().eventFilter(obj, event)


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.scale_factor = 1.25
        self.pos_init_class = QtCore.QPointF()

        scene = QtWidgets.QGraphicsScene(self)
        self.setScene(scene)

        self.setStyleSheet("background:transparent;")
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

        self.setMouseTracking(True)

    def wheelEvent(self, event):
        if event.angleDelta().y() > 0:
            self.scale(self.scale_factor, self.scale_factor)
        else:
            self.scale(1 / self.scale_factor, 1 / self.scale_factor)
        super().wheelEvent(event)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = self.mapToScene(event.pos())
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ClosedHandCursor)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not self.pos_init_class.isNull():
            delta = self.pos_init_class - self.mapToScene(event.pos())
            r = self.mapToScene(self.viewport().rect()).boundingRect()
            self.setSceneRect(r.translated(delta))
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if not self.pos_init_class.isNull() and event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = QtCore.QPointF()
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
        super().mouseReleaseEvent(event)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.resize(1000, 1000)

        self.web_view = QtWebEngineWidgets.QWebEngineView(self)
        self.graphics_view = GraphicsView(self)

        geometry = QtCore.QRect(0, 0, 1000, 1000)

        self.web_view.setGeometry(geometry)
        self.graphics_view.setGeometry(geometry)

        self.draw_lines()
        self.zoom_ext()

        m = folium.Map(
            tiles="http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}",
            attr="Google Satellite",
            zoomSnap=0.0,
            wheelDebounceTime=-1,
            wheelPxPerZoomLevel=105,
            prefer_canvas=True,
            control_scale=True,
            max_zoom=100000,
            zoomControl=False,
        )

        sw, ne = (
            (-2.903683906544795, -79.00835706455769),
            (-2.9026234455284583, -79.00729860799157),
        )
        m.fit_bounds([sw, ne], padding=(0, 0))
        data = io.BytesIO()
        m.save(data, close_file=False)
        self.web_view.setHtml(data.getvalue().decode())

        r = ReplyEvents(self.graphics_view.viewport(), self.web_view.focusProxy(), self)

    def draw_lines(self):
        cancha = QtGui.QPolygonF(
            [
                QtCore.QPointF(721383.8266, -9678885.4514),
                QtCore.QPointF(721404.5488, -9678961.6564),
                QtCore.QPointF(721453.4389, -9678948.7816),
                QtCore.QPointF(721433.20288, -9678871.92070),
            ]
        )
        self.graphics_view.scene().addPolygon(
            cancha, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine)
        )
        caseta = QtGui.QPolygonF(
            [
                QtCore.QPointF(721455.8594, -9678925.4517),
                QtCore.QPointF(721492.5411, -9678915.2403),
                QtCore.QPointF(721498.3404, -9678937.9702),
                QtCore.QPointF(721461.7904, -9678947.5050),
            ]
        )
        self.graphics_view.scene().addPolygon(
            caseta, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine)
        )

    def zoom_ext(self):
        "Zoom extent"
        x_range, y_range, h_range, w_range = (
            self.graphics_view.scene().itemsBoundingRect().getRect()
        )
        rect_scene = QtCore.QRectF(x_range, y_range, h_range, w_range)
        self.graphics_view.setSceneRect(rect_scene)
        unity = self.graphics_view.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
        self.graphics_view.scale(1 / unity.width(), 1 / unity.height())
        viewrect = self.graphics_view.viewport().rect()
        scenerect = self.graphics_view.transform().mapRect(rect_scene)
        factor = min(
            viewrect.width() / scenerect.width(), viewrect.height() / scenerect.height()
        )
        self.graphics_view.scale(factor, factor)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())
Related