示例#1
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)
示例#2
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)
示例#3
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)