def initialize(self) -> None: """ Method to initialize the display of this item :return: None """ op = self.sceneBoundingRect().center() self.setTransformOriginPoint(op) self.setRotation(self.angle) cx, cy = self.center r1, r2 = self.height / 2, self.width / 2 # Draw major axis major_axis = QGraphicsLineItem(-r2, 0, r2, 0) major_axis.setPos(cx, cy) major_axis.setParentItem(self) # Draw minor axis minor_axis = QGraphicsLineItem(-r1, 0, r1, 0) minor_axis.setPos(cx, cy) minor_axis.setParentItem(self) minor_axis.setRotation(90) rect = EditingRectangle(self.x, self.y, self.center[0], self.center[1], self.width, self.height) rect.setTransformOriginPoint(rect.sceneBoundingRect().center()) rect.setRotation(self.angle) self.indicators.extend([ major_axis, minor_axis, ]) self.edit_rect = rect self.rect = self.boundingRect() self.edit_rect.activate(False) self.setEnabled(False)
def __init__(self, name, surname, birth, death, mom, dad, photo, scene): self.circle = callbackEllipse(-40, -40, 80, 80) self.name = name self.circle.name = name self.surname = surname self.circle.surname = surname humans[name + " " + surname] = self self.birth = birth self.circle.birth = birth self.death = death self.circle.death = death self.circle.mom = mom self.circle.dad = dad self.photo = photo self.family = [] self.edges = [] self.scene = scene self.circle.setFlag(QGraphicsItem.isWidget(self.circle)) self.circle.setFlag(QGraphicsItem.ItemIsMovable) self.circle.setToolTip(self.name + " " + self.surname) if mom != "" and mom in humans and mom != name + " " + surname: self.family.append(humans[mom]) humans[mom].family.append(self) line = QGraphicsLineItem(QLineF(self.circle.pos(), humans[mom].circle.pos())) line.setFlag(QGraphicsLineItem.ItemIsMovable) self.scene.addItem(line) self.edges.append(line) humans[mom].edges.append(line) line.setParentItem(self.circle) if dad != "" and dad in humans and dad != name + " " + surname: self.family.append(humans[dad]) humans[dad].family.append(self) line = QGraphicsLineItem(QLineF(self.circle.pos(), humans[dad].circle.pos())) line.setFlag(QGraphicsLineItem.ItemIsMovable) self.scene.addItem(line) self.edges.append(line) humans[dad].edges.append(line) self.scene.addItem(self.circle)
def onMouseMove_draw(self, imageview, event): self._navIntr.onMouseMove_default(imageview, event) o = imageview.scene().data2scene.map( QPointF(imageview.oldX, imageview.oldY)) n = imageview.scene().data2scene.map(QPointF(imageview.x, imageview.y)) # Draw temporary line for the brush stroke so the user gets feedback before the data is really updated. pen = QPen(QBrush(self._brushingCtrl._brushingModel.drawColor), self._brushingCtrl._brushingModel.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) line = QGraphicsLineItem(o.x(), o.y(), n.x(), n.y()) line.setPen(pen) imageview.scene().addItem(line) line.setParentItem(imageview.scene().dataRectItem) self._lineItems.append(line) self._brushingCtrl._brushingModel.moveTo(imageview.mousePos)
def onMouseMove_draw(self, imageview, event): self._navIntr.onMouseMove_default(imageview, event) o = imageview.scene().data2scene.map(QPointF(imageview.oldX, imageview.oldY)) n = imageview.scene().data2scene.map(QPointF(imageview.x, imageview.y)) # Draw temporary line for the brush stroke so the user gets feedback before the data is really updated. pen = QPen( QBrush(self._brushingCtrl._brushingModel.drawColor), self._brushingCtrl._brushingModel.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin, ) line = QGraphicsLineItem(o.x(), o.y(), n.x(), n.y()) line.setPen(pen) imageview.scene().addItem(line) line.setParentItem(imageview.scene().dataRectItem) self._lineItems.append(line) self._brushingCtrl._brushingModel.moveTo(imageview.mousePos)
class AbstractSliceTool(QGraphicsObject): """Summary Attributes: angles (TYPE): Description FILTER_NAME (str): Description is_started (bool): Description manager (TYPE): Description part_item (TYPE): Description sgv (TYPE): Description vectors (TYPE): Description """ _RADIUS = styles.SLICE_HELIX_RADIUS _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS) FILTER_NAME = 'virtual_helix' # _CENTER_OF_HELIX = QPointF(0. 0.) """Abstract base class to be subclassed by all other pathview tools.""" def __init__(self, manager): """Summary Args: manager (TYPE): Description """ super(AbstractSliceTool, self).__init__(parent=manager.viewroot) """ Pareting to viewroot to prevent orphan _line_item from occuring """ self.sgv = None self.manager = manager self._active = False self._last_location = None self._line_item = QGraphicsLineItem(self) self._line_item.hide() self._vhi = None self.hide() self.is_started = False self.angles = [math.radians(x) for x in range(0, 360, 30)] self.vectors = self.setVectors() self.part_item = None # end def ######################## Drawing ####################################### def setVectors(self): """Summary Returns: TYPE: Description """ rad = self._RADIUS return [ QLineF(rad, rad, rad * (1. + 2. * math.cos(x)), rad * (1. + 2. * math.sin(x))) for x in self.angles ] # end def def setVirtualHelixItem(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ rad = self._RADIUS self._vhi = virtual_helix_item li = self._line_item li.setParentItem(virtual_helix_item) li.setLine(rad, rad, rad, rad) # li.setLine(0., 0., 0., 0.) # end def def resetTool(self): """Summary Returns: TYPE: Description """ self._line_item.setParentItem(self) def idNum(self): """Summary Returns: TYPE: Description """ if self._vhi is not None: return self._vhi.idNum() def setPartItem(self, part_item): """Summary Args: part_item (TYPE): Description Returns: TYPE: Description """ self.part_item = part_item # end def def boundingRect(self): """Required to prevent NotImplementedError() """ return QRectF() def eventToPosition(self, part_item, event): """take an event and return a position as a QPointF update widget as well Args: part_item (TYPE): Description event (TYPE): Description """ if self.is_started: return self.findNearestPoint(part_item, event.scenePos()) else: return event.pos() # end def def findNearestPoint(self, part_item, target_scenepos): """ Args: part_item (TYPE): Description target_scenepos (TYPE): Description """ li = self._line_item pos = li.mapFromScene(target_scenepos) line = li.line() mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos) # Check if the click happened on the origin VH if mouse_point_vec.length() < self._RADIUS: # return part_item.mapFromScene(target_scenepos) return None angle_min = 9999 direction_min = None for vector in self.vectors: angle_new = mouse_point_vec.angleTo(vector) if angle_new < angle_min: direction_min = vector angle_min = angle_new if direction_min is not None: li.setLine(direction_min) return part_item.mapFromItem(li, direction_min.p2()) else: print("default point") line.setP2(pos) li.setLine(line) return part_item.mapFromItem(li, pos) # end def def findNextPoint(self, part_item, target_part_pos): """ Args: part_item (TYPE): Description target_part_pos (TYPE): Description """ li = self._line_item pos = li.mapFromItem(part_item, target_part_pos) for i, vector in enumerate(self.vectors): if vector.p2() == pos: return part_item.mapFromItem(li, self.vectors[i - 1].p2()) # origin VirtualHelixItem is overlapping destination VirtualHelixItem return part_item.mapFromItem(li, self.vectors[0].p2()) # end def def hideLineItem(self): """Summary Returns: TYPE: Description """ li = self._line_item li.setParentItem(self) line = li.line() line.setP2(self._CENTER_OF_HELIX) li.setLine(line) li.hide() self.is_started = False # end def def hoverMoveEvent(self, part_item, event): """Summary Args: part_item (TYPE): Description event (TYPE): Description Returns: TYPE: Description """ return self.eventToPosition(part_item, event) # end def def setActive(self, will_be_active, old_tool=None): """ Called by SliceToolManager.setActiveTool when the tool becomes active. Used, for example, to show/hide tool-specific ui elements. Args: will_be_active (TYPE): Description old_tool (None, optional): Description """ if self._active and not will_be_active: self.deactivate() self._active = will_be_active self.sgv = self.manager.window.slice_graphics_view if hasattr(self, 'getCustomContextMenu'): # print("connecting ccm") try: # Hack to prevent multiple connections self.sgv.customContextMenuRequested.disconnect() except: pass self.sgv.customContextMenuRequested.connect( self.getCustomContextMenu) # end def def deactivate(self): """Summary Returns: TYPE: Description """ if hasattr(self, 'getCustomContextMenu'): # print("disconnecting ccm") self.sgv.customContextMenuRequested.disconnect( self.getCustomContextMenu) self.sgv = None self.is_started = False self.hideLineItem() self._vhi = None self.part_item = None self.hide() self._active = False # end def def isActive(self): """Returns isActive """ return self._active
class AbstractSliceTool(QGraphicsObject): """Summary Attributes: angles (TYPE): Description FILTER_NAME (str): Description is_started (bool): Description manager (TYPE): Description part_item (TYPE): Description sgv (TYPE): Description vectors (TYPE): Description """ _RADIUS = styles.SLICE_HELIX_RADIUS _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS) FILTER_NAME = 'virtual_helix' # _CENTER_OF_HELIX = QPointF(0. 0.) """Abstract base class to be subclassed by all other pathview tools.""" def __init__(self, manager): """Summary Args: manager (TYPE): Description """ super(AbstractSliceTool, self).__init__(parent=manager.viewroot) """ Pareting to viewroot to prevent orphan _line_item from occuring """ self.sgv = None self.manager = manager self._active = False self._last_location = None self._line_item = QGraphicsLineItem(self) self._line_item.hide() self._vhi = None self.hide() self.is_started = False self.angles = [math.radians(x) for x in range(0, 360, 30)] self.vectors = self.setVectors() self.part_item = None self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self) self.vhi_hint_item.setPen(_MOD_PEN) self.vhi_hint_item.setZValue(styles.ZPARTITEM) # end def ######################## Drawing ####################################### def setVectors(self): """Summary Returns: TYPE: Description """ rad = self._RADIUS return [QLineF(rad, rad, rad*(1. + 2.*math.cos(x)), rad*(1. + 2.*math.sin(x)) ) for x in self.angles] # end def def setVirtualHelixItem(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ rad = self._RADIUS self._vhi = virtual_helix_item li = self._line_item li.setParentItem(virtual_helix_item) li.setLine(rad, rad, rad, rad) # li.setLine(0., 0., 0., 0.) # end def def setSelectionFilter(self, filter_name_list): if 'virtual_helix' in filter_name_list: self.vhi_hint_item.setPen(_MOD_PEN) else: self.vhi_hint_item.setPen(_INACTIVE_PEN) # end def def resetTool(self): """Summary Returns: TYPE: Description """ self._line_item.setParentItem(self) def idNum(self): """Summary Returns: TYPE: Description """ if self._vhi is not None: return self._vhi.idNum() def setPartItem(self, part_item): """Summary Args: part_item (TYPE): Description Returns: TYPE: Description """ self.vhi_hint_item.setParentItem(part_item) self.part_item = part_item # end def def boundingRect(self): """Required to prevent NotImplementedError() """ return QRectF() def eventToPosition(self, part_item, event): """take an event and return a position as a QPointF update widget as well Args: part_item (TYPE): Description event (TYPE): Description """ if self.is_started: pos = self.findNearestPoint(part_item, event.scenePos()) else: pos = event.pos() self.vhi_hint_item.setPos( pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) return pos # end def def setHintPos(self, pos): self.vhi_hint_item.setPos( pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) # end def def findNearestPoint(self, part_item, target_scenepos): """ Args: part_item (TYPE): Description target_scenepos (TYPE): Description """ li = self._line_item pos = li.mapFromScene(target_scenepos) line = li.line() mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos) # Check if the click happened on the origin VH if mouse_point_vec.length() < self._RADIUS: # return part_item.mapFromScene(target_scenepos) return None angle_min = 9999 direction_min = None for vector in self.vectors: angle_new = mouse_point_vec.angleTo(vector) if angle_new < angle_min: direction_min = vector angle_min = angle_new if direction_min is not None: li.setLine(direction_min) return part_item.mapFromItem(li, direction_min.p2()) else: print("default point") line.setP2(pos) li.setLine(line) return part_item.mapFromItem(li, pos) # end def def findNextPoint(self, part_item, target_part_pos): """ Args: part_item (TYPE): Description target_part_pos (TYPE): Description """ li = self._line_item pos = li.mapFromItem(part_item, target_part_pos) for i, vector in enumerate(self.vectors): if vector.p2() == pos: return part_item.mapFromItem(li, self.vectors[i - 1].p2()) # origin VirtualHelixItem is overlapping destination VirtualHelixItem return part_item.mapFromItem(li, self.vectors[0].p2()) # end def def hideLineItem(self): """Summary Returns: TYPE: Description """ self.vhi_hint_item.hide() li = self._line_item li.hide() li.setParentItem(self) line = li.line() line.setP2(self._CENTER_OF_HELIX) li.setLine(line) # li.hide() self.is_started = False # end def # def hoverEnterEvent(self, event): # self.vhi_hint_item.show() # #print("Slice VHI hoverEnterEvent") # # def hoverMoveEvent(self, event): # # print("Slice VHI hoverMoveEvent") # def hoverLeaveEvent(self, event): # # self.vhi_hint_item.hide() # #print("Slice VHI hoverLeaveEvent") def hoverMoveEvent(self, part_item, event): """Summary Args: part_item (TYPE): Description event (TYPE): Description Returns: TYPE: Description """ # self.vhi_hint_item.setPos( event.pos()- # QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) pos = self.eventToPosition(part_item, event) return pos # end def def setActive(self, will_be_active, old_tool=None): """ Called by SliceToolManager.setActiveTool when the tool becomes active. Used, for example, to show/hide tool-specific ui elements. Args: will_be_active (TYPE): Description old_tool (None, optional): Description """ if self._active and not will_be_active: self.deactivate() self._active = will_be_active self.sgv = self.manager.window.slice_graphics_view if hasattr(self, 'getCustomContextMenu'): # print("connecting ccm") try: # Hack to prevent multiple connections self.sgv.customContextMenuRequested.disconnect() except: pass self.sgv.customContextMenuRequested.connect(self.getCustomContextMenu) # end def def deactivate(self): """Summary Returns: TYPE: Description """ if hasattr(self, 'getCustomContextMenu'): # print("disconnecting ccm") self.sgv.customContextMenuRequested.disconnect(self.getCustomContextMenu) self.sgv = None self.is_started = False self.hideLineItem() self._vhi = None self.part_item = None self.hide() self._active = False # end def def isActive(self): """Returns isActive """ return self._active
class AbstractGridTool(AbstractTool): _RADIUS = styles.GRID_HELIX_RADIUS _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS) FILTER_NAME = 'virtual_helix' # _CENTER_OF_HELIX = QPointF(0. 0.) """Abstract base class to be subclassed by all other pathview tools.""" def __init__(self, manager: AbstractToolManager): """Summary Args: manager (TYPE): Description """ # Setting parent to viewroot to prevent orphan _line_item from occurring super(AbstractGridTool, self).__init__(parent=manager.viewroot) self.slice_graphics_view = manager.window.grid_graphics_view self.manager = manager self._active = False self._last_location = None self._line_item = QGraphicsLineItem(self) self._line_item.hide() self._vhi = None self.hide() self.is_started = False self.angles = [math.radians(x) for x in range(0, 360, 30)] self.vectors = self.setVectors() self.part_item = None self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self) self.vhi_hint_item.setPen(_MOD_PEN) self.vhi_hint_item.setZValue(styles.ZPARTITEM) # end def ######################## Drawing ####################################### def setVectors(self): """Summary Returns: TYPE: Description """ rad = self._RADIUS return [ QLineF(rad, rad, rad * (1. + 2. * math.cos(x)), rad * (1. + 2. * math.sin(x))) for x in self.angles ] # end def def setVirtualHelixItem(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.views.gridview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ rad = self._RADIUS self._vhi = virtual_helix_item li = self._line_item li.setParentItem(virtual_helix_item) li.setLine(rad, rad, rad, rad) # li.setLine(0., 0., 0., 0.) # end def def setSelectionFilter(self, filter_name_list): if 'virtual_helix' in filter_name_list: self.vhi_hint_item.setPen(_MOD_PEN) else: self.vhi_hint_item.setPen(_INACTIVE_PEN) # end def def resetTool(self): """Summary Returns: TYPE: Description """ self._line_item.setParentItem(self) def idNum(self): """Summary Returns: TYPE: Description """ if self._vhi is not None: return self._vhi.idNum() def setPartItem(self, part_item): """Summary Args: part_item (TYPE): Description Returns: TYPE: Description """ self.vhi_hint_item.setParentItem(part_item) self.part_item = part_item # end def def boundingRect(self): """Required to prevent NotImplementedError() """ return QRectF() def eventToPosition(self, part_item, event): """take an event and return a position as a QPointF update widget as well Args: part_item (TYPE): Description event (TYPE): Description """ if self.is_started: pos = self.findNearestPoint(part_item, event.scenePos()) else: pos = event.pos() self.vhi_hint_item.setPos(pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) return pos # end def def setHintPos(self, pos): self.vhi_hint_item.setPos(pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) # end def def findNearestPoint(self, part_item: QAbstractPartItem, target_scenepos: QPointF) -> QPointF: """ Args: part_item: Description target_scenepos: position in the Scene """ li = self._line_item pos = li.mapFromScene(target_scenepos) line = li.line() mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos) # Check if the click happened on the origin VH if mouse_point_vec.length() < self._RADIUS: # return part_item.mapFromScene(target_scenepos) return None angle_min = 9999 direction_min = None for vector in self.vectors: angle_new = mouse_point_vec.angleTo(vector) if angle_new < angle_min: direction_min = vector angle_min = angle_new if direction_min is not None: li.setLine(direction_min) return part_item.mapFromItem(li, direction_min.p2()) else: print("default point") line.setP2(pos) li.setLine(line) return part_item.mapFromItem(li, pos) # end def def findNextPoint(self, part_item: QAbstractPartItem, target_part_pos: QPointF) -> QPointF: """ Args: part_item: Description target_part_pos: Position in the Part """ li = self._line_item pos = li.mapFromItem(part_item, target_part_pos) for i, vector in enumerate(self.vectors): if vector.p2() == pos: return part_item.mapFromItem(li, self.vectors[i - 1].p2()) # origin VirtualHelixItem is overlapping destination VirtualHelixItem return part_item.mapFromItem(li, self.vectors[0].p2()) # end def def hideLineItem(self): """ Hide the ``_line_item`` and the ``vhi_hint_item`` set ``is_started`` to :bool:`False` """ # print("hideLineItem") self.vhi_hint_item.hide() li = self._line_item li.hide() li.setParentItem(self) line = li.line() line.setP2(self._CENTER_OF_HELIX) li.setLine(line) self.is_started = False # end def # def hoverEnterEvent(self, event): # self.vhi_hint_item.show() # #print("Grid VHI hoverEnterEvent") # # def hoverMoveEvent(self, event): # # print("Grid VHI hoverMoveEvent") # def hoverLeaveEvent(self, event): # # self.vhi_hint_item.hide() # #print("Grid VHI hoverLeaveEvent") def hoverMoveEvent(self, part_item, event): """Summary Args: part_item (TYPE): Description event (TYPE): Description Returns: TYPE: Description """ # self.vhi_hint_item.setPos( event.pos()- # QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) pos = self.eventToPosition(part_item, event) return pos # end def def setActive(self, will_be_active, old_tool=None): """ Called by GridToolManager.setActiveTool when the tool becomes active. Used, for example, to show/hide tool-specific ui elements. Args: will_be_active (TYPE): Description old_tool (None, optional): Description """ if self._active and not will_be_active: self.deactivate() self._active = will_be_active self.slice_graphics_view = self.manager.window.grid_graphics_view if hasattr(self, 'getCustomContextMenu'): # print("connecting ccm") try: # Hack to prevent multiple connections self.slice_graphics_view.customContextMenuRequested.disconnect( ) except (AttributeError, TypeError): pass self.slice_graphics_view.customContextMenuRequested.connect( self.getCustomContextMenu) # end def def deactivate(self): """Summary Returns: TYPE: Description """ if hasattr(self, 'getCustomContextMenu'): # print("disconnecting ccm") self.slice_graphics_view.customContextMenuRequested.disconnect( self.getCustomContextMenu) self.slice_graphics_view = None self.is_started = False self.hideLineItem() self._vhi = None self.part_item = None self.hide() self._active = False # end def def isActive(self) -> bool: """Returns isActive """ return self._active
class LineLabel(QGraphicsTextItem): nameChanged = pyqtSignal() def __init__(self, pos, parent=None): super(LineLabel, self).__init__() # initial text self.setPlainText("abc") # stores index of first point of segment self.index = None # distance from line segment self.gap = None # set graphical setting self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) self.setTextInteractionFlags(Qt.NoTextInteraction) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) # set center of text label at mouse pos self.setPos(pos - self.boundingRect().center()) self.setParentItem(parent) # add line item to test label self.line = QGraphicsLineItem() self.line.setParentItem(self) self.line.setPen(QPen(Qt.black, 2, Qt.SolidLine)) self.line.setFlag(QGraphicsItem.ItemStacksBehindParent) # reset position of line self.resetPos() self.values = defaultdict(lambda: 0) def paint(self, painter, option, widget): # draw ellipse shape painter.save() # save painter painter.setPen(QPen(Qt.black, 2, Qt.SolidLine)) # set pen to painter painter.setBrush(QBrush(Qt.white)) # set brush painter.drawEllipse(self.boundingRect()) # draw ellipse painter.restore() # restore painter as before drawing ellipse super(LineLabel, self).paint(painter, option, widget) def updateLabel(self): # finds new position on segment offset = self.gap # distance from segment points = self.parentItem().points # points of line firstPoint = points[self.index] # first point of segment on which label is attached endPoint = points[self.index + 1] # second point center = self.mapToParent(self.boundingRect().center()) newPos = center # if segment is vertical if firstPoint.x() == endPoint.x(): newPos.setX(firstPoint.x() + self.gap) if min(firstPoint.y(), endPoint.y()) > newPos.y(): newPos.setY(min(firstPoint.y(), endPoint.y())) elif newPos.y() > max(firstPoint.y(), endPoint.y()): newPos.setY(max(firstPoint.y(), endPoint.y())) # segment is horizontal elif firstPoint.y() == endPoint.y(): newPos.setY(firstPoint.y() + self.gap) if min(firstPoint.x(), endPoint.x()) > newPos.x(): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) # transfer center positon to top left newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) self.setPos(newPos) def resetPos(self): """ finds the segment of line on which mouse is clicked for adding label and resets it's position on that segment """ points = self.parentItem().points min_A = QPointF() min_B = QPointF() min_dis = math.inf for i in range(len(points) - 1): A = points[i] B = points[i + 1] C = QPointF(self.pos() + self.boundingRect().center()) BAx = B.x() - A.x() BAy = B.y() - A.y() CAx = C.x() - A.x() CAy = C.y() - A.y() length = math.sqrt(BAx * BAx + BAy * BAy) if BAx == 0: if not min(A.y(), B.y()) <= C.y() <= max(A.y(), B.y()): continue if BAy == 0: if not min(A.x(), B.x()) <= C.x() <= max(A.x(), B.x()): continue if length > 0: dis = (BAx * CAy - CAx * BAy) / length if abs(dis) < abs(min_dis): min_dis = dis min_A = A min_B = B self.index = i point = self.mapFromScene(min_A) if min_A.x() == min_B.x(): self.setPos(self.parentItem().mapFromScene(QPointF(min_A.x() + 10, self.y()))) self.gap = 10 + self.boundingRect().width() / 2 else: self.setPos(self.parentItem().mapFromScene(QPointF(self.x(), min_A.y() - 30))) self.gap = -30 + self.boundingRect().height() / 2 def itemChange(self, change, value): # if position of label changes if change == QGraphicsItem.ItemPositionChange and self.scene(): newPos = QPointF(value) # new position newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) # pos of center points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] if firstPoint.x() == endPoint.x(): if min(firstPoint.y(), endPoint.y()) > newPos.y(): newPos.setY(min(firstPoint.y(), endPoint.y())) elif newPos.y() > max(firstPoint.y(), endPoint.y()): newPos.setY(max(firstPoint.y(), endPoint.y())) elif firstPoint.y() == endPoint.y(): if min(firstPoint.x(), endPoint.x()) > newPos.x(): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) # change center pos to top-left return newPos if change == QGraphicsItem.ItemPositionHasChanged and self.scene(): self.updateGap() self.updateLine() return return super(LineLabel, self).itemChange(change, value) def updateGap(self): # updates distance of line from it's connection segment points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] firstPoint = self.mapFromParent(firstPoint) endPoint = self.mapFromParent(endPoint) center = self.boundingRect().center() if firstPoint.x() == endPoint.x(): self.gap = center.x() - firstPoint.x() else: self.gap = center.y() - firstPoint.y() def updateLine(self): # updates line item of label points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] point = self.mapFromParent(firstPoint) center = self.boundingRect().center() if firstPoint.x() == endPoint.x(): self.line.setLine(center.x(), center.y(), point.x(), center.y()) else: self.line.setLine(center.x(), center.y(), center.x(), point.y()) def mouseDoubleClickEvent(self, event): # set text editable self.setTextInteractionFlags(Qt.TextEditorInteraction) self.setFocus() super(LineLabel, self).mouseDoubleClickEvent(event) def focusOutEvent(self, event): super(LineLabel, self).focusOutEvent(event) self.setTextInteractionFlags(Qt.NoTextInteraction) # set text non interactive self.nameChanged.emit() def __getstate__(self): return { "text": self.toPlainText(), "index": self.index, "gap": self.gap, "pos": (self.pos().x(), self.pos().y()), "values": self.values } def __setstate__(self, dict): self.setPlainText(dict['text']) self.index = dict['index'] self.gap = dict['gap'] for key, value in dict['values'].items(): self.values[key] = value