def drawRow(row, sx, sy, last_end=False): x = sx y = sy keys = row rw = w - sx i = 0 for k in keys: rect = QRectF(x, y, kw, kw) if i == len(keys) - 1 and last_end: rect.setWidth(rw) p.drawRoundedRect(rect, rx, rx) rect.adjust(5, 1, 0, 0) p.setPen(QColor(0xff, 0xff, 0xff)) p.setFont(self.lowerFont) p.drawText(rect, Qt.AlignLeft | Qt.AlignBottom, self.regular_text(k)) p.setPen(QColor(0x9e, 0xde, 0x00)) p.setFont(self.upperFont) p.drawText(rect, Qt.AlignLeft | Qt.AlignTop, self.shift_text(k)) rw = rw - space - kw x = x + space + kw i = i + 1 p.setPen(pen) return (x, rw)
def drawRow(row, sx, sy, last_end=False): x = sx y = sy keys = row rw = w - sx i = 0 for k in keys: rect = QRectF(x, y, kw, kw) if i == len(keys) - 1 and last_end: rect.setWidth(rw) p.drawRoundedRect(rect, rx, rx) p.setPen(Qt.black) rect.adjust(5, 1, 0, 0) p.setFont(self.lowerFont) p.drawText(rect, Qt.AlignLeft | Qt.AlignBottom, self.regular_text(k)) p.setFont(self.upperFont) p.drawText(rect, Qt.AlignLeft | Qt.AlignTop, self.shift_text(k)) rw = rw - space - kw x = x + space + kw i = i + 1 p.setPen(pen) return (x, rw)
def draw_text(self, txt, txtlen, window): if ((txtlen>0) and not ((txt == self.cursor_char) and (self._cursor_visible == False))): # If there IS something to print if (self.pbuffer_painter[window.id] == None): self.brush.setColor(self.ztoq_color[self.cur_bg]) self.pbuffer_painter[window.id] = QPainter(self.pbuffer[window.id]) self.pbuffer_painter[window.id].setPen(self.ztoq_color[self.cur_fg]) self.pbuffer_painter[window.id].setBackground(self.brush) painter = self.pbuffer_painter[window.id] # @type window ZWindow if (window.cursor == None): if (window.id == 0): # Main window window.set_cursor_position(1, self.height) window.set_cursor_real_position(2, self.height*(self.linesize-1)) else: window.set_cursor_position(1, 1) window.set_cursor_real_position(2, self.linesize-1) if (txt=='\n'): if (window.cursor[1]==self.height): if (window.scrolling): self.scroll(painter) window.set_cursor_position(1, window.cursor[1]) window.set_cursor_real_position(2, window.cursor_real_pos[1]) else: window.set_cursor_position(1, window.cursor[1]+1) window.set_cursor_real_position(2, window.cursor_real_pos[1]+self.linesize) else: rect = QRectF(window.cursor_real_pos[0], window.cursor_real_pos[1], self.pbuffer[window.id].width()-window.cursor_real_pos[0], self.linesize) painter.setFont(self.font()) #painter.setRenderHint(QPainter.TextAntialiasing) if (self._input_buffer_printing == False): painter.setBackgroundMode(Qt.OpaqueMode) else: painter.setBackgroundMode(Qt.TransparentMode) bounding_rect = painter.boundingRect(rect,txt) if (rect.contains(bounding_rect)): #print rect.x(), rect.y(), rect.width(),rect.height(), txt, bounding_rect painter.drawText(bounding_rect, txt) if txt != self.cursor_char: window.set_cursor_position(window.cursor[0]+txtlen, window.cursor[1]) window.set_cursor_real_position(rect.x()+bounding_rect.width(), rect.y()) else: # There is not enough space #print "Not enough space to print:", txt self.scroll(painter) window.set_cursor_position(1, self.height) window.set_cursor_real_position(2, self.height*(self.linesize-1)) rect.setX(2) rect.setY(window.cursor_real_pos[1]) rect.setWidth(self.pbuffer[window.id].width()-window.cursor_real_pos[0]) rect.setHeight(self.linesize) bounding_rect = painter.boundingRect(rect,txt) painter.drawText(bounding_rect, txt) if txt != self.cursor_char: window.set_cursor_position(window.cursor[0]+txtlen, window.cursor[1]) window.set_cursor_real_position(rect.x()+bounding_rect.width(), rect.y())
def clean_input_buffer_from_screen(self): rect = QRectF() rect.setX(self.lastwindow.cursor_real_pos[0]) rect.setY(self.lastwindow.cursor_real_pos[1]) rect.setWidth(self.pbuffer[0].width()-self.lastwindow.cursor_real_pos[0]+1) rect.setHeight(self.linesize) txtbuffer = '' for w in self.input_buf: txtbuffer += w bounding_rect = self.pbuffer_painter[0].boundingRect(rect, txtbuffer) if (rect.contains(bounding_rect)): # string fits in this line self.pbuffer_painter[0].eraseRect(bounding_rect) #self.pbuffer_painter.drawRect(bounding_rect) #print 'Erasing rect', bounding_rect else: self.pbuffer_painter[0].eraseRect(rect)
class CanvasPreviewFrame(QFrame): def __init__(self, parent): QFrame.__init__(self, parent) self.fUseCustomPaint = False self.fMouseDown = False self.fViewBg = QColor(0, 0, 0) self.fViewBrush = QBrush(QColor(75, 75, 255, 30)) self.fViewPen = QPen(Qt.blue, 1) self.fScale = 1.0 self.fScene = None self.fRealParent = None self.fFakeWidth = 0.0 self.fFakeHeight = 0.0 self.fRenderSource = self.getRenderSource() self.fRenderTarget = QRectF(0, 0, 0, 0) self.fViewPadX = 0.0 self.fViewPadY = 0.0 self.fViewRect = [0.0, 0.0, 10.0, 10.0] def init(self, scene, realWidth, realHeight, useCustomPaint = False): padding = 6 self.fScene = scene self.fFakeWidth = float(realWidth) / 15 self.fFakeHeight = float(realHeight) / 15 self.setMinimumSize(self.fFakeWidth+padding, self.fFakeHeight+padding) self.setMaximumSize(self.fFakeWidth*4+padding, self.fFakeHeight+padding) self.fRenderTarget.setWidth(realWidth) self.fRenderTarget.setHeight(realHeight) if self.fUseCustomPaint != useCustomPaint: self.fUseCustomPaint = useCustomPaint self.repaint() def setRealParent(self, parent): self.fRealParent = parent def getRenderSource(self): xPadding = (float(self.width()) - self.fFakeWidth) / 2.0 yPadding = (float(self.height()) - self.fFakeHeight) / 2.0 return QRectF(xPadding, yPadding, self.fFakeWidth, self.fFakeHeight) def setViewPosX(self, xp): x = self.fFakeWidth*xp xRatio = (x / self.fFakeWidth) * self.fViewRect[iWidth] / self.fScale self.fViewRect[iX] = x - xRatio + self.fRenderSource.x() self.update() def setViewPosY(self, yp): y = self.fFakeHeight*yp yRatio = (y / self.fFakeHeight) * self.fViewRect[iHeight] / self.fScale self.fViewRect[iY] = y - yRatio + self.fRenderSource.y() self.update() def setViewScale(self, scale): self.fScale = scale QTimer.singleShot(0, self.fRealParent, SLOT("slot_miniCanvasCheckAll()")) def setViewSize(self, width, height): self.fViewRect[iWidth] = width * self.fFakeWidth self.fViewRect[iHeight] = height * self.fFakeHeight self.update() def setViewTheme(self, bgColor, brushColor, penColor): brushColor.setAlpha(40) penColor.setAlpha(100) self.fViewBg = bgColor self.fViewBrush = QBrush(brushColor) self.fViewPen = QPen(penColor, 1) def handleMouseEvent(self, eventX, eventY): x = float(eventX) - self.fRenderSource.x() - (self.fViewRect[iWidth] / self.fScale / 2) y = float(eventY) - self.fRenderSource.y() - (self.fViewRect[iHeight] / self.fScale / 2) maxWidth = self.fViewRect[iWidth] / self.fScale maxHeight = self.fViewRect[iHeight] / self.fScale if maxWidth > self.fFakeWidth: maxWidth = self.fFakeWidth if maxHeight > self.fFakeHeight: maxHeight = self.fFakeHeight if x < 0.0: x = 0.0 elif x > self.fFakeWidth - maxWidth: x = self.fFakeWidth - maxWidth if y < 0.0: y = 0.0 elif y > self.fFakeHeight - maxHeight: y = self.fFakeHeight - maxHeight self.fViewRect[iX] = x + self.fRenderSource.x() self.fViewRect[iY] = y + self.fRenderSource.y() self.update() self.emit(SIGNAL("miniCanvasMoved(double, double)"), x * self.fScale / self.fFakeWidth, y * self.fScale / self.fFakeHeight) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.fMouseDown = True self.setCursor(QCursor(Qt.SizeAllCursor)) self.handleMouseEvent(event.x(), event.y()) event.accept() def mouseMoveEvent(self, event): if self.fMouseDown: self.handleMouseEvent(event.x(), event.y()) event.accept() def mouseReleaseEvent(self, event): if self.fMouseDown: self.setCursor(QCursor(Qt.ArrowCursor)) self.fMouseDown = False QFrame.mouseReleaseEvent(self, event) def paintEvent(self, event): painter = QPainter(self) if self.fUseCustomPaint: painter.setBrush(self.fViewBg) painter.setPen(QColor(12, 12, 12)) painter.drawRect(0, 0, self.width(), self.height()-2) painter.setBrush(QColor(36, 36, 36)) painter.setPen(QColor(62, 62, 62)) painter.drawRect(1, 1, self.width()-2, self.height()-4) painter.setBrush(self.fViewBg) painter.setPen(self.fViewBg) painter.drawRect(2, 3, self.width()-5, self.height()-7) else: painter.setBrush(self.fViewBg) painter.setPen(self.fViewBg) painter.drawRoundRect(2, 2, self.width()-6, self.height()-6, 3, 3) self.fScene.render(painter, self.fRenderSource, self.fRenderTarget, Qt.KeepAspectRatio) maxWidth = self.fViewRect[iWidth] / self.fScale maxHeight = self.fViewRect[iHeight] / self.fScale if maxWidth > self.fFakeWidth: maxWidth = self.fFakeWidth if maxHeight > self.fFakeHeight: maxHeight = self.fFakeHeight painter.setBrush(self.fViewBrush) painter.setPen(self.fViewPen) painter.drawRect(self.fViewRect[iX], self.fViewRect[iY], maxWidth, maxHeight) if self.fUseCustomPaint: event.accept() else: QFrame.paintEvent(self, event) def resizeEvent(self, event): self.fRenderSource = self.getRenderSource() if self.fRealParent: QTimer.singleShot(0, self.fRealParent, SLOT("slot_miniCanvasCheckAll()")) QFrame.resizeEvent(self, event)
class CanvasPreviewFrame(QFrame): def __init__(self, parent): QFrame.__init__(self, parent) self.m_mouseDown = False self.scale = 1.0 self.scene = None self.real_parent = None self.fake_width = 0 self.fake_height = 0 self.render_source = self.getRenderSource() self.render_target = QRectF(0, 0, 0, 0) self.view_pad_x = 0.0 self.view_pad_y = 0.0 self.view_rect = [0.0, 0.0, 10.0, 10.0] def init(self, scene, real_width, real_height): self.scene = scene self.fake_width = float(real_width) / 15 self.fake_height = float(real_height) / 15 self.setMinimumSize(self.fake_width / 2, self.fake_height) self.setMaximumSize(self.fake_width * 4, self.fake_height) self.render_target.setWidth(real_width) self.render_target.setHeight(real_height) def getRenderSource(self): x_pad = (self.width() - self.fake_width) / 2 y_pad = (self.height() - self.fake_height) / 2 return QRectF(x_pad, y_pad, self.fake_width, self.fake_height) def setViewPosX(self, xp): x = xp * self.fake_width x_ratio = (x / self.fake_width) * self.view_rect[iWidth] / self.scale self.view_rect[iX] = x - x_ratio + self.render_source.x() self.update() def setViewPosY(self, yp): y = yp * self.fake_height y_ratio = (y / self.fake_height) * self.view_rect[iHeight] / self.scale self.view_rect[iY] = y - y_ratio + self.render_source.y() self.update() def setViewScale(self, scale): self.scale = scale QTimer.singleShot(0, self.real_parent, SLOT("slot_miniCanvasCheckAll()")) def setViewSize(self, width_p, height_p): width = width_p * self.fake_width height = height_p * self.fake_height self.view_rect[iWidth] = width self.view_rect[iHeight] = height self.update() def setRealParent(self, parent): self.real_parent = parent def handleMouseEvent(self, event_x, event_y): x = float(event_x) - self.render_source.x() - (self.view_rect[iWidth] / self.scale / 2) y = float(event_y) - self.render_source.y() - (self.view_rect[iHeight] / self.scale / 2) max_width = self.view_rect[iWidth] / self.scale max_height = self.view_rect[iHeight] / self.scale if max_width > self.fake_width: max_width = self.fake_width if max_height > self.fake_height: max_height = self.fake_height if x < 0.0: x = 0.0 elif x > self.fake_width - max_width: x = self.fake_width - max_width if y < 0.0: y = 0.0 elif y > self.fake_height - max_height: y = self.fake_height - max_height self.view_rect[iX] = x + self.render_source.x() self.view_rect[iY] = y + self.render_source.y() self.update() self.emit( SIGNAL("miniCanvasMoved(double, double)"), x * self.scale / self.fake_width, y * self.scale / self.fake_height, ) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.m_mouseDown = True self.setCursor(QCursor(Qt.SizeAllCursor)) self.handleMouseEvent(event.x(), event.y()) event.accept() def mouseMoveEvent(self, event): if self.m_mouseDown: self.handleMouseEvent(event.x(), event.y()) event.accept() def mouseReleaseEvent(self, event): if self.m_mouseDown: self.setCursor(QCursor(Qt.ArrowCursor)) self.m_mouseDown = False QFrame.mouseReleaseEvent(self, event) def paintEvent(self, event): painter = QPainter(self) painter.setBrush(QBrush(Qt.darkBlue, Qt.DiagCrossPattern)) painter.drawRect(0, 0, self.width(), self.height()) self.scene.render(painter, self.render_source, self.render_target, Qt.KeepAspectRatio) max_width = self.view_rect[iWidth] / self.scale max_height = self.view_rect[iHeight] / self.scale if max_width > self.fake_width: max_width = self.fake_width if max_height > self.fake_height: max_height = self.fake_height painter.setBrush(QBrush(QColor(75, 75, 255, 30))) painter.setPen(QPen(Qt.blue, 2)) painter.drawRect(self.view_rect[iX], self.view_rect[iY], max_width, max_height) QFrame.paintEvent(self, event) def resizeEvent(self, event): self.render_source = self.getRenderSource() if self.real_parent: QTimer.singleShot(0, self.real_parent, SLOT("slot_miniCanvasCheckAll()")) QFrame.resizeEvent(self, event)
def setWidth(self,w): QRectF.setWidth(self,w)
class PathHelix(QGraphicsItem): """ PathHelix is the primary "view" of the VirtualHelix data. It manages the ui interactions from the user, such as dragging breakpoints or crossovers addition/removal, and updates the data model accordingly. parent should be set to... """ minorGridPen = QPen(styles.minorgridstroke, styles.MINOR_GRID_STROKE_WIDTH) minorGridPen.setCosmetic(True) majorGridPen = QPen(styles.majorgridstroke, styles.MAJOR_GRID_STROKE_WIDTH) majorGridPen.setCosmetic(True) scafPen = QPen(styles.scafstroke, 2) nobrush = QBrush(Qt.NoBrush) baseWidth = styles.PATH_BASE_WIDTH def __init__(self, vhelix, pathHelixGroup): super(PathHelix, self).__init__() self.setAcceptHoverEvents(True) # for pathtools self._pathHelixGroup = pathHelixGroup self._scafBreakpointHandles = [] self._stapBreakpointHandles = [] self._scafXoverHandles = [] self._stapXoverHandles = [] self._preXOverHandles = None self._segmentPaths = None self._loopPaths = None self._minorGridPainterPath = None self._majorGridPainterPath = None self.step = 21 # 32 for Square lattice self.setZValue(styles.ZPATHHELIX) self.rect = QRectF() self._vhelix = None self._handle = None self._mouseDownBase = None self._skipitem = SkipItem() self._loopitem = LoopItem() self.setVHelix(vhelix) if app().ph != None: # Convenience for the command line -i mode app().ph[vhelix.number()] = self # end def def activeTool(self): return self.controller().activeTool() def controller(self): return self._pathHelixGroup.controller() def vhelix(self): return self._vhelix def undoStack(self): return self.vhelix().undoStack() def setVHelix(self, newVH): if self._vhelix: self._vhelix.basesModified.disconnect(self.vhelixBasesModified) self._vhelix.vhelixDimensionsModified.disconnect(\ self.vhelixDimensionsModified) self._vhelix = newVH newVH.basesModified.connect(self.vhelixBasesModified) newVH.dimensionsModified.connect(self.vhelixDimensionsModified) self.vhelixDimensionsModified() self.vhelixBasesModified() def handle(self): if self._handle: return self._handle self._handle = PathHelixHandle(self.vhelix(),parent=self._pathHelixGroup) return self._handle def number(self): return self._vhelix.number() def row(self): return self._vhelix.row() def col(self): return self._vhelix.col() def evenParity(self): return self._vhelix.evenParity() def vhelixDimensionsModified(self): """Sets rect width to reflect number of bases in vhelix. Sets rect height to the width of two bases (one for scaffold and one for staple)""" canvasSize = self._vhelix.part().numBases() self.prepareGeometryChange() self.rect.setWidth(self.baseWidth * canvasSize) self.rect.setHeight(2 * self.baseWidth) self._minorGridPainterPath = None self._majorGridPainterPath = None def boundingRect(self): return self.rect ################# Crossover Handles ################# def preXOverHandlesVisible(self): return self._preXOverHandles!=None def setPreXOverHandlesVisible(self, shouldBeVisible): areVisible = self._preXOverHandles!=None if areVisible and not shouldBeVisible: for pch in self._preXOverHandles: pch.setParentItem(None) self._preXOverHandles = None elif not areVisible and shouldBeVisible: self._preXOverHandles = handles = [] for strandType, facingRight in product((StrandType.Scaffold, StrandType.Staple), (True, False)): # Get potential crossovers in [neighborVirtualHelix, index] format potentialXOvers = self.vhelix().potentialCrossoverList(facingRight, strandType) for (neighborVH, fromIdx) in potentialXOvers: pch = PreCrossoverHandle(self, strandType, fromIdx, neighborVH, fromIdx, not facingRight) handles.append(pch) def makeSelfActiveHelix(self): self._pathHelixGroup.setActiveHelix(self) ################# Loading and Updating State From VHelix ################# def vhelixBasesModified(self): self._endpoints = None # Clear endpoint drawing cache self._segmentPaths = None # Clear drawing cache of lines self._loopPaths = None self.update() ############################# Drawing ########################## def paint(self, painter, option, widget=None): # Note that the methods that fetch the paths # cache the paths and that those caches are # invalidated as the primary mechanism # of updating after a change in vhelix's bases painter.save() painter.setBrush(self.nobrush) painter.setPen(self.minorGridPen) painter.drawPath(self.minorGridPainterPath()) # Minor grid lines painter.setPen(self.majorGridPen) painter.drawPath(self.majorGridPainterPath()) # Major grid lines painter.setBrush(Qt.NoBrush) for paintCommand in self.segmentPaths(): painter.setPen(paintCommand[0]) painter.drawPath(paintCommand[1]) painter.setPen(Qt.NoPen) for paintCommand in self.segmentPaths(): painter.setBrush(paintCommand[2]) painter.drawPath(paintCommand[3]) # Now draw loops and skips painter.setBrush(Qt.NoBrush) for paintCommand in self.loopPaths(): painter.setPen(paintCommand[0]) painter.drawPath(paintCommand[1]) painter.setPen(paintCommand[2]) painter.drawPath(paintCommand[3]) painter.restore() def minorGridPainterPath(self): """ Returns a QPainterPath object for the minor grid lines. The path also includes a border outline and a midline for dividing scaffold and staple bases. """ if self._minorGridPainterPath: return self._minorGridPainterPath path = QPainterPath() canvasSize = self._vhelix.part().numBases() # border path.addRect(0, 0, self.baseWidth * canvasSize, 2 * self.baseWidth) # minor tick marks for i in range(canvasSize): if (i % 7 != 0): x = round(self.baseWidth * i) + .5 path.moveTo(x, 0) path.lineTo(x, 2 * self.baseWidth) # staple-scaffold divider path.moveTo(0, self.baseWidth) path.lineTo(self.baseWidth * canvasSize, self.baseWidth) self._minorGridPainterPath = path return path def majorGridPainterPath(self): """ Returns a QPainterPath object for the major grid lines. This is separated from the minor grid lines so different pens can be used for each. """ if self._majorGridPainterPath: return self._majorGridPainterPath path = QPainterPath() canvasSize = self._vhelix.part().numBases() # major tick marks FIX: 7 is honeycomb-specific for i in range(0, canvasSize + 1, 7): x = round(self.baseWidth * i) + .5 path.moveTo(x, .5) path.lineTo(x, 2 * self.baseWidth - .5) self._majorGridPainterPath = path return path def segmentPaths(self): """Returns an array of (pen, penPainterPath, brush, brushPainterPath) for drawing segment lines and handles.""" if self._segmentPaths: return self._segmentPaths self._segmentPaths = [] vh = self.vhelix() for strandType in (StrandType.Scaffold, StrandType.Staple): top = self.strandIsTop(strandType) for [startIndex, startIsXO, endIndex, endIsXO] in\ self._vhelix.getSegments(strandType): # Left and right centers for drawing the connecting line c1 = self.baseLocation(strandType, startIndex, center=True) c2 = self.baseLocation(strandType, endIndex, center=True) # Upper left corners for translating the breakpoint handles ul1 = self.baseLocation(strandType, startIndex) ul2 = self.baseLocation(strandType, endIndex) # Now we construct the path to cache bp = QPainterPath() if not startIsXO: bp.addPath(ppL5.translated(*ul1) if top else\ ppL3.translated(*ul1)) if not endIsXO: bp.addPath(ppR3.translated(*ul2) if top else\ ppR5.translated(*ul2)) pp = QPainterPath() pp.moveTo(*c1) pp.lineTo(*c2) # Now we combine pen/brush information and push it to the cache # _segmentPaths entries take the form: # (pen, painterPathToBeDrawnOnlyWithPen,\ # brush, paintPathToBeDrawnOnlyWithBrush) color = vh.colorOfBase(strandType, startIndex) width = styles.PATH_STRAND_STROKE_WIDTH pen = QPen(color, width) brush = QBrush(color) self._segmentPaths.append((pen, pp, brush, bp)) return self._segmentPaths def loopPaths(self): """ Returns an array of: (loopPen, loopPainterPath, skipPen, skipPainterPath) for drawing loops and skips """ if self._loopPaths: return self._loopPaths self._loopPaths = [] vh = self.vhelix() lpen = self._loopitem.getPen() spen = self._skipitem.getPen() for strandType in (StrandType.Scaffold, StrandType.Staple): top = self.strandIsTop(strandType) lp = QPainterPath() sp = QPainterPath() count = len(vh._loop(strandType)) if count > 0: for index, loopsize in vh._loop(strandType).iteritems(): ul = self.baseLocation(strandType, index) if loopsize > 0: path = self._loopitem.getLoop(top) lp.addPath(path.translated(*ul)) else: path = self._skipitem.getSkip() sp.addPath(path.translated(*ul)) # end for # end if self._loopPaths.append((lpen, lp, spen, sp)) # end for return self._loopPaths # end def def strandIsTop(self, strandType): return self.evenParity() and strandType == StrandType.Scaffold\ or not self.evenParity() and strandType == StrandType.Staple def baseAtLocation(self, x, y): """Returns the (strandType, index) under the location x,y or None. It shouldn't be possible to click outside a pathhelix and still call this function. However, this sometimes happens if you click exactly on the top or bottom edge, resulting in a negative y value. """ baseIdx = int(floor(x / self.baseWidth)) if baseIdx < 0 or baseIdx >= self.vhelix().numBases(): return None if y < 0: y = 0 # HACK: zero out y due to erroneous click strandIdx = floor(y * 1. / self.baseWidth) if strandIdx < 0 or strandIdx > 1: return None if self.strandIsTop(StrandType.Scaffold): strands = StrandType.Scaffold, StrandType.Staple else: strands = StrandType.Staple, StrandType.Scaffold return (strands[int(strandIdx)], baseIdx) def baseLocation(self, strandType, baseIdx, center=False): """Returns the coordinates of the upper left corner of the base referenced by strandType and baseIdx. If center=True, returns the center of the base instead of the upper left corner.""" if self.strandIsTop(strandType): y = 0 else: y = self.baseWidth x = baseIdx * self.baseWidth if center: x += self.baseWidth / 2 y += self.baseWidth / 2 return (x, y)
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) layers = list(reversed(layers)) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] self.organizeLayer(layer, connection_map) for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes if not None in (min_, max): if direction == Qt.Vertical: off_x = (max_ - min_) / 2.0 - node.rect().width() / 2.0 point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_) / 2.0 - node.rect().height() / 2.0 point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y point_x = point.x() + off_x point_y = point.y() + off_y if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(1) return processed_nodes
class CanvasPreviewFrame(QFrame): def __init__(self, parent): QFrame.__init__(self, parent) self.fUseCustomPaint = False self.fMouseDown = False self.fViewBg = QColor(0, 0, 0) self.fViewBrush = QBrush(QColor(75, 75, 255, 30)) self.fViewPen = QPen(Qt.blue, 1) self.fScale = 1.0 self.fScene = None self.fRealParent = None self.fFakeWidth = 0.0 self.fFakeHeight = 0.0 self.fRenderSource = self.getRenderSource() self.fRenderTarget = QRectF(0, 0, 0, 0) self.fViewPadX = 0.0 self.fViewPadY = 0.0 self.fViewRect = [0.0, 0.0, 10.0, 10.0] def init(self, scene, realWidth, realHeight, useCustomPaint=False): padding = 6 self.fScene = scene self.fFakeWidth = float(realWidth) / 15 self.fFakeHeight = float(realHeight) / 15 self.setMinimumSize(self.fFakeWidth + padding, self.fFakeHeight + padding) self.setMaximumSize(self.fFakeWidth * 4 + padding, self.fFakeHeight + padding) self.fRenderTarget.setWidth(realWidth) self.fRenderTarget.setHeight(realHeight) if self.fUseCustomPaint != useCustomPaint: self.fUseCustomPaint = useCustomPaint self.repaint() def setRealParent(self, parent): self.fRealParent = parent def getRenderSource(self): xPadding = (float(self.width()) - self.fFakeWidth) / 2.0 yPadding = (float(self.height()) - self.fFakeHeight) / 2.0 return QRectF(xPadding, yPadding, self.fFakeWidth, self.fFakeHeight) def setViewPosX(self, xp): x = self.fFakeWidth * xp xRatio = (x / self.fFakeWidth) * self.fViewRect[iWidth] / self.fScale self.fViewRect[iX] = x - xRatio + self.fRenderSource.x() self.update() def setViewPosY(self, yp): y = self.fFakeHeight * yp yRatio = (y / self.fFakeHeight) * self.fViewRect[iHeight] / self.fScale self.fViewRect[iY] = y - yRatio + self.fRenderSource.y() self.update() def setViewScale(self, scale): self.fScale = scale QTimer.singleShot(0, self.fRealParent, SLOT("slot_miniCanvasCheckAll()")) def setViewSize(self, width, height): self.fViewRect[iWidth] = width * self.fFakeWidth self.fViewRect[iHeight] = height * self.fFakeHeight self.update() def setViewTheme(self, bgColor, brushColor, penColor): brushColor.setAlpha(40) penColor.setAlpha(100) self.fViewBg = bgColor self.fViewBrush = QBrush(brushColor) self.fViewPen = QPen(penColor, 1) def handleMouseEvent(self, eventX, eventY): x = float(eventX) - self.fRenderSource.x() - (self.fViewRect[iWidth] / self.fScale / 2) y = float(eventY) - self.fRenderSource.y() - (self.fViewRect[iHeight] / self.fScale / 2) maxWidth = self.fViewRect[iWidth] / self.fScale maxHeight = self.fViewRect[iHeight] / self.fScale if maxWidth > self.fFakeWidth: maxWidth = self.fFakeWidth if maxHeight > self.fFakeHeight: maxHeight = self.fFakeHeight if x < 0.0: x = 0.0 elif x > self.fFakeWidth - maxWidth: x = self.fFakeWidth - maxWidth if y < 0.0: y = 0.0 elif y > self.fFakeHeight - maxHeight: y = self.fFakeHeight - maxHeight self.fViewRect[iX] = x + self.fRenderSource.x() self.fViewRect[iY] = y + self.fRenderSource.y() self.update() self.emit(SIGNAL("miniCanvasMoved(double, double)"), x * self.fScale / self.fFakeWidth, y * self.fScale / self.fFakeHeight) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.fMouseDown = True self.setCursor(QCursor(Qt.SizeAllCursor)) self.handleMouseEvent(event.x(), event.y()) event.accept() def mouseMoveEvent(self, event): if self.fMouseDown: self.handleMouseEvent(event.x(), event.y()) event.accept() def mouseReleaseEvent(self, event): if self.fMouseDown: self.setCursor(QCursor(Qt.ArrowCursor)) self.fMouseDown = False QFrame.mouseReleaseEvent(self, event) def paintEvent(self, event): painter = QPainter(self) if self.fUseCustomPaint: painter.setBrush(self.fViewBg) painter.setPen(QColor(12, 12, 12)) painter.drawRect(0, 0, self.width(), self.height() - 2) painter.setBrush(QColor(36, 36, 36)) painter.setPen(QColor(62, 62, 62)) painter.drawRect(1, 1, self.width() - 2, self.height() - 4) painter.setBrush(self.fViewBg) painter.setPen(self.fViewBg) painter.drawRect(2, 3, self.width() - 5, self.height() - 7) else: painter.setBrush(self.fViewBg) painter.setPen(self.fViewBg) painter.drawRoundRect(2, 2, self.width() - 6, self.height() - 6, 3, 3) self.fScene.render(painter, self.fRenderSource, self.fRenderTarget, Qt.KeepAspectRatio) maxWidth = self.fViewRect[iWidth] / self.fScale maxHeight = self.fViewRect[iHeight] / self.fScale if maxWidth > self.fFakeWidth: maxWidth = self.fFakeWidth if maxHeight > self.fFakeHeight: maxHeight = self.fFakeHeight painter.setBrush(self.fViewBrush) painter.setPen(self.fViewPen) painter.drawRect(self.fViewRect[iX], self.fViewRect[iY], maxWidth, maxHeight) if self.fUseCustomPaint: event.accept() else: QFrame.paintEvent(self, event) def resizeEvent(self, event): self.fRenderSource = self.getRenderSource() if self.fRealParent: QTimer.singleShot(0, self.fRealParent, SLOT("slot_miniCanvasCheckAll()")) QFrame.resizeEvent(self, event)