def __init__(self, part,\
                    controller=None,\
                    parent=None):
     super(PathHelixGroup, self).__init__(parent)
     # Subviews, GraphicsItem business
     self.rect = QRectF()  # Set by _setPathHelixList
     # self._label=None; self.label()  # Poke the cache so the label actually exists
     # Properties
     self._XOverLabels = None
     self._pathHelixes = []  # Primary property
     self.activeHelix = None
     self._part = None
     self.phhSelectionGroup = SelectionItemGroup(\
                                      boxtype=PathHelixHandleSelectionBox,\
                                      constraint='y',\
                                      parent=self)
     self.setPart(part)
     self._controller = controller
     self._activeSliceHandle = ActiveSliceHandle(self)
     self._stapColor = QColor(0, 72, 0)
     self._stapPen = QPen(self._stapColor, 2)
     
     self.floatingXover = XoverHandlePair(self, None, None)
     self.loopHandleGroup = LoopHandleGroup(parent=self)
     self.xovers = {}
     
     self.setZValue(styles.ZPATHHELIXGROUP)
     self.selectionLock = None
     self.setAcceptHoverEvents(True)
     app().phg = self  # Convenience for the command line -i mode
     self._part.partRemoved.connect(self.destroy)  # connect destructor
     self.dragging = False
