Ejemplo n.º 1
0
    def __init__(self, model_virtual_helix, part_item):
        """
        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            part_item (cadnano.gui.views.sliceview.nucleicacidpartitem.NucleicAcidPartItem): the part item
        """
        AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
        QGraphicsEllipseItem.__init__(self, parent=part_item)
        self._controller = VirtualHelixItemController(self, self._model_part, False, True)

        self.hide()
        model_part = self._model_part
        x, y = model_part.locationQt(self._id_num, part_item.scaleFactor())
        # set position to offset for radius
        # self.setTransformOriginPoint(_RADIUS, _RADIUS)
        self.setCenterPos(x, y)

        self.wedge_gizmos = {}
        self._added_wedge_gizmos = set()
        # self._prexo_gizmos = []

        self.setAcceptHoverEvents(True)
        self.setZValue(_ZVALUE)

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()

        self.old_pen = None
        self.is_active = False
        self.updateAppearance()

        self.show()

        self._right_mouse_move = False
Ejemplo n.º 2
0
    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = empty_helix_item
        self.hide()
        # drawing related

        self.isHovered = False
        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(self._ZVALUE)
        self.lastMousePressAddedBases = False

        self.setBrush(self._OUT_OF_SLICE_BRUSH)
        self.setPen(self._OUT_OF_SLICE_PEN)
        self.setRect(self._RECT)

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self.createArrow()

        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.show()
Ejemplo n.º 3
0
    def __init__(self, part_item, model_virtual_helix, viewroot):
        super(VirtualHelixItem, self).__init__(part_item.proxy())
        self._part_item = part_item
        self._model_virtual_helix = model_virtual_helix
        self._viewroot = viewroot
        self._getActiveTool = part_item._getActiveTool
        self._controller = VirtualHelixItemController(self, model_virtual_helix)
        
        self._handle = VirtualHelixHandleItem(model_virtual_helix, part_item, viewroot)
        self._last_strand_set = None
        self._last_idx = None
        self._scaffoldBackground = None
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setBrush(QBrush(Qt.NoBrush))

        view = viewroot.scene().views()[0]
        view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot)
        shouldShowDetails = view.shouldShowDetails()

        pen = QPen(styles.MINOR_GRID_STROKE, styles.MINOR_GRID_STROKE_WIDTH)
        pen.setCosmetic(shouldShowDetails)
        self.setPen(pen)
        
        self.refreshPath()
        self.setAcceptHoverEvents(True)  # for pathtools
        self.setZValue(styles.ZPATHHELIX)
Ejemplo n.º 4
0
    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = empty_helix_item
        self.hide()
        # drawing related

        self.isHovered = False
        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(self._ZVALUE)
        self.lastMousePressAddedBases = False

        self.setBrush(self._OUT_OF_SLICE_BRUSH)
        self.setPen(self._OUT_OF_SLICE_PEN)
        self.setRect(self._RECT)

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self._pen1, self._pen2 = (QPen(), QPen())
        self.createArrows()

        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.show()
Ejemplo n.º 5
0
    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = empty_helix_item
        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.hide()
        # drawing related

        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(self._ZVALUE)
        self.lastMousePressAddedBases = False

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self._pen1, self._pen2 = (QPen(), QPen())
        self.createArrows()
        self.updateProperty()

        self._rect = QRectF()
        self._hover_rect = QRectF()
        self._outer_line = RotateLine(self._rect, self)
        # self._inner_line = RotateLine(self._rect, self)
        self._hover_region = RotateHoverRegion(self._hover_rect, self)
        self.updateRects()

        self.show()
Ejemplo n.º 6
0
    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = ehi = empty_helix_item
        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.hide()

        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(_ZVALUE)
        self.lastMousePressAddedBases = False

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self._pen1, self._pen2 = (QPen(), QPen())
        self.createArrows()
        self.updateAppearance()

        self._prexoveritemgroup = _pxig = PreXoverItemGroup(_RECT, self)
        _pxig.setTransformOriginPoint(_RECT.center())
        # self._rect = QRectF(_RECT)
        # self._hover_rect = QRectF(_RECT)
        # self._outer_line = RotaryDialLine(self._rect, self)
        # self._hover_region = RotaryDialHoverRegion(self._hover_rect, self)
        self.show()

        # self._virtual_helix.setProperty('ehiX', ehi.mapToScene(0,0).x())
        # self._virtual_helix.setProperty('ehiY', ehi.mapToScene(0,0).y())

        self.getNeighbors()
Ejemplo n.º 7
0
    def __init__(self, model_virtual_helix, part_item, viewroot):
        """Summary

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            part_item (TYPE): Description
            viewroot (TYPE): Description
        """
        AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
        QGraphicsPathItem.__init__(self, parent=part_item.proxy())
        self._viewroot = viewroot
        self._getActiveTool = part_item._getActiveTool
        self._controller = VirtualHelixItemController(self, self._model_part,
                                                      False, True)

        self._handle = VirtualHelixHandleItem(self, part_item, viewroot)
        self._last_strand_set = None
        self._last_idx = None
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setBrush(getNoBrush())

        view = self.view()
        view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot)
        should_show_details = view.shouldShowDetails()

        pen = newPenObj(styles.MINOR_GRID_STROKE,
                        styles.MINOR_GRID_STROKE_WIDTH)
        pen.setCosmetic(should_show_details)
        self.setPen(pen)

        self.is_active = False

        self.refreshPath()
        self.setAcceptHoverEvents(True)  # for pathtools
        self.setZValue(styles.ZPATHHELIX)

        self._right_mouse_move = False
        self.drag_last_position = self.handle_start = self.pos()
Ejemplo n.º 8
0
    def __init__(self, **kwargs):
        """Summary

        Args:
            model_part (Part): The model part
            parent (TYPE): Description
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            key (None, optional): Description
        """
        super().__init__(**kwargs)
        if self._key == "name":
            for vh in self.cnModelList():
                self._controller_list.append(
                    VirtualHelixItemController(self, vh.part(), True, False))
Ejemplo n.º 9
0
    def __init__(self, model_virtual_helix, part_item, viewroot):
        """Summary

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            part_item (TYPE): Description
            viewroot (TYPE): Description
        """
        AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
        QGraphicsPathItem.__init__(self, parent=part_item.proxy())
        self._viewroot = viewroot
        self._getActiveTool = part_item._getActiveTool
        self._controller = VirtualHelixItemController(self, self._model_part, False, True)

        self._handle = VirtualHelixHandleItem(self, part_item, viewroot)
        self._last_strand_set = None
        self._last_idx = None
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setBrush(getNoBrush())

        view = self.view()
        view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot)
        should_show_details = view.shouldShowDetails()

        pen = newPenObj(styles.MINOR_GRID_STROKE, styles.MINOR_GRID_STROKE_WIDTH)
        pen.setCosmetic(should_show_details)
        self.setPen(pen)

        self.is_active = False

        self.refreshPath()
        self.setAcceptHoverEvents(True)  # for pathtools
        self.setZValue(styles.ZPATHHELIX)

        self._right_mouse_move = False
        self.drag_last_position = self.handle_start = self.pos()
