Home > Blockchain >  How can I delete this QGraphicsLineItem when context menu is open in a QGraphicsPixmapItem?
How can I delete this QGraphicsLineItem when context menu is open in a QGraphicsPixmapItem?

Time:11-13

I'm developing a GUI where you can connect nodes between them except in a few special cases. This implementation works perfectly fine most of the time, but after some testing i found that, when I connect one QGraphicsPixmapItem with another through a QGraphicsLineItem, and the user opens the contextual menu before completing the link, the line get stuck, and it cannot be deleted.

The process to link two elements is to first press the element, then keep pressing while moving the line and releasing when the pointer is over the other element. This is achieved using mousePressEvent, mouseMoveEvent and mouseReleaseEvent, respectively.

This code is an example:

#!/usr/bin/env python3

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

import sys


class Ellipse(QGraphicsEllipseItem):
    def __init__(self, x, y):
        super(Ellipse, self).__init__(x, y, 30, 30)

        self.setBrush(QBrush(Qt.darkBlue))
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setZValue(100)

    def contextMenuEvent(self, event):
        menu = QMenu()

        first_action = QAction("First action")
        second_action = QAction("Second action")

        menu.addAction(first_action)
        menu.addAction(second_action)

        action = menu.exec(event.screenPos())


class Link(QGraphicsLineItem):
    def __init__(self, x, y):
        super(Link, self).__init__(x, y, x, y)

        self.pen_ = QPen()
        self.pen_.setWidth(2)
        self.pen_.setColor(Qt.red)

        self.setPen(self.pen_)

    def updateEndPoint(self, x2, y2):
        line = self.line()
        self.setLine(line.x1(), line.y1(), x2, y2)


class Scene(QGraphicsScene):
    def __init__(self):
        super(Scene, self).__init__()

        self.link = None
        self.link_original_node = None

        self.addItem(Ellipse(200, 400))
        self.addItem(Ellipse(400, 400))

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            item = self.itemAt(event.scenePos(), QTransform())
            if item is not None:
                self.link_original_node = item
                offset = item.boundingRect().center()
                self.link = Link(item.scenePos().x()   offset.x(), item.scenePos().y()   offset.y())
                self.addItem(self.link)

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        if self.link is not None:
            self.link.updateEndPoint(event.scenePos().x(), event.scenePos().y())

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        if self.link is not None:
            item = self.itemAt(event.scenePos(), QTransform())
            if isinstance(item, (Ellipse, Link)):
                self.removeItem(self.link)
                self.link_original_node = None
                self.link = None


class MainWindow(QMainWindow):
    def __init__(self):
        super(QMainWindow, self).__init__()

        self.scene = Scene()
        self.canvas = QGraphicsView()

        self.canvas.setScene(self.scene)
        self.setCentralWidget(self.canvas)

        self.setGeometry(500, 200, 1000, 600)
        self.setContextMenuPolicy(Qt.NoContextMenu)


app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())

How can I get rid off the line before/after the context menu event? I tried to stop them, but I do not know how.

CodePudding user response:

Assuming that the menu is only triggered from a mouse button press, the solution is to remove any existing link item in the mouseButtonPress too.

    def mousePressEvent(self, event):
        if self.link is not None:
            self.removeItem(self.link)
            self.link_original_node = None
            self.link = None
        # ...

Note that itemAt for very small items is not always reliable, as the item's shape() might be slightly off the mapped mouse position. Since the link would be removed in any case, just do the same in the mouseReleaseEvent():

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        if self.link is not None:
            item = self.itemAt(event.scenePos(), QTransform())
            if isinstance(item, Ellipse):
                # do what you need with the linked ellipses

            # note the indentation level
            self.removeItem(self.link)
            self.link_original_node = None
            self.link = None
  • Related