Example #1
0
 def __init__(self, mID, modelStrand, virtualHelixItem):
     """
     The parent should be a VirtualHelixItem.
     Initialize function creates the Maya Node for the strand, and setups
     the lookup tables inside of mayaObjectManager (Mom) so that the Maya
     Node can be globally found given a strand, and the other way around.
     Also, sets up StrandItemController that is used to setup all the
     slots and signals between strand model and this strandItem.
     """
     self._modelStrand = modelStrand
     self._virtualHelixItem = virtualHelixItem
     self._viewroot = app().activeDocument.win.solidroot
     mayaNodeInfo = ()
     # print "solidview.StrandItem.__init__ %s" % mID
     if(modelStrand.strandSet().isScaffold()):
         mayaNodeInfo = self.createMayaHelixNodes(virtualHelixItem.x(),
                                                  virtualHelixItem.y(),
                                   modelStrand.oligo().color(),
                                   StrandType.SCAFFOLD,
                                   mID)
     else:
         mayaNodeInfo = self.createMayaHelixNodes(virtualHelixItem.x(),
                                                  virtualHelixItem.y(),
                                   modelStrand.oligo().color(),
                                   StrandType.STAPLE,
                                   mID)
     #self.onStrandDidMove(strand)
     m = Mom()
     m.cnToMaya[modelStrand] = mayaNodeInfo
     m.mayaToCn[mayaNodeInfo[2]] = modelStrand
     m.mayaToCn[mayaNodeInfo[0]] = modelStrand
     self.updateSize()
     self._controller = StrandItemController(self, modelStrand)
Example #2
0
    def __init__(self, modelStrand, virtualHelixItem, viewroot):
        """The parent should be a VirtualHelixItem."""
        super(StrandItem, self).__init__(virtualHelixItem)
        self._modelStrand = modelStrand
        self._virtualHelixItem = virtualHelixItem
        self._viewroot = viewroot
        self._activeTool = virtualHelixItem.activeTool()

        self._controller = StrandItemController(self, modelStrand)
        isDrawn5to3 = modelStrand.strandSet().isDrawn5to3()

        self._strandFilter = modelStrand.strandFilter()

        self._insertionItems = {}
        # caps
        self._lowCap = EndpointItem(self, 'low', isDrawn5to3)
        self._highCap = EndpointItem(self, 'high', isDrawn5to3)
        self._dualCap = EndpointItem(self, 'dual', isDrawn5to3)

        # orientation
        self._isDrawn5to3 = isDrawn5to3
        # self._isOnTop = virtualHelixItem.isStrandOnTop(modelStrand)
        # label
        self._seqLabel = QGraphicsSimpleTextItem(self)

        self.refreshInsertionItems(modelStrand)
        self._updateSequenceText()

        # create a larger click area rect to capture mouse events
        self._clickArea = cA = QGraphicsRectItem(_defaultRect, self)
        cA.mousePressEvent = self.mousePressEvent
        cA.setPen(_noPen)
        self.setAcceptHoverEvents(True)
        cA.setAcceptHoverEvents(True)
        cA.hoverMoveEvent = self.hoverMoveEvent

        self.setZValue(styles.ZSTRANDITEM)

        # xover comming from the 3p end
        self._xover3pEnd = XoverItem(self, virtualHelixItem)
        # initial refresh
        self._updateColor(modelStrand)
        self._updateAppearance(modelStrand)

        self.setZValue(styles.ZSTRANDITEM)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
Example #3
0
    def __init__(self, modelStrand, virtualHelixItem, viewroot):
        """The parent should be a VirtualHelixItem."""
        super(StrandItem, self).__init__(virtualHelixItem)
        self._modelStrand = modelStrand
        self._virtualHelixItem = virtualHelixItem
        self._viewroot = viewroot
        self._activeTool = virtualHelixItem.activeTool()

        self._controller = StrandItemController(self, modelStrand)
        isDrawn5to3 = modelStrand.strandSet().isDrawn5to3()

        self._strandFilter = modelStrand.strandFilter()

        self._insertionItems = {}
        # caps
        self._lowCap = EndpointItem(self, 'low', isDrawn5to3)
        self._highCap = EndpointItem(self, 'high', isDrawn5to3)
        self._dualCap = EndpointItem(self, 'dual', isDrawn5to3)

        # orientation
        self._isDrawn5to3 = isDrawn5to3
        # self._isOnTop = virtualHelixItem.isStrandOnTop(modelStrand)
        # label
        self._seqLabel = QGraphicsSimpleTextItem(self)
        
        self.refreshInsertionItems(modelStrand)
        self._updateSequenceText()

        # create a larger click area rect to capture mouse events
        self._clickArea = cA = QGraphicsRectItem(_defaultRect, self)
        cA.mousePressEvent = self.mousePressEvent
        cA.setPen(_noPen)
        self.setAcceptHoverEvents(True)
        cA.setAcceptHoverEvents(True)
        cA.hoverMoveEvent = self.hoverMoveEvent

        self.setZValue(styles.ZSTRANDITEM)

        # xover comming from the 3p end
        self._xover3pEnd = XoverItem(self, virtualHelixItem)
        # initial refresh
        self._updateColor(modelStrand)
        self._updateAppearance(modelStrand)

        self.setZValue(styles.ZSTRANDITEM)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