class PathHelixGroup(QGraphicsObject):
    """
    PathHelixGroup maintains data and state for a set of object that provide
    an interface to the schematic view of a DNA part. These objects include
    the PathHelix, PathHelixHandles, and ActiveSliceHandle.
    """
    handleRadius = styles.SLICE_HELIX_RADIUS
    _scafColor = QColor(0, 102, 204)
    _scafPen = QPen(_scafColor, 2)
    _nobrush = QBrush(Qt.NoBrush)

    def __init__(self, part,\
                       controller=None,\
                       parent=None):
        super(PathHelixGroup, self).__init__(parent)
        # Subviews, GraphicsItem business
        self.rect = QRectF()  # Set by _setPathHelixList
        # self._label=None; self.label()  # Poke the cache so the label actually exists
        # Properties
        self._XOverLabels = None
        self._pathHelixes = []  # Primary property
        self.activeHelix = None
        self._part = None
        self.phhSelectionGroup = SelectionItemGroup(\
                                         boxtype=PathHelixHandleSelectionBox,\
                                         constraint='y',\
                                         parent=self)
        self.setPart(part)
        self._controller = controller
        self._activeSliceHandle = ActiveSliceHandle(self)
        self._stapColor = QColor(0, 72, 0)
        self._stapPen = QPen(self._stapColor, 2)
        
        self.floatingXover = XoverHandlePair(self, None, None)
        self.loopHandleGroup = LoopHandleGroup(parent=self)
        self.xovers = {}
        
        self.setZValue(styles.ZPATHHELIXGROUP)
        self.selectionLock = None
        self.setAcceptHoverEvents(True)
        app().phg = self  # Convenience for the command line -i mode
        self._part.partRemoved.connect(self.destroy)  # connect destructor
        self.dragging = False

    def destroy(self):
        self._part.partRemoved.disconnect(self.destroy)
        self.scene().removeItem(self)
        self.setPart(None)
    # end def

    def __str__(self):
        return "I am a PathHelixGroup!"

    def part(self):
        return self._part

    def activeTool(self):
        return self.controller().activeTool()

    def getActiveHelix(self):
        return self.activeHelix

    def setActiveHelix(self, newActivePH):
        self.activeHelix = newActivePH
        neighborVHs = newActivePH.vhelix().neighbors()
        for ph in self._pathHelixes:
            showHandles = ph==newActivePH or ph.vhelix() in neighborVHs
            ph.setPreXOverHandlesVisible(showHandles)

    def notifyLoopHandleGroupAfterUpdate(self, pathhelix):
        """
        Called by setActiveHelix and loophandlegroup after the vhelix has 
        calculated its new loop positions.
        """
        self.loopHandleGroup.updateActiveHelix(pathhelix)

    def setPart(self, newPart):
        if self._part:
            self._part.selectionWillChange.disconnect(self.selectionWillChange)
            self._part.dimensionsDidChange.disconnect(self.partDimensionsChanged)
            self._part.createXover.disconnect(self.createXoverItem)
            self._part.updateFloatingXover.disconnect(self.updateFloatingXoverItem)
        if newPart:
            newPart.selectionWillChange.connect(self.selectionWillChange)
            newPart.dimensionsDidChange.connect(self.partDimensionsChanged)
            newPart.createXover.connect(self.createXoverItem)
            newPart.updateFloatingXover.connect(self.updateFloatingXoverItem)
        self._part = newPart
        if newPart:
            self.selectionWillChange(newPart.selection())

    def controller(self):
        return self._controller

    def activeSliceHandle(self):
        return self._activeSliceHandle

    # def label(self):
    #     if self._label:
    #         return self._label
    #     font = QFont(styles.thefont, 30, QFont.Bold)
    #     label = QGraphicsTextItem("Part 1")
    #     label.setVisible(False)
    #     label.setFont(font)
    #     label.setParentItem(self)
    #     label.setPos(0, -70)
    #     label.setTextInteractionFlags(Qt.TextEditorInteraction)
    #     label.inputMethodEvent = None
    #     self._label = label
    #     return label

    # @pyqtSlot(object, object, int)
    def createXoverItem(self, Base3p, Base5p, strandtype):
        """
        Base3p is the tuple (3 prime vhelix, index), 
        Base5p is the (5 prime vhelix, index)
        strandtype is Strandtype.Scaffold or Strandtype.Staple
        """
        fromBase = (Base3p[0], strandtype, Base3p[1])
        toBase = (Base5p[0], strandtype, Base5p[1])
        key = (fromBase, toBase)
        if not key in self.xovers:
            self.xovers[key] = XoverHandlePair(self, fromBase, toBase)

    def updateFloatingXoverItem(self, fromBase, toPt):
        self.floatingXover.setFromBase(fromBase)
        self.floatingXover.setToPoint(toPt)

    def pathHelixAtScenePos(self, pos):
        for p in self._pathHelixes:
            pt = p.mapFromScene(pos)
            if p.boundingRect().contains(pt):
                return p
        return None

    def pathHelixForVHelix(self, vh):
        for p in self._pathHelixes:
            if p.vhelix() == vh:
                return p
        return None

    def displayedVHs(self):
        """Returns the list (ordered top to bottom) of VirtualHelix
        that the receiver is displaying"""
        return [ph.vhelix() for ph in self._pathHelixes]

    displayedVHsChanged = pyqtSignal()
    def setDisplayedVHs(self, vhrefs):
        """Spawns or destroys PathHelix such that displayedVHs
        has the same VirtualHelix in the same order as vhrefs
        (though displayedVHs always returns a list of VirtualHelix
        while setDisplayedVHs can take any vhref)"""
        if self.part() != None:
            assert(self.part())  # Can't display VirtualHelix that aren't there!
            new_pathHelixList = []
            vhToPH = dict(((ph.vhelix(), ph) for ph in self._pathHelixes))
            for vhref in vhrefs:
                vh = self.part().getVirtualHelix(vhref)
                ph = vhToPH.get(vh, None)
                if ph == None:
                    ph = PathHelix(vh, self)
                new_pathHelixList.append(ph)
            # print [x.number() for x in new_pathHelixList]
            self._setPathHelixList(new_pathHelixList)
            # print "updating disp vhs"
            self.displayedVHsChanged.emit()

    def partDimensionsChanged(self):
        self._setPathHelixList(self._pathHelixList())

    def _pathHelixList(self):
        return self._pathHelixes

    def topmostPathHelix(self):
        if len(self._pathHelixList())==0:
            return None
        return self._pathHelixList()[0]

    def _setPathHelixList(self, newList):
        """Give me a list of PathHelix and I'll parent them
        to myself if necessary, position them in a column, adopt
        their handles, and position them as well."""
        y = 0  # How far down from the top the next PH should be
        leftmostExtent = 0
        rightmostExtent = 0
        # self.label().setVisible(True)
        for ph in self._pathHelixes:
            if not ph in newList:
                scene = ph.scene()
                handle = ph.handle()
                if handle.focusRing:
                    scene.removeItem(handle.focusRing)
                scene.removeItem(handle)
                scene.removeItem(ph)
        for ph in newList:
            ph.setParentItem(self)
            ph.setPos(0, y)
            ph_height = ph.boundingRect().height()
            step = ph_height + styles.PATH_HELIX_PADDING
            phh = ph.handle()
            if phh.parentItem() != self.phhSelectionGroup:
                phh.setParentItem(self)
            phhr = phh.boundingRect()
            phh.setPos(-2 * phhr.width(), y + (ph_height - phhr.height()) / 2)
            leftmostExtent = min(leftmostExtent, -2 * phhr.width())
            rightmostExtent = max(rightmostExtent, ph.boundingRect().width())
            y += step
            ph.updatePreXOverHandles()
        # end for
        self.prepareGeometryChange()
        self.rect = QRectF(leftmostExtent,\
                           -40,\
                           -leftmostExtent + rightmostExtent,\
                           y + 40)
        self.geometryChanged.emit()
        for ph in self._pathHelixes:
            vhbm = getattr(ph, 'vhelixBasesModifiedCallbackObj', None)
            if vhbm:
                ph.vhelix().basesModified.disconnect(vhbm)
        for ph in newList:
            def vhbmCallbackCreator(self, vh):
                def vhbmCallback():
                    self.vhelixBasesModified(vh)
                return vhbmCallback
            vhbm = vhbmCallbackCreator(self, ph.vhelix())
            ph.vhelix().basesModified.connect(vhbm)
        self._pathHelixes = newList
        for ph in self._pathHelixes:
            ph.positionInPhgChanged()
        self.vhToPathHelix = dict(((ph.vhelix(), ph) for ph in newList))
        self.scene().views()[0].zoomToFit()

    def paint(self, painter, option, widget=None):
        pass

    geometryChanged = pyqtSignal()

    def boundingRect(self):
        # rect set only by _setPathHelixList
        return self.rect

    def moveHelixNumToIdx(self, num, idx):
        """Reinserts helix with number() num such that
        it's at position idx in _pathHelixList"""
        vhs = [vh.number() for vh in self.displayedVHs()]
        vhs.remove(num)
        vhs.insert(idx, num)
        self.setDisplayedVHs(vhs)
        
    def renumber(self):
        self.part().matchHelixNumberingToPhgDisplayOrder(self)
    # end def

    def zoomToFit(self):
        # Auto zoom to center the scene
        thescene = self.scene()
        theview = thescene.views()[0]
        theview.zoomToFit()

    def virtualHelixAtCoordsChangedEvent(self, row, col):
        c = (row, col)
        self._setPathHelixList([ph for ph in self._pathHelixes if ph.vhelix().coord()!=c])

    # Slot called when the slice view's (actually the part's) selection changes
    def selectionWillChange(self, newSelection):
        self.setDisplayedVHs(newSelection)

    def getPathHelix(self, vhref):
        """Given the helix number, return a reference to the PathHelix."""
        if self.part() != None:
            vh = self.part().getVirtualHelix(vhref)
            for ph in self._pathHelixes:
                if ph.vhelix() == vh:
                    return ph
        return None

    def vhelixBasesModified(self, vhelix):
        self.update()
        ph = self.getPathHelix(vhelix)
        if ph != None:
            self.notifyLoopHandleGroupAfterUpdate(ph)

    def reorderHelices(self, first, last, indexDelta):
        """
        Reorder helices by moving helices _pathHelixList[first:last]
        by a distance delta in the list. Notify each PathHelix and
        PathHelixHandle of its new location.
        """
        # print "called reorderHelices", first, last, indexDelta
        # vhs = self.displayedVHs()
        # vhsToMove = vhs[first:last]
        # del vhs[first:last]

        helixNumbers = [ph.number() for ph in self._pathHelixes]
        firstIndex = helixNumbers.index(first)
        lastIndex = helixNumbers.index(last) + 1
        # print "indices", firstIndex, lastIndex
        if indexDelta < 0:  # move group earlier in the list
            newIndex = max(0, indexDelta + firstIndex)
            listPHs = self._pathHelixes[0:newIndex] +\
                                 self._pathHelixes[firstIndex:lastIndex] +\
                                 self._pathHelixes[newIndex:firstIndex] +\
                                 self._pathHelixes[lastIndex:]
        else:  # move group later in list
            newIndex = min(len(self._pathHelixes), indexDelta + lastIndex)
            listPHs = self._pathHelixes[:firstIndex] +\
                                 self._pathHelixes[lastIndex:newIndex] +\
                                 self._pathHelixes[firstIndex:lastIndex] +\
                                 self._pathHelixes[newIndex:]
        listVHs = [ph.vhelix() for ph in listPHs]
        self.setDisplayedVHs(listVHs)
    # end def

    # These methods are required since hover events are accepted
    # and no additional event handler exists in order to prevent additional
    # phg redraws
    def hoverEnterEvent(self, event):
        pass
        # QGraphicsItem.hoverEnterEvent(self, event)
    # end def
    
    def hoverLeaveEvent(self, event):
        pass