Ejemplo n.º 10
0
class VirtualHelixItem(QGraphicsPathItem):
    """VirtualHelixItem for PathView"""
    findChild = util.findChild  # for debug

    def __init__(self, part_item, model_virtual_helix, viewroot):
        super(VirtualHelixItem, self).__init__(part_item.proxy())
        self._part_item = part_item
        self._model_virtual_helix = model_virtual_helix
        self._viewroot = viewroot
        self._getActiveTool = part_item._getActiveTool
        self._controller = VirtualHelixItemController(self, model_virtual_helix)
        
        self._handle = VirtualHelixHandleItem(model_virtual_helix, part_item, viewroot)
        self._last_strand_set = None
        self._last_idx = None
        self._scaffoldBackground = None
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setBrush(QBrush(Qt.NoBrush))

        view = viewroot.scene().views()[0]
        view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot)
        shouldShowDetails = view.shouldShowDetails()

        pen = QPen(styles.MINOR_GRID_STROKE, styles.MINOR_GRID_STROKE_WIDTH)
        pen.setCosmetic(shouldShowDetails)
        self.setPen(pen)
        
        self.refreshPath()
        self.setAcceptHoverEvents(True)  # for pathtools
        self.setZValue(styles.ZPATHHELIX)
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    
    def levelOfDetailChangedSlot(self, boolval):
        """Not connected to the model, only the QGraphicsView"""
        pen = self.pen()
        pen.setCosmetic(boolval)
        self.setPen(pen)
    # end def
    
    def strandAddedSlot(self, sender, strand):
        """
        Instantiates a StrandItem upon notification that the model has a
        new Strand.  The StrandItem is responsible for creating its own
        controller for communication with the model, and for adding itself to
        its parent (which is *this* VirtualHelixItem, i.e. 'self').
        """
        StrandItem(strand, self, self._viewroot)
    # end def

    def decoratorAddedSlot(self, decorator):
        """
        Instantiates a DecoratorItem upon notification that the model has a
        new Decorator.  The Decorator is responsible for creating its own
        controller for communication with the model, and for adding itself to
        its parent (which is *this* VirtualHelixItem, i.e. 'self').
        """
        pass

    def virtualHelixNumberChangedSlot(self, virtualHelix, number):
        self._handle.setNumber()
    # end def

    def virtualHelixRemovedSlot(self, virtualHelix):
        self._controller.disconnectSignals()
        self._controller = None
        
        scene = self.scene()
        self._handle.remove()
        scene.removeItem(self)
        self._part_item.removeVirtualHelixItem(self)
        self._part_item = None
        self._model_virtual_helix = None
        self._getActiveTool = None
        self._handle = None
    # end def

    ### ACCESSORS ###

    def coord(self):
        return self._model_virtual_helix.coord()
    # end def

    def viewroot(self):
        return self._viewroot
    # end def

    def handle(self):
        return self._handle
    # end def

    def part(self):
        return self._part_item.part()
    # end def

    def partItem(self):
        return self._part_item
    # end def

    def number(self):
        return self._model_virtual_helix.number()
    # end def

    def virtualHelix(self):
        return self._model_virtual_helix
    # end def

    def window(self):
        return self._part_item.window()
    # end def

    ### DRAWING METHODS ###
    def isStrandOnTop(self, strand):
        sS = strand.strandSet()
        isEvenParity = self._model_virtual_helix.isEvenParity()
        return isEvenParity and sS.isScaffold() or\
               not isEvenParity and sS.isStaple()
    # end def

    def isStrandTypeOnTop(self, strand_type):
        isEvenParity = self._model_virtual_helix.isEvenParity()
        return isEvenParity and strand_type == StrandType.SCAFFOLD or \
               not isEvenParity and strand_type == StrandType.STAPLE
    # end def

    def upperLeftCornerOfBase(self, idx, strand):
        x = idx * _BASE_WIDTH
        y = 0 if self.isStrandOnTop(strand) else _BASE_WIDTH
        return x, y
    # end def

    def upperLeftCornerOfBaseType(self, idx, strand_type):
        x = idx * _BASE_WIDTH
        y = 0 if self.isStrandTypeOnTop(strand_type) else _BASE_WIDTH
        return x, y
    # end def

    def refreshPath(self):
        """
        Returns a QPainterPath object for the minor grid lines.
        The path also includes a border outline and a midline for
        dividing scaffold and staple bases.
        """
        bw = _BASE_WIDTH
        bw2 = 2 * bw
        part = self.part()
        path = QPainterPath()
        sub_step_size = part.subStepSize()
        canvas_size = part.maxBaseIdx() + 1
        # border
        path.addRect(0, 0, bw * canvas_size, 2 * bw)
        # minor tick marks
        for i in range(canvas_size):
            x = round(bw * i) + .5
            if i % sub_step_size == 0:
                path.moveTo(x - .5,  0)
                path.lineTo(x - .5,  bw2)
                path.lineTo(x - .25, bw2)
                path.lineTo(x - .25, 0)
                path.lineTo(x,       0)
                path.lineTo(x,       bw2)
                path.lineTo(x + .25, bw2)
                path.lineTo(x + .25, 0)
                path.lineTo(x + .5,  0)
                path.lineTo(x + .5,  bw2)

                # path.moveTo(x-.5, 0)
                # path.lineTo(x-.5, 2 * bw)
                # path.lineTo(x+.5, 2 * bw)
                # path.lineTo(x+.5, 0)

            else:
                path.moveTo(x, 0)
                path.lineTo(x, 2 * bw)

        # staple-scaffold divider
        path.moveTo(0, bw)
        path.lineTo(bw * canvas_size, bw)
        
        self.setPath(path)
        
        if self._model_virtual_helix.scaffoldIsOnTop():
            scaffoldY = 0
        else:
            scaffoldY = bw
        # if self._scaffoldBackground == None:
        #     highlightr = QGraphicsRectItem(0, scaffoldY, bw * canvas_size, bw, self)
        #     highlightr.setBrush(QBrush(styles.scaffold_bkg_fill))
        #     highlightr.setPen(QPen(Qt.NoPen))
        #     highlightr.setFlag(QGraphicsItem.ItemStacksBehindParent)
        #     self._scaffoldBackground = highlightr
        # else:
        #     self._scaffoldBackground.setRect(0, scaffoldY, bw * canvas_size, bw)
            
    # end def

    def resize(self):
        """Called by part on resize."""
        self.refreshPath()

    ### PUBLIC SUPPORT METHODS ###
    def setActive(self, idx):
        """Makes active the virtual helix associated with this item."""
        self.part().setActiveVirtualHelix(self._model_virtual_helix, idx)
    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        """
        Parses a mousePressEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.
        """
        self.scene().views()[0].addToPressList(self)
        strand_set, idx = self.baseAtPoint(event.pos())
        self.setActive(idx)
        tool_method_name = self._getActiveTool().methodPrefix() + "MousePress"

        ### uncomment for debugging modifier selection
        # strand_set, idx = self.baseAtPoint(event.pos())
        # row, col = strand_set.virtualHelix().coord()
        # self._part_item.part().selectPreDecorator([(row,col,idx)])

        if hasattr(self, tool_method_name):
            self._last_strand_set, self._last_idx = strand_set, idx
            getattr(self, tool_method_name)(strand_set, idx)
        else:
            event.setAccepted(False)
    # end def

    def mouseMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.
        """
        tool_method_name = self._getActiveTool().methodPrefix() + "MouseMove"
        if hasattr(self, tool_method_name):
            strand_set, idx = self.baseAtPoint(event.pos())
            if self._last_strand_set != strand_set or self._last_idx != idx:
                self._last_strand_set, self._last_idx = strand_set, idx
                getattr(self, tool_method_name)(strand_set, idx)
        else:
            event.setAccepted(False)
    # end def

    def customMouseRelease(self, event):
        """
        Parses a mouseReleaseEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.
        """
        tool_method_name = self._getActiveTool().methodPrefix() + "MouseRelease"
        if hasattr(self, tool_method_name):
            getattr(self, tool_method_name)(self._last_strand_set, self._last_idx)
        else:
            event.setAccepted(False)
    # end def

    ### COORDINATE UTILITIES ###
    def baseAtPoint(self, pos):
        """
        Returns the (strand_type, index) under the location x,y or None.

        It shouldn't be possible to click outside a pathhelix and still call
        this function. However, this sometimes happens if you click exactly
        on the top or bottom edge, resulting in a negative y value.
        """
        x, y = pos.x(), pos.y()
        mVH = self._model_virtual_helix
        base_idx = int(floor(x / _BASE_WIDTH))
        min_base, max_base = 0, mVH.part().maxBaseIdx()
        if base_idx < min_base or base_idx >= max_base:
            base_idx = util.clamp(base_idx, min_base, max_base)
        if y < 0:
            y = 0  # HACK: zero out y due to erroneous click
        strandIdx = floor(y * 1. / _BASE_WIDTH)
        if strandIdx < 0 or strandIdx > 1:
            strandIdx = int(util.clamp(strandIdx, 0, 1))
        strand_set = mVH.getStrandSetByIdx(strandIdx)
        return (strand_set, base_idx)
    # end def

    def keyPanDeltaX(self):
        """How far a single press of the left or right arrow key should move
        the scene (in scene space)"""
        dx = self._part_item.part().stepSize() * _BASE_WIDTH
        return self.mapToScene(QRectF(0, 0, dx, 1)).boundingRect().width()
    # end def

    def hoverLeaveEvent(self, event):
        self._part_item.updateStatusBar("")
    # end def

    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.
        """
        base_idx = int(floor(event.pos().x() / _BASE_WIDTH))
        loc = "%d[%d]" % (self.number(), base_idx)
        self._part_item.updateStatusBar(loc)

        active_tool = self._getActiveTool()
        tool_method_name = self._getActiveTool().methodPrefix() + "HoverMove"
        if hasattr(self, tool_method_name):
            strand_type, idx_x, idx_y = active_tool.baseAtPoint(self, event.pos())
            getattr(self, tool_method_name)(strand_type, idx_x, idx_y)
    # end def

    ### TOOL METHODS ###
    def pencilToolMousePress(self, strand_set, idx):
        """strand.getDragBounds"""
        # print "%s: %s[%s]" % (util.methodName(), strand_set, idx)
        active_tool = self._getActiveTool()
        if not active_tool.isDrawingStrand():
            active_tool.initStrandItemFromVHI(self, strand_set, idx)
            active_tool.setIsDrawingStrand(True)
    # end def

    def pencilToolMouseMove(self, strand_set, idx):
        """strand.getDragBounds"""
        # print "%s: %s[%s]" % (util.methodName(), strand_set, idx)
        active_tool = self._getActiveTool()
        if active_tool.isDrawingStrand():
            active_tool.updateStrandItemFromVHI(self, strand_set, idx)
    # end def

    def pencilToolMouseRelease(self, strand_set, idx):
        """strand.getDragBounds"""
        # print "%s: %s[%s]" % (util.methodName(), strand_set, idx)
        active_tool = self._getActiveTool()
        if active_tool.isDrawingStrand():
            active_tool.setIsDrawingStrand(False)
            active_tool.attemptToCreateStrand(self, strand_set, idx)
    # end def

    def pencilToolHoverMove(self, strand_type, idx_x, idx_y):
        """Pencil the strand is possible."""
        part_item = self.partItem()
        active_tool = self._getActiveTool()
        if not active_tool.isFloatingXoverBegin():
            temp_xover = active_tool.floatingXover()
            temp_xover.updateFloatingFromVHI(self, strand_type, idx_x, idx_y)
Ejemplo n.º 11
0
class VirtualHelixItem(QGraphicsEllipseItem):
    """
    The VirtualHelixItem is an individual circle that gets drawn in the SliceView
    as a child of the PartItem. Taken as a group, many SliceHelix
    instances make up the crossection of the DNAPart. Clicking on a SliceHelix
    adds a VirtualHelix to the DNAPart. The SliceHelix then changes appearence
    and paints its corresponding VirtualHelix number.
    """
    # set up default, hover, and active drawing styles
    _USE_BRUSH = QBrush(styles.ORANGE_FILL)
    _USE_PEN = QPen(styles.ORANGE_STROKE, styles.SLICE_HELIX_STROKE_WIDTH)
    _RADIUS = styles.SLICE_HELIX_RADIUS
    _OUT_OF_SLICE_PEN = QPen(styles.LIGHT_ORANGE_STROKE,\
                         styles.SLICE_HELIX_STROKE_WIDTH)
    _OUT_OF_SLICE_BRUSH = QBrush(styles.LIGHT_ORANGE_FILL)
    _RECT = QRectF(0, 0, 2 * _RADIUS, 2 * _RADIUS)
    _FONT = styles.SLICE_NUM_FONT
    _ZVALUE = styles.ZSLICEHELIX+3

    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = empty_helix_item
        self.hide()
        # drawing related

        self.isHovered = False
        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(self._ZVALUE)
        self.lastMousePressAddedBases = False

        self.setBrush(self._OUT_OF_SLICE_BRUSH)
        self.setPen(self._OUT_OF_SLICE_PEN)
        self.setRect(self._RECT)

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self.createArrow()

        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.show()
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def virtualHelixNumberChangedSlot(self, virtualHelix):
        """
        receives a signal containing a virtualHelix and the oldNumber 
        as a safety check
        """
        self.setNumber()
    # end def

    def virtualHelixRemovedSlot(self, virtualHelix):
        self._controller.disconnectSignals()
        self._controller = None
        self._empty_helix_item.setNotHovered()
        self._virtual_helix = None
        self._empty_helix_item = None
        self.scene().removeItem(self._label)
        self._label = None
        self.scene().removeItem(self)
    # end def

    def strandAddedSlot(self, sender, strand):
        pass
    # end def

    ###

    def createLabel(self):
        label = QGraphicsSimpleTextItem("%d" % self._virtual_helix.number())
        label.setFont(self._FONT)
        label.setZValue(self._ZVALUE)
        label.setParentItem(self)
        return label
    # end def

    def createArrow(self):
        rad = self._RADIUS
        pen = QPen()
        pen.setWidth(3)
        color = QColor(Qt.blue)
        color.setAlphaF(0.25)
        pen.setBrush(color)
        if self._virtual_helix.isEvenParity():
            arrow = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
        else:
            arrow = QGraphicsLineItem(0, rad, rad, rad, self)
        arrow.setTransformOriginPoint(rad, rad)
        arrow.setZValue(400)
        arrow.setPen(pen)
        self.arrow = arrow
        self.arrow.hide()
    # end def

    def updateArrow(self, idx):
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        # for some reason rotation is CW and not CCW with increasing angle
        self.arrow.setRotation(angle + part._TWIST_OFFSET)
    # end def

    def setNumber(self):
        """docstring for setNumber"""
        vh = self._virtual_helix
        num = vh.number()
        label = self._label
        radius = self._RADIUS

        if num != None:
            label.setText("%d" % num)
        else:
            return

        y_val = radius / 3
        if num < 10:
            label.setPos(radius / 1.5, y_val)
        elif num < 100:
            label.setPos(radius / 3, y_val)
        else: # _number >= 100
            label.setPos(0, y_val)
        b_rect = label.boundingRect()
        posx = b_rect.width()/2
        posy = b_rect.height()/2
        label.setPos(radius-posx, radius-posy)
    # end def

    def part(self):
        return self._empty_helix_item.part()

    def virtualHelix(self):
        return self._virtual_helix
    # end def

    def number(self):
        return self.virtualHelix().number()

    def setActiveSliceView(self, is_active_now, idx):
        if is_active_now:
            self.setPen(self._USE_PEN)
            self.setBrush(self._USE_BRUSH)
            self.updateArrow(idx)
            self.arrow.show()
        else:
            self.setPen(self._OUT_OF_SLICE_PEN)
            self.setBrush(self._OUT_OF_SLICE_BRUSH)
            self.arrow.hide()
    # end def

    ############################ User Interaction ############################
    def sceneEvent(self, event):
        """Included for unit testing in order to grab events that are sent
        via QGraphicsScene.sendEvent()."""
        # if self._parent.sliceController.testRecorder:
        #     coord = (self._row, self._col)
        #     self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord)
        if event.type() == QEvent.MouseButtonPress:
            self.mousePressEvent(event)
            return True
        elif event.type() == QEvent.MouseButtonRelease:
            self.mouseReleaseEvent(event)
            return True
        elif event.type() == QEvent.MouseMove:
            self.mouseMoveEvent(event)
            return True
        QGraphicsItem.sceneEvent(self, event)
        return False

    def hoverEnterEvent(self, event):
        """
        If the selection is configured to always select
        everything, we don't draw a focus ring around everything,
        instead we only draw a focus ring around the hovered obj.
        """
        # if self.selectAllBehavior():
        #     self.setSelected(True)
        # forward the event to the empty_helix_item as well
        self._empty_helix_item.hoverEnterEvent(event)
    # end def

    def hoverLeaveEvent(self, event):
        # if self.selectAllBehavior():
        #     self.setSelected(False)
        self._empty_helix_item.hoverEnterEvent(event)