Example #4
0
class StrandItem(object):
    """
    StrandItem is the visual representation of the strand in the 3D SolidView.
    For this visual representation, StrandItem creates HalfCylinderHelixNode
    Node inside of Maya, so while the StrandItem itself does not get drawn in
    any way, it is the object that communicates with Maya Nodes associated
    with a given strand.
    """
    def __init__(self, mID, modelStrand, virtualHelixItem):
        """
        The parent should be a VirtualHelixItem.
        Initialize function creates the Maya Node for the strand, and setups
        the lookup tables inside of mayaObjectManager (Mom) so that the Maya
        Node can be globally found given a strand, and the other way around.
        Also, sets up StrandItemController that is used to setup all the
        slots and signals between strand model and this strandItem.
        """
        self._modelStrand = modelStrand
        self._virtualHelixItem = virtualHelixItem
        self._viewroot = app().activeDocument.win.solidroot
        mayaNodeInfo = ()
        # print "solidview.StrandItem.__init__ %s" % mID
        if(modelStrand.strandSet().isScaffold()):
            mayaNodeInfo = self.createMayaHelixNodes(virtualHelixItem.x(),
                                                     virtualHelixItem.y(),
                                      modelStrand.oligo().color(),
                                      StrandType.SCAFFOLD,
                                      mID)
        else:
            mayaNodeInfo = self.createMayaHelixNodes(virtualHelixItem.x(),
                                                     virtualHelixItem.y(),
                                      modelStrand.oligo().color(),
                                      StrandType.STAPLE,
                                      mID)
        #self.onStrandDidMove(strand)
        m = Mom()
        m.cnToMaya[modelStrand] = mayaNodeInfo
        m.mayaToCn[mayaNodeInfo[2]] = modelStrand
        m.mayaToCn[mayaNodeInfo[0]] = modelStrand
        self.updateSize()
        self._controller = StrandItemController(self, modelStrand)
    # end def

    ### SLOTS ###
    def strandResizedSlot(self, strand, indices):
        """Receives notification from the model when a strand is resized"""
        #print "solid.StrandItem.strandResizedSlot", self._modelStrand.idxs()
        self.updateSize()
        self._virtualHelixItem.updateDecorators()
        m = Mom()
        m.updateSelectionBoxes()

    def strandUpdateSlot(self, strand):
        """strandUpdateSlot - empty"""
        pass

    def sequenceAddedSlot(self, oligo):
        """sequenceAddedSlot - empty"""
        pass

    def sequenceClearedSlot(self, oligo):
        """sequenceClearedSlot - empty"""
        pass

    def strandRemovedSlot(self, strand):
        """
        Receives notification from the model when a strand is removed.
        Deletes the strand related mapping in mayaObjectManager, deletes all
        the Maya nodes, deletes all the decorators(live in the
        virtualHelixItem right now), deletes itself from the virtualHelixItem,
        and disconnects itself from the controller.
        """
        mom = Mom()
        mID = mom.strandMayaID(strand)
        mom.removeIDMapping(mID, strand)
        # print "solidview.StrandItem.strandRemovedSlot %s" % mID
        transformName = "%s%s" % (mom.helixTransformName, mID)
        cylinderName = "%s%s" % (mom.helixNodeName, mID)
        meshName = "%s%s" % (mom.helixMeshName, mID)
        if cmds.objExists(transformName):
            cmds.delete(transformName)
        if cmds.objExists(cylinderName):
            cmds.delete(cylinderName)
        if cmds.objExists(meshName):
            cmds.delete(meshName)
        if mID in self._virtualHelixItem.StrandIDs():
            self._virtualHelixItem.StrandIDs().remove(mID)
        self._virtualHelixItem.updateDecorators()
        self._virtualHelixItem.removeStrandItem(self)
        self._virtualHelixItem = None
        self._modelStrand = None
        self._controller.disconnectSignals()
        self._controller = None
    # end def

    def oligoAppearanceChangedSlot(self, oligo):
        """
        Receives notification from the model when a oligo changes appearance.
        Updates the color of the strandItem associated with this strand
        """
        mom = Mom()
        id = mom.strandMayaID(self._modelStrand)
        self.updateColor(id, oligo.color())
        pass

    def oligoSequenceAddedSlot(self, oligo):
        """oligoSequenceAddedSlot - empty"""
        pass

    def oligoSequenceClearedSlot(self, oligo):
        """oligoSequenceClearedSlot - empty"""
        pass

    def strandHasNewOligoSlot(self, strand):
        """
        Receives notification from the model when there is a new oligo.
        Updates the color of the strandItem associated with this strand
        """
        mom = Mom()
        self._controller.reconnectOligoSignals()
        mID = mom.strandMayaID(strand)
        self.updateColor(mID, strand.oligo().color())

    def strandInsertionAddedSlot(self, strand, insertion):
        """strandInsertionAddedSlot - empty"""
        pass

    def strandInsertionChangedSlot(self, strand, insertion):
        """strandInsertionChangedSlot - empty"""
        pass

    def strandInsertionRemovedSlot(self, strand, index):
        """strandInsertionRemovedSlot - empty"""
        pass

    def strandModsAddedSlot(self, strand, mods):
        """strandModsAddedSlot - empty"""
        pass

    def strandModsChangedSlot(self, strand, mods):
        """strandModsChangedSlot - empty"""
        pass

    def strandModsRemovedSlot(self, strand, index):
        """strandModsRemovedSlot - empty"""
        pass

    def strandModifierAddedSlot(self, strand, modifier):
        """strandModifierAddedSlot - empty"""
        pass

    def strandModifierChangedSlot(self, strand, modifier):
        """strandModifierChangedSlot - empty"""
        pass

    def strandModifierRemovedSlot(self, strand, index):
        """strandModifierRemovedSlot - empty"""
        pass

    def selectedChangedSlot(self, strand, indices):
        #print "solidview.stranditem.selectedChangedSlot", strand, indices

        mom = Mom()
        if mom.ignoreExternalSelectionSignal:
            return
        mID = mom.strandMayaID(strand)
        mom.ignoreExternalSelectionSignal = True
        transformName = "%s%s" % (mom.helixTransformName, mID)
        if cmds.objExists(transformName):
            if(indices[0] or indices[1]):
                cmds.select(transformName, add=True)
                # print "selecting a strand"
                self._viewroot.addToSelectionDict(strand)
            else:
                # print "deselecting in the slot"
                cmds.select(transformName, deselect=True)
                self._viewroot.removeFromSelectionDict(strand)
        mom.ignoreExternalSelectionSignal = False
    # end def

    ### METHODS ###
    def createMayaHelixNodes(self, x, y, colorname, strandType, mID):
        """
        Create all the Maya nodes, set the initial attributes and connections.
        There are 3 Maya nodes associated with each Strand: Transform Node,
        Shape Node (spHalfCylinderHelixNode), and a Mesh Node (a generic Maya
        Node that is used for rendering) The Mesh Node is the child of the
        Transform Node, and spHalfCylinderHelixNode node inputs the shape data
        into the Mesh Node, using cmds.connectAttr command
         ________________
        | Transform Node |
         ----------------
                |
         ________________  .inMesh          .outputMesh _____________________
        |   Mesh Node    |<---------------------------| HalfCylinderHelixNode |
         ----------------                              -----------------------
        """
        m = Mom()
        cylinderName = "%s%s" % (m.helixNodeName, mID)
        transformName = "%s%s" % (m.helixTransformName, mID)
        meshName = "%s%s" % (m.helixMeshName, mID)
        # shaderName = "%s%s" % (m.helixShaderName, mID)
        cmds.createNode("transform", name=transformName, skipSelect=True)
        cmds.setAttr("%s.rotateX" % transformName, 90)
        cmds.setAttr("%s.translateX" % transformName, x)
        cmds.setAttr("%s.translateY" % transformName, y)
        cmds.createNode("mesh",
                        name=meshName,
                        parent=transformName,
                        skipSelect=True)
        cmds.createNode("spHalfCylinderHelixNode",
                        name=cylinderName,
                        skipSelect=True)
        cmds.connectAttr("%s.outputMesh" % cylinderName,
                         "%s.inMesh" % meshName)
        # XXX - [SB] This should go away and we will ask the model for
        # the right numbers...
        vhi = self._virtualHelixItem
        part = vhi.partItem().part()
        cSType = part.crossSectionType()
        cmds.setAttr("%s.rotation" % cylinderName, part.twistPerBase())
        cmds.setAttr("%s.parity" % cylinderName, vhi.isEvenParity())
        if cSType == LatticeType.HONEYCOMB:
            cmds.setAttr("%s.rotationOffset" % cylinderName, 250)
            cmds.setAttr("%s.decoratorRotOffset" % cylinderName, 90)
        elif cSType == LatticeType.SQUARE:
            cmds.setAttr("%s.rotationOffset" % cylinderName, 125)
            cmds.setAttr("%s.decoratorRotOffset" % cylinderName, 200)
        else:
            raise NotImplementedError
        cmds.setAttr("%s.strandType" % cylinderName, strandType)
        self.updateColor(mID, colorname)

        cmds.select(transformName)
        cmds.polySoftEdge(a=89.99)
        cmds.setAttr("%s.displayEdges" % meshName, 2)
        cmds.select(clear=True)
        return (cylinderName, transformName, meshName)

    def updateColor(self, mID, colorname):
        """
        Update the color of the Maya's Mesh Node associated with a this
        StrandItem, this is done by creating a shadingNode for each color or
        connecting the Mesh Mode to an existing shadingNode if one exists
        for a given color.
        """
        m = Mom()
        meshName = "%s%s" % (m.helixMeshName, mID)
        color = QColor(colorname)
        colorval = "%d_%d_%d" % (color.red(), color.green(), color.blue())
        shaderName = "%s%d_%d_%d" % (m.helixShaderName, color.red(),
                                                  color.green(),
                                                  color.blue())
        if not cmds.objExists(shaderName):
            # Shader does not exist create one
            cmds.shadingNode('lambert', asShader=True, name=shaderName)
            cmds.sets(n="%sSG" % shaderName, r=True, nss=True, em=True)
            cmds.connectAttr("%s.outColor" % shaderName,
                             "%sSG.surfaceShader" % shaderName)
            cmds.setAttr("%s.color" % shaderName,
                         color.redF(), color.greenF(), color.blueF(),
                         type="double3")
            cmds.sets(meshName, forceElement="%sSG" % shaderName)
        else:
            #shader exist connect
            cmds.sets(meshName, forceElement="%sSG" % shaderName)

    def updateSize(self):
        """
        Update Maya's Half Cylinder Node attributes related to the size
        """
        mom = Mom()
        mID = mom.strandMayaID(self._modelStrand)
        cylinderName = "%s%s" % (mom.helixNodeName, mID)
        endpoints = self._modelStrand.idxs()
        totalNumBases = \
                self._virtualHelixItem.virtualHelix().part().maxBaseIdx()
        cmds.setAttr("%s.startBase" % cylinderName,
                             endpoints[0])

        cmds.setAttr("%s.endBase" % cylinderName,
                             endpoints[1])
        cmds.setAttr("%s.totalBases" % cylinderName, int(totalNumBases))
