Мое приложение падает при вызове подключения для сигнала - PullRequest
2 голосов
/ 09 мая 2019

Я пытаюсь создать приложение, которое позволяет вам интерактивно создавать линии.Щелкните левой кнопкой мыши, чтобы создать новую точку, щелкните правой кнопкой мыши, чтобы завершить создание линии.По какой-то причине, когда я нажимаю правую кнопку мыши, программа закрывается без ошибок.

Пытался удалить как можно больше лишнего кода.Все, что было обнаружено, это то, что программа попадает на строку, помеченную "CRUSH ON THIS LINE"

UPD: Интересно, что если класс MVGraphicsLinkTest не наследуется от QGraphicsObject, а удалить decorator @pyqtSlot () из update_point_positions (сам), то все работает без ошибок.

Я не понимаю, почему

# -*- coding: utf-8 -*-

from PyQt5.QtWidgets import (QGraphicsPathItem, QGraphicsObject, QApplication, 
                                QGraphicsView, QGraphicsScene, QGraphicsItem)
from PyQt5.QtCore import Qt, pyqtSignal, QMarginsF, QRectF, QPointF, pyqtSlot
from PyQt5.QtGui import (QPainter, QPainterPath, QPainterPathStroker, QColor, 
                                                                QPen, QBrush)

class MVGraphicsLinkTest(QGraphicsPathItem, QGraphicsObject):

    def __init__(self, scene):
        super().__init__()
        scene.addItem(self)

        self.setFlag(QGraphicsItem.ItemIsFocusable, True)
        self.setAcceptHoverEvents(True)
        # lines width
        self._width = 10

        self._color = QColor(0, 100, 0, 255)
        self._brush = QBrush(self._color)
        self.setBrush(self._brush)
        self.setPen(QPen(Qt.NoPen))
        # stroker
        self._path_stroker = QPainterPathStroker()
        self._path_stroker.setWidth(self._width)
        self._path_stroker.setCapStyle(Qt.RoundCap)
        self._path_stroker.setJoinStyle(Qt.RoundJoin)
        self._path_stroker.setDashPattern(Qt.SolidLine)
        # init first point
        self.nodes = [MVGraphicsLinkNode(QPointF(10,10))]
        self.points = [QPointF(10,10)]
        # init dummy point
        self._dummy_mode = True
        self._dummy_node = MVGraphicsLinkNode(QPointF(10,10), parent=self)
        self._dummy_point = QPointF(10,10)

        self.update_path()

        self.grabMouse()

    def color(self):
        return self._color

    def update_path(self):
        self.prepareGeometryChange()
        for i, point in enumerate(self.points):
            if i==0:
                self._path = QPainterPath(point)
            else:
                self._path.lineTo(point)
        if self._dummy_mode:
            self._path.lineTo(self._dummy_point)
        self.setPath(self._path_stroker.createStroke(self._path))

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            # builds line
            if self._dummy_mode:
                self.points.append(e.pos())
                self.nodes.append(self._dummy_node)
                self._dummy_node = MVGraphicsLinkNode(e.pos(), parent=self)

        if e.button() == Qt.RightButton:
            # exit dummy mode and saving points
            if self._dummy_mode:
                self._dummy_mode = False
                self._dummy_point = None
                self.scene().removeItem(self._dummy_node)
                self._dummy_node = None
                self.connect_nodes()
                self.update_path()
                self.ungrabMouse()
                if len(self.points)<2:
                    self.delete_self()
        super().mousePressEvent(e)
        e.accept()

    def mouseMoveEvent(self, e):
        if self._dummy_mode:
            self._dummy_point = e.pos()
            self._dummy_node.setPos(self._dummy_point)
            self.update_path()
        super().mouseMoveEvent(e)

    def connect_nodes(self):
        # connecting node signals
        for i, node in enumerate(self.nodes):
            node.set_index(i)
            ###############################################################
            # CRUSH ON THIS LINE ##########################################
            ###############################################################
            node.change_position_signal.connect(self.update_point_positions)
            node.delete_signal.connect(self.delete_node)

    def disconnect_nodes(self):
        # disconnecting node signals
        for node in self.nodes:
            node.change_position_signal.disconnect()
            node.delete_signal.disconnect()

    def delete_node(self, index):
        if len(self.points)>2:
            self.disconnect_nodes()
            self.points.pop(index)
            del_node = self.nodes.pop(index)
            self.scene().removeItem(del_node)
            self.connect_nodes()
            self.update_path()

            print('scene items: {0}'.format(len(self.scene().items())))

    @pyqtSlot()
    def update_point_positions(self):
        for i, node in enumerate(self.nodes):
            self.points[i] = node.scenePos()
        self.update_path()

class MVGraphicsLinkNode(QGraphicsObject):
    change_position_signal = pyqtSignal()
    delete_signal = pyqtSignal(int)
    def __init__(self, pos, movable=True, parent=None):
        super().__init__(parent=parent)
        self.setPos(pos)
        self._movable = movable
        self._index = -1

        self.setFlag(QGraphicsItem.ItemIsMovable, self._movable)
        self.setFlag(QGraphicsItem.ItemIsSelectable, self._movable)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True)
        self.setAcceptHoverEvents(True)
        self.setEnabled(self._movable)

        self._hover = False
        self._size = 12

        self._color = QColor(200, 200, 50, 200)
        if parent:
            self._color = parent.color()
        self._color_sel = QColor(self._color)
        self._color_sel.setAlpha(100)

        self._brush1 = QBrush(self._color_sel)
        self._brush2 = QBrush(self._color)
        self._pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)

    def set_index(self, index):
        self._index = index
        self.setToolTip('index = {0}'.format(self._index))

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemScenePositionHasChanged and self.scene():
            self.change_position_signal.emit()
        return super().itemChange(change, value)

    def size(self):
        if self._hover:
            return 2*self._size
        else:
            return self._size

    def boundingRect(self):
        return QRectF(-self._size, -self._size,
                                                2*self._size, 2*self._size)

    def shape(self):
        path = QPainterPath()
        path.addEllipse(QPointF(), self.size()/2, self.size()/2)
        return path

    def paint(self, painter, option, widget=None):
        painter.setBrush(self._brush1)
        painter.setPen(self._pen)
        if self._hover or self.isSelected():
            painter.drawEllipse(QPointF(), self._size, self._size)
        painter.setBrush(self._brush2)
        painter.drawEllipse(QPointF(), self._size/2, self._size/2)

    def hoverEnterEvent(self, e):
        self._hover = self._movable
        super().hoverLeaveEvent(e)

    def hoverLeaveEvent(self, e):
        self._hover = False
        super().hoverLeaveEvent(e)

    def mouseDoubleClickEvent(self, e):
        print('delete sender: {0}'.format(self._index))
        self.delete_signal.emit(self._index)
        super().mouseDoubleClickEvent(e)


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)

    view = QGraphicsView()
    view.setDragMode(QGraphicsView.RubberBandDrag)
    scene = QGraphicsScene()
    m = QMarginsF(30, 30, 30, 30)
    scene_rect = QRectF(0, 0, 600, 600)
    scene.setSceneRect(scene_rect.marginsAdded(m))
    view.setScene(scene)
    view.setRenderHint(QPainter.Antialiasing)
    view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)

    MVGraphicsLinkTest(scene)

    view.show()

    sys.exit(app.exec_())

...