示例#1
0
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)
示例#2
0
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()
示例#3
0
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()
示例#4
0
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()
示例#5
0
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
示例#6
0
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."""
示例#7
0
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()
示例#8
0
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)
示例#9
0
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)
示例#10
0
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()
示例#11
0
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()
示例#12
0
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
示例#13
0
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)
示例#14
0
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)