Ejemplo n.º 12
0
class VirtualHelixItem(QGraphicsEllipseItem, AbstractVirtualHelixItem):
    """
    The VirtualHelixItem is an individual circle that gets drawn in the SliceView
    as a child of the OrigamiPartItem. Taken as a group, many SliceHelix
    instances make up the crossection of the PlasmidPart. Clicking on a SliceHelix
    adds a VirtualHelix to the PlasmidPart. The SliceHelix then changes appearence
    and paints its corresponding VirtualHelix number.
    """
    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = ehi = empty_helix_item
        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.hide()

        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(_ZVALUE)
        self.lastMousePressAddedBases = False

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self._pen1, self._pen2 = (QPen(), QPen())
        self.createArrows()
        self.updateAppearance()

        self._prexoveritemgroup = _pxig = PreXoverItemGroup(_RECT, self)
        _pxig.setTransformOriginPoint(_RECT.center())
        # self._rect = QRectF(_RECT)
        # self._hover_rect = QRectF(_RECT)
        # self._outer_line = RotaryDialLine(self._rect, self)
        # self._hover_region = RotaryDialHoverRegion(self._hover_rect, self)
        self.show()

        # self._virtual_helix.setProperty('ehiX', ehi.mapToScene(0,0).x())
        # self._virtual_helix.setProperty('ehiY', ehi.mapToScene(0,0).y())

        self.getNeighbors()
    # end def

    def getNeighbors(self):
        items = filter(lambda x: type(x) is VirtualHelixItem, self.collidingItems())
        name = self._virtual_helix.getName()
        pos = self.scenePos()
        print("\n%s (%d, %d)" % (name, pos.x(), pos.y()))
        for nvhi in items:
            npos = nvhi.scenePos()
            line = QLineF(pos, npos)
            print("\t%s (%d, %d) %d" % (nvhi.virtualHelix().getName(), npos.x(), npos.y(), line.length()))

    def modelColor(self):
        return self.part().getProperty('color')
    # end def

    def updateAppearance(self):
        part_color = self.part().getProperty('color')

        self._USE_PEN = getPenObj(part_color, styles.SLICE_HELIX_STROKE_WIDTH)
        self._OUT_OF_SLICE_PEN = getPenObj(part_color, styles.SLICE_HELIX_STROKE_WIDTH)
        self._USE_BRUSH = getBrushObj(part_color, alpha=128)
        self._OUT_OF_SLICE_BRUSH = getBrushObj(part_color, alpha=64)
        self._OUT_OF_SLICE_TEXT_BRUSH = getBrushObj(styles.OUT_OF_SLICE_TEXT_COLOR)

        if self.part().crossSectionType() == LatticeType.HONEYCOMB:
            self._USE_PEN = getPenObj(styles.BLUE_STROKE, styles.SLICE_HELIX_STROKE_WIDTH)
            self._OUT_OF_SLICE_PEN = getPenObj(styles.BLUE_STROKE,\
                                          styles.SLICE_HELIX_STROKE_WIDTH)

        if self.part().partType() == PartType.NUCLEICACIDPART:
            self._OUT_OF_SLICE_BRUSH = _OUT_OF_SLICE_BRUSH_DEFAULT
            # self._USE_BRUSH = getBrushObj(part_color, lighter=180)
            self._USE_BRUSH = getBrushObj(part_color, alpha=150)

        self._label.setBrush(self._OUT_OF_SLICE_TEXT_BRUSH)
        self.setBrush(self._OUT_OF_SLICE_BRUSH)
        self.setPen(self._OUT_OF_SLICE_PEN)
        self.setRect(_RECT)
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def virtualHelixNumberChangedSlot(self, virtual_helix):
        """
        receives a signal containing a virtual_helix and the oldNumber
        as a safety check
        """
        self.setNumber()
    # end def

    def virtualHelixPropertyChangedSlot(self, virtual_helix, property_key, new_value):
        if property_key == 'eulerZ':
            self._prexoveritemgroup.setRotation(new_value)
            scamZ = self._virtual_helix.getProperty('scamZ') 
            if scamZ != new_value: 
                self._virtual_helix.setProperty('scamZ', new_value)
        elif property_key == 'scamZ':
            eulerZ = self._virtual_helix.getProperty('eulerZ') 
            if eulerZ != new_value: 
                self._virtual_helix.setProperty('eulerZ', new_value)
        elif property_key == 'ehiX':
            ehi_pos = self._empty_helix_item.scenePos()
            self._empty_helix_item.setPos(new_value, ehi_pos.y())
            pass 
        elif property_key == 'ehiY':
            ehi_pos = self._empty_helix_item.scenePos()
            self._empty_helix_item.setPos(ehi_pos.x(),new_value)
    # end def


    def virtualHelixRemovedSlot(self, virtual_helix):
        self._controller.disconnectSignals()
        self._controller = None
        self._empty_helix_item.setNotHovered()
        self._virtual_helix = None
        self._empty_helix_item = None
        self.scene().removeItem(self._label)
        self._label = None
        self.scene().removeItem(self)
    # end def

    def createLabel(self):
        label = QGraphicsSimpleTextItem("%d" % self._virtual_helix.number())
        label.setFont(_FONT)
        label.setZValue(_ZVALUE)
        label.setParentItem(self)
        return label
    # end def

    def createArrows(self):
        rad = _RADIUS
        pen1 = self._pen1
        pen2 = self._pen2
        pen1.setWidth(3)
        pen2.setWidth(3)
        pen1.setBrush(Qt.gray)
        pen2.setBrush(Qt.lightGray)
        if self._virtual_helix.isEvenParity():
            arrow1 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
            arrow2 = QGraphicsLineItem(0, rad, rad, rad, self)
        else:
            arrow1 = QGraphicsLineItem(0, rad, rad, rad, self)
            arrow2 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
        arrow1.setTransformOriginPoint(rad, rad)
        arrow2.setTransformOriginPoint(rad, rad)
        arrow1.setZValue(400)
        arrow2.setZValue(400)
        arrow1.setPen(pen1)
        arrow2.setPen(pen2)
        self.arrow1 = arrow1
        self.arrow2 = arrow2
        self.arrow1.hide()
        self.arrow2.hide()
    # end def

    def updateScafArrow(self, idx):
        scaf_strand = self._virtual_helix.scaf(idx)
        if scaf_strand:
            scaf_strand_color = scaf_strand.oligo().getColor()
            scaf_alpha = 230 if scaf_strand.hasXoverAt(idx) else 128
        else:
            scaf_strand_color = '#a0a0a4' #Qt.gray
            scaf_alpha = 26

        scaf_strand_color_obj = getColorObj(scaf_strand_color, alpha=scaf_alpha)
        self._pen1.setBrush(scaf_strand_color_obj)
        self.arrow1.setPen(self._pen1)
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        # for some reason rotation is CW and not CCW with increasing angle
        self.arrow1.setRotation(angle + part._TWIST_OFFSET)

    def updateStapArrow(self, idx):
        stap_strand = self._virtual_helix.stap(idx)
        if stap_strand:
            stap_strand_color = stap_strand.oligo().getColor()
            stap_alpha = 230 if stap_strand.hasXoverAt(idx) else 128
        else:
            stap_strand_color = '#c0c0c0' # Qt.lightGray
            stap_alpha = 26
        stap_strand_color_obj = getColorObj(stap_strand_color, alpha=stap_alpha)
        self._pen2.setBrush(stap_strand_color_obj)
        self.arrow2.setPen(self._pen2)
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        self.arrow2.setRotation(angle + part._TWIST_OFFSET)
    # end def

    def setNumber(self):
        """docstring for setNumber"""
        vh = self._virtual_helix
        num = vh.number()
        label = self._label

        if num is not None:
            label.setText("%d" % num)
        else:
            return

        y_val = _RADIUS / 3
        if num < 10:
            label.setPos(_RADIUS / 1.5, y_val)
        elif num < 100:
            label.setPos(_RADIUS / 3, y_val)
        else: # _number >= 100
            label.setPos(0, y_val)
        b_rect = label.boundingRect()
        posx = b_rect.width()/2
        posy = b_rect.height()/2
        label.setPos(_RADIUS-posx, _RADIUS-posy)
    # end def

    def part(self):
        return self._empty_helix_item.part()

    def virtualHelix(self):
        return self._virtual_helix
    # end def

    def number(self):
        return self.virtualHelix().number()

    def setActiveSliceView(self, idx, has_scaf, has_stap):
        if has_scaf:
            self.setPen(self._USE_PEN)
            self.setBrush(self._USE_BRUSH)
            self._label.setBrush(_USE_TEXT_BRUSH)
            self.updateScafArrow(idx)
            self.arrow1.show()
        else:
            self.setPen(self._OUT_OF_SLICE_PEN)
            self.setBrush(self._OUT_OF_SLICE_BRUSH)
            self._label.setBrush(self._OUT_OF_SLICE_TEXT_BRUSH)
            self.arrow1.hide()
        if has_stap:
            self.updateStapArrow(idx)
            self.arrow2.show()
        else:
            self.arrow2.hide()
    # end def

    ############################ User Interaction ############################
    def sceneEvent(self, event):
        """Included for unit testing in order to grab events that are sent
        via QGraphicsScene.sendEvent()."""
        # if self._parent.sliceController.testRecorder:
        #     coord = (self._row, self._col)
        #     self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord)
        if event.type() == QEvent.MouseButtonPress:
            self.mousePressEvent(event)
            return True
        elif event.type() == QEvent.MouseButtonRelease:
            self.mouseReleaseEvent(event)
            return True
        elif event.type() == QEvent.MouseMove:
            self.mouseMoveEvent(event)
            return True
        QGraphicsItem.sceneEvent(self, event)
        return False

    def hoverEnterEvent(self, event):
        """
        If the selection is configured to always select
        everything, we don't draw a focus ring around everything,
        instead we only draw a focus ring around the hovered obj.
        """
        # if self.selectAllBehavior():
        #     self.setSelected(True)
        # forward the event to the empty_helix_item as well
        self._empty_helix_item.hoverEnterEvent(event)
    # end def

    def hoverLeaveEvent(self, event):
        # if self.selectAllBehavior():
        #     self.setSelected(False)
        self._empty_helix_item.hoverEnterEvent(event)
