I'm trying to mimick path editing similar to what you would see in photoshop, which interacts this way...
Where I'm having issues are
Here is what i have:
Here is a reference to something I'm trying to match:
import sys
import math
import random
from PySide2 import QtWidgets, QtGui, QtCore
# SETTINGS
handle_size = 16
handle_color = QtGui.QColor(40,130,230)
handle_radius = 8
class AnnotationPointItem(QtWidgets.QGraphicsEllipseItem):
def __init__(self, positionFlag=0, pos=QtCore.QPointF(), parent=None):
super(AnnotationPointItem, self).__init__(-handle_radius, -handle_radius, 2*handle_radius, 2*handle_radius, parent)
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges)
self.setPen(QtGui.QPen(handle_color, 4, QtCore.Qt.SolidLine))
self.setBrush(QtGui.QBrush(QtGui.QColor('white')))
self.positionFlag = positionFlag
def paint(self, painter, option, widget=None):
# Remove the selection outline
# if self.isSelected():
# option.state &= ~QtWidgets.QStyle.State_Selected
super(AnnotationPointItem, self).paint(painter, option, widget)
# def mousePressEvent(self, event):
# # Handle the event, but don't propagate to the parent
# # event.accept()
# print('clicked....')
# return super(AnnotationPointItem, self).mousePressEvent(event)
def itemChange(self, change, value):
# print(change, self.isSelected())
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
# print('ItemPositionChange')
pass
elif change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
# print('ItemPositionHasChanged')
parent = self.parentItem()
if parent:
# Get the position of the cursor in the view's coordinates
if self.positionFlag == 0:
parent.setPoints(start=self.pos())
elif self.positionFlag == 1:
parent.setPoints(end=self.pos())
elif change == QtWidgets.QGraphicsItem.ItemSelectedChange:
pass
return super(AnnotationPointItem, self).itemChange(change, value)
class AnnotationPathItem(QtWidgets.QGraphicsLineItem):
def __init__(self,
start=QtCore.QPointF(),
end=QtCore.QPointF(),
color=QtCore.Qt.green,
thickness=10,
parent=None):
super(AnnotationPathItem, self).__init__(start.x(), start.y(), end.x(), end.y(), parent)
self._color = color
self._thickness = thickness
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setPen(QtGui.QPen(self._color, self._thickness, QtCore.Qt.SolidLine))
# child items
self.startPointItem = AnnotationPointItem(positionFlag=0, parent=self)
self.startPointItem.hide()
self.startPointItem.setPos(self.line().p1())
self.endPointItem = AnnotationPointItem(positionFlag=1, parent=self)
self.endPointItem.hide()
self.endPointItem.setPos(self.line().p2())
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
self.selectionChanged(value)
return super(AnnotationPathItem, self).itemChange(change, value)
def selectionChanged(self, selected):
# Implement what you want to do when the selection changes
print(self.startPointItem.isSelected(), self.endPointItem.isSelected())
if selected or self.startPointItem.isSelected() or self.endPointItem.isSelected():
self.startPointItem.show()
self.endPointItem.show()
# else:
# self.startPointItem.hide()
# self.endPointItem.hide()
def paint(self, painter, option, widget=None):
# Remove the selection outline
if self.isSelected():
option.state &= ~QtWidgets.QStyle.State_Selected
super(AnnotationPathItem, self).paint(painter, option, widget)
def setPoints(self, start=None, end=None):
currentLine = self.line()
if start != None:
currentLine.setP1(start)
if end != None:
currentLine.setP2(end)
self.setLine(currentLine)
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(1200,1200)
self.scene = QtWidgets.QGraphicsScene(self)
self.scene.setBackgroundBrush(QtGui.QColor(40,40,40))
self.view = QtWidgets.QGraphicsView(self)
self.view.setSceneRect(-4000, -4000, 8000, 8000)
self.view.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
self.view.setMouseTracking(True)
self.view.setScene(self.scene)
self.addButton = QtWidgets.QPushButton("Add Annotation", self)
self.addButton.clicked.connect(self.add_annotation)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.addButton)
self.setLayout(layout)
# samples
item = AnnotationPathItem(QtCore.QPointF(-70, -150), QtCore.QPointF(150, -350))
self.scene.addItem(item)
def add_annotation(self):
r = random.randint(0,255)
g = random.randint(0,255)
b = random.randint(0,255)
color = QtGui.QColor(r,g,b)
startPos = QtCore.QPointF(random.randint(-200,200), random.randint(-200,200))
endPos = QtCore.QPointF(random.randint(-200,200), random.randint(-200,200))
item = AnnotationPathItem(startPos, endPos, color)
self.scene.addItem(item)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
You cannot achieve this by checking the selection changes of the item, because when an item gets selected upon mouse press, the view has already deselected all the other.
Instead, you can use the selectionChanged
signal of the scene, which is emitted when all selection changes are completed, and decide whether to show items or not. Since items are usually not created with a scene at first, you have to connect to the signal upon the ItemSceneChange
item change:
def itemChange(self, change, value):
if change == QGraphicsItem.ItemSceneChange:
if self.scene():
self.scene().selectionChanged.disconnect(
self.updateChildSelection)
if value:
value.selectionChanged.connect(self.updateChildSelection)
return super(AnnotationPathItem, self).itemChange(change, value)
def updateChildSelection(self):
selection = set(self.scene().selectedItems())
showItems = bool(
set((self, self.startPointItem, self.endPointItem)) & selection
)
self.startPointItem.setVisible(showItems)
self.endPointItem.setVisible(showItems)