Add ports to the QGraphicsPixmapItem object

纵然是瞬间 提交于 2019-12-01 02:13:59

Using a QGraphicsPixmapItem as a basis for making connections is a bad choice because it is difficult to detect where the terminals are, instead I propose to use a QGraphicsPathItem that has as children to QGraphicsLineItem which may be the connection nodes. For the implementation I based on my old answer.

from PyQt5 import QtCore, QtGui, QtWidgets


class Edge(QtWidgets.QGraphicsLineItem):
    def __init__(self, source, dest, parent=None):
        super().__init__(parent)
        self.source = source
        self.dest = dest
        self.source.addEdge(self)
        self.dest.addEdge(self)
        self.setPen(QtGui.QPen(QtCore.Qt.red, 1.75))
        self.adjust()

    def adjust(self):
        self.prepareGeometryChange()
        self.setLine(QtCore.QLineF(self.dest.end(), self.source.end()))


class TerminalItem(QtWidgets.QGraphicsLineItem):
    def __init__(self, line, parent=None):
        super().__init__(line, parent)
        self.setAcceptHoverEvents(True)
        self.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.transparent), 4))
        self.edges = []

    def addEdge(self, edge):
        self.edges.append(edge)

    def mousePressEvent(self, event):
        event.accept()

    def mouseMoveEvent(self, event):
        event.accept()

    def end(self):
        return self.mapToScene(self.line().p1())

    def hoverEnterEvent(self, event):
        self.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.red), 4))

    def hoverLeaveEvent(self, event):
        self.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.transparent), 4))


class PortItem(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.end_ports = []
        self.setFlags(
            QtWidgets.QGraphicsItem.ItemIsMovable
            | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
        )
        self.create_path()
        self.create_end_ports()

    def create_path(self):
        raise NotImplementedError

    def create_end_ports(self):
        raise NotImplementedError

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
            for ports in self.end_ports:
                for edge in ports.edges:
                    edge.adjust()
        return super().itemChange(change, value)


class ANDPortItem(PortItem):
    def create_path(self):
        path = QtGui.QPainterPath()
        s = 20
        path.moveTo(0, 1)
        path.lineTo(2, 1)
        path.lineTo(2, 0)
        path.lineTo(6, 0)
        path.arcTo(QtCore.QRectF(4, 0, 4, 4), 90, -180)
        path.lineTo(2, 4)
        path.lineTo(2, 1)
        path.moveTo(2, 3)
        path.lineTo(0, 3)
        path.moveTo(8, 2)
        path.lineTo(10, 2)
        tr = QtGui.QTransform()
        tr.scale(s, s)
        path = tr.map(path)
        self.setPath(path)

    def create_end_ports(self):
        s = 20
        for p1, p2 in (
            ((0, 1), (0.5, 1)),
            ((0, 3), (0.5, 3)),
            ((10, 2), (9.5, 2)),
        ):
            item = TerminalItem(
                QtCore.QLineF(s * QtCore.QPointF(*p1), s * QtCore.QPointF(*p2)),
                self,
            )
            self.end_ports.append(item)


class NANDPortItem(ANDPortItem):
    def create_path(self):
        path = QtGui.QPainterPath()
        s = 20
        path.moveTo(0, 1)
        path.lineTo(2, 1)
        path.lineTo(2, 0)
        path.lineTo(6, 0)
        path.arcTo(QtCore.QRectF(4, 0, 4, 4), 90, -180)
        path.lineTo(2, 4)
        path.lineTo(2, 1)
        path.moveTo(2, 3)
        path.lineTo(0, 3)
        path.moveTo(8, 2)
        path.addEllipse(QtCore.QRectF(8, 2 - 0.25, 0.5, 0.5))
        path.moveTo(8.5, 2)
        path.lineTo(10, 2)
        tr = QtGui.QTransform()
        tr.scale(s, s)
        path = tr.map(path)
        self.setPath(path)


class BulBItem(PortItem):
    def create_path(self):
        s = 20
        path = QtGui.QPainterPath()
        path.moveTo(0, 2)
        path.lineTo(1, 2)
        path.addEllipse(QtCore.QRectF(1, 0, 4, 4))
        tr = QtGui.QTransform()
        tr.scale(s, s)
        path = tr.map(path)
        self.setPath(path)

    def create_end_ports(self):
        s = 20
        item = TerminalItem(
            QtCore.QLineF(
                s * QtCore.QPointF(0, 2), s * QtCore.QPointF(0.75, 2)
            ),
            self,
        )
        self.end_ports.append(item)


class ClockItem(PortItem):
    def create_path(self):
        s = 20
        path = QtGui.QPainterPath()
        path.moveTo(0, 2)
        path.lineTo(1, 2)
        path.addRect(QtCore.QRectF(1, 0, 4, 4))
        tr = QtGui.QTransform()
        tr.scale(s, s)
        path = tr.map(path)
        self.setPath(path)

    def create_end_ports(self):
        s = 20
        item = TerminalItem(
            QtCore.QLineF(
                s * QtCore.QPointF(0, 2), s * QtCore.QPointF(0.75, 2)
            ),
            self,
        )
        self.end_ports.append(item)


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, scene=None, parent=None):
        super().__init__(scene, parent)
        self.setRenderHints(QtGui.QPainter.Antialiasing)
        self.line_item = QtWidgets.QGraphicsLineItem()
        self.line_item.setPen(QtGui.QPen(QtGui.QColor("black"), 4))
        self.scene().addItem(self.line_item)
        self.line_item.hide()

        self.start_item = None

    def mousePressEvent(self, event):
        items = self.items(event.pos())
        for item in items:
            if isinstance(item, TerminalItem):
                item.setPen(QtGui.QPen(QtCore.Qt.red, 4))
                self.line_item.show()
                sp = item.end()
                line = QtCore.QLineF(sp, sp)
                self.line_item.setLine(line)
                self.start_item = item
                break
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.line_item.isVisible():
            l = self.line_item.line()
            l.setP2(self.mapToScene(event.pos()))
            self.line_item.setLine(l)
            for item in self.items():
                if (
                    isinstance(item, TerminalItem)
                    and item is not self.start_item
                ):
                    item.setPen(QtGui.QPen(QtCore.Qt.transparent, 4))

            for item in self.items(event.pos()):
                if isinstance(item, TerminalItem):
                    item.setPen(QtGui.QPen(QtCore.Qt.red, 4))
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self.line_item.isVisible():
            self.line_item.hide()
            for item in self.items():
                if isinstance(item, TerminalItem) and item:
                    item.setPen(QtGui.QPen(QtCore.Qt.transparent, 4))
            end_item = None
            for item in self.items(event.pos()):
                if isinstance(item, TerminalItem):
                    end_item = item
                    break
            if end_item is not None:
                edge = Edge(self.start_item, end_item)
                self.scene().addItem(edge)
                self.start_item = None
        super().mouseReleaseEvent(event)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        scene = QtWidgets.QGraphicsScene()
        graphicsview = GraphicsView(scene)
        for t, p in (
            (ANDPortItem, QtCore.QPointF(0, 0)),
            (BulBItem, QtCore.QPointF(250, 0)),
            (ClockItem, QtCore.QPointF(0, 250)),
            (NANDPortItem, QtCore.QPointF(250, 250)),
        ):
            item = t()
            item.setPos(p)
            scene.addItem(item)
        self.setCentralWidget(graphicsview)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!