Ejemplo n.º 13
0
class VirtualHelixItem(QGraphicsEllipseItem, AbstractVirtualHelixItem):
    """
    The VirtualHelixItem is an individual circle that gets drawn in the SliceView
    as a child of the OrigamiPartItem. Taken as a group, many SliceHelix
    instances make up the crossection of the PlasmidPart. Clicking on a SliceHelix
    adds a VirtualHelix to the PlasmidPart. The SliceHelix then changes appearence
    and paints its corresponding VirtualHelix number.
    """
    # set up default, hover, and active drawing styles
    _RADIUS = styles.SLICE_HELIX_RADIUS
    _RECT = QRectF(0, 0, 2 * _RADIUS, 2 * _RADIUS)
    rect_gain = 0.25
    _RECT = _RECT.adjusted(rect_gain, rect_gain, rect_gain, rect_gain)
    _FONT = styles.SLICE_NUM_FONT
    _ZVALUE = styles.ZSLICEHELIX+3
    _OUT_OF_SLICE_BRUSH_DEFAULT = getBrushObj(styles.OUT_OF_SLICE_FILL) # QBrush(QColor(250, 250, 250))
    _USE_TEXT_BRUSH = getBrushObj(styles.USE_TEXT_COLOR)
    _OUT_OF_SLICE_TEXT_BRUSH = getBrushObj(styles.OUT_OF_SLICE_TEXT_COLOR)

    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = empty_helix_item
        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.hide()
        # drawing related

        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(self._ZVALUE)
        self.lastMousePressAddedBases = False

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self._pen1, self._pen2 = (QPen(), QPen())
        self.createArrows()
        self.updateProperty()

        self._rect = QRectF()
        self._hover_rect = QRectF()
        self._outer_line = RotateLine(self._rect, self)
        # self._inner_line = RotateLine(self._rect, self)
        self._hover_region = RotateHoverRegion(self._hover_rect, self)
        self.updateRects()

        self.show()
    # end def

    def radius(self):
        return self._radius
    # end def

    def modelColor(self):
        return self.part().getProperty('color')
    # end def

    def updateRects(self):
        diameter = self.boundingRect().width()/2 # round(dna_length * pi / 100,2)
        self._radius = self._RADIUS
        diameter = self._RADIUS * 2
        self._rect = QRectF(0, 0, diameter, diameter)
        self._outer_line.updateRect(QRectF(-GAP/2, -GAP/2, diameter+GAP, diameter+GAP))
        # self._inner_line.updateRect(QRectF(GAP/2, GAP/2, diameter-GAP, diameter-GAP))
        # self._hover_rect = self._rect.adjusted(-H_W, -H_W, H_W, H_W)
        self._hover_region.updateRect(self._rect)
    # end def

    def updateProperty(self):
        part_color = self.part().getProperty('color')

        self._USE_PEN = getPenObj(part_color, styles.SLICE_HELIX_STROKE_WIDTH)
        self._OUT_OF_SLICE_PEN = getPenObj(part_color, styles.SLICE_HELIX_STROKE_WIDTH)
        self._USE_BRUSH = getBrushObj(part_color, alpha=128)
        self._OUT_OF_SLICE_BRUSH = getBrushObj(part_color, alpha=64)

        if self.part().crossSectionType() == LatticeType.HONEYCOMB:
            self._USE_PEN = getPenObj(styles.BLUE_STROKE, styles.SLICE_HELIX_STROKE_WIDTH)
            self._OUT_OF_SLICE_PEN = getPenObj(styles.BLUE_STROKE,\
                                          styles.SLICE_HELIX_STROKE_WIDTH)

        if self.part().partType() == PartType.NUCLEICACIDPART:
            self._OUT_OF_SLICE_BRUSH = self._OUT_OF_SLICE_BRUSH_DEFAULT
            # self._USE_BRUSH = getBrushObj(part_color, lighter=180)
            self._USE_BRUSH = getBrushObj(part_color, alpha=150)

        self._label.setBrush(self._OUT_OF_SLICE_TEXT_BRUSH)
        self.setBrush(self._OUT_OF_SLICE_BRUSH)
        self.setPen(self._OUT_OF_SLICE_PEN)
        self.setRect(self._RECT)
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def virtualHelixNumberChangedSlot(self, virtual_helix):
        """
        receives a signal containing a virtual_helix and the oldNumber
        as a safety check
        """
        self.setNumber()
    # end def

    def virtualHelixPropertyChangedSlot(self, virtual_helix, property_key, new_value):
        if property_key == 'eulerZ':
            self.setTransformOriginPoint(self.boundingRect().center())
            self.setRotation(new_value)
            self.setTransformOriginPoint(QPointF(0,0))
    # end def

    def virtualHelixRemovedSlot(self, virtual_helix):
        self._controller.disconnectSignals()
        self._controller = None
        self._empty_helix_item.setNotHovered()
        self._virtual_helix = None
        self._empty_helix_item = None
        self.scene().removeItem(self._label)
        self._label = None
        self.scene().removeItem(self)
    # end def

    def createLabel(self):
        label = QGraphicsSimpleTextItem("%d" % self._virtual_helix.number())
        label.setFont(self._FONT)
        label.setZValue(self._ZVALUE)
        label.setParentItem(self)
        return label
    # end def

    def createArrows(self):
        rad = self._RADIUS
        pen1 = self._pen1
        pen2 = self._pen2
        pen1.setWidth(3)
        pen2.setWidth(3)
        pen1.setBrush(Qt.gray)
        pen2.setBrush(Qt.lightGray)
        if self._virtual_helix.isEvenParity():
            arrow1 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
            arrow2 = QGraphicsLineItem(0, rad, rad, rad, self)
        else:
            arrow1 = QGraphicsLineItem(0, rad, rad, rad, self)
            arrow2 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
        arrow1.setTransformOriginPoint(rad, rad)
        arrow2.setTransformOriginPoint(rad, rad)
        arrow1.setZValue(400)
        arrow2.setZValue(400)
        arrow1.setPen(pen1)
        arrow2.setPen(pen2)
        self.arrow1 = arrow1
        self.arrow2 = arrow2
        self.arrow1.hide()
        self.arrow2.hide()
    # end def

    def updateScafArrow(self, idx):
        scaf_strand = self._virtual_helix.scaf(idx)
        if scaf_strand:
            scaf_strand_color = scaf_strand.oligo().color()
            scaf_alpha = 230 if scaf_strand.hasXoverAt(idx) else 128
        else:
            scaf_strand_color = '#a0a0a4' #Qt.gray
            scaf_alpha = 26

        scaf_strand_color_obj = getColorObj(scaf_strand_color, alpha=scaf_alpha)
        self._pen1.setBrush(scaf_strand_color_obj)
        self.arrow1.setPen(self._pen1)
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        # for some reason rotation is CW and not CCW with increasing angle
        self.arrow1.setRotation(angle + part._TWIST_OFFSET)

    def updateStapArrow(self, idx):
        stap_strand = self._virtual_helix.stap(idx)
        if stap_strand:
            stap_strand_color = stap_strand.oligo().color()
            stap_alpha = 230 if stap_strand.hasXoverAt(idx) else 128
        else:
            stap_strand_color = '#c0c0c0' # Qt.lightGray
            stap_alpha = 26
        stap_strand_color_obj = getColorObj(stap_strand_color, alpha=stap_alpha)
        self._pen2.setBrush(stap_strand_color_obj)
        self.arrow2.setPen(self._pen2)
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        self.arrow2.setRotation(angle + part._TWIST_OFFSET)
    # end def

    def setNumber(self):
        """docstring for setNumber"""
        vh = self._virtual_helix
        num = vh.number()
        label = self._label
        radius = self._RADIUS

        if num is not None:
            label.setText("%d" % num)
        else:
            return

        y_val = radius / 3
        if num < 10:
            label.setPos(radius / 1.5, y_val)
        elif num < 100:
            label.setPos(radius / 3, y_val)
        else: # _number >= 100
            label.setPos(0, y_val)
        b_rect = label.boundingRect()
        posx = b_rect.width()/2
        posy = b_rect.height()/2
        label.setPos(radius-posx, radius-posy)
    # end def

    def part(self):
        return self._empty_helix_item.part()

    def virtualHelix(self):
        return self._virtual_helix
    # end def

    def number(self):
        return self.virtualHelix().number()

    def setActiveSliceView(self, idx, has_scaf, has_stap):
        if has_scaf:
            self.setPen(self._USE_PEN)
            self.setBrush(self._USE_BRUSH)
            self._label.setBrush(self._USE_TEXT_BRUSH)
            self.updateScafArrow(idx)
            self.arrow1.show()
        else:
            self.setPen(self._OUT_OF_SLICE_PEN)
            self.setBrush(self._OUT_OF_SLICE_BRUSH)
            self._label.setBrush(self._OUT_OF_SLICE_TEXT_BRUSH)
            self.arrow1.hide()
        if has_stap:
            self.updateStapArrow(idx)
            self.arrow2.show()
        else:
            self.arrow2.hide()
    # end def

    ############################ User Interaction ############################
    def sceneEvent(self, event):
        """Included for unit testing in order to grab events that are sent
        via QGraphicsScene.sendEvent()."""
        # if self._parent.sliceController.testRecorder:
        #     coord = (self._row, self._col)
        #     self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord)
        if event.type() == QEvent.MouseButtonPress:
            self.mousePressEvent(event)
            return True
        elif event.type() == QEvent.MouseButtonRelease:
            self.mouseReleaseEvent(event)
            return True
        elif event.type() == QEvent.MouseMove:
            self.mouseMoveEvent(event)
            return True
        QGraphicsItem.sceneEvent(self, event)
        return False

    def hoverEnterEvent(self, event):
        """
        If the selection is configured to always select
        everything, we don't draw a focus ring around everything,
        instead we only draw a focus ring around the hovered obj.
        """
        # if self.selectAllBehavior():
        #     self.setSelected(True)
        # forward the event to the empty_helix_item as well
        self._empty_helix_item.hoverEnterEvent(event)
    # end def

    def hoverLeaveEvent(self, event):
        # if self.selectAllBehavior():
        #     self.setSelected(False)
        self._empty_helix_item.hoverEnterEvent(event)
