Search code examples
pythonpyqt5gesture

Zooming and Panning with Pinch and Swipe gesture in a QGraphicsView with PyQt5 on Mac and Windows


I need to implement a zooming for a QGraphicsView on a pinch gesture (with 2 fingers) and panning along the scene with a swipe gesture (also with 2 fingers) in PyQt5.15.7 on Windows and Mac (and optimaly working for Linux aswell). I tried to achieve this with the following code:

import sys

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

class View(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.zoom_in_factor = 1.25
        self.zoom = 10
        self.zoom_step = 1
        self.zoom_clamp = True
        self.zoom_range = [1, 10]
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)

        self.scene = QGraphicsScene()
        self.scene.setSceneRect(-5000, -5000, 10000, 10000)
        self.scene.addRect(0, 0, 100, 100, QPen(Qt.NoPen), QBrush(Qt.green))

        self.setScene(self.scene)

    def wheelEvent(self, event):
        if event.modifiers() == Qt.ControlModifier:
            # Check if mouse wheel up or down
            if event.angleDelta().y() > 0:
                zoom_factor = self.zoom_in_factor
                self.zoom += self.zoom_step
            else:
                zoom_factor = 1 / self.zoom_in_factor
                self.zoom -= self.zoom_step

            # Clamping
            clamped = False
            if self.zoom < self.zoom_range[0]:
                self.zoom = self.zoom_range[0]
                clamped = True
            if self.zoom > self.zoom_range[1]:
                self.zoom = self.zoom_range[1]
                clamped = True

            if not clamped or self.zoom_clamp is False:
                self.scale(zoom_factor, zoom_factor)

            self.scene.update()

        else:
            return super().wheelEvent(event)

app = QApplication([sys.argv])

view = View()
view.show()

app.exec()

This code works as I want to on my Windows laptop (Asus ROG Flow X13). When I do a swipe gesture it calls the super method and handles it accordingly and (I don't know why) on a pinch gesture PyQt thinks that I am holding control and therefore executes the zooming part of the code. However on a MacBook (as you would expect) the super method is called always (except when I hold down cmd). Therefore the zooming does not work with a pinch gesture there but the panning works.

I've tested usingQGestures to implement the zooming with this code:

import sys

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

class View(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.grabGesture(Qt.PinchGesture)
        self.grabGesture(Qt.SwipeGesture)

        self.scene = QGraphicsScene()
        self.scene.setSceneRect(-5000, -5000, 10000, 10000)
        self.scene.addRect(0, 0, 100, 100, QPen(Qt.NoPen), QBrush(Qt.green))

        self.setScene(self.scene)

    def event(self, event):
        if event.type() == QEvent.Gesture:
            return self.gestureEvent(QGestureEvent(event))
        return super().event(event)

    def gestureEvent(self, event):
        print("Gesture event")

        if event.gesture(Qt.PinchGesture):
            print("Pinch gesture")
            self.pinchTriggered(QPinchGesture(event.gesture(Qt.PinchGesture)))
        if event.gesture(Qt.SwipeGesture):
            print("Swipe gesture")
            self.swipeTriggered(QSwipeGesture(event.gesture(Qt.SwipeGesture)))

        print()
        return True

    def pinchTriggered(self, gesture):
        changeFlags = gesture.changeFlags()
        
        if changeFlags & QPinchGesture.ScaleFactorChanged:
            print("Scale factor changed", gesture.scaleFactor(), gesture.totalScaleFactor(), gesture.lastScaleFactor())

    def swipeTriggered(self, gesture):
        pass

app = QApplication([sys.argv])

view = View()
view.show()

app.exec()

While testing with this code the following things happend on the different platforms: On Windows no gestures are registered. On Mac only the the pinch gesture was registered. Therefore I tried realising the zooming on Mac with a pinch gesture trying a similar approach to the official PyQt docs. This did not seem to work because the scaleFactor, totalScaleFactor and totalScaleFactor of the QPinchGesture do not change when doing the pinch gesture. They always stays at 1.0.

Is it possible to fix this error or is there any other approach than QGestures to solve this problem?


Solution

  • I was able to achieve it by using QNativeGesture (ZoomNativeGesture)for MacOS and using the wheelEvent solution for Windows. Both using simimlar logic to realize the zooming