3

Inspired by the RectangleMapTool provided in Qgis documentation (https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/canvas.html#writing-custom-map-tools) , I made a CircleMapTool that draws a circle when moving cursor on the canvas : Drawing circle when cursor moves

I would like to detect mouse moves out of canvas, in order to avoid this situation (circle is "stucked" on canvas border) enter image description here

Code extract:

class CircleMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
        QgsMapToolEmitPoint.__init__(self, canvas)
        self.rubberBand = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry)
        self.rubberBand.setColor(QColor(255, 0, 0))
        self.rubberBand.setWidth(2)

    def canvasMoveEvent(self, e):
        # out of canvas events are not raised
        point = self.toMapCoordinates(e.pos())
        self.rubberBand.movePoint(point)

I would like to avoid QObject.eventFilter, if possible

3
  • I prefer that the point disappears
    – jsiot
    Commented Mar 6 at 7:32
  • 1
    Why don't you want to use eventFilter? it seems like a good way to achieve this.
    – Kalak
    Commented Mar 6 at 11:43
  • Also If you provide a code sample, it needs to be a working one, or close to it, to be able to reproduce the behavior. Either the current behavior or the intended one (working or not).
    – Kalak
    Commented Mar 6 at 13:57

1 Answer 1

3

I don't think this can be done without an eventFilter function (afaik).

If you are ok with eventFilter then this solution will work using a QEvent.HoverLeave detector, built like QgsFocusWatcher.

enter image description here

class QgsHoverWatcher(QObject):
    hoverEnter = pyqtSignal()
    hoverLeave = pyqtSignal()
    
    def __init__(self, parent):
        QObject.__init__(self)
        self._parent = parent
        self._parent.installEventFilter(self)

    def eventFilter(self, object, event):
        if not (object is self._parent):
            return False
        elif event.type() == QEvent.HoverEnter:
            self.hoverEnter.emit()
            return True
        elif event.type() == QEvent.HoverLeave:
            self.hoverLeave.emit()
            return True
        else:
            return False

    def __del__(self):
        while True:
            try:
                self.hoverEnter.disconnect()
            except TypeError:
                break
        while True:
            try:
                self.hoverLeave.disconnect()
            except TypeError:
                break
        self._parent.removeEventFilter(self)
        
        
class CircleMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
        QgsMapToolEmitPoint.__init__(self, canvas)
        self.rubber_band = QgsRubberBand(canvas, QgsWkbTypes.PointGeometry)
        self.rubber_band.setColor(Qt.red)
        self.rubber_band.setWidth(20)
        
        self.canvas_hover_watcher = QgsHoverWatcher(canvas)
        self.canvas_hover_watcher.hoverLeave.connect(self.reset)

    def reset(self):
        self.rubber_band.reset(QgsWkbTypes.PointGeometry)

    def canvasMoveEvent(self, e):
        point = self.toMapCoordinates(e.pos())
        if self.rubber_band.size() == 0:
            self.rubber_band.addPoint(point)
        else:
            self.rubber_band.movePoint(point)

    def deactivate(self):
        self.reset()
        self.canvas_hover_watcher.__del__()
        QgsMapTool.deactivate(self)

tool = CircleMapTool(iface.mapCanvas())
iface.mapCanvas().setMapTool(tool)

PS: The reason for not using the built-in QgsFocusWatcher is that, with focus, you need to click somewhere outside the map canvas instead of just moving your mouse cursor.

2
  • Nice job, thank you ! Actually, I didn't want to use eventFilter because I found it quite overkill, but in the end, why not
    – jsiot
    Commented Mar 8 at 8:58
  • Note : in my case, Qevent.HoverEvent/HoverLeave are not raised, I replaced them by QEvent.Enter/Leave (Qgis 3.34.1)
    – jsiot
    Commented Mar 11 at 10:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.