Ejemplo n.º 14
0
class SliceVirtualHelixItem(AbstractVirtualHelixItem, QGraphicsEllipseItem):
    """The VirtualHelixItem is an individual circle that gets drawn in the SliceView
    as a child of the NucleicAcidPartItem. Taken as a group, many SliceHelix
    instances make up the crossection of the NucleicAcidPart. Clicking on a SliceHelix
    adds a VirtualHelix to the PlasmidPart. The SliceHelix then changes appearence
    and paints its corresponding VirtualHelix number.

    Attributes:
        FILTER_NAME (str): Belongs to the filter class 'virtual_helix'.
        is_active (bool): Does the item have focus.
        old_pen (QPen): temp storage for pen for easy restoration on appearance change.
        wedge_gizmos (dict): dictionary of `WedgeGizmo` objects.
    """
    FILTER_NAME = 'virtual_helix'

    def __init__(self, model_virtual_helix, part_item):
        """
        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            part_item (cadnano.gui.views.sliceview.nucleicacidpartitem.NucleicAcidPartItem): the part item
        """
        AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
        QGraphicsEllipseItem.__init__(self, parent=part_item)
        self._controller = VirtualHelixItemController(self, self._model_part, False, True)

        self.hide()
        model_part = self._model_part
        x, y = model_part.locationQt(self._id_num, part_item.scaleFactor())
        # set position to offset for radius
        # self.setTransformOriginPoint(_RADIUS, _RADIUS)
        self.setCenterPos(x, y)

        self.wedge_gizmos = {}
        self._added_wedge_gizmos = set()
        # self._prexo_gizmos = []

        self.setAcceptHoverEvents(True)
        self.setZValue(_ZVALUE)

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()

        self.old_pen = None
        self.is_active = False
        self.updateAppearance()

        self.show()

        self._right_mouse_move = False
    # end def

    ### ACCESSORS ###
    def part(self):
        """Reference to the model part associated with the parent part item.

        Returns:
            NucleicAcidPart: the model part
        """
        return self._part_item.part()
    # end def

    def setSnapOrigin(self, is_snap):
        """Used to toggle an item as the snap origin. See `SelectSliceTool`.

        Args:
            is_snap (bool): True if this should be the snap origin, False otherwise.
        """
        if is_snap:
            op = self.pen()
            if self.old_pen is None:
                self.old_pen = op
            self.setPen(getPenObj(op.color().name(), SNAP_WIDTH))
        else:
            self.setPen(self.old_pen)
            self.old_pen = None

    def partItem(self):
        """Reference to the parent part item.

        Returns:
            NucleicAcidPartItem: the part item
        """
        return self._part_item
    # end def

    def idNum(self):
        """Virual helix ID number.

        Returns:
            int: the id_num of the virtual helix object.
        """
        return self._id_num
    # end def

    def activate(self):
        """Sets the VirtualHelixItem object as active (i.e. having focus due
        to user input) and calls updateAppearance.
        """
        self.is_active = True
        self.updateAppearance()

    def deactivate(self):
        """Sets the VirtualHelixItem object as not active (i.e. does not have
        focus) and calls updateAppearance.
        """
        self.is_active = False
        self.updateAppearance()

    def setCenterPos(self, x, y):
        """Moves this item a new position such that its center is located at
        (x,y).

        Args:
            x (float): new x coordinate
            y (float): new y coordinate
        """
        # invert the y axis
        part_item = self._part_item
        parent_item = self.parentItem()
        pos = QPointF(x - _RADIUS, y - _RADIUS)
        if parent_item != part_item:
            pos = parent_item.mapFromItem(part_item, pos)
        self.setPos(pos)
    # end def

    def getCenterScenePos(self):
        """
        Returns:
            QPointF: the scenePos of the virtualhelixitem center
        """
        return self.scenePos() + QPointF(_RADIUS, _RADIUS)
    # end def

    def modelColor(self):
        """The color associated with this item's `Part`.

        Returns:
            str: hex representation of Color, e.g. `#0066cc`.
        """
        return self._model_part.getProperty('color')
    # end def

    def partCrossoverSpanAngle(self):
        """The angle span, centered at each base, within which crossovers
        will be allowed by the interface. The span is drawn by the WedgeGizmo.

        Returns:
            int: The span angle (default is set in NucleicAcidPart init)
        """
        return float(self._model_part.getProperty('crossover_span_angle'))
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def mousePressEvent(self, event):
        """Event handler for when the mouse button is pressed inside
        this item. If a tool-specific mouse press method is defined, it will be
        called for the currently active tool. Otherwise, the default
        QGraphicsItem.mousePressEvent will be called.

        Note:
            Only applies the event if the clicked item is in the part
            item's active filter set.

        Args:
            event (QMouseEvent): contains parameters that describe the mouse event.
        """
        if self.FILTER_NAME not in self._part_item.getFilterSet():
            return
        if event.button() == Qt.RightButton:
            return
        part_item = self._part_item
        tool = part_item._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MousePress"
        if hasattr(self, tool_method_name):
            getattr(self, tool_method_name)(tool, part_item, event)
        else:
            QGraphicsItem.mousePressEvent(self, event)
    # end def

    def selectToolMousePress(self, tool, part_item, event):
        """The event handler for when the mouse button is pressed inside this
        item with the SelectTool active.

        Args:
            tool (SelectSliceTool): reference to call tool-specific methods
            part_item (cadnano.gui.views.sliceview.nucleicacidpartitem.NucleicAcidPartItem): reference to the part item
            event (QMouseEvent): contains parameters that describe the mouse event

        """
        part = self._model_part
        part.setSelected(True)
        tool.selectOrSnap(part_item, self, event)
        # return QGraphicsItem.mousePressEvent(self, event)
    # end def

    def pencilToolMousePress(self, tool, part_item, event):
        """Summary

        Args:
            tool (SelectSliceTool): reference to call tool-specific methods
            part_item (cadnano.gui.views.sliceview.nucleicacidpartitem.NucleicAcidPartItem): reference to the part item
            event (QMouseEvent): contains parameters that describe the mouse event
        """
        part = self._model_part
        print("pencilToolMousePress", part)
        # tool.attemptToCreateStrand

    def virtualHelixPropertyChangedSlot(self, keys, values):
        """The event handler for when the a model virtual helix propety has
        changed. See partVirtualHelixPropertyChangedSignal.

        Calls updateAppearance(), which will refresh styles if needed.

        Args:
            keys (tuple): names of properties that changed
            values (tuple): new values of properties that change

        """
        # for key, val in zip(keys, values):
        #     pass
        self.updateAppearance()
    # end def

    def virtualHelixRemovedSlot(self):
        """The event handler for when a virtual helix is removed from the model.

        Disconnects signals, and sets  internal references to label, part_item,
        and model_part to None, and finally removes the item from the scene.
        """
        self._controller.disconnectSignals()
        self._controller = None
        part_item = self._part_item
        tool = part_item._getActiveTool()
        if tool.methodPrefix() == "selectTool":
            tool.hideLineItem()
        self.scene().removeItem(self._label)
        self._label = None
        self._part_item = None
        self._model_part = None
        self.scene().removeItem(self)
    # end def

    def updateAppearance(self):
        """Check item's current visibility, color and active state, and sets
        pen, brush, text according to style defaults.
        """
        is_visible, color = self._model_vh.getProperty(['is_visible', 'color'])
        if is_visible:
            self.show()
        else:
            self.hide()
            return

        pwidth = styles.SLICE_HELIX_STROKE_WIDTH if self.old_pen is None else SNAP_WIDTH

        if self.is_active:
            self._USE_PEN = getPenObj(styles.ACTIVE_STROKE, pwidth)
        else:
            self._USE_PEN = getPenObj(color, pwidth)

        self._TEXT_BRUSH = getBrushObj(styles.SLICE_TEXT_COLOR)

        self._BRUSH = _BRUSH_DEFAULT
        self._USE_BRUSH = getBrushObj(color, alpha=150)

        self._label.setBrush(self._TEXT_BRUSH)
        self.setBrush(self._BRUSH)
        self.setPen(self._USE_PEN)
        self.setRect(_RECT)
    # end def

    def updatePosition(self):
        """Queries the model virtual helix for latest x,y coodinates, and sets
        them in the scene if necessary.

        Note:
            coordinates in the model are always in the part
        """
        part_item = self._part_item
        # sf = part_item.scaleFactor()
        x, y = self._model_part.locationQt(self._id_num, part_item.scaleFactor())
        new_pos = QPointF(x - _RADIUS, y - _RADIUS)         # top left
        tl_pos = part_item.mapFromScene(self.scenePos())    # top left

        """
        better to compare QPointF's since it handles difference
        tolerances for you with !=
        """
        if new_pos != tl_pos:
            parent_item = self.parentItem()
            if parent_item != part_item:
                new_pos = parent_item.mapFromItem(part_item, new_pos)
            self.setPos(new_pos)
    # end def

    def createLabel(self):
        """Creates a text label to display the ID number. Font and Z are set
        in slicestyles.

        Returns:
            QGraphicsSimpleTextItem: the label
        """
        label = QGraphicsSimpleTextItem("%d" % self.idNum())
        label.setFont(_FONT)
        label.setZValue(_ZVALUE)
        label.setParentItem(self)
        return label
    # end def

    def beginAddWedgeGizmos(self):
        """Resets the list of WedgeGizmos that will be processed by
        endAddWedgeGizmos.

        Called by NucleicAcidPartItem _refreshVirtualHelixItemGizmos, before
        setWedgeGizmo and endAddWedgeGizmos.
        """
        self._added_wedge_gizmos.clear()
    # end def

    def endAddWedgeGizmos(self):
        """Removes beginAddWedgeGizmos that no longer point at a neighbor virtual helix.

        Called by NucleicAcidPartItem _refreshVirtualHelixItemGizmos in,
        after beginAddWedgeGizmos and setWedgeGizmo.
        """
        remove_list = []
        scene = self.scene()
        wg_dict = self.wedge_gizmos
        recently_added = self._added_wedge_gizmos
        for neighbor_virtual_helix in wg_dict.keys():
            if neighbor_virtual_helix not in recently_added:
                remove_list.append(neighbor_virtual_helix)
        for nvh in remove_list:
            wg = wg_dict.get(nvh)
            del wg_dict[nvh]
            scene.removeItem(wg)
    # end def

    def setWedgeGizmo(self, neighbor_virtual_helix, neighbor_virtual_helix_item):
        """Adds a WedgeGizmo to oriented toward the specified neighbor vhi.

        Called by NucleicAcidPartItem _refreshVirtualHelixItemGizmos, in between
        with beginAddWedgeGizmos and endAddWedgeGizmos.

        Args:
            neighbor_virtual_helix (int): the id_num of neighboring virtual helix
            neighbor_virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem):
            the neighboring virtual helix item
        """
        wg_dict = self.wedge_gizmos
        nvhi = neighbor_virtual_helix_item

        nvhi_name = nvhi.cnModel().getProperty('name')
        pos = self.scenePos()
        line = QLineF(pos, nvhi.scenePos())
        line.translate(_RADIUS, _RADIUS)
        if line.length() > (_RADIUS*1.99):
            color = '#5a8bff'
        else:
            color = '#cc0000'
            nvhi_name = nvhi_name + '*'  # mark as invalid
        line.setLength(_RADIUS)
        if neighbor_virtual_helix in wg_dict:
            wedge_item = wg_dict[neighbor_virtual_helix]
        else:
            wedge_item = WedgeGizmo(_RADIUS, WEDGE_RECT, self)
            wg_dict[neighbor_virtual_helix] = wedge_item
        wedge_item.showWedge(line.angle(), color, outline_only=False)
        self._added_wedge_gizmos.add(neighbor_virtual_helix)
    # end def

    def setNumber(self):
        """Updates the associated QGraphicsSimpleTextItem label text to match
        the id_num. Adjusts the label position so it is centered regardless
        of number of digits in the label.
        """
        num = self.idNum()
        label = self._label

        if num is not None:
            label.setText("%d" % num)
        else:
            return

        y_val = _RADIUS / 3
        if num < 10:
            label.setPos(_RADIUS / 1.5, y_val)
        elif num < 100:
            label.setPos(_RADIUS / 3, y_val)
        else:  # _number >= 100
            label.setPos(0, y_val)
        b_rect = label.boundingRect()
        posx = b_rect.width()/2
        posy = b_rect.height()/2
        label.setPos(_RADIUS-posx, _RADIUS-posy)
Ejemplo n.º 15
0
 def __init__(self, model_virtual_helix, part_item):
     AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
     CNOutlinerItem.__init__(self, model_virtual_helix, parent=part_item)
     self.setFlags(LEAF_FLAGS)
     self._controller = VirtualHelixItemController(self, model_virtual_helix.part(), False, False)
