使用 QGraphicsPixmapItem 作为建立连接的基础是一个糟糕的选择,因为很难检测到终端的位置,相反我建议使用 QGraphicsPathItem 作为 QGraphicsLineItem 的子节点,它可能是连接节点。对于我基于我的旧答案的实施。
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_())