Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Drawing QGraphicsObject at Mouse Position



  • Dear Experts, I'am struggling with mouse position mapping to a QGraphicsScene and any help is highly appreciated.

    I've a custom QGraphicsScene class with a mouseMoveEvent emiting my mouse position like so:

    class MyScene(QGraphicsScene):
        """
        This is a custom QGraphicsScene implementation.
        """
        hoverOnSceneSignal = QtCore.pyqtSignal(QPointF)
        
        ....
        
        def mouseMoveEvent(self, event):
            self.hoverOnSceneSignal.emit(event.scenePos())
            super(MyScene, self).mouseMoveEvent(event)
    

    Then I have a custom Widget drawing and image on a custom QGraphicsView with MyScene attached. This widget listens to the signal hoverOnSceneSignal:

    class Viewer(QWidget):
        def __init__(self, parent=None):
            super(Viewer, self).__init__(parent=parent)
            self.myscene = MyScene(self)
            self.myview = MyView(self)
            self.myview.setScene(self.myscene)
            self._mousebrush = MouseBrushItem()
    
        def enterEvent(self, event):
            self.myscene.addItem(self._mousebrush)
            return super(Viewer, self).enterEvent(event)
    
        @QtCore.pyqtSlot(QPointF)
        def onHoverTriggered(self, pos):
            self._mousebrush.setPosition(pos)
    

    _mousebrush is a custom QGraphicsObject added on MyScene painting an ellipse. The goal is to draw an ellipse at my mouse position, which should result in a photoshop like brush.

    class MouseBrushItem(QGraphicsObject):
        def __init__(self):
            super(MouseBrushItem, self).__init__()
            self._size = 1
            self._x = 0
            self._y = 0
            self._color = None
            self._pen = None
            self._brush = None
            self.setColor(QColor(255, 255, 255, 255))
    
        def paint(self, painter, option, widget):
            rect = self.boundingRect()
            painter.setPen(self._pen)
            painter.setBrush(self._brush)
            painter.drawEllipse(rect)
    
        def boundingRect(self):
            return QRectF(self._x, self._y, self._size, self._size)
    
        def setColor(self, color):
            self._color = color
            self._pen = QPen(self._color, 1)
            self._brush = QBrush(QColor(self._color.red(), self._color.green(), self._color.blue(), 40))
    
        def setSize(self, size):
            self._size = size
    
        def setPosition(self, pos):
            self._x = pos.x()
            self._y = pos.y()
            self.setPos(pos)
    

    Unfortunately I'am not able to draw the circle sticked to my mouse. Everything works so far but the ellipse is running away from my mouse. I tried to play with mapFromWorld, mapToWorld, mapFromScene, mapToScene with no success. Seem I misunderstand anything important. Would be really thankful for comments helping me to draw a circle at my mouse if it moves on my QGraphicsScene.

    Many Thanks and best regards


  • Banned

    Hi @patrice079 while I cannot help you with your specific problem as you currently have it outlined (see below) I can help you with a few issues I noted within the code you presented.

    1. Unless you fully understand super( ) and what it was meant to handle and what pitfalls it has I would strongly suggest that you do not use it -- yes many unknowing coders and lazy coders are using it prolifically but that does not make it a good thing. The fact is that the issues it causes are more frequent than the rare case that it resolves -- and most newbie coders are never even going to run into that rare case as often as they are the issues of using super( ) further super makes your code actually more complex than using the explicit simpler version as follows (just one example of explicit vs super in your code)
    class Viewer(QWidget):
        def __init__(self, parent=None):
            QWidget.__init__(self, parent=parent)
    

    Also when import you ought not Import whole libraries just the functions you are using thus if you were to have imported as follows:

    from PyQt5.QtCore import pyqtSignal, pyqtSlot
    

    Your coded version of that would look like this

    hoverOnSceneSignal = pyqtSignal(QPointF)
    ...
    @pyqtSlot(QPointF)
    

    And you would not have the entirety of the QtCore library pulled into your working code which appears to be what you have done with the other PyQt Objects you are using.

    Lastly if you were to create a MRE (Minimal Reproducible Example) I am sure some of us would be able to answer your question even faster -- just by testing and playing with it -- I for one can figure some things out without even knowing how to do them by just playing with working code



  • @Denni-0 Many Thanks for your good advices, I will take the time reading more about super. To be honest, I saw it in examples and consequently copied it expecting that it is the modern version of QWidget.init(self, parent=parent). I will also try to extract my problem to make the code small enough to post . Thought what I posted might be enough to see where my errors are.



  • @Denni-0 Here is a minimal code example, the question is how do I correctly draw the circle at the mouse position and get the coodinates on my pixmap for drawing? Can anyone help me with this issue?

    import sys
    from PyQt5.QtCore import Qt, QRectF, QPointF
    from PyQt5.QtGui import QPixmap, QTransform, QBrush, QColor, QPen
    from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy, QSpacerItem, QGraphicsObject
    
    
    class MouseBrushObject(QGraphicsObject):
        def __init__(self):
            QGraphicsObject.__init__(self)
            self._size = 10
            self._x = 0
            self._y = 0
            self._pen = None
            self._brush = None
            self._color = None
            self.setColor(QColor(255, 0, 0, 255))
    
        def paint(self, painter, option, widget):
            rect = self.boundingRect()
            painter.setPen(self._pen)
            painter.setBrush(self._brush)
            painter.drawEllipse(rect)
    
        def boundingRect(self):
            return QRectF(self._x, self._y, self._size, self._size)
    
        def setColor(self, color):
            self._color = color
            self._pen = QPen(self._color, 1)
            self._brush = QBrush(QColor(self._color.red(), self._color.green(), self._color.blue(), 40))
    
        def setSize(self, size):
            self._size = size
    
        def setPosition(self, pos):
            print(f"brush pos: {pos.x()}, {pos.y()}")
            self._x = pos.x()-self._size/2
            self._y = pos.y()-self._size/2
            self.setPos(QPointF(self._x, self._y))
    
    
    class View(QGraphicsView):
        def __init__(self, parent=None):
            QGraphicsView.__init__(self, parent=parent)
            self.setMouseTracking(True)
            self.scene = QGraphicsScene(self)
            self.setScene(self.scene)
            pixmap = QPixmap(800, 440)
            self.scene.addItem(QGraphicsPixmapItem(pixmap))
            self.setTransform(QTransform().scale(1, 1).rotate(0))
            self.scene.setBackgroundBrush(QBrush(Qt.lightGray))
            self._brushItem = MouseBrushObject()
    
        def mouseMoveEvent(self, event):
            pos = event.pos()
            #pos = self.mapToScene(pos)
            #pos = self.mapFromScene(pos)
            #pos = self.mapToGlobal(pos)
            #pos = self.mapFromGlobal(self.mapToGlobal(pos))
            #pos = self.mapToGlobal(self.mapFromGlobal(pos))
            #pos = self.mapToGlobal(self.mapFromScene(pos))
            self._brushItem.setPosition(pos)
    
        def enterEvent(self, event):
            self.scene.addItem(self._brushItem)
            return super(View, self).enterEvent(event)
    
        def leaveEvent(self, event):
            self.scene.removeItem(self._brushItem)
            return super(View, self).leaveEvent(event)
    
    
    class Viewer(QWidget):
        def __init__(self, parent=None):
            QWidget.__init__(self, parent=parent)
    
            layout = QVBoxLayout()
            self.view = View(self)
            self.setLayout(layout)
    
            layout.addWidget(self.view)
            layout.addStretch(1)
    
    
    class MainWindow(QMainWindow):
    
        def __init__(self):
            QMainWindow.__init__(self)
            self.viewer = Viewer(self)
            self.viewer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
    
            layout = QVBoxLayout()
            layout.addWidget(self.viewer)
            centralwidget = QWidget(self)
            centralwidget.setLayout(layout)
            self.setCentralWidget(centralwidget)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = MainWindow()
        main.show()
        sys.exit(app.exec_())
    

  • Banned

    Okay @patrice079 I will look this over and see if I can give you any pointers but a BIG word of warning -- there are numerous arguments out there for and against using super( ) and if you dig into them you might find them a bit confusing -- in a nutshell what I eventually learned reading them all over is that -- super( ) was designed for a specific purpose that rarely happens in most programs (except the extremely complicated ones perhaps) however using super carries with it a different set of issues that actually happens more frequently than the specific normal issue. Still for some reason folks have been propagating super( ) like it was a hot fix while it is actually a very specific fix for a rare situation and it ought not be used without full understanding of what problems it creates when using it. Still one of the defenses for it is it is easier to use (aka lazy coder) however I do not see how it is easier than being explicit since most programs do not even capitalize on the lazy aspect of it.


Log in to reply