Reputation: 53
I'm writing an application in PyQt that will allow users to select
images placed on a QGraphicsScene
(using a custom
QGraphicsPixmapItem
). Upon selection I would like a rotation handle to
appear on the image that the user can "grab" with the mouse and
rotate, thus rotating the QGraphicsPixmapItem
. Basically, I am looking
for the rotation handle feature you get in PowerPoint upon selecting a
shape. This seems like a really basic feature that many people would
have implemented, but I cannot find any good examples on the web. Can
anyone point me in the right direction?
Upvotes: 4
Views: 3103
Reputation: 10860
Let's first divide the problem into smaller ones and then assemble everything again. I use PyQt5 in this solution.
1. Rotate a QGraphicsItem
For that you need to set a rotation angle in degree using setRotation
on the item. The rotation will be around the point specified by setTransformOriginPoint
. Usually one would take the center of a shape. If you do not specify this point, usually the upper, left corner of a shape is taken.
2. Drag a QGraphicsItem
For performance reasons QGraphicsItems aren't movable and do not send position changes to the event framework. By setting the appropriate flags QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges
you can change that. Additionally QGraphicsItem
does not inherit from QObject
, so for using signals I usually have an additional object which inherits from QObject.
3. Draw a handle item and determine the rotation angle to rotate
In the example below I have a very small rectangle as handle, you can of course use any QGraphicsItem you like. My method make_GraphicsItem_draggable
takes any QGraphicsItem derived class and makes it draggable. For determining the rotation angle given the current position of the draggable handle item and the transformation origin of the item to be rotated use math.atan2
and the differences in x
and y
coordinates of these positions.
Example
import math
from PyQt5 import QtCore, QtWidgets
class DraggableGraphicsItemSignaller(QtCore.QObject):
positionChanged = QtCore.pyqtSignal(QtCore.QPointF)
def __init__(self):
super().__init__()
def make_GraphicsItem_draggable(parent):
class DraggableGraphicsItem(parent):
def __init__(self, *args, **kwargs):
"""
By default QGraphicsItems are not movable and also do not emit signals when the position is changed for
performance reasons. We need to turn this on.
"""
parent.__init__(self, *args, **kwargs)
self.parent = parent
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges)
self.signaller = DraggableGraphicsItemSignaller()
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
self.signaller.positionChanged.emit(value)
return parent.itemChange(self, change, value)
return DraggableGraphicsItem
def rotate_item(position):
item_position = item.transformOriginPoint()
angle = math.atan2(item_position.y() - position.y(), item_position.x() - position.x()) / math.pi * 180 - 45 # -45 because handle item is at upper left border, adjust to your needs
print(angle)
item.setRotation(angle)
DraggableRectItem = make_GraphicsItem_draggable(QtWidgets.QGraphicsRectItem)
app = QtWidgets.QApplication([])
scene = QtWidgets.QGraphicsScene()
item = scene.addRect(0, 0, 100, 100)
item.setTransformOriginPoint(50, 50)
handle_item = DraggableRectItem()
handle_item.signaller.positionChanged.connect(rotate_item)
handle_item.setRect(-40, -40, 20, 20)
scene.addItem(handle_item)
view = QtWidgets.QGraphicsView(scene)
view.setFixedSize(300, 200)
view.show()
app.exec_()
Start (item = big rectangle and handle = small rectangle)
Rotated after dragging the handle (small rectangle)
One thing that is missing: The handle does not have a fixed distance to the item position (i.e. instead of moving in a circle you can drag it further away or closer). While this does not change the rotation angle, it does not look perfect. But the main points are covered here and should set you on the right track.
Upvotes: 4