Ejemplo n.º 16
0
class PathVirtualHelixItem(AbstractVirtualHelixItem, QGraphicsPathItem):
    """VirtualHelixItem for PathView

    Attributes:
        drag_last_position (TYPE): Description
        FILTER_NAME (str): Description
        findChild (TYPE): Description
        handle_start (TYPE): Description
        is_active (bool): Description
    """
    findChild = util.findChild  # for debug
    FILTER_NAME = "virtual_helix"

    def __init__(self, model_virtual_helix, part_item, viewroot):
        """Summary

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            part_item (TYPE): Description
            viewroot (TYPE): Description
        """
        AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
        QGraphicsPathItem.__init__(self, parent=part_item.proxy())
        self._viewroot = viewroot
        self._getActiveTool = part_item._getActiveTool
        self._controller = VirtualHelixItemController(self, self._model_part,
                                                      False, True)

        self._handle = VirtualHelixHandleItem(self, part_item, viewroot)
        self._last_strand_set = None
        self._last_idx = None
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setBrush(getNoBrush())

        view = self.view()
        view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot)
        should_show_details = view.shouldShowDetails()

        pen = newPenObj(styles.MINOR_GRID_STROKE,
                        styles.MINOR_GRID_STROKE_WIDTH)
        pen.setCosmetic(should_show_details)
        self.setPen(pen)

        self.is_active = False

        self.refreshPath()
        self.setAcceptHoverEvents(True)  # for pathtools
        self.setZValue(styles.ZPATHHELIX)

        self._right_mouse_move = False
        self.drag_last_position = self.handle_start = self.pos()

    # end def

    ### SIGNALS ###

    ### SLOTS indirectly called from the part ###

    def levelOfDetailChangedSlot(self, boolval):
        """Not connected to the model, only the QGraphicsView

        Args:
            boolval (TYPE): Description
        """
        pen = self.pen()
        pen.setCosmetic(boolval)
        # print("levelOfDetailChangedSlot", boolval, pen.width())
        # if boolval:
        #     pass
        # else:
        #     pass
        self.setPen(pen)

    # end def

    def strandAddedSlot(self, sender, strand):
        """
        Instantiates a StrandItem upon notification that the model has a
        new Strand.  The StrandItem is responsible for creating its own
        controller for communication with the model, and for adding itself to
        its parent (which is *this* VirtualHelixItem, i.e. 'self').

        Args:
            sender (obj): Model object that emitted the signal.
            strand (TYPE): Description
        """
        StrandItem(strand, self, self._viewroot)

    # end def

    def virtualHelixRemovedSlot(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self.view().levelOfDetailChangedSignal.disconnect(
            self.levelOfDetailChangedSlot)
        self._controller.disconnectSignals()
        self._controller = None

        scene = self.scene()
        self._handle.remove()
        scene.removeItem(self)
        self._part_item = None
        self._model_part = None
        self._getActiveTool = None
        self._handle = None

    # end def

    def virtualHelixPropertyChangedSlot(self, keys, values):
        """Summary

        Args:
            keys (TYPE): Description
            values (TYPE): Description

        Returns:
            TYPE: Description
        """
        for key, val in zip(keys, values):
            if key == 'is_visible':
                if val:
                    self.show()
                    self._handle.show()
                    self.showXoverItems()
                else:
                    self.hideXoverItems()
                    self.hide()
                    self._handle.hide()
                    return
            if key == 'z':
                part_item = self._part_item
                z = part_item.convertToQtZ(val)
                if self.x() != z:
                    self.setX(z)
                    """ The handle is selected, so deselect to
                    accurately position then reselect
                    """
                    vhi_h = self._handle
                    vhi_h.tempReparent()
                    vhi_h.setX(z - _VH_XOFFSET)
                    # if self.isSelected():
                    #     print("ImZ", self.idNum())
                    part_item.updateXoverItems(self)
                    vhi_h_selection_group = self._viewroot.vhiHandleSelectionGroup(
                    )
                    vhi_h_selection_group.addToGroup(vhi_h)
            elif key == 'eulerZ':
                self._handle.rotateWithCenterOrigin(val)
                # self._prexoveritemgroup.updatePositionsAfterRotation(value)
            ### GEOMETRY PROPERTIES ###
            elif key == 'repeat_hint':
                pass
                # self.updateRepeats(int(val))
            elif key == 'bases_per_repeat':
                pass
                # self.updateBasesPerRepeat(int(val))
            elif key == 'turns_per_repeat':
                # self.updateTurnsPerRepeat(int(val))
                pass
            ### RUNTIME PROPERTIES ###
            elif key == 'neighbors':
                # this means a virtual helix in the slice view has moved
                # so we need to clear and redraw the PreXoverItems just in case
                if self.isActive():
                    self._part_item.setPreXoverItemsVisible(self)
        self.refreshPath()

    # end def

    def showXoverItems(self):
        """Summary

        Returns:
            TYPE: Description
        """
        for item in self.childItems():
            if isinstance(item, XoverNode3):
                item.showXover()

    # end def

    def hideXoverItems(self):
        """Summary

        Returns:
            TYPE: Description
        """
        for item in self.childItems():
            if isinstance(item, XoverNode3):
                item.hideXover()

    # end def

    ### ACCESSORS ###
    def viewroot(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._viewroot

    # end def

    def handle(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._handle

    # end def

    def window(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._part_item.window()

    # end def

    def view(self):
        return self._viewroot.scene().views()[0]

    # end def

    ### DRAWING METHODS ###
    def upperLeftCornerOfBase(self, idx, strand):
        """Summary

        Args:
            idx (int): the base index within the virtual helix
            strand (TYPE): Description

        Returns:
            TYPE: Description
        """
        x = idx * _BASE_WIDTH
        y = 0 if strand.isForward() else _BASE_WIDTH
        return x, y

    # end def

    def upperLeftCornerOfBaseType(self, idx, is_fwd):
        """Summary

        Args:
            idx (int): the base index within the virtual helix
            is_fwd (TYPE): Description

        Returns:
            TYPE: Description
        """
        x = idx * _BASE_WIDTH
        y = 0 if is_fwd else _BASE_WIDTH
        return x, y

    # end def

    def refreshPath(self):
        """
        Returns a QPainterPath object for the minor grid lines.
        The path also includes a border outline and a midline for
        dividing scaffold and staple bases.
        """
        bw = _BASE_WIDTH
        bw2 = 2 * bw
        part = self.part()
        path = QPainterPath()
        sub_step_size = part.subStepSize()
        canvas_size = self._model_vh.getSize()
        # border
        path.addRect(0, 0, bw * canvas_size, 2 * bw)
        # minor tick marks
        for i in range(canvas_size):
            x = round(bw * i) + .5
            if i % sub_step_size == 0:
                path.moveTo(x - .5, 0)
                path.lineTo(x - .5, bw2)
                path.lineTo(x - .25, bw2)
                path.lineTo(x - .25, 0)
                path.lineTo(x, 0)
                path.lineTo(x, bw2)
                path.lineTo(x + .25, bw2)
                path.lineTo(x + .25, 0)
                path.lineTo(x + .5, 0)
                path.lineTo(x + .5, bw2)

                # path.moveTo(x-.5, 0)
                # path.lineTo(x-.5, 2 * bw)
                # path.lineTo(x+.5, 2 * bw)
                # path.lineTo(x+.5, 0)

            else:
                path.moveTo(x, 0)
                path.lineTo(x, 2 * bw)

        # staple-scaffold divider
        path.moveTo(0, bw)
        path.lineTo(bw * canvas_size, bw)

        self.setPath(path)

    # end def

    def resize(self):
        """Called by part on resize.
        """
        self.refreshPath()

    # end def

    ### PUBLIC SUPPORT METHODS ###
    def activate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        pen = self.pen()
        pen.setColor(getColorObj(styles.MINOR_GRID_STROKE_ACTIVE))
        self.setPen(pen)
        self.is_active = True

    # end def

    def deactivate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        pen = self.pen()
        pen.setColor(getColorObj(styles.MINOR_GRID_STROKE))
        self.setPen(pen)
        self.is_active = False

    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        """
        Parses a mousePressEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        # 1. Check if we are doing a Z translation
        if event.button() == Qt.RightButton:
            viewroot = self._viewroot
            current_filter_set = viewroot.selectionFilterSet()
            if self.FILTER_NAME in current_filter_set and self.part(
            ).isZEditable():
                self._right_mouse_move = True
                self.drag_last_position = event.scenePos()
                self.handle_start = self.pos()
            return

        self.scene().views()[0].addToPressList(self)
        strand_set, idx = self.baseAtPoint(event.pos())
        self._model_vh.setActive(strand_set.isForward(), idx)
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MousePress"

        if hasattr(self, tool_method_name):
            self._last_strand_set, self._last_idx = strand_set, idx
            getattr(self, tool_method_name)(strand_set, idx, event.modifiers())
        else:
            event.setAccepted(False)

    # end def

    def mouseMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        # 1. Check if we are doing a Z translation
        if self._right_mouse_move:
            MOVE_THRESHOLD = 0.01  # ignore small moves
            new_pos = event.scenePos()
            delta = new_pos - self.drag_last_position
            dx = int(floor(delta.x() / _BASE_WIDTH)) * _BASE_WIDTH
            x = self.handle_start.x() + dx
            if abs(dx) > MOVE_THRESHOLD or dx == 0.0:
                old_x = self.x()
                self.setX(x)
                vhi_h = self._handle
                vhi_h.tempReparent()
                vhi_h.setX(x - _VH_XOFFSET)
                self._part_item.updateXoverItems(self)
                dz = self._part_item.convertToModelZ(x - old_x)
                self._model_part.translateVirtualHelices([self.idNum()],
                                                         0,
                                                         0,
                                                         dz,
                                                         False,
                                                         use_undostack=False)
                return
        # 2. Forward event to tool
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MouseMove"
        if hasattr(self, tool_method_name):
            strand_set, idx = self.baseAtPoint(event.pos())
            if self._last_strand_set != strand_set or self._last_idx != idx:
                self._last_strand_set, self._last_idx = strand_set, idx
                getattr(self, tool_method_name)(strand_set, idx)
        else:
            event.setAccepted(False)

    # end def

    def mouseReleaseEvent(self, event):
        """Called in the event of doing a Z translation drag

        Args:
            event (TYPE): Description
        """
        if self._right_mouse_move and event.button() == Qt.RightButton:
            MOVE_THRESHOLD = 0.01  # ignore small moves
            self._right_mouse_move = False
            delta = self.pos() - self.handle_start
            dz = delta.x()
            if abs(dz) > MOVE_THRESHOLD:
                dz = self._part_item.convertToModelZ(dz)
                self._model_part.translateVirtualHelices([self.idNum()],
                                                         0,
                                                         0,
                                                         dz,
                                                         True,
                                                         use_undostack=True)

    # end def

    def customMouseRelease(self, event):
        """
        Parses a mouseReleaseEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MouseRelease"
        if hasattr(self, tool_method_name):
            getattr(self, tool_method_name)(self._last_strand_set,
                                            self._last_idx)
        else:
            event.setAccepted(False)

    # end def

    ### COORDINATE UTILITIES ###
    def baseAtPoint(self, pos):
        """
        Returns the (Strandset, index) under the location x, y or None.

        It shouldn't be possible to click outside a pathhelix and still call
        this function. However, this sometimes happens if you click exactly
        on the top or bottom edge, resulting in a negative y value.

        Args:
            pos (TYPE): Description
        """
        x, y = pos.x(), pos.y()
        part = self._model_part
        id_num = self._id_num
        base_idx = int(floor(x / _BASE_WIDTH))
        min_base, max_base = 0, part.maxBaseIdx(id_num)
        if base_idx < min_base or base_idx >= max_base:
            base_idx = util.clamp(base_idx, min_base, max_base)
        if y < 0:
            y = 0  # HACK: zero out y due to erroneous click
        strand_type = floor(y * 1. / _BASE_WIDTH)  # 0 for fwd, 1 for rev
        strand_type = int(util.clamp(strand_type, 0, 1))
        strand_set = part.getStrandSets(id_num)[strand_type]
        return (strand_set, base_idx)

    # end def

    def keyPanDeltaX(self):
        """How far a single press of the left or right arrow key should move
        the scene (in scene space)
        """
        dx = self._part_item.part().stepSize() * _BASE_WIDTH
        return self.mapToScene(QRectF(0, 0, dx, 1)).boundingRect().width()

    # end def

    def hoverLeaveEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self._part_item.updateStatusBar("")

    # end def

    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        base_idx = int(floor(event.pos().x() / _BASE_WIDTH))
        loc = "%d[%d]" % (self._id_num, base_idx)
        self._part_item.updateStatusBar(loc)

        active_tool = self._getActiveTool()
        tool_method_name = active_tool.methodPrefix() + "HoverMove"
        if hasattr(self, tool_method_name):
            is_fwd, idx_x, idx_y = active_tool.baseAtPoint(self, event.pos())
            getattr(self, tool_method_name)(is_fwd, idx_x, idx_y)

    # end def

    ### TOOL METHODS ###
    def pencilToolMousePress(self, strand_set, idx, modifiers):
        """strand.getDragBounds

        Args:
            strand_set (StrandSet): Description
            idx (int): the base index within the virtual helix
        """
        # print("%s: %s[%s]" % (util.methodName(), strand_set, idx))
        if modifiers & Qt.ShiftModifier:
            bounds = strand_set.getBoundsOfEmptyRegionContaining(idx)
            ret = strand_set.createStrand(*bounds)
            print("creating strand {} was successful: {}".format(bounds, ret))
            return
        active_tool = self._getActiveTool()
        if not active_tool.isDrawingStrand():
            active_tool.initStrandItemFromVHI(self, strand_set, idx)
            active_tool.setIsDrawingStrand(True)

    # end def

    def pencilToolMouseMove(self, strand_set, idx):
        """strand.getDragBounds

        Args:
            strand_set (StrandSet): Description
            idx (int): the base index within the virtual helix
        """
        # print("%s: %s[%s]" % (util.methodName(), strand_set, idx))
        active_tool = self._getActiveTool()
        if active_tool.isDrawingStrand():
            active_tool.updateStrandItemFromVHI(self, strand_set, idx)

    # end def

    def pencilToolMouseRelease(self, strand_set, idx):
        """strand.getDragBounds

        Args:
            strand_set (StrandSet): Description
            idx (int): the base index within the virtual helix
        """
        # print("%s: %s[%s]" % (util.methodName(), strand_set, idx))
        active_tool = self._getActiveTool()
        if active_tool.isDrawingStrand():
            active_tool.setIsDrawingStrand(False)
            active_tool.attemptToCreateStrand(self, strand_set, idx)

    # end def

    def pencilToolHoverMove(self, is_fwd, idx_x, idx_y):
        """Pencil the strand is possible.

        Args:
            is_fwd (TYPE): Description
            idx_x (TYPE): Description
            idx_y (TYPE): Description
        """
        active_tool = self._getActiveTool()
        if not active_tool.isFloatingXoverBegin():
            temp_xover = active_tool.floatingXover()
            temp_xover.updateFloatingFromVHI(self, is_fwd, idx_x, idx_y)
Ejemplo n.º 17
0
class VirtualHelixItem(QGraphicsEllipseItem):
    """
    The VirtualHelixItem is an individual circle that gets drawn in the SliceView
    as a child of the PartItem. Taken as a group, many SliceHelix
    instances make up the crossection of the DNAPart. Clicking on a SliceHelix
    adds a VirtualHelix to the DNAPart. The SliceHelix then changes appearence
    and paints its corresponding VirtualHelix number.
    """
    # set up default, hover, and active drawing styles
    _USE_BRUSH = QBrush(styles.ORANGE_FILL)
    _USE_PEN = QPen(styles.ORANGE_STROKE, styles.SLICE_HELIX_STROKE_WIDTH)
    _RADIUS = styles.SLICE_HELIX_RADIUS
    _OUT_OF_SLICE_PEN = QPen(styles.LIGHT_ORANGE_STROKE,\
                         styles.SLICE_HELIX_STROKE_WIDTH)
    _OUT_OF_SLICE_BRUSH = QBrush(styles.LIGHT_ORANGE_FILL)
    _RECT = QRectF(0, 0, 2 * _RADIUS, 2 * _RADIUS)
    _FONT = styles.SLICE_NUM_FONT
    _ZVALUE = styles.ZSLICEHELIX+3

    def __init__(self, model_virtual_helix, empty_helix_item):
        """
        empty_helix_item is a EmptyHelixItem that will act as a QGraphicsItem parent
        """
        super(VirtualHelixItem, self).__init__(parent=empty_helix_item)
        self._virtual_helix = model_virtual_helix
        self._empty_helix_item = empty_helix_item
        self.hide()
        # drawing related

        self.isHovered = False
        self.setAcceptHoverEvents(True)
        # self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setZValue(self._ZVALUE)
        self.lastMousePressAddedBases = False

        self.setBrush(self._OUT_OF_SLICE_BRUSH)
        self.setPen(self._OUT_OF_SLICE_PEN)
        self.setRect(self._RECT)

        # handle the label specific stuff
        self._label = self.createLabel()
        self.setNumber()
        self._pen1, self._pen2 = (QPen(), QPen())
        self.createArrows()

        self._controller = VirtualHelixItemController(self, model_virtual_helix)

        self.show()
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def virtualHelixNumberChangedSlot(self, virtualHelix):
        """
        receives a signal containing a virtualHelix and the oldNumber 
        as a safety check
        """
        self.setNumber()
    # end def

    def virtualHelixRemovedSlot(self, virtualHelix):
        self._controller.disconnectSignals()
        self._controller = None
        self._empty_helix_item.setNotHovered()
        self._virtual_helix = None
        self._empty_helix_item = None
        self.scene().removeItem(self._label)
        self._label = None
        self.scene().removeItem(self)
    # end def

    def strandAddedSlot(self, sender, strand):
        pass
    # end def

    def createLabel(self):
        label = QGraphicsSimpleTextItem("%d" % self._virtual_helix.number())
        label.setFont(self._FONT)
        label.setZValue(self._ZVALUE)
        label.setParentItem(self)
        return label
    # end def

    def createArrows(self):
        rad = self._RADIUS
        pen1 = self._pen1
        pen2 = self._pen2
        pen1.setWidth(3)
        pen2.setWidth(3)
        pen1.setBrush(Qt.gray)
        pen2.setBrush(Qt.lightGray)
        if self._virtual_helix.isEvenParity():
            arrow1 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
            arrow2 = QGraphicsLineItem(0, rad, rad, rad, self)
        else:
            arrow1 = QGraphicsLineItem(0, rad, rad, rad, self)
            arrow2 = QGraphicsLineItem(rad, rad, 2*rad, rad, self)
        arrow1.setTransformOriginPoint(rad, rad)
        arrow2.setTransformOriginPoint(rad, rad)
        arrow1.setZValue(400)
        arrow2.setZValue(400)
        arrow1.setPen(pen1)
        arrow2.setPen(pen2)
        self.arrow1 = arrow1
        self.arrow2 = arrow2
        self.arrow1.hide()
        self.arrow2.hide()
    # end def

    def updateScafArrow(self, idx):
        scafStrand = self._virtual_helix.scaf(idx)
        if scafStrand:
            scafStrandColor = QColor(scafStrand.oligo().color())
            scafAlpha = 0.9 if scafStrand.hasXoverAt(idx) else 0.3
        else:
            scafStrandColor = QColor(Qt.gray)
            scafAlpha = 0.1
        scafStrandColor.setAlphaF(scafAlpha)
        self._pen1.setBrush(scafStrandColor)
        self.arrow1.setPen(self._pen1)
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        # for some reason rotation is CW and not CCW with increasing angle
        self.arrow1.setRotation(angle + part._TWIST_OFFSET)

    def updateStapArrow(self, idx):
        stapStrand = self._virtual_helix.stap(idx)
        if stapStrand:
            stapStrandColor = QColor(stapStrand.oligo().color()) 
            stapAlpha = 0.9 if stapStrand.hasXoverAt(idx) else 0.3
        else:
            stapStrandColor = QColor(Qt.lightGray)
            stapAlpha = 0.1
        stapStrandColor.setAlphaF(stapAlpha)
        self._pen2.setBrush(stapStrandColor)
        self.arrow2.setPen(self._pen2)
        part = self.part()
        tpb = part._TWIST_PER_BASE
        angle = idx*tpb
        self.arrow2.setRotation(angle + part._TWIST_OFFSET)
    # end def

    def setNumber(self):
        """docstring for setNumber"""
        vh = self._virtual_helix
        num = vh.number()
        label = self._label
        radius = self._RADIUS

        if num != None:
            label.setText("%d" % num)
        else:
            return

        y_val = radius / 3
        if num < 10:
            label.setPos(radius / 1.5, y_val)
        elif num < 100:
            label.setPos(radius / 3, y_val)
        else: # _number >= 100
            label.setPos(0, y_val)
        b_rect = label.boundingRect()
        posx = b_rect.width()/2
        posy = b_rect.height()/2
        label.setPos(radius-posx, radius-posy)
    # end def

    def part(self):
        return self._empty_helix_item.part()

    def virtualHelix(self):
        return self._virtual_helix
    # end def

    def number(self):
        return self.virtualHelix().number()

    def setActiveSliceView(self, idx, has_scaf, has_stap):
        if has_scaf:
            self.setPen(self._USE_PEN)
            self.setBrush(self._USE_BRUSH)
            self.updateScafArrow(idx)
            self.arrow1.show()
        else:
            self.setPen(self._OUT_OF_SLICE_PEN)
            self.setBrush(self._OUT_OF_SLICE_BRUSH)
            self.arrow1.hide()
        if has_stap:
            self.updateStapArrow(idx)
            self.arrow2.show()
        else:
            self.arrow2.hide()
    # end def

    ############################ User Interaction ############################
    def sceneEvent(self, event):
        """Included for unit testing in order to grab events that are sent
        via QGraphicsScene.sendEvent()."""
        # if self._parent.sliceController.testRecorder:
        #     coord = (self._row, self._col)
        #     self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord)
        if event.type() == QEvent.MouseButtonPress:
            self.mousePressEvent(event)
            return True
        elif event.type() == QEvent.MouseButtonRelease:
            self.mouseReleaseEvent(event)
            return True
        elif event.type() == QEvent.MouseMove:
            self.mouseMoveEvent(event)
            return True
        QGraphicsItem.sceneEvent(self, event)
        return False

    def hoverEnterEvent(self, event):
        """
        If the selection is configured to always select
        everything, we don't draw a focus ring around everything,
        instead we only draw a focus ring around the hovered obj.
        """
        # if self.selectAllBehavior():
        #     self.setSelected(True)
        # forward the event to the empty_helix_item as well
        self._empty_helix_item.hoverEnterEvent(event)
    # end def

    def hoverLeaveEvent(self, event):
        # if self.selectAllBehavior():
        #     self.setSelected(False)
        self._empty_helix_item.hoverEnterEvent(event)
Ejemplo n.º 18
0
class PathVirtualHelixItem(AbstractVirtualHelixItem, QGraphicsPathItem):
    """VirtualHelixItem for PathView

    Attributes:
        drag_last_position (TYPE): Description
        FILTER_NAME (str): Description
        findChild (TYPE): Description
        handle_start (TYPE): Description
        is_active (bool): Description
    """
    findChild = util.findChild  # for debug
    FILTER_NAME = "virtual_helix"

    def __init__(self, model_virtual_helix, part_item, viewroot):
        """Summary

        Args:
            id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods.
            part_item (TYPE): Description
            viewroot (TYPE): Description
        """
        AbstractVirtualHelixItem.__init__(self, model_virtual_helix, part_item)
        QGraphicsPathItem.__init__(self, parent=part_item.proxy())
        self._viewroot = viewroot
        self._getActiveTool = part_item._getActiveTool
        self._controller = VirtualHelixItemController(self, self._model_part, False, True)

        self._handle = VirtualHelixHandleItem(self, part_item, viewroot)
        self._last_strand_set = None
        self._last_idx = None
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setBrush(getNoBrush())

        view = self.view()
        view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot)
        should_show_details = view.shouldShowDetails()

        pen = newPenObj(styles.MINOR_GRID_STROKE, styles.MINOR_GRID_STROKE_WIDTH)
        pen.setCosmetic(should_show_details)
        self.setPen(pen)

        self.is_active = False

        self.refreshPath()
        self.setAcceptHoverEvents(True)  # for pathtools
        self.setZValue(styles.ZPATHHELIX)

        self._right_mouse_move = False
        self.drag_last_position = self.handle_start = self.pos()

    # end def

    ### SIGNALS ###

    ### SLOTS indirectly called from the part ###

    def levelOfDetailChangedSlot(self, boolval):
        """Not connected to the model, only the QGraphicsView

        Args:
            boolval (TYPE): Description
        """
        pen = self.pen()
        pen.setCosmetic(boolval)
        # print("levelOfDetailChangedSlot", boolval, pen.width())
        # if boolval:
        #     pass
        # else:
        #     pass
        self.setPen(pen)
    # end def

    def strandAddedSlot(self, sender, strand):
        """
        Instantiates a StrandItem upon notification that the model has a
        new Strand.  The StrandItem is responsible for creating its own
        controller for communication with the model, and for adding itself to
        its parent (which is *this* VirtualHelixItem, i.e. 'self').

        Args:
            sender (obj): Model object that emitted the signal.
            strand (TYPE): Description
        """
        StrandItem(strand, self, self._viewroot)
    # end def

    def virtualHelixRemovedSlot(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self.view().levelOfDetailChangedSignal.disconnect(self.levelOfDetailChangedSlot)
        self._controller.disconnectSignals()
        self._controller = None

        scene = self.scene()
        self._handle.remove()
        scene.removeItem(self)
        self._part_item = None
        self._model_part = None
        self._getActiveTool = None
        self._handle = None
    # end def

    def virtualHelixPropertyChangedSlot(self, keys, values):
        """Summary

        Args:
            keys (TYPE): Description
            values (TYPE): Description

        Returns:
            TYPE: Description
        """
        for key, val in zip(keys, values):
            if key == 'is_visible':
                if val:
                    self.show()
                    self._handle.show()
                    self.showXoverItems()
                else:
                    self.hideXoverItems()
                    self.hide()
                    self._handle.hide()
                    return
            if key == 'z':
                part_item = self._part_item
                z = part_item.convertToQtZ(val)
                if self.x() != z:
                    self.setX(z)
                    """ The handle is selected, so deselect to
                    accurately position then reselect
                    """
                    vhi_h = self._handle
                    vhi_h.tempReparent()
                    vhi_h.setX(z - _VH_XOFFSET)
                    # if self.isSelected():
                    #     print("ImZ", self.idNum())
                    part_item.updateXoverItems(self)
                    vhi_h_selection_group = self._viewroot.vhiHandleSelectionGroup()
                    vhi_h_selection_group.addToGroup(vhi_h)
            elif key == 'eulerZ':
                self._handle.rotateWithCenterOrigin(val)
                # self._prexoveritemgroup.updatePositionsAfterRotation(value)
            ### GEOMETRY PROPERTIES ###
            elif key == 'repeat_hint':
                pass
                # self.updateRepeats(int(val))
            elif key == 'bases_per_repeat':
                pass
                # self.updateBasesPerRepeat(int(val))
            elif key == 'turns_per_repeat':
                # self.updateTurnsPerRepeat(int(val))
                pass
            ### RUNTIME PROPERTIES ###
            elif key == 'neighbors':
                # this means a virtual helix in the slice view has moved
                # so we need to clear and redraw the PreXoverItems just in case
                if self.isActive():
                    self._part_item.setPreXoverItemsVisible(self)
        self.refreshPath()
    # end def

    def showXoverItems(self):
        """Summary

        Returns:
            TYPE: Description
        """
        for item in self.childItems():
            if isinstance(item, XoverNode3):
                item.showXover()
    # end def

    def hideXoverItems(self):
        """Summary

        Returns:
            TYPE: Description
        """
        for item in self.childItems():
            if isinstance(item, XoverNode3):
                item.hideXover()
    # end def

    ### ACCESSORS ###
    def viewroot(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._viewroot
    # end def

    def handle(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._handle
    # end def

    def window(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._part_item.window()
    # end def

    def view(self):
        return self._viewroot.scene().views()[0]
    # end def

    ### DRAWING METHODS ###
    def upperLeftCornerOfBase(self, idx, strand):
        """Summary

        Args:
            idx (int): the base index within the virtual helix
            strand (TYPE): Description

        Returns:
            TYPE: Description
        """
        x = idx * _BASE_WIDTH
        y = 0 if strand.isForward() else _BASE_WIDTH
        return x, y
    # end def

    def upperLeftCornerOfBaseType(self, idx, is_fwd):
        """Summary

        Args:
            idx (int): the base index within the virtual helix
            is_fwd (TYPE): Description

        Returns:
            TYPE: Description
        """
        x = idx * _BASE_WIDTH
        y = 0 if is_fwd else _BASE_WIDTH
        return x, y
    # end def

    def refreshPath(self):
        """
        Returns a QPainterPath object for the minor grid lines.
        The path also includes a border outline and a midline for
        dividing scaffold and staple bases.
        """
        bw = _BASE_WIDTH
        bw2 = 2 * bw
        part = self.part()
        path = QPainterPath()
        sub_step_size = part.subStepSize()
        canvas_size = self._model_vh.getSize()
        # border
        path.addRect(0, 0, bw * canvas_size, 2 * bw)
        # minor tick marks
        for i in range(canvas_size):
            x = round(bw * i) + .5
            if i % sub_step_size == 0:
                path.moveTo(x - .5, 0)
                path.lineTo(x - .5, bw2)
                path.lineTo(x - .25, bw2)
                path.lineTo(x - .25, 0)
                path.lineTo(x, 0)
                path.lineTo(x, bw2)
                path.lineTo(x + .25, bw2)
                path.lineTo(x + .25, 0)
                path.lineTo(x + .5, 0)
                path.lineTo(x + .5, bw2)

                # path.moveTo(x-.5, 0)
                # path.lineTo(x-.5, 2 * bw)
                # path.lineTo(x+.5, 2 * bw)
                # path.lineTo(x+.5, 0)

            else:
                path.moveTo(x, 0)
                path.lineTo(x, 2 * bw)

        # staple-scaffold divider
        path.moveTo(0, bw)
        path.lineTo(bw * canvas_size, bw)

        self.setPath(path)
    # end def

    def resize(self):
        """Called by part on resize.
        """
        self.refreshPath()
    # end def

    ### PUBLIC SUPPORT METHODS ###
    def activate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        pen = self.pen()
        pen.setColor(getColorObj(styles.MINOR_GRID_STROKE_ACTIVE))
        self.setPen(pen)
        self.is_active = True
    # end def

    def deactivate(self):
        """Summary

        Returns:
            TYPE: Description
        """
        pen = self.pen()
        pen.setColor(getColorObj(styles.MINOR_GRID_STROKE))
        self.setPen(pen)
        self.is_active = False
    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        """
        Parses a mousePressEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        # 1. Check if we are doing a Z translation
        if event.button() == Qt.RightButton:
            viewroot = self._viewroot
            current_filter_set = viewroot.selectionFilterSet()
            if self.FILTER_NAME in current_filter_set and self.part().isZEditable():
                self._right_mouse_move = True
                self.drag_last_position = event.scenePos()
                self.handle_start = self.pos()
            return

        self.scene().views()[0].addToPressList(self)
        strand_set, idx = self.baseAtPoint(event.pos())
        self._model_vh.setActive(strand_set.isForward(), idx)
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MousePress"

        if hasattr(self, tool_method_name):
            self._last_strand_set, self._last_idx = strand_set, idx
            getattr(self, tool_method_name)(strand_set, idx, event.modifiers())
        else:
            event.setAccepted(False)
    # end def

    def mouseMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        # 1. Check if we are doing a Z translation
        if self._right_mouse_move:
            MOVE_THRESHOLD = 0.01   # ignore small moves
            new_pos = event.scenePos()
            delta = new_pos - self.drag_last_position
            dx = int(floor(delta.x() / _BASE_WIDTH))*_BASE_WIDTH
            x = self.handle_start.x() + dx
            if abs(dx) > MOVE_THRESHOLD or dx == 0.0:
                old_x = self.x()
                self.setX(x)
                vhi_h = self._handle
                vhi_h.tempReparent()
                vhi_h.setX(x - _VH_XOFFSET)
                self._part_item.updateXoverItems(self)
                dz = self._part_item.convertToModelZ(x - old_x)
                self._model_part.translateVirtualHelices([self.idNum()],
                                                         0, 0, dz, False,
                                                         use_undostack=False)
                return
        # 2. Forward event to tool
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MouseMove"
        if hasattr(self, tool_method_name):
            strand_set, idx = self.baseAtPoint(event.pos())
            if self._last_strand_set != strand_set or self._last_idx != idx:
                self._last_strand_set, self._last_idx = strand_set, idx
                getattr(self, tool_method_name)(strand_set, idx)
        else:
            event.setAccepted(False)
    # end def

    def mouseReleaseEvent(self, event):
        """Called in the event of doing a Z translation drag

        Args:
            event (TYPE): Description
        """
        if self._right_mouse_move and event.button() == Qt.RightButton:
            MOVE_THRESHOLD = 0.01   # ignore small moves
            self._right_mouse_move = False
            delta = self.pos() - self.handle_start
            dz = delta.x()
            if abs(dz) > MOVE_THRESHOLD:
                dz = self._part_item.convertToModelZ(dz)
                self._model_part.translateVirtualHelices([self.idNum()],
                                                         0, 0, dz, True,
                                                         use_undostack=True)
    # end def

    def customMouseRelease(self, event):
        """
        Parses a mouseReleaseEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        tool = self._getActiveTool()
        tool_method_name = tool.methodPrefix() + "MouseRelease"
        if hasattr(self, tool_method_name):
            getattr(self, tool_method_name)(self._last_strand_set, self._last_idx)
        else:
            event.setAccepted(False)
    # end def

    ### COORDINATE UTILITIES ###
    def baseAtPoint(self, pos):
        """
        Returns the (Strandset, index) under the location x, y or None.

        It shouldn't be possible to click outside a pathhelix and still call
        this function. However, this sometimes happens if you click exactly
        on the top or bottom edge, resulting in a negative y value.

        Args:
            pos (TYPE): Description
        """
        x, y = pos.x(), pos.y()
        part = self._model_part
        id_num = self._id_num
        base_idx = int(floor(x / _BASE_WIDTH))
        min_base, max_base = 0, part.maxBaseIdx(id_num)
        if base_idx < min_base or base_idx >= max_base:
            base_idx = util.clamp(base_idx, min_base, max_base)
        if y < 0:
            y = 0  # HACK: zero out y due to erroneous click
        strand_type = floor(y * 1. / _BASE_WIDTH)   # 0 for fwd, 1 for rev
        strand_type = int(util.clamp(strand_type, 0, 1))
        strand_set = part.getStrandSets(id_num)[strand_type]
        return (strand_set, base_idx)
    # end def

    def keyPanDeltaX(self):
        """How far a single press of the left or right arrow key should move
        the scene (in scene space)
        """
        dx = self._part_item.part().stepSize() * _BASE_WIDTH
        return self.mapToScene(QRectF(0, 0, dx, 1)).boundingRect().width()
    # end def

    def hoverLeaveEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self._part_item.updateStatusBar("")
    # end def

    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strand_set and base index,
        forwarding them to approproate tool method as necessary.

        Args:
            event (TYPE): Description
        """
        base_idx = int(floor(event.pos().x() / _BASE_WIDTH))
        loc = "%d[%d]" % (self._id_num, base_idx)
        self._part_item.updateStatusBar(loc)

        active_tool = self._getActiveTool()
        tool_method_name = active_tool.methodPrefix() + "HoverMove"
        if hasattr(self, tool_method_name):
            is_fwd, idx_x, idx_y = active_tool.baseAtPoint(self, event.pos())
            getattr(self, tool_method_name)(is_fwd, idx_x, idx_y)
    # end def

    ### TOOL METHODS ###
    def pencilToolMousePress(self, strand_set, idx, modifiers):
        """strand.getDragBounds

        Args:
            strand_set (StrandSet): Description
            idx (int): the base index within the virtual helix
        """
        # print("%s: %s[%s]" % (util.methodName(), strand_set, idx))
        if modifiers & Qt.ShiftModifier:
            bounds = strand_set.getBoundsOfEmptyRegionContaining(idx)
            ret = strand_set.createStrand(*bounds)
            print("creating strand {} was successful: {}".format(bounds, ret))
            return
        active_tool = self._getActiveTool()
        if not active_tool.isDrawingStrand():
            active_tool.initStrandItemFromVHI(self, strand_set, idx)
            active_tool.setIsDrawingStrand(True)
    # end def

    def pencilToolMouseMove(self, strand_set, idx):
        """strand.getDragBounds

        Args:
            strand_set (StrandSet): Description
            idx (int): the base index within the virtual helix
        """
        # print("%s: %s[%s]" % (util.methodName(), strand_set, idx))
        active_tool = self._getActiveTool()
        if active_tool.isDrawingStrand():
            active_tool.updateStrandItemFromVHI(self, strand_set, idx)
    # end def

    def pencilToolMouseRelease(self, strand_set, idx):
        """strand.getDragBounds

        Args:
            strand_set (StrandSet): Description
            idx (int): the base index within the virtual helix
        """
        # print("%s: %s[%s]" % (util.methodName(), strand_set, idx))
        active_tool = self._getActiveTool()
        if active_tool.isDrawingStrand():
            active_tool.setIsDrawingStrand(False)
            active_tool.attemptToCreateStrand(self, strand_set, idx)
    # end def

    def pencilToolHoverMove(self, is_fwd, idx_x, idx_y):
        """Pencil the strand is possible.

        Args:
            is_fwd (TYPE): Description
            idx_x (TYPE): Description
            idx_y (TYPE): Description
        """
        active_tool = self._getActiveTool()
        if not active_tool.isFloatingXoverBegin():
            temp_xover = active_tool.floatingXover()
            temp_xover.updateFloatingFromVHI(self, is_fwd, idx_x, idx_y)