I'm experiencing strange behaviour when I attempt to register mouse hover events on a QGraphicsPathItem.
In the example code (which I hacked from the qt5 elastic nodes example) I draw a simple curve. Hovering over the curve will change the colour of the curve but the hover events aren't registered on the lower right half of the curve. Additionally, only the top left half of the curve actually changes colour.
I added a QPainterPathStroker thinking that the hover events would register within this area but this is not the case.
My goal is to have mouse movements into, within and out of the QPainterPathStroker area register as hover events on the QGraphicsPathItem.
Any help is appreciated. Thanks
from PySide import QtCore, QtGui
class Edge(QtGui.QGraphicsPathItem):
def __init__(self):
QtGui.QGraphicsPathItem.__init__(self)
self.setAcceptsHoverEvents(True)
path = QtGui.QPainterPath()
x1 = -100
x2 = 120
y1 = -100
y2 = 120
dx = abs(x1-x2)/2
dy = abs(y1-y2)/2
a = QtCore.QPointF(x1, y1)
b = QtCore.QPointF(x1+dx, y1)
c = QtCore.QPointF(x2-dy, y2)
d = QtCore.QPointF(x2, y2)
path.moveTo(a)
path.cubicTo(b,c,d)
self.setPath(path)
self.hover = False
def hoverEnterEvent(self, event):
self.hover = True
QtGui.QGraphicsPathItem.hoverEnterEvent(self, event)
def hoverMoveEvent(self, event):
QtGui.QGraphicsPathItem.hoverMoveEvent(self, event)
def hoverLeaveEvent(self, event):
self.hover = False
QtGui.QGraphicsPathItem.hoverLeaveEvent(self, event)
def boundingRect(self):
return QtCore.QRectF(-100,-100,120,120)
def paint(self, painter, option, widget):
if self.hover:
c = QtCore.Qt.red
else:
c = QtCore.Qt.black
painter.setPen(QtGui.QPen(c, 10, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawPath(self.path())
painter.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawPath(self.shape())
def shape(self):
s = QtGui.QPainterPathStroker()
s.setWidth(30)
s.setCapStyle(QtCore.Qt.RoundCap)
path = s.createStroke(self.path())
return path
class GraphWidget(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
scene = QtGui.QGraphicsScene(self)
scene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex)
scene.setSceneRect(-200, -200, 400, 400)
self.setScene(scene)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
self.edge = Edge()
scene.addItem(self.edge)
self.scale(0.8, 0.8)
self.setMinimumSize(400, 400)
self.setWindowTitle(self.tr("Elastic Nodes"))
def wheelEvent(self, event):
self.scaleView(math.pow(2.0, -event.delta() / 240.0))
def drawBackground(self, painter, rect):
sceneRect = self.sceneRect()
rightShadow = QtCore.QRectF(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height())
bottomShadow = QtCore.QRectF(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5)
if rightShadow.intersects(rect) or rightShadow.contains(rect):
painter.fillRect(rightShadow, QtCore.Qt.darkGray)
if bottomShadow.intersects(rect) or bottomShadow.contains(rect):
painter.fillRect(bottomShadow, QtCore.Qt.darkGray)
gradient = QtGui.QLinearGradient(sceneRect.topLeft(), sceneRect.bottomRight())
gradient.setColorAt(0, QtCore.Qt.white)
gradient.setColorAt(1, QtCore.Qt.lightGray)
painter.fillRect(rect.intersect(sceneRect), QtGui.QBrush(gradient))
painter.setBrush(QtCore.Qt.NoBrush)
painter.drawRect(sceneRect)
def scaleView(self, scaleFactor):
factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
if factor < 0.07 or factor > 100:
return
self.scale(scaleFactor, scaleFactor)
def mouseMoveEvent(self, event):
self.edge.update()
QtGui.QGraphicsView.mouseMoveEvent(self, event)
widget = GraphWidget()
widget.show()
The problem in this case is the boundingRect()
that does not cover the complete path()
, and this is used by the paint()
method, the solution is to return self.shape().boundingRect()
:
class Edge(QtGui.QGraphicsPathItem):
def __init__(self):
QtGui.QGraphicsPathItem.__init__(self)
self.setAcceptsHoverEvents(True)
path = QtGui.QPainterPath()
x1 = -100
x2 = 120
y1 = -100
y2 = 120
dx = abs(x1-x2)/2
dy = abs(y1-y2)/2
a = QtCore.QPointF(x1, y1)
b = a + QtCore.QPointF(dx, 0)
d = QtCore.QPointF(x2, y2)
c = d - QtCore.QPointF(dy, 0)
path.moveTo(a)
path.cubicTo(b,c,d)
self.setPath(path)
self.hover = False
def hoverEnterEvent(self, event):
QtGui.QGraphicsPathItem.hoverEnterEvent(self, event)
self.hover = True
self.update()
def hoverMoveEvent(self, event):
# print(event)
QtGui.QGraphicsPathItem.hoverMoveEvent(self, event)
def hoverLeaveEvent(self, event):
QtGui.QGraphicsPathItem.hoverLeaveEvent(self, event)
self.hover = False
self.update()
def boundingRect(self):
return self.shape().boundingRect()
def paint(self, painter, option, widget):
c = QtCore.Qt.red if self.hover else QtCore.Qt.black
painter.setPen(QtGui.QPen(c, 10, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawPath(self.path())
painter.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawPath(self.shape())
def shape(self):
s = QtGui.QPainterPathStroker()
s.setWidth(30)
s.setCapStyle(QtCore.Qt.RoundCap)
path = s.createStroke(self.path())
return path