class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (TYPE): Description idx (int): the base index within the virtual helix is_fwd (TYPE): Description prexoveritemgroup (TYPE): Description to_vh_id_num (TYPE): Description """ def __init__(self, from_virtual_helix_item, is_fwd, from_index, to_vh_id_num, prexoveritemgroup, color): """Summary Args: from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_fwd (TYPE): Description from_index (TYPE): Description to_vh_id_num (TYPE): Description prexoveritemgroup (TYPE): Description color (TYPE): Description """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, color, self) self._phos_item = Triangle(FWDPHOS_PP, self) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, to_vh_id_num, prexoveritemgroup, color) # end def def shutdown(self): """Summary Returns: TYPE: Description """ self.setBrush(getBrushObj(self._color, alpha=0)) self.to_vh_id_num = None self.adapter.resetAnimations() phos = self._phos_item phos.adapter.resetAnimations() phos.resetTransform() phos.setPos(0, 0) self.setAcceptHoverEvents(False) self.setFlag(KEYINPUT_ACTIVE_FLAG, False) self.hide() # end def def resetItem(self, from_virtual_helix_item, is_fwd, from_index, to_vh_id_num, prexoveritemgroup, color): """Summary Args: from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_fwd (TYPE): Description from_index (TYPE): Description to_vh_id_num (TYPE): Description prexoveritemgroup (TYPE): Description color (TYPE): Description Returns: TYPE: Description """ self.setParentItem(from_virtual_helix_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self.idx = from_index self.is_fwd = is_fwd self.to_vh_id_num = to_vh_id_num self._color = color self.prexoveritemgroup = prexoveritemgroup self._bond_item.hide() self._label_txt = lbt = None if to_vh_id_num is None else str( to_vh_id_num) self.setLabel(text=lbt) self._label.resetItem(is_fwd, color) phos = self._phos_item bonditem = self._bond_item if is_fwd: phos.setPath(FWDPHOS_PP) phos.setTransformOriginPoint(0, phos.boundingRect().center().y()) phos.setPos(0.5 * BASE_WIDTH, BASE_WIDTH) phos.setPen(getNoPen()) phos.setBrush(getBrushObj(color)) bonditem.setPen(getPenObj(color, styles.PREXOVER_STROKE_WIDTH)) self.setPos(from_index * BASE_WIDTH, -BASE_WIDTH) else: phos.setPath(REVPHOS_PP) phos.setTransformOriginPoint(0, phos.boundingRect().center().y()) phos.setPos(0.5 * BASE_WIDTH, 0) phos.setPen(getPenObj(color, 0.25)) phos.setBrush(getNoBrush()) bonditem.setPen( getPenObj(color, styles.PREXOVER_STROKE_WIDTH, penstyle=Qt.DotLine, capstyle=Qt.RoundCap)) self.setPos(from_index * BASE_WIDTH, 2 * BASE_WIDTH) if to_vh_id_num is not None: inactive_alpha = PROX_ALPHA self.setBrush(getBrushObj(color, alpha=inactive_alpha)) else: self.setBrush(getBrushObj(color, alpha=0)) self.show() # end def def getInfo(self): """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) ### ACCESSORS ### def color(self): """Summary Returns: TYPE: Description """ return self._color def remove(self): """Summary Returns: TYPE: Description """ scene = self.scene() self.adapter.destroy() if scene: scene.removeItem(self._label) self._label = None self._phos_item.adapter.resetAnimations() self._phos_item.adapter = None scene.removeItem(self._phos_item) self._phos_item = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event): """Only if enableActive(True) is called hover and key events disabled by default Args: event (TYPE): Description """ self.setFocus(Qt.MouseFocusReason) self.prexoveritemgroup.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) # end def def hoverLeaveEvent(self, event): """Summary Args: event (TYPE): Description Returns: TYPE: Description """ self.prexoveritemgroup.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") # end def def keyPressEvent(self, event): """Summary Args: event (TYPE): Description Returns: TYPE: Description """ self.prexoveritemgroup.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text=None, outline=False): """Summary Args: text (None, optional): Description outline (bool, optional): Description Returns: TYPE: Description """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item, property_name, duration, start_value, end_value): """Summary Args: item (TYPE): Description property_name (TYPE): Description duration (TYPE): Description start_value (TYPE): Description end_value (TYPE): Description Returns: TYPE: Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active (bool): whether or not the PreXoverItem is parented to the active VirtualHelixItem """ if is_active: self.setBrush(getBrushObj(self._color, alpha=128)) self.animate(self, 'brush_alpha', 1, 0, 128) # overwrite running anim # if self.to_vh_id_num is not None: self.animate(self._phos_item, 'rotation', 500, 0, -90) else: inactive_alpha = 0 if self.to_vh_id_num is None else PROX_ALPHA self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha) self.animate(self._phos_item, 'rotation', 500, -90, 0) # end def def enableActive(self, is_active, to_vh_id_num=None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active (TYPE): Description to_vh_id_num (None, optional): Description """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) self.setBrush(getBrushObj(self._color, alpha=0)) else: self.setLabel(text=str(to_vh_id_num)) inactive_alpha = PROX_ALPHA self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha) self.setFlag(KEYINPUT_ACTIVE_FLAG, True) else: self.setBrush(getNoBrush()) # self.setLabel(text=None) self.setAcceptHoverEvents(False) self.setFlag(KEYINPUT_ACTIVE_FLAG, False) def activateNeighbor(self, active_prexoveritem, shortcut=None): """To be called with whatever the active_prexoveritem is for the parts `active_base` Args: active_prexoveritem (TYPE): Description shortcut (None, optional): Description """ p1 = self._phos_item.scenePos() p2 = active_prexoveritem._phos_item.scenePos() scale = 3 delta1 = -BASE_WIDTH * scale if self.is_fwd else BASE_WIDTH * scale delta2 = BASE_WIDTH * scale if active_prexoveritem.is_fwd else -BASE_WIDTH * scale c1 = self.mapFromScene(QPointF(p1.x(), p1.y() + delta1)) c2 = self.mapFromScene(QPointF(p2.x(), p2.y() - delta2)) pp = QPainterPath() pp.moveTo(self._phos_item.pos()) pp.cubicTo(c1, c2, self._bond_item.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() alpha = 32 idx, active_idx = self.idx, active_prexoveritem.idx if self.is_fwd != active_prexoveritem.is_fwd: if idx == active_idx: alpha = 255 elif idx == active_idx + 1: alpha = 255 elif idx == active_idx - 1: alpha = 255 inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0 self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 500, inactive_alpha, alpha) self.animate(self._phos_item, 'rotation', 500, 0, -90) self.setLabel(text=shortcut, outline=True) # end def def deactivateNeighbor(self): """Summary Returns: TYPE: Description """ if self.isVisible(): inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0 self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha) self.animate(self._phos_item, 'rotation', 500, -90, 0) self._bond_item.hide() self.setLabel(text=self._label_txt)
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn Attributes: is_forward (TYPE): Description """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): """Summary Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): from vhi xover_item (TYPE): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self.is_forward = strand3p.strandSet().isForward() self._is_on_top = self.is_forward self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, is_forward, idx_x, idx_y): """ Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_forward (TYPE): Description idx_x (TYPE): Description idx_y (TYPE): Description """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._idx = idx_x self._is_on_top = self.is_forward = True if is_forward else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = self.is_forward = strand3p.strandSet().isForward() self.updatePositionAndAppearance() # end def def configurePath(self): """Summary Returns: TYPE: Description """ self._path_thing.setBrush(getBrushObj(_PENCIL_COLOR)) path = PPR3 if self.is_forward else PPL3 offset = -_BASE_WIDTH if self.is_forward else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self.is_forward else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): """Summary Returns: TYPE: Description """ self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ self._partner_virtual_helix = virtual_helix_item # end def def idx(self): """Summary Returns: TYPE: Description """ return self._idx # end def def virtualHelixItem(self): """Summary Returns: TYPE: Description """ return self._vhi # end def def point(self): """Summary Returns: TYPE: Description """ return self._vhi.upperLeftCornerOfBaseType(self._idx, self.is_forward) # end def def floatPoint(self): """Summary Returns: TYPE: Description """ pt = self.pos() return pt.x(), pt.y() # end def def isForward(self): """Summary Returns: TYPE: Description """ return self.is_forward # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. Args: is_from_strand (bool, optional): Description """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self.is_forward else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def remove(self): """Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """Called by updatePositionAndAppearance during init. Updates drawing and position of the label. Args: is_left (TYPE): Description """ lbl = self._label if self._idx is not None: bw = _BASE_WIDTH num = self._partner_virtual_helix.idNum() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl is None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.idNum())) lbl.show() # end if # end def def hideItems(self): """Summary Returns: TYPE: Description """ if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn Attributes: is_forward (TYPE): Description """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): """Summary Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): from vhi xover_item (TYPE): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self.is_forward = strand3p.strandSet().isForward() self._is_on_top = self.is_forward self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, is_forward, idx_x, idx_y): """ Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_forward (TYPE): Description idx_x (TYPE): Description idx_y (TYPE): Description """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._idx = idx_x self._is_on_top = self.is_forward = True if is_forward else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = self.is_forward = strand3p.strandSet().isForward() self.updatePositionAndAppearance() # end def def configurePath(self): """Summary Returns: TYPE: Description """ self._path_thing.setBrush(getBrushObj(_PENCIL_COLOR)) path = PPR3 if self.is_forward else PPL3 offset = -_BASE_WIDTH if self.is_forward else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self.is_forward else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): """Summary Returns: TYPE: Description """ self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ self._partner_virtual_helix = virtual_helix_item # end def def idx(self): """Summary Returns: TYPE: Description """ return self._idx # end def def virtualHelixItem(self): """Summary Returns: TYPE: Description """ return self._vhi # end def def point(self): """Summary Returns: TYPE: Description """ return self._vhi.upperLeftCornerOfBaseType(self._idx, self.is_forward) # end def def floatPoint(self): """Summary Returns: TYPE: Description """ pt = self.pos() return pt.x(), pt.y() # end def def isForward(self): """Summary Returns: TYPE: Description """ return self.is_forward # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. Args: is_from_strand (bool, optional): Description """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self.is_forward else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def remove(self): """Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """Called by updatePositionAndAppearance during init. Updates drawing and position of the label. Args: is_left (TYPE): Description """ lbl = self._label if self._idx is not None: bw = _BASE_WIDTH num = self._partner_virtual_helix.idNum() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl is None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.idNum())) lbl.show() # end if # end def def hideItems(self): """Summary Returns: TYPE: Description """ if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self.setPen(_NO_PEN) self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, strand_type, idx_x, idx_y): """ """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._strand_type = strand_type self._idx = idx_x self._is_on_top = self._is_drawn_5_to_3 = True if idx_y == 0 else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self.updatePositionAndAppearance() # end def def strandType(self): return self._strand_type # end def def configurePath(self): self._path_thing.setBrush(QBrush(styles.RED_STROKE)) path = PPR3 if self._is_drawn_5_to_3 else PPL3 offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): self._partner_virtual_helix = virtual_helix_item # end def def idx(self): return self._idx # end def def virtualHelixItem(self): return self._vhi # end def def point(self): return self._vhi.upperLeftCornerOfBaseType(self._idx, self._strand_type) # end def def floatPoint(self): pt = self.pos() return pt.x(), pt.y() # end def def isOnTop(self): return self._is_on_top # end def def isDrawn5to3(self): return self._is_drawn_5_to_3 # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def updateConnectivity(self): is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) # end def def remove(self): """ Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText( str(self._partner_virtual_helix.number()) ) lbl.show() # end if # end def def hideItems(self): if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class InsertionItem(QGraphicsPathItem): """ This is just the shape of the Insert item """ def __init__(self, virtual_helix_item, strand, insertion): super(InsertionItem, self).__init__(virtual_helix_item) self.hide() self._strand = strand self._insertion = insertion self._seq_item = QGraphicsPathItem(parent=self) self._is_on_top = is_on_top = strand.isForward() y = 0 if is_on_top else _BW self.setPos(_BW*insertion.idx(), y) self.setZValue(styles.ZINSERTHANDLE) self._initLabel() self._initClickArea() self.updateItem() self.show() # end def def _initLabel(self): """Display the length of the insertion.""" self._label = InsertionLabel(self) self._seq_item = QGraphicsPathItem(parent=self) self._seq_text = None self.show() # end def def _initClickArea(self): """docstring for _initClickArea""" self._clickArea = cA = QGraphicsRectItem(_DEFAULT_RECT, self) cA.setPen(_NO_PEN) cA.mousePressEvent = self.mousePressEvent cA.mouseDoubleClickEvent = self.mouseDoubleClickEvent # end def ### PUBLIC SUPPORT METHODS ### def remove(self): """ Called from the following stranditem methods: strandRemovedSlot strandInsertionRemovedSlot refreshInsertionItems """ scene = self.scene() self._label.setTextInteractionFlags(Qt.NoTextInteraction) self._label.clearFocus() scene.removeItem(self._label) self._label = None scene.removeItem(self._seq_item) self._seq_item = None scene.removeItem(self) self._insertion = None self._strand = None # end def def updateItem(self): self._updatePath() self._label.updateLabel() self._updateSequenceText() self._label.resetPosition() # end def ### PRIVATE SUPPORT METHODS ### def _updatePath(self): strand = self._strand if strand is None: self.hide() return else: self.show() is_on_top = self._is_on_top if self._insertion.length() > 0: self.setPen(QPen(QColor(strand.oligo().getColor()), styles.INSERTWIDTH)) self.setBrush(QBrush(Qt.NoBrush)) self.setPath(_insert_path.getInsert(is_on_top)) else: # insertion_size < 0 (a skip) self.setPen(_skip_path.getPen()) self.setPath(_skip_path.getSkip()) # end def def setSequence(self, sequence): self._seq_text = sequence self._updateSequenceText() self._seq_item.show() # end def def hideSequence(self): self._seq_item.hide() # end def def _updateSequenceText(self): seq_item = self._seq_item is_on_top = self._is_on_top index = self._insertion.idx() base_text = self._seq_text font = styles.SEQUENCEFONT seq_font_h = styles.SEQUENCEFONTH insert_w = styles.INSERTWIDTH seq_font_char_w = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if base_text: # only draw sequences if they exist i.e. not None! len_BT = len(base_text) if is_on_top: angle_offset = 0 else: angle_offset = 180 if len_BT > 20: base_text = base_text[:17] + '...' len_BT = len(base_text) fraction_arc_len_per_char = (1.0 - 2.0*_FRACTION_INSERT_TO_PAD) / (len_BT + 1) seq_item.setPen(QPen(Qt.NoPen)) seq_item.setBrush(QBrush(Qt.black)) seq_path = QPainterPath() loop_path = self.path() for i in range(len_BT): frac = _FRACTION_INSERT_TO_PAD + (i+1)*fraction_arc_len_per_char pt = loop_path.pointAtPercent(frac) tang_ang = loop_path.angleAtPercent(frac) temp_path = QPainterPath() # 1. draw the text temp_path.addText(0, 0, font, base_text[i if is_on_top else -i-1]) # 2. center it at the zero point different for top and bottom # strands if not is_on_top: temp_path.translate(0, -seq_font_h - insert_w) temp_path.translate(QPointF(-seq_font_char_w / 2., -2 if is_on_top else seq_font_h)) mat = QTransform() # 3. rotate it mat.rotate(-tang_ang + angle_offset) rotated_path = mat.map(temp_path) # 4. translate the rotate object to it's position on the part rotated_path.translate(pt) seq_path.addPath(rotated_path) # end for seq_item.setPath(seq_path) # end if # end def ### EVENT HANDLERS ### def mouseDoubleClickEvent(self, event): """Double clicks remove the insertion/skip.""" self._strand.changeInsertion(self._insertion.idx(), 0) def mousePressEvent(self, event): """This needs to be present for mouseDoubleClickEvent to work.""" pass
class InsertionItem(QGraphicsPathItem): """This is just the shape of the Insert item """ def __init__(self, virtual_helix_item: PathVirtualHelixItemT, strand: StrandT, insertion: InsertionT): super(InsertionItem, self).__init__(virtual_helix_item) self.hide() self._strand = strand self._insertion = insertion self._seq_item = QGraphicsPathItem(parent=self) self._is_on_top = is_on_top = strand.isForward() y = 0 if is_on_top else _BW self.setPos(_BW*insertion.idx(), y) self.setZValue(styles.ZINSERTHANDLE) self._initLabel() self._initClickArea() self.updateItem() self.show() # end def def _initLabel(self): """Display the length of the insertion.""" self._label = InsertionLabel(self) self._seq_item = QGraphicsPathItem(parent=self) self._seq_text = None self.show() # end def def _initClickArea(self): """""" self._clickArea = cA = QGraphicsRectItem(_DEFAULT_RECT, self) cA.setPen(_NO_PEN) cA.mousePressEvent = self.mousePressEvent cA.mouseDoubleClickEvent = self.mouseDoubleClickEvent # end def ### PUBLIC SUPPORT METHODS ### def destroyItem(self): """Called from the following stranditem methods: strandRemovedSlot strandInsertionRemovedSlot refreshInsertionItems """ scene = self.scene() self._label.setTextInteractionFlags(Qt.NoTextInteraction) self._label.clearFocus() scene.removeItem(self._label) self._label = None scene.removeItem(self._seq_item) self._seq_item = None scene.removeItem(self) self._insertion = None self._strand = None # end def def updateItem(self): self._updatePath() self._label.updateLabel() self._updateSequenceText() self._label.resetPosition() # end def ### PRIVATE SUPPORT METHODS ### def _updatePath(self): strand = self._strand if strand is None: self.hide() return else: self.show() is_on_top = self._is_on_top if self._insertion.length() > 0: self.setPen(QPen(QColor(strand.oligo().getColor()), styles.INSERTWIDTH)) self.setBrush(QBrush(Qt.NoBrush)) self.setPath(_insert_path.getInsert(is_on_top)) else: # insertion_size < 0 (a skip) self.setPen(_skip_path.getPen()) self.setPath(_skip_path.getSkip()) # end def def setSequence(self, sequence: str): self._seq_text = sequence self._updateSequenceText() self._seq_item.show() # end def def hideSequence(self): self._seq_item.hide() # end def def _updateSequenceText(self): seq_item = self._seq_item is_on_top = self._is_on_top self._insertion.idx() base_text = self._seq_text font = styles.SEQUENCEFONT seq_font_h = styles.SEQUENCEFONTH insert_w = styles.INSERTWIDTH seq_font_char_w = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if base_text: # only draw sequences if they exist i.e. not None! len_BT = len(base_text) if is_on_top: angle_offset = 0 else: angle_offset = 180 if len_BT > 20: base_text = base_text[:17] + '...' len_BT = len(base_text) fraction_arc_len_per_char = (1.0 - 2.0*_FRACTION_INSERT_TO_PAD) / (len_BT + 1) seq_item.setPen(QPen(Qt.NoPen)) seq_item.setBrush(QBrush(Qt.black)) seq_path = QPainterPath() loop_path = self.path() for i in range(len_BT): frac = _FRACTION_INSERT_TO_PAD + (i+1)*fraction_arc_len_per_char pt = loop_path.pointAtPercent(frac) tang_ang = loop_path.angleAtPercent(frac) temp_path = QPainterPath() # 1. draw the text temp_path.addText(0, 0, font, base_text[i if is_on_top else -i-1]) # 2. center it at the zero point different for top and bottom # strands if not is_on_top: temp_path.translate(0, -seq_font_h - insert_w) temp_path.translate(QPointF(-seq_font_char_w / 2., -2 if is_on_top else seq_font_h)) mat = QTransform() # 3. rotate it mat.rotate(-tang_ang + angle_offset) rotated_path = mat.map(temp_path) # 4. translate the rotate object to its position on the part rotated_path.translate(pt) seq_path.addPath(rotated_path) # end for seq_item.setPath(seq_path) # end if # end def ### EVENT HANDLERS ### def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent): """Double clicks remove the insertion/skip.""" self._strand.changeInsertion(self._insertion.idx(), 0) def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """This needs to be present for mouseDoubleClickEvent to work."""
class InsertionItem(QGraphicsPathItem): """ This is just the shape of the Insert item """ def __init__(self, virtualHelixItem, strand, insertion): super(InsertionItem, self).__init__(virtualHelixItem) self.hide() self._strand = strand self._insertion = insertion self._seqItem = QGraphicsPathItem(parent=self) self._isOnTop = isOnTop = virtualHelixItem.isStrandOnTop(strand) y = 0 if isOnTop else _bw self.setPos(_bw*insertion.idx(), y) self.setZValue(styles.ZINSERTHANDLE) self._initLabel() self._initClickArea() self.updateItem() self.show() # end def def _initLabel(self): """Display the length of the insertion.""" self._label = label = QGraphicsTextItem("", parent=self) label.setFont(_font) label.setTextInteractionFlags(Qt.TextEditorInteraction) label.inputMethodEvent = self.inputMethodEventHandler label.keyPressEvent = self.textkeyPressEvent label.mousePressEvent = self.labelMousePressEvent label.mouseDoubleClickEvent = self.mouseDoubleClickEvent label.setTextWidth(-1) self._label = label self._seqItem = QGraphicsPathItem(parent=self) self._seqText = None self.updateItem() self.show() # end def def _initClickArea(self): """docstring for _initClickArea""" self._clickArea = cA = QGraphicsRectItem(_defaultRect, self) cA.setPen(_noPen) cA.mousePressEvent = self.mousePressEvent cA.mouseDoubleClickEvent = self.mouseDoubleClickEvent # end def ### PUBLIC SUPPORT METHODS ### def remove(self): """ Called from the following stranditem methods: strandRemovedSlot strandInsertionRemovedSlot refreshInsertionItems """ scene = self.scene() self._label.setTextInteractionFlags(Qt.NoTextInteraction) self._label.clearFocus() scene.removeItem(self._label) self._label = None scene.removeItem(self._seqItem) self._seqItem = None scene.removeItem(self) self._insertion = None self._strand = None # end def def updateItem(self): self._updatePath() self._updateLabel() self._updateSequenceText() self._resetPosition() # end def ### PRIVATE SUPPORT METHODS ### def _focusOut(self): lbl = self._label if lbl == None: return cursor = lbl.textCursor() cursor.clearSelection() lbl.setTextCursor(cursor) lbl.clearFocus() # end def def _resetPosition(self): """ Set the label position based on orientation and text alignment. """ lbl = self._label if lbl == None: return txtOffset = lbl.boundingRect().width()/2 insertion = self._insertion y = -_bw if self._isOnTop else _bw lbl.setPos(_offset2-txtOffset, y) if insertion.length() > 0: lbl.show() else: lbl.hide() # end def def _updateLabel(self): self._label.setPlainText("%d" % (self._insertion.length())) # end def def _updatePath(self): strand = self._strand if strand == None: self.hide() return else: self.show() isOnTop = self._isOnTop if self._insertion.length() > 0: self.setPen(QPen(QColor(strand.oligo().color()), styles.INSERTWIDTH)) self.setBrush(QBrush(Qt.NoBrush)) self.setPath(_insertPath.getInsert(isOnTop)) else: # insertionSize < 0 (a skip) self.setPen(_skipPath.getPen()) self.setPath(_skipPath.getSkip()) # end def def setSequence(self, sequence): self._seqText = sequence self._updateSequenceText() self._seqItem.show() # end def def hideSequence(self): self._seqItem.hide() # end def def _updateSequenceText(self): seqItem = self._seqItem isOnTop = self._isOnTop index = self._insertion.idx() baseText = self._seqText font = styles.SEQUENCEFONT seqFontH = styles.SEQUENCEFONTH insertW = styles.INSERTWIDTH seqFontCharW = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if baseText: # only draw sequences if they exist i.e. not None! lenBT = len(baseText) if isOnTop: angleOffset = 0 else: angleOffset = 180 if lenBT > 20: baseText = baseText[:17] + '...' lenBT = len(baseText) fractionArclenPerChar = (1.0-2.0*_fractionInsertToPad)/(lenBT+1) seqItem.setPen(QPen(Qt.NoPen)) seqItem.setBrush(QBrush(Qt.black)) seqPath = QPainterPath() loopPath = self.path() for i in range(lenBT): frac = _fractionInsertToPad + (i+1)*fractionArclenPerChar pt = loopPath.pointAtPercent(frac) tangAng = loopPath.angleAtPercent(frac) tempPath = QPainterPath() # 1. draw the text tempPath.addText(0,0, font, baseText[i if isOnTop else -i-1]) # 2. center it at the zero point different for top and bottom # strands if not isOnTop: tempPath.translate(0, -seqFontH - insertW) tempPath.translate(QPointF(-seqFontCharW/2., -2 if isOnTop else seqFontH)) mat = QMatrix3x3() # 3. rotate it mat.rotate(-tangAng + angleOffset) rotatedPath = mat.map(tempPath) # 4. translate the rotate object to it's position on the part rotatedPath.translate(pt) seqPath.addPath(rotatedPath) # end for seqItem.setPath(seqPath) # end if # end def ### EVENT HANDLERS ### def mouseDoubleClickEvent(self, event): """Double clicks remove the insertion/skip.""" self._strand.changeInsertion(self._insertion.idx(), 0) def mousePressEvent(self, event): """This needs to be present for mouseDoubleClickEvent to work.""" pass def labelMousePressEvent(self, event): """ Pre-selects the text for editing when you click the label. """ lbl = self._label lbl.setTextInteractionFlags(Qt.TextEditorInteraction) cursor = lbl.textCursor() cursor.setPosition(0) cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) lbl.setTextCursor(cursor) def textkeyPressEvent(self, event): """ Must intercept invalid input events. Make changes here """ a = event.key() text = event.text() if a in [Qt.Key_Space, Qt.Key_Tab]: return elif a in [Qt.Key_Return, Qt.Key_Enter]: self.inputMethodEventHandler(event) return # elif unicode(text).isalpha(): elif text.isalpha(): return else: return QGraphicsTextItem.keyPressEvent(self._label, event) def inputMethodEventHandler(self, event): """ This is run on the label being changed or losing focus """ lbl = self._label if lbl == None: return # test = unicode(lbl.toPlainText()) test = lbl.toPlainText() try: insertionSize = int(test) except: insertionSize = None insertion = self._insertion length = insertion.length() if insertionSize != None and insertionSize != length: self._strand.changeInsertion(insertion.idx(), insertionSize) if insertion.length(): self._resetPosition() else: self._updateLabel() # end if self._focusOut()
class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (:obj:`PropertyWrapperObject`): Description idx (int): the base index within the virtual helix is_fwd (bool): is this a forward strand? prexoveritem_manager (:obj:`PreXoverManager`): Manager of the PreXoverItems to_vh_id_num (int): Virtual Helix number this Xover point might connect to """ FILTER_NAME = "xover" def __init__(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Summary Args: from_virtual_helix_item: Description is_fwd: is this a forward strand? from_index: index of the Virtual Helix this xover is coming from nearby_idxs: to_vh_id_num: Virtual Helix number this Xover point might connect to prexoveritem_manager: Manager of the PreXoverItems """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._tick_marks = QGraphicsPathItem(self) self._tick_marks.setAcceptHoverEvents(True) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, self) self._path = QGraphicsPathItem() self.setZValue(styles.ZPREXOVERITEM) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager) self._getActiveTool = from_virtual_helix_item.viewroot( ).manager.activeToolGetter # end def def shutdown(self): """Summary """ self.setBrush(getNoBrush()) self.to_vh_id_num = None self.adapter.resetAnimations() self.setAcceptHoverEvents(False) self.hide() # end def def resetItem(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Update this pooled PreXoverItem with current info. Called by PreXoverManager. Args: from_virtual_helix_item: the associated vh_item is_fwd: True if associated with fwd strand, False if rev strand from_index: idx of associated vh nearby_idxs: to_vh_id_num: id_num of the other vh prexoveritem_manager: the manager """ # to_vh_item = from_virtual_helix_item.partItem().idToVirtualHelixItem(to_vh_id_num) self.setParentItem(from_virtual_helix_item) # self.setParentItem(to_vh_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self._model_part = from_virtual_helix_item.part() self.idx = from_index self.is_low = False self.is_high = False self.nearby_idxs = nearby_idxs self.is_fwd = is_fwd self.color = None self.is3p = None self.enter_pos = None self.exit_pos = None self.to_vh_id_num = to_vh_id_num self._label_txt = None self.prexoveritem_manager = prexoveritem_manager # todo: check here if xover present and disable result = self.setPathAppearance(from_virtual_helix_item) if result: self.setBrush(getNoBrush()) if is_fwd: self.setPos(from_index * BASE_WIDTH, -BASE_WIDTH - 0.1 * BASE_WIDTH) else: self.setPos(from_index * BASE_WIDTH, 2 * BASE_WIDTH) self.show() # label self._label_txt = lbt = None if to_vh_id_num is None else str( to_vh_id_num) self._label.resetItem(is_fwd, self.color) self.setLabel(text=lbt) # bond line bonditem = self._bond_item bonditem.setPen( getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH, penstyle=Qt.DotLine)) bonditem.hide() # end def def setPathAppearance( self, from_virtual_helix_item: PathVirtualHelixItemT) -> bool: """Sets the PainterPath according to the index (low = Left, high = Right) and strand position (top = Up, bottom = Down). Args: from_virtual_helix_item: """ part = self._model_part idx = self.idx is_fwd = self.is_fwd id_num = self._id_num strand_type = StrandEnum.FWD if is_fwd else StrandEnum.REV # relative position info bpr = from_virtual_helix_item.getProperty('bases_per_repeat') self.is_low = is_low = idx + 1 in self.nearby_idxs or ( idx + 1) % bpr in self.nearby_idxs self.is_high = is_high = idx - 1 in self.nearby_idxs or ( idx - 1) % bpr in self.nearby_idxs # check strand for xover and color if part.hasStrandAtIdx(id_num, idx)[strand_type]: strand = part.getStrand(self.is_fwd, id_num, idx) if strand.hasXoverAt(idx): return False self.color = strand.getColor() if strand is not None else EMPTY_COL else: self.color = EMPTY_COL if is_low and is_high: path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type] raise NotImplementedError("Dual xovers not yet supported") elif is_low: path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type] self.is3p = True if is_fwd else False self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][ -1] self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] elif is_high: path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type] self.is3p = False if is_fwd else True self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][ -1] self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] else: # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs) return False self._tick_marks.setPen( getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH, capstyle=Qt.FlatCap, joinstyle=Qt.RoundJoin)) self._tick_marks.setPath(path) self._tick_marks.show() return True # end def ### ACCESSORS ### def color(self) -> str: """The PreXoverItem's color, derived from the associated strand's oligo. Returns: str: color in hex code """ return self.color def getInfo(self) -> ABInfoT: """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) def destroyItem(self): """Removes animation adapter, label, bond_item, and this item from scene. """ scene = self.scene() self.adapter.destroyItem() if scene: scene.removeItem(self._label) self._label = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): """Only ``if enableActive(True)`` is called hover and key events disabled by default Args: event: the hover event """ if self._getActiveTool().methodPrefix() != "selectTool": return self.setFocus(Qt.MouseFocusReason) self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) return QGraphicsItem.hoverEnterEvent(self, event) # end def def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): """Summary Args: event (QGraphicsSceneHoverEvent): the hover event """ self.prexoveritem_manager.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") return QGraphicsItem.hoverLeaveEvent(self, event) # end def def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """TODO: NEED TO ADD FILTER FOR A CLICK ON THE 3' MOST END OF THE XOVER TO DISALLOW OR HANDLE DIFFERENTLY """ viewroot = self.parentItem().viewroot() current_filter_set = viewroot.selectionFilterSet() if (self._getActiveTool().methodPrefix() != "selectTool" or (self.FILTER_NAME not in current_filter_set)): return part = self._model_part is_fwd = self.is_fwd if self.is3p: strand5p = part.getStrand(is_fwd, self._id_num, self.idx) strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) else: strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) strand3p = part.getStrand(is_fwd, self._id_num, self.idx) if strand5p is None or strand3p is None: return # print(strand3p, strand5p) part.createXover(strand5p, self.idx, strand3p, self.idx) nkey = (self.to_vh_id_num, not is_fwd, self.idx) npxi = self.prexoveritem_manager.neighbor_prexover_items.get( nkey, None) if npxi: npxi.shutdown() self.shutdown() part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx) # self.prexoveritem_manager.handlePreXoverClick(self) def keyPressEvent(self, event: QKeyEvent): """ Args: event: Description """ self.prexoveritem_manager.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text: str = None, outline: bool = False): """Summary Args: text: Default is ``None`` outline: Default is ``False`` """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item: QGraphicsItem, property_name: str, duration: int, start_value, end_value): """ Args: item: Description property_name: Description duration: Description start_value (QVariant): Description end_value (QVariant): Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active: bool): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active: whether or not the PreXoverItem is parented to the active VirtualHelixItem """ pass # end def def enableActive(self, is_active: bool, to_vh_id_num: int = None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active: Description to_vh_id_num: Default is ``None`` """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) else: self.setLabel(text=str(to_vh_id_num)) else: self.setBrush(getNoBrush()) self.setAcceptHoverEvents(False) def activateNeighbor(self, active_prexoveritem: 'PreXoverItem', shortcut: str = None): """Draws a quad line starting from the item5p to the item3p. To be called with whatever the active_prexoveritem is for the parts `active_base`. Args: active_prexoveritem: Description shortcut: Default is None """ if self._getActiveTool().methodPrefix() != "selectTool": return if self.is3p and not active_prexoveritem.is3p: item5p = active_prexoveritem item3p = self elif not self.is3p and active_prexoveritem.is3p: item5p = self item3p = active_prexoveritem else: return same_parity = self.is_fwd == active_prexoveritem.is_fwd p1 = item5p._tick_marks.scenePos() + item5p.exit_pos p2 = item3p._tick_marks.scenePos() + item3p.exit_pos c1 = QPointF() # case 1: same parity if same_parity: dy = abs(p2.y() - p1.y()) c1.setX(p1.x() + _X_SCALE * dy) c1.setY(0.5 * (p1.y() + p2.y())) # case 2: different parity else: if item3p.is_fwd: c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y())) else: c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y())) c1.setY(0.5 * (p1.y() + p2.y())) pp = QPainterPath() pp.moveTo(self._tick_marks.mapFromScene(p1)) pp.quadTo(self._tick_marks.mapFromScene(c1), self._tick_marks.mapFromScene(p2)) # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() # end def def deactivateNeighbor(self): """Summary """ if self.isVisible(): self._bond_item.hide() self.setLabel(text=self._label_txt)
class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (:obj:`PropertyWrapperObject`): Description idx (int): the base index within the virtual helix is_fwd (bool): is this a forward strand? prexoveritem_manager (:obj:`PreXoverManager`): Manager of the PreXoverItems to_vh_id_num (int): Virtual Helix number this Xover point might connect to """ FILTER_NAME = "xover" def __init__(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Summary Args: from_virtual_helix_item: Description is_fwd: is this a forward strand? from_index: index of the Virtual Helix this xover is coming from nearby_idxs: to_vh_id_num: Virtual Helix number this Xover point might connect to prexoveritem_manager: Manager of the PreXoverItems """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._tick_marks = QGraphicsPathItem(self) self._tick_marks.setAcceptHoverEvents(True) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, self) self._path = QGraphicsPathItem() self.setZValue(styles.ZPREXOVERITEM) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager) self._getActiveTool = from_virtual_helix_item.viewroot().manager.activeToolGetter # end def def shutdown(self): """Summary """ self.setBrush(getNoBrush()) self.to_vh_id_num = None self.adapter.resetAnimations() self.setAcceptHoverEvents(False) self.hide() # end def def resetItem(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Update this pooled PreXoverItem with current info. Called by PreXoverManager. Args: from_virtual_helix_item: the associated vh_item is_fwd: True if associated with fwd strand, False if rev strand from_index: idx of associated vh nearby_idxs: to_vh_id_num: id_num of the other vh prexoveritem_manager: the manager """ # to_vh_item = from_virtual_helix_item.partItem().idToVirtualHelixItem(to_vh_id_num) self.setParentItem(from_virtual_helix_item) # self.setParentItem(to_vh_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self._model_part = from_virtual_helix_item.part() self.idx = from_index self.is_low = False self.is_high = False self.nearby_idxs = nearby_idxs self.is_fwd = is_fwd self.color = None self.is3p = None self.enter_pos = None self.exit_pos = None self.to_vh_id_num = to_vh_id_num self._label_txt = None self.prexoveritem_manager = prexoveritem_manager # todo: check here if xover present and disable result = self.setPathAppearance(from_virtual_helix_item) if result: self.setBrush(getNoBrush()) if is_fwd: self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH - 0.1*BASE_WIDTH) else: self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH) self.show() # label self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num) self._label.resetItem(is_fwd, self.color) self.setLabel(text=lbt) # bond line bonditem = self._bond_item bonditem.setPen(getPenObj( self.color, styles.PREXOVER_STROKE_WIDTH, penstyle=Qt.DotLine)) bonditem.hide() # end def def setPathAppearance(self, from_virtual_helix_item: PathVirtualHelixItemT) -> bool: """Sets the PainterPath according to the index (low = Left, high = Right) and strand position (top = Up, bottom = Down). Args: from_virtual_helix_item: """ part = self._model_part idx = self.idx is_fwd = self.is_fwd id_num = self._id_num strand_type = StrandEnum.FWD if is_fwd else StrandEnum.REV # relative position info bpr = from_virtual_helix_item.getProperty('bases_per_repeat') self.is_low = is_low = idx+1 in self.nearby_idxs or (idx+1) % bpr in self.nearby_idxs self.is_high = is_high = idx-1 in self.nearby_idxs or (idx-1) % bpr in self.nearby_idxs # check strand for xover and color if part.hasStrandAtIdx(id_num, idx)[strand_type]: strand = part.getStrand(self.is_fwd, id_num, idx) if strand.hasXoverAt(idx): return False self.color = strand.getColor() if strand is not None else EMPTY_COL else: self.color = EMPTY_COL if is_low and is_high: path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type] raise NotImplementedError("Dual xovers not yet supported") elif is_low: path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type] self.is3p = True if is_fwd else False self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] elif is_high: path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type] self.is3p = False if is_fwd else True self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] else: # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs) return False self._tick_marks.setPen(getPenObj( self.color, styles.PREXOVER_STROKE_WIDTH, capstyle=Qt.FlatCap, joinstyle=Qt.RoundJoin)) self._tick_marks.setPath(path) self._tick_marks.show() return True # end def ### ACCESSORS ### def color(self) -> str: """The PreXoverItem's color, derived from the associated strand's oligo. Returns: str: color in hex code """ return self.color def getInfo(self) -> ABInfoT: """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) def destroyItem(self): """Removes animation adapter, label, bond_item, and this item from scene. """ scene = self.scene() self.adapter.destroyItem() if scene: scene.removeItem(self._label) self._label = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): """Only ``if enableActive(True)`` is called hover and key events disabled by default Args: event: the hover event """ if self._getActiveTool().methodPrefix() != "selectTool": return self.setFocus(Qt.MouseFocusReason) self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) return QGraphicsItem.hoverEnterEvent(self, event) # end def def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): """Summary Args: event (QGraphicsSceneHoverEvent): the hover event """ self.prexoveritem_manager.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") return QGraphicsItem.hoverLeaveEvent(self, event) # end def def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """TODO: NEED TO ADD FILTER FOR A CLICK ON THE 3' MOST END OF THE XOVER TO DISALLOW OR HANDLE DIFFERENTLY """ viewroot = self.parentItem().viewroot() current_filter_set = viewroot.selectionFilterSet() if (self._getActiveTool().methodPrefix() != "selectTool" or (self.FILTER_NAME not in current_filter_set) ): return part = self._model_part is_fwd = self.is_fwd if self.is3p: strand5p = part.getStrand(is_fwd, self._id_num, self.idx) strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) else: strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) strand3p = part.getStrand(is_fwd, self._id_num, self.idx) if strand5p is None or strand3p is None: return # print(strand3p, strand5p) part.createXover(strand5p, self.idx, strand3p, self.idx) nkey = (self.to_vh_id_num, not is_fwd, self.idx) npxi = self.prexoveritem_manager.neighbor_prexover_items.get(nkey, None) if npxi: npxi.shutdown() self.shutdown() part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx) # self.prexoveritem_manager.handlePreXoverClick(self) def keyPressEvent(self, event: QKeyEvent): """ Args: event: Description """ self.prexoveritem_manager.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text: str = None, outline: bool = False): """Summary Args: text: Default is ``None`` outline: Default is ``False`` """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item: QGraphicsItem, property_name: str, duration: int, start_value, end_value): """ Args: item: Description property_name: Description duration: Description start_value (QVariant): Description end_value (QVariant): Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active: bool): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active: whether or not the PreXoverItem is parented to the active VirtualHelixItem """ pass # end def def enableActive(self, is_active: bool, to_vh_id_num: int = None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active: Description to_vh_id_num: Default is ``None`` """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) else: self.setLabel(text=str(to_vh_id_num)) else: self.setBrush(getNoBrush()) self.setAcceptHoverEvents(False) def activateNeighbor(self, active_prexoveritem: 'PreXoverItem', shortcut: str = None): """Draws a quad line starting from the item5p to the item3p. To be called with whatever the active_prexoveritem is for the parts `active_base`. Args: active_prexoveritem: Description shortcut: Default is None """ if self._getActiveTool().methodPrefix() != "selectTool": return if self.is3p and not active_prexoveritem.is3p: item5p = active_prexoveritem item3p = self elif not self.is3p and active_prexoveritem.is3p: item5p = self item3p = active_prexoveritem else: return same_parity = self.is_fwd == active_prexoveritem.is_fwd p1 = item5p._tick_marks.scenePos() + item5p.exit_pos p2 = item3p._tick_marks.scenePos() + item3p.exit_pos c1 = QPointF() # case 1: same parity if same_parity: dy = abs(p2.y() - p1.y()) c1.setX(p1.x() + _X_SCALE * dy) c1.setY(0.5 * (p1.y() + p2.y())) # case 2: different parity else: if item3p.is_fwd: c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y())) else: c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y())) c1.setY(0.5 * (p1.y() + p2.y())) pp = QPainterPath() pp.moveTo(self._tick_marks.mapFromScene(p1)) pp.quadTo(self._tick_marks.mapFromScene(c1), self._tick_marks.mapFromScene(p2)) # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() # end def def deactivateNeighbor(self): """Summary """ if self.isVisible(): self._bond_item.hide() self.setLabel(text=self._label_txt)
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self.setPen(_NO_PEN) self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, strand_type, idx_x, idx_y): """ """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._strand_type = strand_type self._idx = idx_x self._is_on_top = self._is_drawn_5_to_3 = True if idx_y == 0 else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self.updatePositionAndAppearance() # end def def strandType(self): return self._strand_type # end def def configurePath(self): self._path_thing.setBrush(QBrush(styles.RED_STROKE)) path = PPR3 if self._is_drawn_5_to_3 else PPL3 offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): self._partner_virtual_helix = virtual_helix_item # end def def idx(self): return self._idx # end def def virtualHelixItem(self): return self._vhi # end def def point(self): return self._vhi.upperLeftCornerOfBaseType(self._idx, self._strand_type) # end def def floatPoint(self): pt = self.pos() return pt.x(), pt.y() # end def def isOnTop(self): return self._is_on_top # end def def isDrawn5to3(self): return self._is_drawn_5_to_3 # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def updateConnectivity(self): is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) # end def def remove(self): """ Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height() / 2.0 half_label_w = tBR.width() / 2.0 # determine x and y positions label_x = bw / 2.0 - half_label_w if self._is_on_top: label_y = -0.25 * half_label_h - 0.5 - 0.5 * bw else: label_y = 2 * half_label_h + 0.5 + 0.5 * bw # adjust x for left vs right label_x_offset = 0.25 * bw if is_left else -0.25 * bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w / 2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.number())) lbl.show() # end if # end def def hideItems(self): if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class InsertionItem(QGraphicsPathItem): """ This is just the shape of the Insert item """ def __init__(self, virtualHelixItem, strand, insertion): super(InsertionItem, self).__init__(virtualHelixItem) self.hide() self._strand = strand self._insertion = insertion self._seqItem = QGraphicsPathItem(parent=self) self._isOnTop = isOnTop = virtualHelixItem.isStrandOnTop(strand) y = 0 if isOnTop else _bw self.setPos(_bw * insertion.idx(), y) self.setZValue(styles.ZINSERTHANDLE) self._initLabel() self._initClickArea() self.updateItem() self.show() # end def def _initLabel(self): """Display the length of the insertion.""" self._label = label = QGraphicsTextItem("", parent=self) label.setFont(_font) label.setTextInteractionFlags(Qt.TextEditorInteraction) label.inputMethodEvent = self.inputMethodEventHandler label.keyPressEvent = self.textkeyPressEvent label.mousePressEvent = self.labelMousePressEvent label.mouseDoubleClickEvent = self.mouseDoubleClickEvent label.setTextWidth(-1) self._label = label self._seqItem = QGraphicsPathItem(parent=self) self._seqText = None self.updateItem() self.show() # end def def _initClickArea(self): """docstring for _initClickArea""" self._clickArea = cA = QGraphicsRectItem(_defaultRect, self) cA.setPen(_noPen) cA.mousePressEvent = self.mousePressEvent cA.mouseDoubleClickEvent = self.mouseDoubleClickEvent # end def ### PUBLIC SUPPORT METHODS ### def remove(self): """ Called from the following stranditem methods: strandRemovedSlot strandInsertionRemovedSlot refreshInsertionItems """ scene = self.scene() self._label.setTextInteractionFlags(Qt.NoTextInteraction) self._label.clearFocus() scene.removeItem(self._label) self._label = None scene.removeItem(self._seqItem) self._seqItem = None scene.removeItem(self) self._insertion = None self._strand = None # end def def updateItem(self): self._updatePath() self._updateLabel() self._updateSequenceText() self._resetPosition() # end def ### PRIVATE SUPPORT METHODS ### def _focusOut(self): lbl = self._label if lbl == None: return cursor = lbl.textCursor() cursor.clearSelection() lbl.setTextCursor(cursor) lbl.clearFocus() # end def def _resetPosition(self): """ Set the label position based on orientation and text alignment. """ lbl = self._label if lbl == None: return txtOffset = lbl.boundingRect().width() / 2 insertion = self._insertion y = -_bw if self._isOnTop else _bw lbl.setPos(_offset2 - txtOffset, y) if insertion.length() > 0: lbl.show() else: lbl.hide() # end def def _updateLabel(self): self._label.setPlainText("%d" % (self._insertion.length())) # end def def _updatePath(self): strand = self._strand if strand == None: self.hide() return else: self.show() isOnTop = self._isOnTop if self._insertion.length() > 0: self.setPen( QPen(QColor(strand.oligo().color()), styles.INSERTWIDTH)) self.setBrush(QBrush(Qt.NoBrush)) self.setPath(_insertPath.getInsert(isOnTop)) else: # insertionSize < 0 (a skip) self.setPen(_skipPath.getPen()) self.setPath(_skipPath.getSkip()) # end def def setSequence(self, sequence): self._seqText = sequence self._updateSequenceText() self._seqItem.show() # end def def hideSequence(self): self._seqItem.hide() # end def def _updateSequenceText(self): seqItem = self._seqItem isOnTop = self._isOnTop index = self._insertion.idx() baseText = self._seqText font = styles.SEQUENCEFONT seqFontH = styles.SEQUENCEFONTH insertW = styles.INSERTWIDTH seqFontCharW = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if baseText: # only draw sequences if they exist i.e. not None! lenBT = len(baseText) if isOnTop: angleOffset = 0 else: angleOffset = 180 if lenBT > 20: baseText = baseText[:17] + '...' lenBT = len(baseText) fractionArclenPerChar = (1.0 - 2.0 * _fractionInsertToPad) / (lenBT + 1) seqItem.setPen(QPen(Qt.NoPen)) seqItem.setBrush(QBrush(Qt.black)) seqPath = QPainterPath() loopPath = self.path() for i in range(lenBT): frac = _fractionInsertToPad + (i + 1) * fractionArclenPerChar pt = loopPath.pointAtPercent(frac) tangAng = loopPath.angleAtPercent(frac) tempPath = QPainterPath() # 1. draw the text tempPath.addText(0, 0, font, baseText[i if isOnTop else -i - 1]) # 2. center it at the zero point different for top and bottom # strands if not isOnTop: tempPath.translate(0, -seqFontH - insertW) tempPath.translate( QPointF(-seqFontCharW / 2., -2 if isOnTop else seqFontH)) mat = QMatrix3x3() # 3. rotate it mat.rotate(-tangAng + angleOffset) rotatedPath = mat.map(tempPath) # 4. translate the rotate object to it's position on the part rotatedPath.translate(pt) seqPath.addPath(rotatedPath) # end for seqItem.setPath(seqPath) # end if # end def ### EVENT HANDLERS ### def mouseDoubleClickEvent(self, event): """Double clicks remove the insertion/skip.""" self._strand.changeInsertion(self._insertion.idx(), 0) def mousePressEvent(self, event): """This needs to be present for mouseDoubleClickEvent to work.""" pass def labelMousePressEvent(self, event): """ Pre-selects the text for editing when you click the label. """ lbl = self._label lbl.setTextInteractionFlags(Qt.TextEditorInteraction) cursor = lbl.textCursor() cursor.setPosition(0) cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) lbl.setTextCursor(cursor) def textkeyPressEvent(self, event): """ Must intercept invalid input events. Make changes here """ a = event.key() text = event.text() if a in [Qt.Key_Space, Qt.Key_Tab]: return elif a in [Qt.Key_Return, Qt.Key_Enter]: self.inputMethodEventHandler(event) return # elif unicode(text).isalpha(): elif text.isalpha(): return else: return QGraphicsTextItem.keyPressEvent(self._label, event) def inputMethodEventHandler(self, event): """ This is run on the label being changed or losing focus """ lbl = self._label if lbl == None: return # test = unicode(lbl.toPlainText()) test = lbl.toPlainText() try: insertionSize = int(test) except: insertionSize = None insertion = self._insertion length = insertion.length() if insertionSize != None and insertionSize != length: self._strand.changeInsertion(insertion.idx(), insertionSize) if insertion.length(): self._resetPosition() else: self._updateLabel() # end if self._focusOut()
class RCircleSegment(QObject): def __init__(self, radius: float, center_x: float, center_y: float, start_angle: float, end_angle: float, clockwise, line_width: float, line_color=Qt.black, fill_color=Qt.transparent): super().__init__() # The supporting rectangle if end_angle < start_angle: end_angle += 2 * math.pi start_angle = -start_angle end_angle = -end_angle shift = end_angle - start_angle if clockwise: shift = -shift - 2 * math.pi x, y = center_x - radius, center_y - radius self.rect = QRectF(x, y, 2 * radius, 2 * radius) # The underlying QGraphicsPathItem self.painter_path = QPainterPath( QPointF(center_x + math.cos(start_angle) * radius, center_y - math.sin(start_angle) * radius)) self.painter_path.arcTo(self.rect, math.degrees(start_angle), math.degrees(shift)) self.path = QGraphicsPathItem(self.painter_path) self.path.setBrush(QtGui.QBrush(fill_color)) pen = QPen() pen.setWidthF(line_width) pen.setColor(line_color) self.path.setPen(pen) self._visible = 1 # def x(self): # return self._pos.x() # # def y(self): # return self._pos.y() # # # The following functions are for animation support # # @pyqtProperty(QPointF) # def pos(self): # return self._pos # # @pos.setter # def pos(self, value): # self.rect = QRectF(value.x() - self._radius, value.y() - self._radius, 2 * self._radius, 2 * self._radius) # self.path.setRect(self.rect) # self._pos = value # @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if (value > 0): self.path.show() else: self.path.hide() self._visible = value
class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (TYPE): Description idx (int): the base index within the virtual helix is_fwd (TYPE): Description prexoveritem_manager (TYPE): Description to_vh_id_num (TYPE): Description """ def __init__(self, from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager): """Summary Args: from_virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_fwd (TYPE): Description from_index (TYPE): Description to_vh_id_num (TYPE): Description prexoveritem_manager (TYPE): Description """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._tick_marks = QGraphicsPathItem(self) self._tick_marks.setAcceptHoverEvents(True) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, self) self._path = QGraphicsPathItem() self.setZValue(styles.ZPREXOVERITEM) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager) # end def def shutdown(self): """Summary Returns: TYPE: Description """ self.setBrush(getNoBrush()) self.to_vh_id_num = None self.adapter.resetAnimations() self.setAcceptHoverEvents(False) self.hide() # end def def resetItem(self, from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager): """Update this pooled PreXoverItem with current info. Called by PreXoverManager. Args: from_virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): the associated vh_item is_fwd (bool): True if associated with fwd strand, False if rev strand from_index (int): idx of associated vh to_vh_id_num (int): id_num of the other vh prexoveritem_manager (cadnano.views.pathview.prexoermanager.PreXoverManager): the manager """ self.setParentItem(from_virtual_helix_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self._model_vh = from_virtual_helix_item.cnModel() self.idx = from_index self.is_low = False self.is_high = False self.nearby_idxs = nearby_idxs self.is_fwd = is_fwd self.color = None self.is3p = None self.enter_pos = None self.exit_pos = None self.to_vh_id_num = to_vh_id_num self.prexoveritem_manager = prexoveritem_manager # todo: check here if xover present and disable result = self.setPathAppearance(from_virtual_helix_item) if result: self.setBrush(getNoBrush()) if is_fwd: self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH) else: self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH) self.show() # label self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num) self.setLabel(text=lbt) self._label.resetItem(is_fwd, self.color) # bond line bonditem = self._bond_item bonditem.setPen(getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH)) bonditem.hide() # end def def setPathAppearance(self, from_virtual_helix_item): """ Sets the PainterPath according to the index (low = Left, high = Right) and strand position (top = Up, bottom = Down). """ part = self._model_vh.part() idx = self.idx is_fwd = self.is_fwd id_num = self._id_num strand_type = StrandType.FWD if is_fwd else StrandType.REV # relative position info bpr = from_virtual_helix_item.getProperty('bases_per_repeat') self.is_low = is_low = idx+1 in self.nearby_idxs or (idx+1) % bpr in self.nearby_idxs self.is_high = is_high = idx-1 in self.nearby_idxs or (idx-1) % bpr in self.nearby_idxs # check strand for xover and color if part.hasStrandAtIdx(id_num, idx)[strand_type]: strand = part.getStrand(self.is_fwd, id_num, idx) if strand.hasXoverAt(idx): return False self.color = strand.getColor() if strand is not None else EMPTY_COL else: self.color = EMPTY_COL if is_low and is_high: print("dual xover") path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type] elif is_low: path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type] self.is3p = True if is_fwd else False self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] elif is_high: path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type] self.is3p = False if is_fwd else True self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] else: # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs) return False self._tick_marks.setPen(getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH, capstyle=Qt.FlatCap, joinstyle=Qt.RoundJoin)) self._tick_marks.setPath(path) self._tick_marks.show() return True # end def ### ACCESSORS ### def color(self): """The PreXoverItem's color, derived from the associated strand's oligo. Returns: str: color in hex code """ return self.color def getInfo(self): """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) def remove(self): """Removes animation adapter, label, bond_item, and this item from scene. """ scene = self.scene() self.adapter.destroy() if scene: scene.removeItem(self._label) self._label = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event): """Only if enableActive(True) is called hover and key events disabled by default Args: event (QGraphicsSceneHoverEvent): the hover event """ self.setFocus(Qt.MouseFocusReason) self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) # end def def hoverLeaveEvent(self, event): """Summary Args: event (QGraphicsSceneHoverEvent): the hover event Returns: TYPE: Description """ self.prexoveritem_manager.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") # end def def mousePressEvent(self, event): part = self._model_vh.part() is_fwd = self.is_fwd if self.is3p: strand5p = part.getStrand(is_fwd, self._id_num, self.idx) strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) else: strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) strand3p = part.getStrand(is_fwd, self._id_num, self.idx) if strand5p is None or strand3p is None: return part.createXover(strand5p, self.idx, strand3p, self.idx) nkey = (self.to_vh_id_num, not is_fwd, self.idx) npxi = self.prexoveritem_manager.neighbor_prexover_items.get(nkey, None) if npxi: npxi.shutdown() self.shutdown() part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx) # self.prexoveritem_manager.handlePreXoverClick(self) def keyPressEvent(self, event): """Summary Args: event (TYPE): Description Returns: TYPE: Description """ self.prexoveritem_manager.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text=None, outline=False): """Summary Args: text (None, optional): Description outline (bool, optional): Description Returns: TYPE: Description """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item, property_name, duration, start_value, end_value): """Summary Args: item (TYPE): Description property_name (TYPE): Description duration (TYPE): Description start_value (TYPE): Description end_value (TYPE): Description Returns: TYPE: Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active (bool): whether or not the PreXoverItem is parented to the active VirtualHelixItem """ pass # end def def enableActive(self, is_active, to_vh_id_num=None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active (TYPE): Description to_vh_id_num (None, optional): Description """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) else: self.setLabel(text=str(to_vh_id_num)) else: self.setBrush(getNoBrush()) self.setAcceptHoverEvents(False) def activateNeighbor(self, active_prexoveritem, shortcut=None): """ Draws a quad line starting from the item5p to the item3p. To be called with whatever the active_prexoveritem is for the parts `active_base`. Args: active_prexoveritem (TYPE): Description shortcut (None, optional): Description """ if self.is3p and not active_prexoveritem.is3p: item5p = active_prexoveritem item3p = self elif not self.is3p and active_prexoveritem.is3p: item5p = self item3p = active_prexoveritem else: return same_parity = self.is_fwd == active_prexoveritem.is_fwd p1 = item5p._tick_marks.scenePos() + item5p.exit_pos p2 = item3p._tick_marks.scenePos() + item3p.exit_pos c1 = QPointF() # case 1: same parity if same_parity: dy = abs(p2.y() - p1.y()) c1.setX(p1.x() + _X_SCALE * dy) c1.setY(0.5 * (p1.y() + p2.y())) # case 2: different parity else: if item3p.is_fwd: c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y())) else: c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y())) c1.setY(0.5 * (p1.y() + p2.y())) pp = QPainterPath() pp.moveTo(self._tick_marks.mapFromScene(p1)) pp.quadTo(self._tick_marks.mapFromScene(c1), self._tick_marks.mapFromScene(p2)) # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() # end def def deactivateNeighbor(self): """Summary Returns: TYPE: Description """ if self.isVisible(): self._bond_item.hide() self.setLabel(text=self._label_txt)
class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (TYPE): Description idx (int): the base index within the virtual helix is_fwd (TYPE): Description prexoveritemgroup (TYPE): Description to_vh_id_num (TYPE): Description """ def __init__(self, from_virtual_helix_item, is_fwd, from_index, to_vh_id_num, prexoveritemgroup, color): """Summary Args: from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_fwd (TYPE): Description from_index (TYPE): Description to_vh_id_num (TYPE): Description prexoveritemgroup (TYPE): Description color (TYPE): Description """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, color, self) self._phos_item = Triangle(FWDPHOS_PP, self) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, to_vh_id_num, prexoveritemgroup, color) # end def def shutdown(self): """Summary Returns: TYPE: Description """ self.setBrush(getBrushObj(self._color, alpha=0)) self.to_vh_id_num = None self.adapter.resetAnimations() phos = self._phos_item phos.adapter.resetAnimations() phos.resetTransform() phos.setPos(0, 0) self.setAcceptHoverEvents(False) self.setFlag(KEYINPUT_ACTIVE_FLAG, False) self.hide() # end def def resetItem(self, from_virtual_helix_item, is_fwd, from_index, to_vh_id_num, prexoveritemgroup, color): """Summary Args: from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_fwd (TYPE): Description from_index (TYPE): Description to_vh_id_num (TYPE): Description prexoveritemgroup (TYPE): Description color (TYPE): Description Returns: TYPE: Description """ self.setParentItem(from_virtual_helix_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self.idx = from_index self.is_fwd = is_fwd self.to_vh_id_num = to_vh_id_num self._color = color self.prexoveritemgroup = prexoveritemgroup self._bond_item.hide() self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num) self.setLabel(text=lbt) self._label.resetItem(is_fwd, color) phos = self._phos_item bonditem = self._bond_item if is_fwd: phos.setPath(FWDPHOS_PP) phos.setTransformOriginPoint(0, phos.boundingRect().center().y()) phos.setPos(0.5*BASE_WIDTH, BASE_WIDTH) phos.setPen(getNoPen()) phos.setBrush(getBrushObj(color)) bonditem.setPen(getPenObj(color, styles.PREXOVER_STROKE_WIDTH)) self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH) else: phos.setPath(REVPHOS_PP) phos.setTransformOriginPoint(0, phos.boundingRect().center().y()) phos.setPos(0.5*BASE_WIDTH, 0) phos.setPen(getPenObj(color, 0.25)) phos.setBrush(getNoBrush()) bonditem.setPen(getPenObj(color, styles.PREXOVER_STROKE_WIDTH, penstyle=Qt.DotLine, capstyle=Qt.RoundCap)) self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH) if to_vh_id_num is not None: inactive_alpha = PROX_ALPHA self.setBrush(getBrushObj(color, alpha=inactive_alpha)) else: self.setBrush(getBrushObj(color, alpha=0)) self.show() # end def def getInfo(self): """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) ### ACCESSORS ### def color(self): """Summary Returns: TYPE: Description """ return self._color def remove(self): """Summary Returns: TYPE: Description """ scene = self.scene() self.adapter.destroy() if scene: scene.removeItem(self._label) self._label = None self._phos_item.adapter.resetAnimations() self._phos_item.adapter = None scene.removeItem(self._phos_item) self._phos_item = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event): """Only if enableActive(True) is called hover and key events disabled by default Args: event (TYPE): Description """ self.setFocus(Qt.MouseFocusReason) self.prexoveritemgroup.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) # end def def hoverLeaveEvent(self, event): """Summary Args: event (TYPE): Description Returns: TYPE: Description """ self.prexoveritemgroup.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") # end def def keyPressEvent(self, event): """Summary Args: event (TYPE): Description Returns: TYPE: Description """ self.prexoveritemgroup.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text=None, outline=False): """Summary Args: text (None, optional): Description outline (bool, optional): Description Returns: TYPE: Description """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item, property_name, duration, start_value, end_value): """Summary Args: item (TYPE): Description property_name (TYPE): Description duration (TYPE): Description start_value (TYPE): Description end_value (TYPE): Description Returns: TYPE: Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active (bool): whether or not the PreXoverItem is parented to the active VirtualHelixItem """ if is_active: self.setBrush(getBrushObj(self._color, alpha=128)) self.animate(self, 'brush_alpha', 1, 0, 128) # overwrite running anim # if self.to_vh_id_num is not None: self.animate(self._phos_item, 'rotation', 500, 0, -90) else: inactive_alpha = 0 if self.to_vh_id_num is None else PROX_ALPHA self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha) self.animate(self._phos_item, 'rotation', 500, -90, 0) # end def def enableActive(self, is_active, to_vh_id_num=None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active (TYPE): Description to_vh_id_num (None, optional): Description """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) self.setBrush(getBrushObj(self._color, alpha=0)) else: self.setLabel(text=str(to_vh_id_num)) inactive_alpha = PROX_ALPHA self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha) self.setFlag(KEYINPUT_ACTIVE_FLAG, True) else: self.setBrush(getNoBrush()) # self.setLabel(text=None) self.setAcceptHoverEvents(False) self.setFlag(KEYINPUT_ACTIVE_FLAG, False) def activateNeighbor(self, active_prexoveritem, shortcut=None): """To be called with whatever the active_prexoveritem is for the parts `active_base` Args: active_prexoveritem (TYPE): Description shortcut (None, optional): Description """ p1 = self._phos_item.scenePos() p2 = active_prexoveritem._phos_item.scenePos() scale = 3 delta1 = -BASE_WIDTH*scale if self.is_fwd else BASE_WIDTH*scale delta2 = BASE_WIDTH*scale if active_prexoveritem.is_fwd else -BASE_WIDTH*scale c1 = self.mapFromScene(QPointF(p1.x(), p1.y() + delta1)) c2 = self.mapFromScene(QPointF(p2.x(), p2.y() - delta2)) pp = QPainterPath() pp.moveTo(self._phos_item.pos()) pp.cubicTo(c1, c2, self._bond_item.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() alpha = 32 idx, active_idx = self.idx, active_prexoveritem.idx if self.is_fwd != active_prexoveritem.is_fwd: if idx == active_idx: alpha = 255 elif idx == active_idx + 1: alpha = 255 elif idx == active_idx - 1: alpha = 255 inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0 self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 500, inactive_alpha, alpha) self.animate(self._phos_item, 'rotation', 500, 0, -90) self.setLabel(text=shortcut, outline=True) # end def def deactivateNeighbor(self): """Summary Returns: TYPE: Description """ if self.isVisible(): inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0 self.setBrush(getBrushObj(self._color, alpha=inactive_alpha)) self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha) self.animate(self._phos_item, 'rotation', 500, -90, 0) self._bond_item.hide() self.setLabel(text=self._label_txt)