Example #5
0
class StrandItem(QGraphicsLineItem):
    _filterName = "strand"

    def __init__(self, modelStrand, virtualHelixItem, viewroot):
        """The parent should be a VirtualHelixItem."""
        super(StrandItem, self).__init__(virtualHelixItem)
        self._modelStrand = modelStrand
        self._virtualHelixItem = virtualHelixItem
        self._viewroot = viewroot
        self._activeTool = virtualHelixItem.activeTool()

        self._controller = StrandItemController(self, modelStrand)
        isDrawn5to3 = modelStrand.strandSet().isDrawn5to3()

        self._strandFilter = modelStrand.strandFilter()

        self._insertionItems = {}
        # caps
        self._lowCap = EndpointItem(self, 'low', isDrawn5to3)
        self._highCap = EndpointItem(self, 'high', isDrawn5to3)
        self._dualCap = EndpointItem(self, 'dual', isDrawn5to3)

        # orientation
        self._isDrawn5to3 = isDrawn5to3
        # self._isOnTop = virtualHelixItem.isStrandOnTop(modelStrand)
        # label
        self._seqLabel = QGraphicsSimpleTextItem(self)

        self.refreshInsertionItems(modelStrand)
        self._updateSequenceText()

        # create a larger click area rect to capture mouse events
        self._clickArea = cA = QGraphicsRectItem(_defaultRect, self)
        cA.mousePressEvent = self.mousePressEvent
        cA.setPen(_noPen)
        self.setAcceptHoverEvents(True)
        cA.setAcceptHoverEvents(True)
        cA.hoverMoveEvent = self.hoverMoveEvent

        self.setZValue(styles.ZSTRANDITEM)

        # xover comming from the 3p end
        self._xover3pEnd = XoverItem(self, virtualHelixItem)
        # initial refresh
        self._updateColor(modelStrand)
        self._updateAppearance(modelStrand)

        self.setZValue(styles.ZSTRANDITEM)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def strandResizedSlot(self, strand, indices):
        """docstring for strandResizedSlot"""
        lowMoved = self._lowCap.updatePosIfNecessary(self.idxs()[0])
        highMoved = self._highCap.updatePosIfNecessary(self.idxs()[1])
        group = self.group()
        self.tempReparent()
        if lowMoved:
            self.updateLine(self._lowCap)
        if highMoved:
            self.updateLine(self._highCap)
        if strand.connection3p():
            self._xover3pEnd.update(strand)
        self.refreshInsertionItems(strand)
        self._updateSequenceText()
        if group:
            group.addToGroup(self)

    # end def

    def sequenceAddedSlot(self, oligo):
        """docstring for sequenceAddedSlot"""
        pass

    # end def

    def sequenceClearedSlot(self, oligo):
        """docstring for sequenceClearedSlot"""
        pass

    # end def

    def strandRemovedSlot(self, strand):
        # self._modelStrand = None
        self._controller.disconnectSignals()
        self._controller = None
        scene = self.scene()
        scene.removeItem(self._clickArea)
        scene.removeItem(self._highCap)
        scene.removeItem(self._lowCap)
        scene.removeItem(self._seqLabel)
        self._xover3pEnd.remove()
        self._xover3pEnd = None
        for insertionItem in self._insertionItems.itervalues():
            insertionItem.remove()
        self._insertionItems = None
        self._clickArea = None
        self._highCap = None
        self._lowCap = None
        self._seqLabel = None
        self._modelStrand = None
        self._virtualHelixItem = None
        scene.removeItem(self)

    # end def

    def strandUpdateSlot(self, strand):
        """
        Slot for just updating connectivity and color, and endpoint showing
        """
        self._updateAppearance(strand)

    # end def

    def oligoAppearanceChangedSlot(self, oligo):
        strand = self._modelStrand
        self._updateColor(strand)
        if strand.connection3p():
            self._xover3pEnd._updateColor(strand)
        for insertion in self.insertionItems().itervalues():
            insertion.updateItem()

    # end def

    def oligoSequenceAddedSlot(self, oligo):
        self._updateSequenceText()

    # end def

    def oligoSequenceClearedSlot(self, oligo):
        self._updateSequenceText()

    # end def

    def strandHasNewOligoSlot(self, strand):
        strand = self._modelStrand
        self._controller.reconnectOligoSignals()
        self._updateColor(strand)
        if strand.connection3p():
            self._xover3pEnd._updateColor(strand)

    # end def

    def strandInsertionAddedSlot(self, strand, insertion):
        self.insertionItems()[insertion.idx()] = \
                    InsertionItem(self._virtualHelixItem, strand, insertion)

    # end def
    def strandInsertionChangedSlot(self, strand, insertion):
        self.insertionItems()[insertion.idx()].updateItem()

    # end def

    def strandInsertionRemovedSlot(self, strand, index):
        instItem = self.insertionItems()[index]
        instItem.remove()
        del self.insertionItems()[index]

    # end def

    def strandDecoratorAddedSlot(self, strand, decorator):
        pass

    # end def
    def strandDecoratorChangedSlot(self, strand, decorator):
        pass

    # end def
    def strandDecoratorRemovedSlot(self, strand, index):
        pass

    # end def
    def strandModifierAddedSlot(self, strand, modifier):
        pass

    # end def
    def strandModifierChangedSlot(self, strand, modifier):
        pass

    # end def
    def strandModifierRemovedSlot(self, strand, index):
        pass

    # end def

    def selectedChangedSlot(self, strand, indices):
        self.selectIfRequired(self.partItem().document(), indices)

    # end def

    ### ACCESSORS ###
    def activeTool(self):
        return self._activeTool

    # end def

    def viewroot(self):
        return self._viewroot

    # end def

    def insertionItems(self):
        return self._insertionItems

    # end def

    def strand(self):
        return self._modelStrand

    # end def

    def strandFilter(self):
        return self._strandFilter

    # end def

    def idxs(self):
        return self._modelStrand.idxs()

    # end def

    def virtualHelixItem(self):
        return self._virtualHelixItem

    # end def

    def partItem(self):
        return self._virtualHelixItem.partItem()

    # end def

    def window(self):
        return self._virtualHelixItem.window()

    ### PUBLIC METHODS FOR DRAWING / LAYOUT ###
    def refreshInsertionItems(self, strand):
        iItems = self.insertionItems()
        iModel = strand.insertionsOnStrand()

        was_in_use = set(iItems)
        in_use = set()
        # add in the ones supposed to be there
        for insertion in iModel:
            idx = insertion.idx()
            in_use.add(idx)
            if idx in iItems:
                pass
            else:
                iItems[insertion.idx()] = \
                    InsertionItem(self._virtualHelixItem, strand, insertion)
        # end for

        # remove all in items
        not_in_use = was_in_use - in_use
        for index in not_in_use:
            iItems[index].remove()
            del iItems[index]
        # end for

    # end def

    def resetStrandItem(self, virtualHelixItem, isDrawn5to3):
        self.setParentItem(virtualHelixItem)
        self._virtualHelixItem = virtualHelixItem
        self.resetEndPointItems(isDrawn5to3)

    # end def

    def resetEndPointItems(self, isDrawn5to3):
        self._isDrawn5to3 = isDrawn5to3
        self._lowCap.resetEndPoint(isDrawn5to3)
        self._highCap.resetEndPoint(isDrawn5to3)
        self._dualCap.resetEndPoint(isDrawn5to3)

    # end def

    def updateLine(self, movedCap):
        # setup
        bw = _baseWidth
        cA = self._clickArea
        line = self.line()
        # set new line coords
        if movedCap == self._lowCap:
            p1 = line.p1()
            newX = self._lowCap.pos().x() + bw
            p1.setX(newX)
            line.setP1(p1)
            temp = cA.rect()
            temp.setLeft(newX)
            cA.setRect(temp)
        else:
            p2 = line.p2()
            newX = self._highCap.pos().x()
            p2.setX(newX)
            line.setP2(p2)
            temp = cA.rect()
            temp.setRight(newX)
            cA.setRect(temp)
        self.setLine(line)

    # end def

    ### PRIVATE SUPPORT METHODS ###
    def _updateAppearance(self, strand):
        """
        Prepare Strand for drawing, positions are relative to the VirtualHelixItem:
        1. Show or hide caps depending on L and R connectivity.
        2. Determine line coordinates.
        3. Apply paint styles.
        """
        # 0. Setup
        vhi = self._virtualHelixItem
        bw = _baseWidth
        halfBaseWidth = bw / 2.0
        lowIdx, highIdx = strand.lowIdx(), strand.highIdx()

        lUpperLeftX, lUpperLeftY = vhi.upperLeftCornerOfBase(lowIdx, strand)
        hUpperLeftX, hUpperLeftY = vhi.upperLeftCornerOfBase(highIdx, strand)
        lowCap = self._lowCap
        highCap = self._highCap
        dualCap = self._dualCap

        # 1. Cap visibilty
        lx = lUpperLeftX + bw  # draw from right edge of base
        lowCap.safeSetPos(lUpperLeftX, lUpperLeftY)
        if strand.connectionLow() != None:  # has low xover
            # if we are hiding it, we might as well make sure it is reparented to the StrandItem
            lowCap.restoreParent()
            lowCap.hide()
        else:  # has low cap
            if not lowCap.isVisible():
                lowCap.show()

        hx = hUpperLeftX  # draw to edge of base
        highCap.safeSetPos(hUpperLeftX, hUpperLeftY)
        if strand.connectionHigh() != None:  # has high xover
            # if we are hiding it, we might as well make sure it is reparented to the StrandItem
            highCap.restoreParent()
            highCap.hide()
        else:  # has high cap
            if not highCap.isVisible():
                highCap.show()

        # special case: single-base strand with no L or H connections,
        # (unconnected caps were made visible in previous block of code)
        if strand.length() == 1 and \
                  (lowCap.isVisible() and highCap.isVisible()):
            lowCap.hide()
            highCap.hide()
            dualCap.safeSetPos(lUpperLeftX, lUpperLeftY)
            dualCap.show()
        else:
            dualCap.hide()

        # 2. Xover drawing
        xo = self._xover3pEnd
        if strand.connection3p():
            xo.update(strand)
            xo.showIt()
        else:
            xo.restoreParent()
            xo.hideIt()

        # 3. Refresh insertionItems if necessary drawing
        self.refreshInsertionItems(strand)

        # 4. Line drawing
        hy = ly = lUpperLeftY + halfBaseWidth
        self.setLine(lx, ly, hx, hy)
        rectf = QRectF(lUpperLeftX + bw, lUpperLeftY,
                       bw * (highIdx - lowIdx - 1), bw)
        self._clickArea.setRect(rectf)
        self._updateHighlight(self.pen().color())

    # end def

    def _updateColor(self, strand):
        oligo = self._modelStrand.oligo()
        color = QColor(oligo.color())
        self._updateHighlight(color)

    # end def

    def _updateHighlight(self, color):
        """
        
        """
        oligo = self._modelStrand.oligo()
        penWidth = styles.PATH_STRAND_STROKE_WIDTH
        if oligo.shouldHighlight():
            color.setAlpha(128)
            penWidth = styles.PATH_STRAND_HIGHLIGHT_STROKE_WIDTH
        pen = QPen(color, penWidth)
        # pen.setCosmetic(True)
        brush = QBrush(color)
        pen.setCapStyle(Qt.FlatCap)
        self.setPen(pen)
        self._lowCap.updateHighlight(brush)
        self._highCap.updateHighlight(brush)
        self._dualCap.updateHighlight(brush)

    # end def

    def _updateSequenceText(self):
        """
        docstring for _updateSequenceText
        """
        bw = _baseWidth
        seqLbl = self._seqLabel
        strand = self.strand()

        seqTxt = strand.sequence()
        isDrawn3to5 = not self._isDrawn5to3
        textXCenteringOffset = styles.SEQUENCETEXTXCENTERINGOFFSET

        if seqTxt == '':
            seqLbl.hide()
            for iItem in self.insertionItems().itervalues():
                iItem.hideSequence()
            return
        # end if

        strandSeqList = strand.getSequenceList()
        seqList = [x[1][0] for x in strandSeqList]
        insertSeqList = [(x[0], x[1][1]) for x in strandSeqList]

        iItems = self.insertionItems()
        for idx, seqTxt in insertSeqList:
            if seqTxt != '':
                iItems[idx].setSequence(seqTxt)

        if isDrawn3to5:
            seqList = seqList[::-1]

        seqTxt = ''.join(seqList)

        # seqLbl.setPen(QPen( Qt.NoPen))    # leave the Pen as None for unless required
        seqLbl.setBrush(QBrush(Qt.black))
        seqLbl.setFont(styles.SEQUENCEFONT)

        # this will always draw from the 5 Prime end!
        seqX = 2 * textXCenteringOffset + bw * strand.idx5Prime()
        seqY = styles.SEQUENCETEXTYCENTERINGOFFSET

        if isDrawn3to5:
            # offset it towards the bottom
            seqY += bw * .8
            # offset X by the reverse centering offset and the string length
            seqX += textXCenteringOffset
            # rotate the characters upside down this does not affect positioning
            # coordinate system, +Y is still Down, and +X is still Right
            seqLbl.setRotation(180)
            # draw the text and reverse the string to draw 5 prime to 3 prime
            # seqTxt = seqTxt[::-1]
        # end if
        seqLbl.setPos(seqX, seqY)
        seqLbl.setText(seqTxt)
        seqLbl.show()

    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        """
        Parses a mousePressEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        activeToolStr = str(self._activeTool())
        self.scene().views()[0].addToPressList(self)
        idx = int(floor((event.pos().x()) / _baseWidth))
        self._virtualHelixItem.setActive(idx)
        toolMethodName = activeToolStr + "MousePress"
        if hasattr(self, toolMethodName):
            getattr(self, toolMethodName)(event, idx)
        else:
            event.setAccepted(False)

    # end def

    def mouseMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        toolMethodName = str(self._activeTool()) + "MouseMove"
        if hasattr(self, toolMethodName):
            idx = int(floor((event.pos().x()) / _baseWidth))
            getattr(self, toolMethodName)(idx)

    # end def

    def hoverLeaveEvent(self, event):
        self.partItem().updateStatusBar("")

    # end def

    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        vhiNum = self._virtualHelixItem.number()
        idx = int(floor((event.pos().x()) / _baseWidth))
        oligoLength = self._modelStrand.oligo().length()
        self.partItem().updateStatusBar("%d[%d]\tlength: %d" %
                                        (vhiNum, idx, oligoLength))
        toolMethodName = str(self._activeTool()) + "HoverMove"
        if hasattr(self, toolMethodName):
            getattr(self, toolMethodName)(idx)

    # end def

    def customMouseRelease(self, event):
        """
        Parses a mouseReleaseEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        toolMethodName = str(self._activeTool()) + "MouseRelease"
        if hasattr(self, toolMethodName):
            idx = int(floor((event.pos().x()) / _baseWidth))
            getattr(self, toolMethodName)(idx)

    # end def

    ### TOOL METHODS ###
    def breakToolMousePress(self, event, idx):
        """Break the strand is possible."""
        mStrand = self._modelStrand
        mStrand.split(idx)

    # end def

    def breakToolHoverMove(self, idx):
        pass
        # mStrand = self._modelStrand
        # vhi = self._virtualHelixItem
        # breakTool = self._activeTool()
        # breakTool.updateHoverRect(vhi, mStrand, idx, show=True)

    # end def

    def selectToolMousePress(self, event, idx):
        event.setAccepted(False)
        currentFilterDict = self._viewroot.selectionFilterDict()
        if self.strandFilter(
        ) in currentFilterDict and self._filterName in currentFilterDict:
            selectionGroup = self._viewroot.strandItemSelectionGroup()
            mod = Qt.MetaModifier
            if not (event.modifiers() & mod):
                selectionGroup.clearSelection(False)
            selectionGroup.setSelectionLock(selectionGroup)
            selectionGroup.pendToAdd(self)
            selectionGroup.pendToAdd(self._lowCap)
            selectionGroup.pendToAdd(self._highCap)
            selectionGroup.processPendingToAddList()
            event.setAccepted(True)
            return selectionGroup.mousePressEvent(event)

    # end def

    def pencilToolMousePress(self, event, idx):
        """Break the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        partItem = vhi.partItem()
        activeTool = self._activeTool()
        if activeTool.isFloatingXoverBegin():
            if idx == mStrand.idx5Prime():
                return
            tempXover = activeTool.floatingXover()
            tempXover.updateBase(vhi, mStrand, idx)
            activeTool.setFloatingXoverBegin(False)
        else:
            activeTool.setFloatingXoverBegin(True)
            # install Xover
            activeTool.attemptToCreateXover(vhi, mStrand, idx)

    # end def

    def pencilToolHoverMove(self, idx):
        """Pencil the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        activeTool = self._activeTool()
        if not activeTool.isFloatingXoverBegin():
            tempXover = activeTool.floatingXover()
            tempXover.updateFloatingFromStrandItem(vhi, mStrand, idx)

    # end def

    def eraseToolMousePress(self, event, idx):
        mStrand = self._modelStrand
        mStrand.strandSet().removeStrand(mStrand)

    # end def

    def insertionToolMousePress(self, event, idx):
        """Add an insert to the strand if possible."""
        mStrand = self._modelStrand
        mStrand.addInsertion(idx, 1)

    # end def

    def paintToolMousePress(self, event, idx):
        """Add an insert to the strand if possible."""
        mStrand = self._modelStrand
        if mStrand.isStaple():
            color = self.window().pathColorPanel.stapColorName()
        else:
            color = self.window().pathColorPanel.scafColorName()
        mStrand.oligo().applyColor(color)

    # end def

    def pencilToolHoverMove(self, idx):
        """Pencil the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        activeTool = self._activeTool()

        if not activeTool.isFloatingXoverBegin():
            tempXover = activeTool.floatingXover()
            tempXover.updateFloatingFromStrandItem(vhi, mStrand, idx)
            # if mStrand.idx5Prime() == idx:
            #     tempXover.hide3prime()
            # elif mStrand.idx3Prime() != idx:
            #     tempXover.show3prime()

    # end def

    def pencilToolMousePress(self, event, idx):
        """Break the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        partItem = vhi.partItem()
        activeTool = self._activeTool()

        if activeTool.isFloatingXoverBegin():
            # block xovers starting at a 5 prime end
            if mStrand.idx5Prime() == idx:
                return
            else:
                tempXover = activeTool.floatingXover()
                tempXover.updateBase(vhi, mStrand, idx)
                activeTool.setFloatingXoverBegin(False)
        else:
            activeTool.setFloatingXoverBegin(True)
            # install Xover
            activeTool.attemptToCreateXover(vhi, mStrand, idx)

    # end def

    def skipToolMousePress(self, event, idx):
        """Add an insert to the strand if possible."""
        mStrand = self._modelStrand
        mStrand.addInsertion(idx, -1)

    # end def

    def addSeqToolMousePress(self, event, idx):
        """
        Checks that a scaffold was clicked, and then calls apply sequence
        to the clicked strand via its oligo.
        """
        mStrand = self._modelStrand
        if mStrand.isScaffold():
            olgLen, seqLen = self._activeTool().applySequence(mStrand.oligo())
            if olgLen:
                msg = "Populated %d of %d scaffold bases." % (min(
                    seqLen, olgLen), olgLen)
                if olgLen > seqLen:
                    d = olgLen - seqLen
                    msg = msg + " Warning: %d bases have no sequence." % d
                elif olgLen < seqLen:
                    d = seqLen - olgLen
                    msg = msg + " Warning: %d sequence bases unused." % d
                self.partItem().updateStatusBar(msg)

    # end def

    def restoreParent(self, pos=None):
        """
        Required to restore parenting and positioning in the partItem
        """
        # map the position
        # print "restoring parent si"
        self.tempReparent(pos)
        self.setSelectedColor(False)
        self.setSelected(False)

    # end def

    def tempReparent(self, pos=None):
        vhItem = self.virtualHelixItem()
        if pos == None:
            pos = self.scenePos()
        self.setParentItem(vhItem)
        tempP = vhItem.mapFromScene(pos)
        self.setPos(tempP)

    # end def

    def setSelectedColor(self, value):
        if value == True:
            color = QColor("#ff3333")
        else:
            oligo = self._modelStrand.oligo()
            color = QColor(oligo.color())
            if oligo.shouldHighlight():
                color.setAlpha(128)
        pen = self.pen()
        pen.setColor(color)
        self.setPen(pen)

    # end def

    def itemChange(self, change, value):
        # for selection changes test against QGraphicsItem.ItemSelectedChange
        # intercept the change instead of the has changed to enable features.
        if change == QGraphicsItem.ItemSelectedChange and self.scene():
            activeTool = self._activeTool()
            if str(activeTool) == "selectTool":
                viewroot = self._viewroot
                currentFilterDict = viewroot.selectionFilterDict()
                selectionGroup = viewroot.strandItemSelectionGroup()

                # only add if the selectionGroup is not locked out
                isNormalSelect = selectionGroup.isNormalSelect()
                if value == True and (self._filterName in currentFilterDict
                                      or not isNormalSelect):
                    if self._strandFilter in currentFilterDict:
                        if self.group() != selectionGroup:
                            self.setSelectedColor(True)
                            # This should always be the case, but...
                            if isNormalSelect:
                                selectionGroup.pendToAdd(self)
                                selectionGroup.setSelectionLock(selectionGroup)
                                selectionGroup.pendToAdd(self._lowCap)
                                selectionGroup.pendToAdd(self._highCap)
                            # this else will capture the error.  Basically, the
                            # strandItem should be member of the group before this
                            # ever gets fired
                            else:
                                selectionGroup.addToGroup(self)
                        return True
                    else:
                        return False
                # end if
                elif value == True:
                    # Don't select
                    return False
                # end elif
                else:
                    # Deselect
                    # print "Deselecting strand"
                    selectionGroup.pendToRemove(self)
                    self.setSelectedColor(False)
                    selectionGroup.pendToRemove(self._lowCap)
                    selectionGroup.pendToRemove(self._highCap)
                    return False
                # end else
            # end if
            elif str(activeTool) == "paintTool":
                viewroot = self._viewroot
                currentFilterDict = viewroot.selectionFilterDict()
                if self._strandFilter in currentFilterDict:
                    if not activeTool.isMacrod():
                        activeTool.setMacrod()
                    self.paintToolMousePress(None, None)
            # end elif
            return False
        # end if
        return QGraphicsItem.itemChange(self, change, value)

    # end def

    def selectIfRequired(self, document, indices):
        """
        Select self or xover item as necessary
        """
        strand5p = self._modelStrand
        con3p = strand5p.connection3p()
        selectionGroup = self._viewroot.strandItemSelectionGroup()
        # check this strand's xover
        idxL, idxH = indices
        if con3p:
            # perhaps change this to a direct call, but here are seeds of an
            # indirect way of doing selection checks
            if document.isModelStrandSelected(
                    con3p) and document.isModelStrandSelected(strand5p):
                val3p = document.getSelectedStrandValue(con3p)
                # print "xover idx", indices
                test3p = val3p[0] if con3p.isDrawn5to3() else val3p[1]
                test5p = idxH if strand5p.isDrawn5to3() else idxL
                if test3p and test5p:
                    xoi = self._xover3pEnd
                    if not xoi.isSelected() or not xoi.group():
                        selectionGroup.setNormalSelect(False)
                        selectionGroup.addToGroup(xoi)
                        xoi.modelSelect(document)
                        selectionGroup.setNormalSelect(True)
                # end if
            # end if
        # end if
        # Now check the endpoints

        lowCap = self._lowCap
        if idxL == True:
            if not lowCap.isSelected() or not lowCap.group():
                selectionGroup.addToGroup(lowCap)
                lowCap.modelSelect(document)
        else:
            if lowCap.isSelected() or lowCap.group():
                lowCap.restoreParent()
        highCap = self._highCap
        if idxH == True:
            if not highCap.isSelected() or not highCap.group():
                selectionGroup.addToGroup(highCap)
                highCap.modelSelect(document)
        else:
            if highCap.isSelected() or highCap.group():
                highCap.restoreParent()

        # now check the strand itself
        if idxL == True and idxH == True:
            if not self.isSelected() or not self.group():
                selectionGroup.setNormalSelect(False)
                selectionGroup.addToGroup(self)
                self.modelSelect(document)
                selectionGroup.setNormalSelect(True)
        elif idxL == False and idxH == False:
            self.modelDeselect(document)

    # end def

    def modelDeselect(self, document):
        self.restoreParent()
        self._lowCap.modelDeselect(document)
        self._highCap.modelDeselect(document)

    # end def

    def modelSelect(self, document):
        self.setSelected(True)
        self.setSelectedColor(True)

    # end def

    def paint(self, painter, option, widget):
        painter.setPen(self.pen())
        painter.drawLine(self.line())
