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

Question:

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?

Asked By: MariSchn

||

Answers:

Did you try to monitor the enum QEvent::Type via:

...
    def event(self, event: QEvent):
        print(f"Handling `event` ({event.type()})")  # << Print enum QEvent::Type Value
        if event.type() == QEvent.Type.Gesture:
            return self.gestureEvent(QGestureEvent(event))
        return super().event(event)
...

And then check which event it may be in this list – search for the number:

https://doc.qt.io/qt-6/qevent.html

I also found out your code doesn’t work for me. What works is this:

...
from PyQt5.QtGui import QNativeGestureEvent
from PyQt5.QtCore import Qt
...
    def event(self, event):
        if isinstance(event, QNativeGestureEvent) and event.gestureType() == Qt.NativeGestureType.ZoomNativeGesture:
            return self.zoomNativeEvent(event)
        return super().event(event)

    def zoomNativeEvent(self, event: QNativeGestureEvent):
        print(f"Pinch Gesture Event: pos{event.pos().x(), event.pos().y()} value({event.value()})")
        return super().event(event)
...

More infos you can find here:

Qt::ZoomNativeGesture: https://doc.qt.io/qt-5/qnativegestureevent.html#details

Qt::ZoomNativeGesture Constant Value: https://doc.qt.io/qt-5/qt.html#NativeGestureType-enum

Answered By: Dirk Schiller

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

Answered By: MariSchn
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.