0

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()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241

2 Answers2

3

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

enter image description here

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
0

With PySide2, there are two issues with the code above:

In Edge.__init__ instead of

self.setAcceptsHoverEvents(True)

use:

self.setAcceptHoverEvents(True)

(without 's' in the method name).

In GraphWidget.drawBackground, instead of

painter.fillRect(rect.intersects(sceneRect), QBrush(gradient))

use one of

painter.fillRect(rect.intersected(sceneRect), QBrush(gradient))

('ed' instead of 's' in the method name) or

painter.fillRect(rect & sceneRect, QBrush(gradient))
rengel
  • 453
  • 1
  • 5
  • 12