Example #6
0
class StrandItem(QGraphicsLineItem):
    _filterName = "strand"
    
    def __init__(self, modelStrand, virtualHelixItem, viewroot):
        """The parent should be a VirtualHelixItem."""
        super(StrandItem, self).__init__(virtualHelixItem)
        self._modelStrand = modelStrand
        self._virtualHelixItem = virtualHelixItem
        self._viewroot = viewroot
        self._activeTool = virtualHelixItem.activeTool()

        self._controller = StrandItemController(self, modelStrand)
        isDrawn5to3 = modelStrand.strandSet().isDrawn5to3()

        self._strandFilter = modelStrand.strandFilter()

        self._insertionItems = {}
        # caps
        self._lowCap = EndpointItem(self, 'low', isDrawn5to3)
        self._highCap = EndpointItem(self, 'high', isDrawn5to3)
        self._dualCap = EndpointItem(self, 'dual', isDrawn5to3)

        # orientation
        self._isDrawn5to3 = isDrawn5to3
        # self._isOnTop = virtualHelixItem.isStrandOnTop(modelStrand)
        # label
        self._seqLabel = QGraphicsSimpleTextItem(self)
        
        self.refreshInsertionItems(modelStrand)
        self._updateSequenceText()

        # create a larger click area rect to capture mouse events
        self._clickArea = cA = QGraphicsRectItem(_defaultRect, self)
        cA.mousePressEvent = self.mousePressEvent
        cA.setPen(_noPen)
        self.setAcceptHoverEvents(True)
        cA.setAcceptHoverEvents(True)
        cA.hoverMoveEvent = self.hoverMoveEvent

        self.setZValue(styles.ZSTRANDITEM)

        # xover comming from the 3p end
        self._xover3pEnd = XoverItem(self, virtualHelixItem)
        # initial refresh
        self._updateColor(modelStrand)
        self._updateAppearance(modelStrand)

        self.setZValue(styles.ZSTRANDITEM)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def strandResizedSlot(self, strand, indices):
        """docstring for strandResizedSlot"""
        lowMoved = self._lowCap.updatePosIfNecessary(self.idxs()[0])
        highMoved = self._highCap.updatePosIfNecessary(self.idxs()[1])
        group = self.group()
        self.tempReparent()  
        if lowMoved:
            self.updateLine(self._lowCap)
        if highMoved:
            self.updateLine(self._highCap)
        if strand.connection3p():
            self._xover3pEnd.update(strand)
        self.refreshInsertionItems(strand)
        self._updateSequenceText()
        if group:
            group.addToGroup(self)
    # end def

    def sequenceAddedSlot(self, oligo):
        """docstring for sequenceAddedSlot"""
        pass
    # end def

    def sequenceClearedSlot(self, oligo):
        """docstring for sequenceClearedSlot"""
        pass
    # end def

    def strandRemovedSlot(self, strand):
        # self._modelStrand = None
        self._controller.disconnectSignals()
        self._controller = None
        scene = self.scene()
        scene.removeItem(self._clickArea)
        scene.removeItem(self._highCap)
        scene.removeItem(self._lowCap)
        scene.removeItem(self._seqLabel)
        self._xover3pEnd.remove()
        self._xover3pEnd = None
        for insertionItem in self._insertionItems.itervalues():
            insertionItem.remove()
        self._insertionItems = None
        self._clickArea = None
        self._highCap = None
        self._lowCap = None
        self._seqLabel = None
        self._modelStrand = None
        self._virtualHelixItem = None
        scene.removeItem(self)
    # end def

    def strandUpdateSlot(self, strand):
        """
        Slot for just updating connectivity and color, and endpoint showing
        """
        self._updateAppearance(strand)
    # end def

    def oligoAppearanceChangedSlot(self, oligo):
        strand = self._modelStrand
        self._updateColor(strand)
        if strand.connection3p():
            self._xover3pEnd._updateColor(strand)
        for insertion in self.insertionItems().itervalues():
            insertion.updateItem()
    # end def

    def oligoSequenceAddedSlot(self, oligo):
        self._updateSequenceText()
    # end def

    def oligoSequenceClearedSlot(self, oligo):
        self._updateSequenceText()
    # end def

    def strandHasNewOligoSlot(self, strand):
        strand = self._modelStrand
        self._controller.reconnectOligoSignals()
        self._updateColor(strand)
        if strand.connection3p():
            self._xover3pEnd._updateColor(strand)
    # end def

    def strandInsertionAddedSlot(self, strand, insertion):
        self.insertionItems()[insertion.idx()] = \
                    InsertionItem(self._virtualHelixItem, strand, insertion)
    # end def
    def strandInsertionChangedSlot(self, strand, insertion):
        self.insertionItems()[insertion.idx()].updateItem()
    # end def

    def strandInsertionRemovedSlot(self, strand, index):
        instItem = self.insertionItems()[index]
        instItem.remove()
        del self.insertionItems()[index]
    # end def

    def strandDecoratorAddedSlot(self, strand, decorator):
        pass
    # end def
    def strandDecoratorChangedSlot(self, strand, decorator):
        pass
    # end def
    def strandDecoratorRemovedSlot(self, strand, index):
        pass
    # end def
    def strandModifierAddedSlot(self, strand, modifier):
        pass
    # end def
    def strandModifierChangedSlot(self, strand, modifier):
        pass
    # end def
    def strandModifierRemovedSlot(self, strand, index):
        pass
    # end def

    def selectedChangedSlot(self, strand, indices):
        self.selectIfRequired(self.partItem().document(), indices)
    # end def

    ### ACCESSORS ###
    def activeTool(self):
        return self._activeTool
    # end def
    
    def viewroot(self):
        return self._viewroot
    # end def

    def insertionItems(self):
        return self._insertionItems
    # end def

    def strand(self):
        return self._modelStrand
    # end def

    def strandFilter(self):
        return self._strandFilter
    # end def

    def idxs(self):
        return self._modelStrand.idxs()
    # end def

    def virtualHelixItem(self):
        return self._virtualHelixItem
    # end def

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

    def window(self):
        return self._virtualHelixItem.window()

    ### PUBLIC METHODS FOR DRAWING / LAYOUT ###
    def refreshInsertionItems(self, strand):
        iItems = self.insertionItems()
        iModel = strand.insertionsOnStrand()
        
        was_in_use = set(iItems)
        in_use = set()
        # add in the ones supposed to be there
        for insertion in iModel:
            idx = insertion.idx()
            in_use.add(idx)
            if idx in iItems:
                pass
            else:
                iItems[insertion.idx()] = \
                    InsertionItem(self._virtualHelixItem, strand, insertion)
        # end for
        
        # remove all in items
        not_in_use = was_in_use - in_use
        for index in not_in_use:
            iItems[index].remove()
            del iItems[index]
        # end for
    # end def

    def resetStrandItem(self, virtualHelixItem, isDrawn5to3):
        self.setParentItem(virtualHelixItem)
        self._virtualHelixItem = virtualHelixItem
        self.resetEndPointItems(isDrawn5to3)
    # end def

    def resetEndPointItems(self, isDrawn5to3):
        self._isDrawn5to3 = isDrawn5to3
        self._lowCap.resetEndPoint(isDrawn5to3)
        self._highCap.resetEndPoint(isDrawn5to3)
        self._dualCap.resetEndPoint(isDrawn5to3)
    # end def

    def updateLine(self, movedCap):
        # setup
        bw = _baseWidth
        cA = self._clickArea
        line = self.line()
        # set new line coords
        if movedCap == self._lowCap:
            p1 = line.p1()
            newX = self._lowCap.pos().x() + bw
            p1.setX(newX)
            line.setP1(p1)
            temp = cA.rect()
            temp.setLeft(newX)
            cA.setRect(temp)
        else:
            p2 = line.p2()
            newX = self._highCap.pos().x()
            p2.setX(newX)
            line.setP2(p2)
            temp = cA.rect()
            temp.setRight(newX)
            cA.setRect(temp)
        self.setLine(line)
    # end def

    ### PRIVATE SUPPORT METHODS ###
    def _updateAppearance(self, strand):
        """
        Prepare Strand for drawing, positions are relative to the VirtualHelixItem:
        1. Show or hide caps depending on L and R connectivity.
        2. Determine line coordinates.
        3. Apply paint styles.
        """
        # 0. Setup
        vhi = self._virtualHelixItem
        bw = _baseWidth
        halfBaseWidth = bw / 2.0
        lowIdx, highIdx = strand.lowIdx(), strand.highIdx()

        lUpperLeftX, lUpperLeftY = vhi.upperLeftCornerOfBase(lowIdx, strand)
        hUpperLeftX, hUpperLeftY = vhi.upperLeftCornerOfBase(highIdx, strand)
        lowCap = self._lowCap
        highCap = self._highCap
        dualCap = self._dualCap

        # 1. Cap visibilty
        lx = lUpperLeftX + bw  # draw from right edge of base
        lowCap.safeSetPos(lUpperLeftX, lUpperLeftY)
        if strand.connectionLow() != None:  # has low xover
            # if we are hiding it, we might as well make sure it is reparented to the StrandItem
            lowCap.restoreParent()
            lowCap.hide()
        else:  # has low cap
            if not lowCap.isVisible():
                lowCap.show()

        hx = hUpperLeftX  # draw to edge of base
        highCap.safeSetPos(hUpperLeftX, hUpperLeftY)
        if strand.connectionHigh() != None:  # has high xover
            # if we are hiding it, we might as well make sure it is reparented to the StrandItem
            highCap.restoreParent()
            highCap.hide()
        else:  # has high cap
            if not highCap.isVisible():
                highCap.show()

        # special case: single-base strand with no L or H connections,
        # (unconnected caps were made visible in previous block of code)
        if strand.length() == 1 and \
                  (lowCap.isVisible() and highCap.isVisible()):
            lowCap.hide()
            highCap.hide()
            dualCap.safeSetPos(lUpperLeftX, lUpperLeftY)
            dualCap.show()
        else:
            dualCap.hide()

        # 2. Xover drawing
        xo = self._xover3pEnd
        if strand.connection3p():
            xo.update(strand)
            xo.showIt()
        else:
            xo.restoreParent()
            xo.hideIt()

        # 3. Refresh insertionItems if necessary drawing
        self.refreshInsertionItems(strand)

        # 4. Line drawing
        hy = ly = lUpperLeftY + halfBaseWidth
        self.setLine(lx, ly, hx, hy)
        rectf = QRectF(lUpperLeftX+bw, lUpperLeftY, bw*(highIdx-lowIdx-1), bw)
        self._clickArea.setRect(rectf)
        self._updateHighlight(self.pen().color())
    # end def

    def _updateColor(self, strand):
        oligo = self._modelStrand.oligo()
        color = QColor(oligo.color())
        self._updateHighlight(color)
    # end def

    def _updateHighlight(self, color):
        """
        
        """
        oligo = self._modelStrand.oligo()
        penWidth = styles.PATH_STRAND_STROKE_WIDTH
        if oligo.shouldHighlight():
            color.setAlpha(128)
            penWidth = styles.PATH_STRAND_HIGHLIGHT_STROKE_WIDTH
        pen = QPen(color, penWidth)
        # pen.setCosmetic(True)
        brush = QBrush(color)
        pen.setCapStyle(Qt.FlatCap)
        self.setPen(pen)
        self._lowCap.updateHighlight(brush)
        self._highCap.updateHighlight(brush)
        self._dualCap.updateHighlight(brush)
    # end def

    def _updateSequenceText(self):
        """
        docstring for _updateSequenceText
        """
        bw = _baseWidth
        seqLbl = self._seqLabel
        strand = self.strand()
        
        seqTxt = strand.sequence()
        isDrawn3to5 = not self._isDrawn5to3
        textXCenteringOffset = styles.SEQUENCETEXTXCENTERINGOFFSET

        if seqTxt == '':
            seqLbl.hide()
            for iItem in self.insertionItems().itervalues():
                iItem.hideSequence()
            return
        # end if

        strandSeqList = strand.getSequenceList()
        seqList = [x[1][0] for x in strandSeqList]
        insertSeqList = [(x[0], x[1][1]) for x in strandSeqList]
        
        iItems = self.insertionItems()
        for idx, seqTxt in insertSeqList:
            if seqTxt != '':
                iItems[idx].setSequence(seqTxt)

        if isDrawn3to5:
            seqList = seqList[::-1]
            
        seqTxt = ''.join(seqList)
        
        # seqLbl.setPen(QPen( Qt.NoPen))    # leave the Pen as None for unless required
        seqLbl.setBrush(QBrush(Qt.black))
        seqLbl.setFont(styles.SEQUENCEFONT)

        # this will always draw from the 5 Prime end!
        seqX = 2*textXCenteringOffset + bw*strand.idx5Prime()
        seqY = styles.SEQUENCETEXTYCENTERINGOFFSET

        if isDrawn3to5:
            # offset it towards the bottom
            seqY += bw * .8
            # offset X by the reverse centering offset and the string length
            seqX += textXCenteringOffset
            # rotate the characters upside down this does not affect positioning
            # coordinate system, +Y is still Down, and +X is still Right
            seqLbl.setRotation(180)
            # draw the text and reverse the string to draw 5 prime to 3 prime
            # seqTxt = seqTxt[::-1]
        # end if
        seqLbl.setPos(seqX,seqY)
        seqLbl.setText(seqTxt)
        seqLbl.show()
    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        """
        Parses a mousePressEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        activeToolStr = str(self._activeTool())
        self.scene().views()[0].addToPressList(self)
        idx = int(floor((event.pos().x()) / _baseWidth))
        self._virtualHelixItem.setActive(idx)
        toolMethodName =  activeToolStr + "MousePress"
        if hasattr(self, toolMethodName):
            getattr(self, toolMethodName)(event, idx)
        else:
            event.setAccepted(False)
    # end def

    def mouseMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        toolMethodName = str(self._activeTool()) + "MouseMove"
        if hasattr(self, toolMethodName):
            idx = int(floor((event.pos().x()) / _baseWidth))
            getattr(self, toolMethodName)(idx)
    # end def

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

    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        vhiNum = self._virtualHelixItem.number()
        idx = int(floor((event.pos().x()) / _baseWidth))
        oligoLength = self._modelStrand.oligo().length()
        self.partItem().updateStatusBar("%d[%d]\tlength: %d" % (vhiNum, idx, oligoLength))
        toolMethodName = str(self._activeTool()) + "HoverMove"
        if hasattr(self, toolMethodName):
            getattr(self, toolMethodName)(idx)
    # end def

    def customMouseRelease(self, event):
        """
        Parses a mouseReleaseEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        toolMethodName = str(self._activeTool()) + "MouseRelease"
        if hasattr(self, toolMethodName):
            idx = int(floor((event.pos().x()) / _baseWidth))
            getattr(self, toolMethodName)(idx)
    # end def

    ### TOOL METHODS ###
    def breakToolMousePress(self, event, idx):
        """Break the strand is possible."""
        mStrand = self._modelStrand
        mStrand.split(idx)
    # end def

    def breakToolHoverMove(self, idx):
        pass
        # mStrand = self._modelStrand
        # vhi = self._virtualHelixItem
        # breakTool = self._activeTool()
        # breakTool.updateHoverRect(vhi, mStrand, idx, show=True)
    # end def

    def selectToolMousePress(self, event, idx):
        event.setAccepted(False)
        currentFilterDict = self._viewroot.selectionFilterDict()
        if self.strandFilter() in currentFilterDict and self._filterName in currentFilterDict:
            selectionGroup = self._viewroot.strandItemSelectionGroup()
            mod = Qt.MetaModifier
            if not (event.modifiers() & mod):
                 selectionGroup.clearSelection(False)
            selectionGroup.setSelectionLock(selectionGroup)
            selectionGroup.pendToAdd(self)
            selectionGroup.pendToAdd(self._lowCap)
            selectionGroup.pendToAdd(self._highCap)
            selectionGroup.processPendingToAddList()
            event.setAccepted(True)
            return selectionGroup.mousePressEvent(event)
    # end def

    def pencilToolMousePress(self, event, idx):
        """Break the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        partItem = vhi.partItem()
        activeTool = self._activeTool()
        if activeTool.isFloatingXoverBegin():
            if idx == mStrand.idx5Prime():
                return
            tempXover = activeTool.floatingXover()
            tempXover.updateBase(vhi, mStrand, idx)
            activeTool.setFloatingXoverBegin(False)
        else:
            activeTool.setFloatingXoverBegin(True)
            # install Xover
            activeTool.attemptToCreateXover(vhi, mStrand, idx)
    # end def

    def pencilToolHoverMove(self, idx):
        """Pencil the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        activeTool = self._activeTool()
        if not activeTool.isFloatingXoverBegin():
            tempXover = activeTool.floatingXover()
            tempXover.updateFloatingFromStrandItem(vhi, mStrand, idx)
    # end def

    def eraseToolMousePress(self, event, idx):
        mStrand = self._modelStrand
        mStrand.strandSet().removeStrand(mStrand)
    # end def

    def insertionToolMousePress(self, event, idx):
        """Add an insert to the strand if possible."""
        mStrand = self._modelStrand
        mStrand.addInsertion(idx, 1)
    # end def

    def paintToolMousePress(self, event, idx):
        """Add an insert to the strand if possible."""
        mStrand = self._modelStrand
        if mStrand.isStaple():
            color = self.window().pathColorPanel.stapColorName()
        else:
            color = self.window().pathColorPanel.scafColorName()
        mStrand.oligo().applyColor(color)
    # end def

    def pencilToolHoverMove(self, idx):
        """Pencil the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        activeTool = self._activeTool()

        if not activeTool.isFloatingXoverBegin():
            tempXover = activeTool.floatingXover()
            tempXover.updateFloatingFromStrandItem(vhi, mStrand, idx)
            # if mStrand.idx5Prime() == idx:
            #     tempXover.hide3prime()
            # elif mStrand.idx3Prime() != idx:
            #     tempXover.show3prime()
    # end def

    def pencilToolMousePress(self, event, idx):
        """Break the strand is possible."""
        mStrand = self._modelStrand
        vhi = self._virtualHelixItem
        partItem = vhi.partItem()
        activeTool = self._activeTool()

        if activeTool.isFloatingXoverBegin():
            # block xovers starting at a 5 prime end
            if mStrand.idx5Prime() == idx:
                return
            else:
                tempXover = activeTool.floatingXover()
                tempXover.updateBase(vhi, mStrand, idx)
                activeTool.setFloatingXoverBegin(False)
        else:
            activeTool.setFloatingXoverBegin(True)
            # install Xover
            activeTool.attemptToCreateXover(vhi, mStrand, idx)
    # end def

    def skipToolMousePress(self, event, idx):
        """Add an insert to the strand if possible."""
        mStrand = self._modelStrand
        mStrand.addInsertion(idx, -1)
    # end def
    
    def addSeqToolMousePress(self, event, idx):
        """
        Checks that a scaffold was clicked, and then calls apply sequence
        to the clicked strand via its oligo.
        """
        mStrand = self._modelStrand
        if mStrand.isScaffold():
            olgLen, seqLen = self._activeTool().applySequence(mStrand.oligo())
            if olgLen:
                msg = "Populated %d of %d scaffold bases." % (min(seqLen, olgLen), olgLen)
                if olgLen > seqLen:
                    d = olgLen - seqLen
                    msg = msg + " Warning: %d bases have no sequence." % d
                elif olgLen < seqLen:
                    d = seqLen - olgLen
                    msg = msg + " Warning: %d sequence bases unused." % d
                self.partItem().updateStatusBar(msg)
    # end def
    
    def restoreParent(self, pos=None):
        """
        Required to restore parenting and positioning in the partItem
        """
        # map the position
        # print "restoring parent si"
        self.tempReparent(pos)
        self.setSelectedColor(False)
        self.setSelected(False)
    # end def
    
    def tempReparent(self, pos=None):
        vhItem = self.virtualHelixItem()
        if pos == None:
            pos = self.scenePos()
        self.setParentItem(vhItem)
        tempP = vhItem.mapFromScene(pos)
        self.setPos(tempP)
    # end def

    def setSelectedColor(self, value):
        if value == True:
            color = QColor("#ff3333")
        else:
            oligo = self._modelStrand.oligo()
            color = QColor(oligo.color())
            if oligo.shouldHighlight():
                color.setAlpha(128)
        pen = self.pen()
        pen.setColor(color)
        self.setPen(pen)
    # end def

    def itemChange(self, change, value):
        # for selection changes test against QGraphicsItem.ItemSelectedChange
        # intercept the change instead of the has changed to enable features.
        if change == QGraphicsItem.ItemSelectedChange and self.scene():
            activeTool = self._activeTool()
            if str(activeTool) == "selectTool":
                viewroot = self._viewroot
                currentFilterDict = viewroot.selectionFilterDict()
                selectionGroup = viewroot.strandItemSelectionGroup()
        
                # only add if the selectionGroup is not locked out
                isNormalSelect = selectionGroup.isNormalSelect()
                if value == True and (self._filterName in currentFilterDict or not isNormalSelect):
                    if self._strandFilter in currentFilterDict:
                        if self.group() != selectionGroup:
                            self.setSelectedColor(True)
                            # This should always be the case, but...
                            if isNormalSelect:
                                selectionGroup.pendToAdd(self)
                                selectionGroup.setSelectionLock(selectionGroup)
                                selectionGroup.pendToAdd(self._lowCap)
                                selectionGroup.pendToAdd(self._highCap)
                            # this else will capture the error.  Basically, the
                            # strandItem should be member of the group before this
                            # ever gets fired
                            else:
                                selectionGroup.addToGroup(self)
                        return True
                    else:
                        return False
                # end if
                elif value == True:
                    # Don't select
                    return False
                # end elif
                else:
                    # Deselect
                    # print "Deselecting strand"
                    selectionGroup.pendToRemove(self)
                    self.setSelectedColor(False)
                    selectionGroup.pendToRemove(self._lowCap)
                    selectionGroup.pendToRemove(self._highCap)
                    return False
                # end else
            # end if
            elif str(activeTool) == "paintTool":
                viewroot = self._viewroot
                currentFilterDict = viewroot.selectionFilterDict()
                if self._strandFilter in currentFilterDict:
                    if not activeTool.isMacrod():
                        activeTool.setMacrod()
                    self.paintToolMousePress(None, None)
            # end elif
            return False
        # end if
        return QGraphicsItem.itemChange(self, change, value)
    # end def

    def selectIfRequired(self, document, indices):
        """
        Select self or xover item as necessary
        """
        strand5p = self._modelStrand
        con3p = strand5p.connection3p()
        selectionGroup = self._viewroot.strandItemSelectionGroup()
        # check this strand's xover
        idxL, idxH = indices
        if con3p:
            # perhaps change this to a direct call, but here are seeds of an 
            # indirect way of doing selection checks    
            if document.isModelStrandSelected(con3p) and document.isModelStrandSelected(strand5p):
                val3p = document.getSelectedStrandValue(con3p)
                # print "xover idx", indices
                test3p = val3p[0] if con3p.isDrawn5to3() else val3p[1]
                test5p = idxH if strand5p.isDrawn5to3() else idxL
                if test3p and test5p:
                    xoi = self._xover3pEnd
                    if not xoi.isSelected() or not xoi.group():
                        selectionGroup.setNormalSelect(False)
                        selectionGroup.addToGroup(xoi)
                        xoi.modelSelect(document)
                        selectionGroup.setNormalSelect(True)
                # end if
            # end if
        # end if
        # Now check the endpoints
        
        lowCap = self._lowCap
        if idxL == True:
            if not lowCap.isSelected() or not lowCap.group():
                selectionGroup.addToGroup(lowCap)
                lowCap.modelSelect(document)
        else:
            if lowCap.isSelected() or lowCap.group():
                lowCap.restoreParent()
        highCap = self._highCap
        if idxH == True:
            if not highCap.isSelected() or not highCap.group():
                selectionGroup.addToGroup(highCap)
                highCap.modelSelect(document)
        else:
            if highCap.isSelected() or highCap.group():
                highCap.restoreParent()
        
        # now check the strand itself
        if idxL == True and idxH == True:
            if not self.isSelected() or not self.group():
                selectionGroup.setNormalSelect(False)
                selectionGroup.addToGroup(self)
                self.modelSelect(document)
                selectionGroup.setNormalSelect(True)
        elif idxL == False and idxH == False:
            self.modelDeselect(document)
    # end def

    def modelDeselect(self, document):
        self.restoreParent()
        self._lowCap.modelDeselect(document)
        self._highCap.modelDeselect(document)
    # end def

    def modelSelect(self, document):
        self.setSelected(True)
        self.setSelectedColor(True)
    # end def
    
    def paint(self, painter, option, widget):
        painter.setPen(self.pen())
        painter.drawLine(self.line())