based on the code in this page, I took the liberty of adapting it to my needs, but now I'm stuck. I've added the ability to create squares from a button, I've also changed the location of the points so that they're not just on one side but above and below.
Now my problem is that I can't seal the line at one of the points. When I create the line and connect two points on two items, once I move one of the items or even both items, the connections change. If the line was connected to the point below, it changes to be connected to the point above. I'd like to seal the links regardless of movement.
Thank you for your help.
`import sys
from PyQt5 import QtWidgets, QtCore, QtGui
class CustomItem(QtWidgets.QGraphicsItem):
def __init__(self, pointONLeft=False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ellipseOnLeft = pointONLeft
self.point = None
self.endPoint = None
self.isStart = None
self.line = None
self.setAcceptHoverEvents(True)
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemSendsGeometryChanges)
self.lines = []
def addLine(self, line, isStart):
self.lines.append((line, isStart))
def itemChange(self, change, value):
if change == self.ItemPositionChange and self.scene():
self.moveLineToCenter(value)
return super(CustomItem, self).itemChange(change, value)
def moveLineToCenter(self, newPos):
for line, isStart in self.lines:
xOffset = 50
yOffsetStart = -5
yOffsetEnd = 105
yOffset = yOffsetStart if isStart else yOffsetEnd
newCenterPos = QtCore.QPointF(newPos.x() + xOffset, newPos.y() + yOffset)
p1 = newCenterPos if isStart else line.line().p1()
p2 = line.line().p2() if isStart else newCenterPos
line.setLine(QtCore.QLineF(p1, p2))
def containsPoint(self, pos):
topEllipse = self.mapToScene(QtCore.QRectF(45, -5, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
bottomEllipse = self.mapToScene(QtCore.QRectF(45, 95, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
if topEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill):
return "top"
elif bottomEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill):
return "bottom"
else:
return None
def boundingRect(self):
return QtCore.QRectF(-5, 0, 110, 110)
def paint(self, painter, option, widget):
pen = QtGui.QPen(QtCore.Qt.red)
pen.setWidth(2)
painter.setPen(pen)
painter.setBrush(QtGui.QBrush(QtGui.QColor(31, 176, 224)))
painter.drawRoundedRect(QtCore.QRectF(0, 0, 100, 100), 4, 4)
painter.setBrush(QtGui.QBrush(QtGui.QColor(214, 13, 36)))
painter.drawEllipse(QtCore.QRectF(45, -5, 10, 10))
painter.drawEllipse(QtCore.QRectF(45, 95, 10, 10))
def containsPoint(self, pos):
topEllipse = self.mapToScene(QtCore.QRectF(45, -5, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
bottomEllipse = self.mapToScene(QtCore.QRectF(45, 95, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
return topEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill) or bottomEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill)
class Scene(QtWidgets.QGraphicsScene):
def __init__(self, *args, **kwargs):
super(Scene, self).__init__(*args, **kwargs)
self.startPoint = None
self.endPoint = None
self.line = None
self.graphics_line = None
self.item1 = None
self.item2 = None
self.isStart = None
def mousePressEvent(self, event):
self.line = None
self.graphics_line = None
self.item1 = None
self.item2 = None
self.startPoint = None
self.endPoint = None
if self.itemAt(event.scenePos(), QtGui.QTransform()) and isinstance(self.itemAt(event.scenePos(), QtGui.QTransform()), CustomItem):
self.item1 = self.itemAt(event.scenePos(), QtGui.QTransform())
self.checkPoint1(event.scenePos())
if self.startPoint:
self.line = QtCore.QLineF(self.startPoint, self.endPoint)
self.graphics_line = self.addLine(self.line)
self.update_path()
super(Scene, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton and self.startPoint:
self.endPoint = event.scenePos()
self.update_path()
super(Scene, self).mouseMoveEvent(event)
def filterCollidingItems(self, items): # filters out all the colliding items and returns only instances of CustomItem
return [x for x in items if isinstance(x, CustomItem) and x != self.item1]
def mouseReleaseEvent(self, event):
if self.graphics_line:
self.checkPoint2(event.scenePos())
self.update_path()
if self.item2:
self.item1.addLine(self.graphics_line, True)
self.item2.addLine(self.graphics_line, False)
else:
self.removeItem(self.graphics_line)
self.graphics_line = None
super(Scene, self).mouseReleaseEvent(event)
def checkPoint1(self, pos):
if self.item1.containsPoint(pos):
self.item1.setFlag(self.item1.ItemIsMovable, False)
self.startPoint = self.endPoint = pos
else:
self.item1.setFlag(self.item1.ItemIsMovable, True)
def checkPoint2(self, pos):
item_lst = self.filterCollidingItems(self.graphics_line.collidingItems())
contains = False
if not item_lst: # checks if there are any items in the list
return
for self.item2 in item_lst:
if self.item2.containsPoint(pos):
contains = True
self.endPoint = pos
break
if not contains:
self.item2 = None
def update_path(self):
if self.startPoint and self.endPoint:
self.line.setP2(self.endPoint)
self.graphics_line.setLine(self.line)
if self.item1:
self.item1.moveLineToCenter(self.item1.pos())
def main():
app = QtWidgets.QApplication(sys.argv)
scene = Scene()
mainWindow = QtWidgets.QMainWindow()
mainWindow.setWindowTitle("Créateur de carrés")
mainWindow.resize(800, 600)
view = QtWidgets.QGraphicsView(scene)
view.setViewportUpdateMode(view.FullViewportUpdate)
view.setMouseTracking(True)
mainWindow.setCentralWidget(view)
menuBar = mainWindow.menuBar()
fileMenu = menuBar.addMenu("&File")
createAction = QtWidgets.QAction("&Créer Carré", mainWindow)
createAction.triggered.connect(lambda: createSquare(scene))
fileMenu.addAction(createAction)
mainWindow.show()
sys.exit(app.exec_())
def createSquare(scene):
item = CustomItem()
scene.addItem(item)
item.setPos(QtCore.QPointF(50, 50))
if __name__ == '__main__':
main()`