def __init__(self, part,\ controller=None,\ parent=None): super(PathHelixGroup, self).__init__(parent) # Subviews, GraphicsItem business self.rect = QRectF() # Set by _set_pathHelixList self._label=None; self.label() # Poke the cache so the label actually exists # Properties self._pathHelixList = [] # Primary property self._part = None; self.setPart(part) self._controller = controller self._activeSliceHandle = ActiveSliceHandle(self) self._stapColor = QColor(0, 72, 0) self._stapPen = QPen(self._stapColor, 2) self.activeHelix = None self.loopHandleGroup = LoopHandleGroup(parent=self) self.xoverGet = XoverHandle() self.setZValue(styles.ZPATHHELIXGROUP) self.phhSelectionGroup = SelectionItemGroup(\ boxtype=PathHelixHandleSelectionBox,\ constraint='y',\ parent=self) self.selectionLock = None app().phg = self # Convenience for the command line -i mode
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 _set_pathHelixList self._label=None; self.label() # Poke the cache so the label actually exists # Properties self._pathHelixList = [] # Primary property self._part = None; self.setPart(part) self._controller = controller self._activeSliceHandle = ActiveSliceHandle(self) self._stapColor = QColor(0, 72, 0) self._stapPen = QPen(self._stapColor, 2) self.activeHelix = None self.loopHandleGroup = LoopHandleGroup(parent=self) self.xoverGet = XoverHandle() self.setZValue(styles.ZPATHHELIXGROUP) self.phhSelectionGroup = SelectionItemGroup(\ boxtype=PathHelixHandleSelectionBox,\ constraint='y',\ parent=self) self.selectionLock = None app().phg = self # Convenience for the command line -i mode def __str__(self): return "I am a PHG!" def part(self): return self._part def activeTool(self): return controller().activeTool() def activeHelix(self): return self.activeHelix def setActiveHelix(self, newActivePH): neighborVHs = newActivePH.vhelix().neighbors() for ph in self._pathHelixList: showHandles = ph==newActivePH or ph.vhelix() in neighborVHs ph.setPreXOverHandlesVisible(showHandles) def setPart(self, newPart): if self._part: self._part.selectionWillChange.disconnect(self.selectionWillChange) newPart.selectionWillChange.connect(self.selectionWillChange) 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("Times", 30, QFont.Bold) label = QGraphicsTextItem("Part 1") label.setVisible(False) label.setFont(font) label.setParentItem(self) label.setPos(0, -40) label.setTextInteractionFlags(Qt.TextEditorInteraction) label.inputMethodEvent = None self._label = label return label def displayedVHs(self): """Returns the list (ordered top to bottom) of VirtualHelix that the receiver is displaying""" return [ph.vhelix() for ph in self._pathHelixList] 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)""" assert(self.part()) # Can't display VirtualHelix that aren't there! new_pathHelixList = [] vhToPH = dict(((ph.vhelix(), ph) for ph in self._pathHelixList)) 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) self._set_pathHelixList(new_pathHelixList) self.displayedVHsChanged.emit() def __pathHelixList(self): return self._pathHelixList def _set_pathHelixList(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._pathHelixList: if not ph in newList: ph.handle().setParentItem(None) ph.setParentItem(None) 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() 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 # end for self.prepareGeometryChange() self.geometryChanged.emit() self.rect = QRectF(leftmostExtent,\ -40,\ -leftmostExtent + rightmostExtent,\ y + 40) self._pathHelixList = newList self.vhToPathHelix = dict(((ph.vhelix(), ph) for ph in newList)) self.scene().views()[0].zoomToFit() def paint(self, painter, option, widget=None): # painter.save() painter.setBrush(self._nobrush) self.drawXovers(painter) # painter.restore() def drawXovers(self, painter): """Return a QPainterPath ready to paint the crossovers""" for ph in self._pathHelixList: painter.setPen(self._scafPen) for ((fromhelix, fromindex), (tohelix, toindex)) in \ ph.vhelix().get3PrimeXovers(StrandType.Scaffold): path = self.xoverGet.getXover(self,\ StrandType.Scaffold,\ ph,\ fromindex,\ self.getPathHelix(tohelix),\ toindex) painter.drawPath(path) # end for painter.setPen(self._stapPen) for ((fromhelix, fromindex), (tohelix, toindex)) in \ ph.vhelix().get3PrimeXovers(StrandType.Staple): path = self.xoverGet.getXover(self,\ StrandType.Staple,\ ph,\ fromindex,\ self.getPathHelix(tohelix),\ toindex) color = ph.vhelix().colorOfBase(StrandType.Staple, fromindex) self._stapPen.setColor(QColor(color)) painter.drawPath(path) # end for # end for # end def geometryChanged = pyqtSignal() def boundingRect(self): # rect set only by _set_pathHelixList 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 zoomToFit(self): # Auto zoom to center the scene thescene = self.scene() theview = thescene.views()[0] theview.zoomToFit() @pyqtSlot(int) def helixAddedSlot(self, vhref): vhs = self.displayedVHs() if vhref in vhs: return vhs.append(vhref) self.setDisplayedVHs(vhs) @pyqtSlot(int) def helixRemovedSlot(self, vh): vhs = self.displayedVHs() if not vhref in vhs: return vhs.remove(vh) self.setDisplayedVHs(vh) # 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.""" vh = self.part().getVirtualHelix(vhref) for ph in self._pathHelixList: if ph.vhelix() == vh: return ph return None 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. """ vhs = self.displayedVHs() vhsToMove = vhs[first:last] del vhs[first:last] self.setDisplayedVHs(vhs[0:first + indexDelta] +\ vhsToMove + vhs[first + indexDelta:-1])