I'm trying to mimick path editing similar to what you would see in photoshop, which interacts this way...
- You select the Path and it's Points become visible
- Users can click and drag any Point item of the Path, to adjust the Path
- When users click and drag the Path directly it moves the Path
- When the Path is deselected the Points become hidden again
Where I'm having issues are
- Making the Points hidden when the Path is deselected but not hidden when a Point of the selected spline is being Edited
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
selectionChangedsignal 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 theItemSceneChangeitem change: