class Meterbar(Button): def __init__(self, max=255, filename=None, width=None, height=None, x=0, y=0, pixmap=None, group=None, pos=None, size=None, padding=None): Button.__init__(self, filename, width, height, x, y, pixmap, group, pos, size, padding) self.max = max self.outer = QGraphicsRectItem(x,y,width,max + 2) self.outer.setPen(QPen(QColor(Qt.black), 1, Qt.SolidLine)) self.outer.setBrush(Qt.green) # self.outer.hide() self.inner = QGraphicsRectItem(x + 1,y + 1,width - 2,max) self.inner.setPen(QPen(QColor(Qt.green), 1, Qt.SolidLine)) self.inner.setBrush(Qt.blue) self.items = [self.outer, self.inner] self.current = 255 self.effect = QGraphicsDropShadowEffect() self.effect.setOffset(0, 0) self.effect.setBlurRadius(0) self.effect.setColor(Qt.green) self.item = self.outer self.addEffect('shadow', self.effect, True) self.addAnimation('glow', Glow(15, 300, self, maxRadius=80, minRadius=5)) # self.test(10) def test(self, x): self.tl = QTimeLine(10000) self.tl.setFrameRange(0, 10000) self.a = QGraphicsItemAnimation() self.a.setItem(self.inner) self.a.setTimeLine(self.tl) # self.a.setPosAt(0, QPointF(self.getX(), self.current)) # self.a.setTranslationAt(1, self.getX(), self.getY() + self.max - x + 1) self.a.setScaleAt(0, 1, 1) self.a.setScaleAt(1, 1, 0.1) self.current = x self.tl.start() def update(self, x): x2 = 1 - (float(x) * 1.0 / float(self.max)) # print x # return self.tl = QTimeLine(10) self.tl.setFrameRange(0, 10) self.a = QGraphicsItemAnimation() self.a.setItem(self.inner) self.a.setTimeLine(self.tl) # self.a.setPosAt(0, QPointF(self.getX(), self.current)) # self.a.setTranslationAt(1, self.getX(), self.getY() + self.max - x + 1) self.a.setScaleAt(0, 1, self.current) self.a.setScaleAt(1, 1, x2) self.current = x self.tl.start() if x > 3 : self.play('glow') def setScene(self, scene): self.scene = scene for item in self.items : self.scene.addItem(item)
def scientific_name_face(node, *args, **kwargs): scientific_name_text = QGraphicsTextItem() #underscore = node.name.replace("_", " ") words = node.name.split("_") text = [] if len(words) < 2: # some sort of acronym or bin name, leave it alone text = words elif len(words) > 2: if len(words) >= 5: text.extend( ['<b>' + words[0] + ' <i> ' + words[1], words[2] + ' </i> ']) text.extend(words[3:] + ['</b>']) elif len(words) == 3: text.extend([ ' <span style="color:grey"><i> ' + words[0], words[1] + words[2] + ' </i></span>' ]) else: # assume that everything after the # second word is strain name # which should not get italicized text.extend([ ' <span style="color:grey"><i> ' + words[0], words[1] + ' </i></span>' ]) text.extend(words[2:]) else: text.extend([ ' <span style="color:grey"><i> ' + words[0], words[1] + ' </i></span> ' ]) scientific_name_text.setHtml(' '.join(text)) # below is a bit of a hack - I've found that the height of the bounding # box gives a bit too much padding around the name, so I just minus 10 # from the height and recenter it. Don't know whether this is a generally # applicable number to use #myFont = QFont() masterItem = QGraphicsRectItem( 0, 0, scientific_name_text.boundingRect().width(), scientific_name_text.boundingRect().height() - 10) scientific_name_text.setParentItem(masterItem) center = masterItem.boundingRect().center() scientific_name_text.setPos( masterItem.boundingRect().x(), center.y() - scientific_name_text.boundingRect().height() / 2) # I don't want a border around the masterItem masterItem.setPen(QPen(QtCore.Qt.NoPen)) return masterItem
def scientific_name_face(node, *args, **kwargs): scientific_name_text = QGraphicsTextItem() words = node.visual_label.split() text = [] if hasattr(node, 'bg_col'): container_div = '<div style="background-color:{};">'.format(node.bgcolor) text.append(container_div) if len(words) < 2: # some sort of acronym or bin name, leave it alone text.extend(words) elif len(words) > 2: if words[0] == 'Candidatus': # for candidatus names, only the Candidatus part is italicised # name shortening it for brevity text.append('<i>Ca.</i>') text.extend(words[1:]) elif re.match('^[A-Z]+$', words[0]): # If the first word is in all caps then it is an abreviation # so we don't want to italicize that at all text.extend(words) else: # assume that everything after the second word is strain name # which should not get italicised text.extend(['<i>'+words[0],words[1]+'</i>']) text.extend(words[2:]) else: text.extend(['<i>'+words[0],words[1]+'</i>']) if hasattr(node, 'bg_col'): text.append('</div>') scientific_name_text.setHtml(' '.join(text)) #print(scientific_name_text.boundingRect().width(), scientific_name_text.boundingRect().height()) # below is a bit of a hack - I've found that the height of the bounding # box gives a bit too much padding around the name, so I just minus 10 # from the height and recenter it. Don't know whether this is a generally # applicable number to use masterItem = QGraphicsRectItem(0, 0, scientific_name_text.boundingRect().width(), scientific_name_text.boundingRect().height() - 10) scientific_name_text.setParentItem(masterItem) center = masterItem.boundingRect().center() scientific_name_text.setPos(masterItem.boundingRect().x(), center.y() - scientific_name_text.boundingRect().height()/2) # I dont want a border around the masterItem masterItem.setPen(QPen(QtCore.Qt.NoPen)) return masterItem
class RectangleCurve(OWCurve): """ A plot item that shows a rectangle. This class accepts the same options as :obj:`.PolygonCurve`. The rectangle is calculated as the smallest rectangle that contains all points in ``xData`` and ``yData``. """ def __init__(self, pen = QPen(Qt.black), brush = QBrush(Qt.white), xData = None, yData = None, tooltip = None): OWCurve.__init__(self, xData, yData, tooltip=tooltip) self.set_pen(pen) self.set_brush(brush) self._item = QGraphicsRectItem(self) def update_properties(self): self._item.setRect(self.graph_transform().mapRect(self.data_rect())) self._item.setPen(self.pen()) self._item.setBrush(self.brush())
class GraphicsScene(QGraphicsScene): selectionRectPointChanged = Signal(QPointF) def __init__(self, *args): QGraphicsScene.__init__(self, *args) self.selectionRect = None def mousePressEvent(self, event): QGraphicsScene.mousePressEvent(self, event) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: screenPos = event.screenPos() buttonDown = event.buttonDownScreenPos(Qt.LeftButton) if (screenPos - buttonDown).manhattanLength() > 2.0: self.updateSelectionRect(event) QGraphicsScene.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.selectionRect: self.removeItem(self.selectionRect) self.selectionRect = None QGraphicsScene.mouseReleaseEvent(self, event) def updateSelectionRect(self, event): pos = event.scenePos() buttonDownPos = event.buttonDownScenePos(Qt.LeftButton) rect = QRectF(pos, buttonDownPos).normalized() rect = rect.intersected(self.sceneRect()) if not self.selectionRect: self.selectionRect = QGraphicsRectItem() self.selectionRect.setBrush(QColor(10, 10, 10, 20)) self.selectionRect.setPen(QPen(QColor(200, 200, 200, 200))) self.addItem(self.selectionRect) self.selectionRect.setRect(rect) if event.modifiers() & Qt.ControlModifier or \ event.modifiers() & Qt.ShiftModifier: path = self.selectionArea() else: path = QPainterPath() path.addRect(rect) self.setSelectionArea(path) self.selectionRectPointChanged.emit(pos)
class GraphicsScene(QGraphicsScene): selectionRectPointChanged = Signal(QPointF) def __init__(self, *args): QGraphicsScene.__init__(self, *args) self.selectionRect = None def mousePressEvent(self, event): QGraphicsScene.mousePressEvent(self, event) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: screenPos = event.screenPos() buttonDown = event.buttonDownScreenPos(Qt.LeftButton) if (screenPos - buttonDown).manhattanLength() > 2.0: self.updateSelectionRect(event) QGraphicsScene.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.selectionRect: self.removeItem(self.selectionRect) self.selectionRect = None QGraphicsScene.mouseReleaseEvent(self, event) def updateSelectionRect(self, event): pos = event.scenePos() buttonDownPos = event.buttonDownScenePos(Qt.LeftButton) rect = QRectF(pos, buttonDownPos).normalized() rect = rect.intersected(self.sceneRect()) if not self.selectionRect: self.selectionRect = QGraphicsRectItem() self.selectionRect.setBrush(QColor(10, 10, 10, 20)) self.selectionRect.setPen(QPen(QColor(200, 200, 200, 200))) self.addItem(self.selectionRect) self.selectionRect.setRect(rect) if event.modifiers() & Qt.ControlModifier or \ event.modifiers() & Qt.ShiftModifier: path = self.selectionArea() else: path = QPainterPath() path.addRect(rect) self.setSelectionArea(path) self.selectionRectPointChanged.emit(pos)
def ugly_name_face(node, *args, **kargs): """ This is my item generator. It must receive a node object, and returns a Qt4 graphics item that can be used as a node face. """ width = node.dist * 2.5 height = 12 masterItem = InteractiveItem(0, 0, width, height) masterItem.node = node masterItem.setPen(QPen(QtCore.Qt.NoPen)) color_rect = QGraphicsRectItem(masterItem.rect()) color_rect.setParentItem(masterItem) color_rect.setBrush(QBrush(QColor(100, 100, 200, 100))) color_rect.setPen(QPen(QtCore.Qt.NoPen)) masterItem.color_rect = color_rect return masterItem
class OWLegendItem(QGraphicsObject): """ Represents a legend item with a title and a point symbol. :param name: The text to display :type name: str :param point: The point symbol :type point: :obj:`.OWPoint` :param parent: The parent item, passed to QGraphicsItem :type parent: :obj:`QGraphicsItem` .. seealso:: :meth:`.OWLegend.add_item`, :meth:`.OWLegend.add_curve`. """ def __init__(self, name, point, parent): QGraphicsObject.__init__(self, parent) self.text_item = QGraphicsTextItem(name, self) if point: s = point.size() height = max(2 * s, self.text_item.boundingRect().height()) else: height = self.text_item.boundingRect().height() p = 0.5 * height self.text_item.setPos(height, 0) self.point_item = point if point: self.point_item.setParentItem(self) self.point_item.setPos(p, p) self._rect = QRectF(0, 0, height + self.text_item.boundingRect().width(), height) self.rect_item = QGraphicsRectItem(self._rect, self) self.rect_item.setPen(QPen(Qt.NoPen)) self.rect_item.stackBefore(self.text_item) if self.point_item: self.rect_item.stackBefore(self.point_item) def boundingRect(self): return self._rect def paint(self, painter, option, widget): pass
class OWLegendTitle(QGraphicsObject): """ A legend item that shows ``text`` with a bold font and no symbol. """ def __init__(self, text, parent): QGraphicsObject.__init__(self, parent) self.text_item = QGraphicsTextItem(text + ':', self) f = self.text_item.font() f.setBold(True) self.text_item.setFont(f) self.rect_item = QGraphicsRectItem(self.text_item.boundingRect(), self) self.rect_item.setPen(QPen(Qt.NoPen)) self.rect_item.stackBefore(self.text_item) def boundingRect(self): return self.text_item.boundingRect() def paint(self, painter, option, widget): pass
class OWLegendTitle(QGraphicsObject): """ A legend item that shows ``text`` with a bold font and no symbol. """ def __init__(self, text, parent): QGraphicsObject.__init__(self, parent) self.text_item = QGraphicsTextItem(text + ':', self) f = self.text_item.font() f.setBold(True) self.text_item.setFont(f) self.rect_item = QGraphicsRectItem(self.text_item.boundingRect(), self) self.rect_item.setPen(QPen(Qt.NoPen)) self.rect_item.stackBefore(self.text_item) def boundingRect(self): return self.text_item.boundingRect() def paint(self, painter, option, widget): pass
def render_drop_shadow_frame(pixmap, shadow_rect, shadow_color, offset, radius, rect_fill_color): pixmap.fill(QColor(0, 0, 0, 0)) scene = QGraphicsScene() rect = QGraphicsRectItem(shadow_rect) rect.setBrush(QColor(rect_fill_color)) rect.setPen(QPen(Qt.NoPen)) scene.addItem(rect) effect = QGraphicsDropShadowEffect(color=shadow_color, blurRadius=radius, offset=offset) rect.setGraphicsEffect(effect) scene.setSceneRect(QRectF(QPointF(0, 0), QSizeF(pixmap.size()))) painter = QPainter(pixmap) scene.render(painter) painter.end() scene.clear() scene.deleteLater() return pixmap
class OWLegendItem(QGraphicsObject): """ Represents a legend item with a title and a point symbol. :param name: The text to display :type name: str :param point: The point symbol :type point: :obj:`.OWPoint` :param parent: The parent item, passed to QGraphicsItem :type parent: :obj:`QGraphicsItem` .. seealso:: :meth:`.OWLegend.add_item`, :meth:`.OWLegend.add_curve`. """ def __init__(self, name, point, parent): QGraphicsObject.__init__(self, parent) self.text_item = QGraphicsTextItem(name, self) if point: s = point.size() height = max(2*s, self.text_item.boundingRect().height()) else: height = self.text_item.boundingRect().height() p = 0.5 * height self.text_item.setPos(height, 0) self.point_item = point if point: self.point_item.setParentItem(self) self.point_item.setPos(p, p) self._rect = QRectF(0, 0, height + self.text_item.boundingRect().width(), height ) self.rect_item = QGraphicsRectItem(self._rect, self) self.rect_item.setPen(QPen(Qt.NoPen)) self.rect_item.stackBefore(self.text_item) if self.point_item: self.rect_item.stackBefore(self.point_item) def boundingRect(self): return self._rect def paint(self, painter, option, widget): pass
class RectangleCurve(OWCurve): """ A plot item that shows a rectangle. This class accepts the same options as :obj:`.PolygonCurve`. The rectangle is calculated as the smallest rectangle that contains all points in ``xData`` and ``yData``. """ def __init__(self, pen=QPen(Qt.black), brush=QBrush(Qt.white), xData=None, yData=None, tooltip=None): OWCurve.__init__(self, xData, yData, tooltip=tooltip) self.set_pen(pen) self.set_brush(brush) self._item = QGraphicsRectItem(self) def update_properties(self): self._item.setRect(self.graph_transform().mapRect(self.data_rect())) self._item.setPen(self.pen()) self._item.setBrush(self.brush())
def trinomial_face(binom, spp, ftype="Verdana", fsize=10, fgcolor="black", fstyle="normal", bold=False): """ Stolen from: https://connorskennerton.wordpress.com/2016/11/16/python-ete3-formatting-organism-names-the-way-i-want/ """ font = QFont(ftype, fsize) font.setBold(bold) if fstyle == "italic": font.setStyle(QFont.StyleItalic) elif fstyle == "oblique": font.setStyle(QFont.StyleOblique) text = QGraphicsTextItem() text.setFont(font) if spp is None: text.setHtml("<i>{}</i>".format(binom)) else: text.setHtml("<i>{}</i> {}".format(binom, spp)) rect = QGraphicsRectItem(0, 0, text.boundingRect().width(), text.boundingRect().height() - 10) text.setParentItem(rect) center = rect.boundingRect().center() text.setPos(rect.boundingRect().x(), center.y() - text.boundingRect().height() / 2) # no border rect.setPen(QPen(QtCore.Qt.NoPen)) return ete3.faces.StaticItemFace(rect)
class OWLegendGradient(QGraphicsObject): gradient_width = 20 def __init__(self, palette, values, parent): QGraphicsObject.__init__(self, parent) self.parent = parent self.palette = palette self.values = values self.legend = parent self.label_items = [QGraphicsTextItem(text, self) for text in values] for i in self.label_items: i.setTextWidth(50) self.rect = QRectF() self.gradient_item = QGraphicsRectItem(self) self.gradient = QLinearGradient() self.gradient.setStops([(v * 0.1, self.palette[v * 0.1]) for v in range(11)]) self.orientation = Qt.Horizontal self.set_orientation(Qt.Vertical) def set_orientation(self, orientation): if self.orientation == orientation: return self.orientation = orientation if self.orientation == Qt.Vertical: height = max( [item.boundingRect().height() for item in self.label_items]) total_height = height * max(5, len(self.label_items)) interval = (total_height - self.label_items[-1].boundingRect().height()) / ( len(self.label_items) - 1) self.gradient_item.setRect(10, 0, self.gradient_width, total_height) self.gradient.setStart(10, 0) self.gradient.setFinalStop(10, total_height) self.gradient_item.setBrush(QBrush(self.gradient)) self.gradient_item.setPen(QPen(Qt.NoPen)) y = 0 x = 30 for item in self.label_items: move_item_xy(item, x, y, self.parent.graph.animate_plot) y += interval self.rect = QRectF( 10, 0, self.gradient_width + max([item.boundingRect().width() for item in self.label_items]), self.label_items[0].boundingRect().height() * max(5, len(self.label_items))) else: width = 50 height = max( [item.boundingRect().height() for item in self.label_items]) total_width = width * max(5, len(self.label_items)) interval = (total_width - self.label_items[-1].boundingRect().width()) / ( len(self.label_items) - 1) self.gradient_item.setRect(0, 0, total_width, self.gradient_width) self.gradient.setStart(0, 0) self.gradient.setFinalStop(total_width, 0) self.gradient_item.setBrush(QBrush(self.gradient)) self.gradient_item.setPen(QPen(Qt.NoPen)) x = 0 y = 30 for item in self.label_items: move_item_xy(item, x, y, self.parent.graph.animate_plot) x += interval self.rect = QRectF(0, 0, total_width, self.gradient_width + height) def boundingRect(self): return getattr(self, 'rect', QRectF()) def paint(self, painter, option, widget): pass
class GradientLegendItem(QGraphicsObject, GraphicsWidgetAnchor): gradient_width = 20 def __init__(self, title, palette, values, parent): QGraphicsObject.__init__(self, parent) GraphicsWidgetAnchor.__init__(self) self.parent = self.legend = parent self.palette = palette self.values = values self.title = QGraphicsTextItem('%s:' % title, self) f = self.title.font() f.setBold(True) self.title.setFont(f) self.title_item = QGraphicsRectItem(self.title.boundingRect(), self) self.title_item.setPen(QPen(Qt.NoPen)) self.title_item.stackBefore(self.title) self.label_items = [QGraphicsTextItem(text, self) for text in values] for i in self.label_items: i.setTextWidth(50) self.rect = QRectF() self.gradient_item = QGraphicsRectItem(self) self.gradient = QLinearGradient() self.gradient.setStops([(v * 0.1, self.palette[v * 0.1]) for v in range(11)]) self.orientation = Qt.Horizontal self.set_orientation(Qt.Vertical) self.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.setFlag(QGraphicsItem.ItemIsMovable, True) def set_orientation(self, orientation): return if self.orientation == orientation: return self.orientation = orientation if self.orientation == Qt.Vertical: height = max(item.boundingRect().height() for item in self.label_items) total_height = height * max(5, len(self.label_items)) interval = (total_height - self.label_items[-1].boundingRect().height() ) / (len(self.label_items) - 1) self.gradient_item.setRect(10, 0, self.gradient_width, total_height) self.gradient.setStart(10, 0) self.gradient.setFinalStop(10, total_height) self.gradient_item.setBrush(QBrush(self.gradient)) self.gradient_item.setPen(QPen(Qt.NoPen)) y = -20 # hja, no; dela --> pri boundingRect() zato pristejem +20 x = 0 move_item_xy(self.title, x, y, False) y = 10 x = 30 for item in self.label_items: move_item_xy(item, x, y, False) # self.parent.graph.animate_plot) y += interval self.rect = QRectF(10, 0, self.gradient_width + max(item.boundingRect().width() for item in self.label_items), self.label_items[0].boundingRect().height() * max(5, len(self.label_items))) else: # za horizontalno orientacijo nisem dodajal title-a width = 50 height = max(item.boundingRect().height() for item in self.label_items) total_width = width * max(5, len(self.label_items)) interval = (total_width - self.label_items[-1].boundingRect().width() ) / (len(self.label_items) - 1) self.gradient_item.setRect(0, 0, total_width, self.gradient_width) self.gradient.setStart(0, 0) self.gradient.setFinalStop(total_width, 0) self.gradient_item.setBrush(QBrush(self.gradient)) self.gradient_item.setPen(QPen(Qt.NoPen)) x = 0 y = 30 for item in self.label_items: move_item_xy(item, x, y, False) # self.parent.graph.animate_plot) x += interval self.rect = QRectF(0, 0, total_width, self.gradient_width + height) # noinspection PyPep8Naming def boundingRect(self): width = max(self.rect.width(), self.title_item.boundingRect().width()) height = self.rect.height() + self.title_item.boundingRect().height() return QRectF(self.rect.left(), self.rect.top(), width, height) def paint(self, painter, option, widget): pass
class MJScene(QGraphicsScene): """our scene with a potential Qt bug fix""" def __init__(self): QGraphicsScene.__init__(self) self.__disableFocusRect = False self._focusBoard = None self.focusRect = QGraphicsRectItem() pen = QPen(QColor(Qt.blue)) pen.setWidth(6) self.focusRect.setPen(pen) self.addItem(self.focusRect) self.focusRect.setZValue(ZValues.marker) self.focusRect.hide() def focusInEvent(self, event): """work around a qt bug. See https://bugreports.qt-project.org/browse/QTBUG-32890 This can be reproduced as follows: ./kajongg.py --game=whatever --autoplay=SomeRuleset such that the human player is the first one to discard a tile. wait until the main screen has been built click with the mouse into the middle of that window press left arrow key this will violate the assertion in GraphicsTileItem.keyPressEvent """ prev = self.focusItem() QGraphicsScene.focusInEvent(self, event) if prev and bool(prev.flags() & QGraphicsItem.ItemIsFocusable) and prev != self.focusItem(): self.setFocusItem(prev) def __focusRectVisible(self): """should we show it?""" game = Internal.field.game board = self._focusBoard return bool(not self.__disableFocusRect and board and board.hasFocus and board.focusTile and game and not game.autoPlay) @property def disableFocusRect(self): """suppress focusrect""" return self.__disableFocusRect @disableFocusRect.setter def disableFocusRect(self, value): """always place or hide, even if value does not change""" self.__disableFocusRect = value if value: self.focusRect.hide() else: self.placeFocusRect() @property def focusBoard(self): """get / set the board that has its focusRect shown""" return self._focusBoard @focusBoard.setter def focusBoard(self, board): """get / set the board that has its focusRect shown""" self._focusBoard = board focusTile = board.focusTile if board else None if focusTile: focusTile.graphics.setFocus() self.placeFocusRect() self.focusRect.setVisible(self.__focusRectVisible()) def placeFocusRect(self): """show a blue rect around tile""" board = self._focusBoard if isAlive(board) and self.__focusRectVisible(): rect = board.tileFaceRect() rect.setWidth(rect.width()*board.focusRectWidth()) self.focusRect.setRect(rect) self.focusRect.setPos(board.focusTile.graphics.pos()) self.focusRect.setRotation(board.sceneRotation()) self.focusRect.setScale(board.scale()) self.focusRect.show() else: self.focusRect.hide() def graphicsTileItems(self): """returns all GraphicsTileItems in the scene""" return (x for x in self.items() if isinstance(x, GraphicsTileItem)) def nonTiles(self): """returns all other items in the scene""" return (x for x in self.items() if not isinstance(x, GraphicsTileItem)) def removeTiles(self): """remove all tiles from scene""" for item in self.graphicsTileItems(): self.removeItem(item) self.focusRect.hide()
class PatternLegendText(QGraphicsTextItem): Type = 70000 + 3 def __init__(self, text, parent = None): super(PatternLegendText, self).__init__(text, parent) # NOTE: need this distinction for cache mode based on # the Qt version otherwise rendering is broken if NO_ITEM_CACHING: self.setCacheMode(QGraphicsItem.NoCache) else: self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setZValue(1) self.setFlag(QGraphicsItem.ItemIsMovable) self.setTextInteractionFlags(Qt.TextEditorInteraction) self._position = self.pos() self._outline = None def hoverEnterEvent(self, event): """ Stuff related to hover enter events. For now we just show a rectangular outline. """ if not self._outline: self._outline = QGraphicsRectItem(self.boundingRect(), self) highlightColor = QColor(Qt.blue) highlightColor.setAlpha(30) self._outline.setBrush(highlightColor) highlightPen = QPen(Qt.blue) highlightPen.setWidth(2) self._outline.setPen(highlightPen) else: self._outline.show() def hoverLeaveEvent(self, event): """ Stuff related to hover leave events. For now we just show a rectangular outline. """ self._outline.hide() def keyPressEvent(self, event): """ Stuff to do during key press events. For now we have to adjust the outline box. """ QGraphicsTextItem.keyPressEvent(self, event) self.adjust_size() def adjust_size(self): """ This function takes care of changing the size of the outline rectangle, e.g., when text is added or removed or during font size changes. """ if self._outline: self._outline.setRect(self.boundingRect()) def mousePressEvent(self, event): """ We reimplement this function to store the position of the item when a user issues a mouse press. """ self._position = self.pos() if (event.modifiers() & Qt.ControlModifier): QApplication.setOverrideCursor(QCursor(Qt.SizeAllCursor)) self.setTextInteractionFlags(Qt.NoTextInteraction) return QGraphicsTextItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ We reimplement this function to check if its position has changed since the last mouse click. If yes we let the canvas know so it can store the action as a Redo/Undo event. """ self.setTextInteractionFlags(Qt.TextEditorInteraction) QApplication.restoreOverrideCursor() # this is needed for undo/redo if self._position != self.pos(): self.scene().canvas_item_position_changed(self, self._position, self.pos()) return QGraphicsTextItem.mouseReleaseEvent(self, event)
class ImageScene2D(QGraphicsScene): """ The 2D scene description of a tiled image generated by evaluating an overlay stack, together with a 2D cursor. """ axesChanged = pyqtSignal(int, bool) dirtyChanged = pyqtSignal() @property def is_swapped(self): """ Indicates whether the dimensions are swapped swapping the axis will swap the dimensions and rotating the roi will swap the dimensions :return: bool """ return bool(self._swapped) != bool(self._rotation % 2) # xor @property def stackedImageSources(self): return self._stackedImageSources @stackedImageSources.setter def stackedImageSources(self, s): self._stackedImageSources = s @property def showTileOutlines(self): return self._showTileOutlines @showTileOutlines.setter def showTileOutlines(self, show): self._showTileOutlines = show self.invalidate() @property def showTileProgress(self): return self._showTileProgress @showTileProgress.setter def showTileProgress(self, show): self._showTileProgress = show self._dirtyIndicator.setVisible(show) def resetAxes(self, finish=True): # rotation is in range(4) and indicates in which corner of the # view the origin lies. 0 = top left, 1 = top right, etc. self._rotation = 0 self._swapped = self._swappedDefault # whether axes are swapped self._newAxes() self._setSceneRect() self.scene2data, isInvertible = self.data2scene.inverted() assert isInvertible if finish: self._finishViewMatrixChange() def _newAxes(self): """Given self._rotation and self._swapped, calculates and sets the appropriate data2scene transformation. """ # TODO: this function works, but it is not elegant. There must # be a simpler way to calculate the appropriate transformation. w, h = self.dataShape assert self._rotation in range(0, 4) # unlike self._rotation, the local variable 'rotation' # indicates how many times to rotate clockwise after swapping # axes. # t1 : do axis swap t1 = QTransform() if self._swapped: t1 = QTransform(0, 1, 0, 1, 0, 0, 0, 0, 1) h, w = w, h # t2 : do rotation t2 = QTransform() t2.rotate(self._rotation * 90) # t3: shift to re-center rot2trans = {0 : (0, 0), 1 : (h, 0), 2 : (w, h), 3 : (0, w)} trans = rot2trans[self._rotation] t3 = QTransform.fromTranslate(*trans) self.data2scene = t1 * t2 * t3 if self._tileProvider: self._tileProvider.axesSwapped = self._swapped self.axesChanged.emit(self._rotation, self._swapped) def rot90(self, direction): """ direction: left ==> -1, right ==> +1""" assert direction in [-1, 1] self._rotation = (self._rotation + direction) % 4 self._newAxes() def swapAxes(self, transform): self._swapped = not self._swapped self._newAxes() def _onRotateLeft(self): self.rot90(-1) self._finishViewMatrixChange() def _onRotateRight(self): self.rot90(1) self._finishViewMatrixChange() def _onSwapAxes(self): self.swapAxes(self.data2scene) self._finishViewMatrixChange() def _finishViewMatrixChange(self): self.scene2data, isInvertible = self.data2scene.inverted() self._setSceneRect() self._tiling.data2scene = self.data2scene self._tileProvider._onSizeChanged() QGraphicsScene.invalidate(self, self.sceneRect()) @property def sceneShape(self): return (self.sceneRect().width(), self.sceneRect().height()) def _setSceneRect(self): w, h = self.dataShape rect = self.data2scene.mapRect(QRect(0, 0, w, h)) sw, sh = rect.width(), rect.height() self.setSceneRect(0, 0, sw, sh) if self._dataRectItem is not None: self.removeItem( self._dataRectItem ) #this property represent a parent to QGraphicsItems which should #be clipped to the data, such as temporary capped lines for brushing. #This works around ilastik issue #516. self._dataRectItem = QGraphicsRectItem(0,0,sw,sh) self._dataRectItem.setPen(QPen(QColor(0,0,0,0))) self._dataRectItem.setFlag(QGraphicsItem.ItemClipsChildrenToShape) self.addItem(self._dataRectItem) @property def dataRectItem(self): return self._dataRectItem @property def dataShape(self): """ The shape of the scene in QGraphicsView's coordinate system. """ return self._dataShape @dataShape.setter def dataShape(self, value): """ Set the size of the scene in QGraphicsView's coordinate system. dataShape -- (widthX, widthY), where the origin of the coordinate system is in the upper left corner of the screen and 'x' points right and 'y' points down """ assert len(value) == 2 self._dataShape = value self.reset() self._finishViewMatrixChange() def setCacheSize(self, cache_size): self._tileProvider.set_cache_size(cache_size) def cacheSize(self): return self._tileProvider.cache_size def setPrefetchingEnabled(self, enable): self._prefetching_enabled = enable def setPreemptiveFetchNumber(self, n): if n > self.cacheSize() - 1: self._n_preemptive = self.cacheSize() - 1 else: self._n_preemptive = n def preemptiveFetchNumber(self): return self._n_preemptive def invalidateViewports(self, sceneRectF): '''Call invalidate on the intersection of all observing viewport-rects and rectF.''' sceneRectF = sceneRectF if sceneRectF.isValid() else self.sceneRect() for view in self.views(): QGraphicsScene.invalidate(self, sceneRectF.intersected(view.viewportRect())) def reset(self): """Reset rotations, tiling, etc. Called when first initialized and when the underlying data changes. """ self.resetAxes(finish=False) self._tiling = Tiling(self._dataShape, self.data2scene, name=self.name) self._tileProvider = TileProvider(self._tiling, self._stackedImageSources) self._tileProvider.sceneRectChanged.connect(self.invalidateViewports) if self._dirtyIndicator: self.removeItem(self._dirtyIndicator) del self._dirtyIndicator self._dirtyIndicator = DirtyIndicator(self._tiling) self.addItem(self._dirtyIndicator) self._dirtyIndicator.setVisible(False) def __init__(self, posModel, along, preemptive_fetch_number=5, parent=None, name="Unnamed Scene", swapped_default=False): """ * preemptive_fetch_number -- number of prefetched slices; 0 turns the feature off * swapped_default -- whether axes should be swapped by default. """ QGraphicsScene.__init__(self, parent=parent) self._along = along self._posModel = posModel # QGraphicsItems can change this if they are in a state that should temporarily forbid brushing # (For example, when the slice intersection marker is in 'draggable' state.) self.allow_brushing = True self._dataShape = (0, 0) self._dataRectItem = None #A QGraphicsRectItem (or None) self._offsetX = 0 self._offsetY = 0 self.name = name self._stackedImageSources = StackedImageSources(LayerStackModel()) self._showTileOutlines = False # FIXME: We don't show the red 'progress pies' because they look terrible. # If we could fix their timing, maybe it would be worth it. self._showTileProgress = False self._tileProvider = None self._dirtyIndicator = None self._prefetching_enabled = False self._swappedDefault = swapped_default self.reset() # BowWave preemptive caching self.setPreemptiveFetchNumber(preemptive_fetch_number) self._course = (1,1) # (along, pos or neg direction) self._time = self._posModel.time self._channel = self._posModel.channel self._posModel.timeChanged.connect(self._onTimeChanged) self._posModel.channelChanged.connect(self._onChannelChanged) self._posModel.slicingPositionChanged.connect(self._onSlicingPositionChanged) self._allTilesCompleteEvent = threading.Event() self.dirty = False # We manually keep track of the tile-wise QGraphicsItems that # we've added to the scene in this dict, otherwise we would need # to use O(N) lookups for every tile by calling QGraphicsScene.items() self.tile_graphicsitems = defaultdict(set) # [Tile.id] -> set(QGraphicsItems) def drawForeground(self, painter, rect): if self._tiling is None: return if self._showTileOutlines: tile_nos = self._tiling.intersected(rect) for tileId in tile_nos: ## draw tile outlines # Dashed black line pen = QPen() pen.setDashPattern([5,5]) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) # Dashed white line # (offset to occupy the spaces in the dashed black line) pen = QPen() pen.setDashPattern([5,5]) pen.setDashOffset(5) pen.setColor(QColor(Qt.white)) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) def indicateSlicingPositionSettled(self, settled): if self._showTileProgress: self._dirtyIndicator.setVisible(settled) def drawBackground(self, painter, sceneRectF): if self._tileProvider is None: return tiles = self._tileProvider.getTiles(sceneRectF) allComplete = True for tile in tiles: #We always draw the tile, even though it might not be up-to-date #In ilastik's live mode, the user sees the old result while adding #new brush strokes on top #See also ilastik issue #132 and tests/lazy_test.py if tile.qimg is not None: painter.drawImage(tile.rectF, tile.qimg) # The tile also contains a list of any QGraphicsItems that were produced by the layers. # If there are any new ones, add them to the scene. new_items = set(tile.qgraphicsitems) - self.tile_graphicsitems[tile.id] obsolete_items = self.tile_graphicsitems[tile.id] - set(tile.qgraphicsitems) for g_item in obsolete_items: self.tile_graphicsitems[tile.id].remove(g_item) self.removeItem(g_item) for g_item in new_items: self.tile_graphicsitems[tile.id].add(g_item) self.addItem(g_item) if tile.progress < 1.0: allComplete = False if self._showTileProgress: self._dirtyIndicator.setTileProgress(tile.id, tile.progress) if allComplete: if self.dirty: self.dirty = False self.dirtyChanged.emit() self._allTilesCompleteEvent.set() else: if not self.dirty: self.dirty = True self.dirtyChanged.emit() self._allTilesCompleteEvent.clear() # preemptive fetching if self._prefetching_enabled: upcoming_through_slices = self._bowWave(self._n_preemptive) for through in upcoming_through_slices: self._tileProvider.prefetch(sceneRectF, through, layer_indexes=None) def triggerPrefetch(self, layer_indexes, time_range='current', spatial_axis_range='current', sceneRectF=None ): """ Trigger a one-time prefetch for the given set of layers. TODO: I'm not 100% sure what happens here for layers with multiple channels. layer_indexes: list-of-ints, or None, which means 'all visible'. time_range: (start_time, stop_time) spatial_axis_range: (start_slice, stop_slice), meaning Z/Y/X depending on our projection (self.along) sceneRectF: Used to determine which tiles to request. An invalid QRectF results in all tiles getting refreshed (visible or not). """ # Process parameters sceneRectF = sceneRectF or QRectF() if time_range == 'current': time_range = (self._posModel.slicingPos5D[0], self._posModel.slicingPos5D[0]+1) elif time_range == 'all': time_range = (0, self._posModel.shape5D[0]) else: assert len(time_range) == 2 assert time_range[0] >= 0 and time_range[1] < self._posModel.shape5D[0] spatial_axis = self._along[1] if spatial_axis_range == 'current': spatial_axis_range = (self._posModel.slicingPos5D[spatial_axis], self._posModel.slicingPos5D[spatial_axis]+1) elif spatial_axis_range == 'all': spatial_axis_range = (0, self._posModel.shape5D[spatial_axis]) else: assert len(spatial_axis_range) == 2 assert 0 <= spatial_axis_range[0] < self._posModel.shape5D[spatial_axis] assert 0 < spatial_axis_range[1] <= self._posModel.shape5D[spatial_axis] # Construct list of 'through' coordinates through_list = [] for t in range( *time_range ): for s in range( *spatial_axis_range ): through_list.append( (t, s) ) # Make sure the tile cache is big enough to hold the prefetched data. if self._tileProvider.cache_size < len(through_list): self._tileProvider.set_cache_size( len(through_list) ) # Trigger prefetches for through in through_list: self._tileProvider.prefetch(sceneRectF, through, layer_indexes) def joinRenderingAllTiles(self, viewport_only=True, rect=None): """ Wait until all tiles in the scene have been 100% rendered. If sceneRectF is None, use the viewport rect. If sceneRectF is an invalid QRectF(), then wait for all tiles. Note: If called from the GUI thread, the GUI thread will block until all tiles are rendered! """ # If this is the main thread, keep repainting (otherwise we'll deadlock). if threading.current_thread().name == "MainThread": if viewport_only: sceneRectF = self.views()[0].viewportRect() else: if rect is None or not isinstance(rect, QRectF): sceneRectF = QRectF() # invalid QRectF means 'get all tiles' else: sceneRectF = rect self._tileProvider.waitForTiles(sceneRectF) else: self._allTilesCompleteEvent.wait() def _bowWave(self, n): through = [ self._posModel.slicingPos5D[axis] for axis in self._along[:-1] ] t_max = [ self._posModel.shape5D[axis] for axis in self._along[:-1] ] BowWave = [] a = self._course[0] for d in xrange(1,n+1): m = through[a] + d * self._course[1] if m < t_max[a] and m >= 0: t = list(through) t[a] = m BowWave.append(tuple(t)) return BowWave def _onSlicingPositionChanged(self, new, old): if (new[self._along[1] - 1] - old[self._along[1] - 1]) < 0: self._course = (1, -1) else: self._course = (1, 1) def _onChannelChanged(self, new): if (new - self._channel) < 0: self._course = (2, -1) else: self._course = (2, 1) self._channel = new def _onTimeChanged(self, new): if (new - self._time) < 0: self._course = (0, -1) else: self._course = (0, 1) self._time = new
class SequencePlotFace_mod(faces.SequencePlotFace): def draw_x_axis(self): lineItem = QGraphicsLineItem(self.col_w / 2, self.coordY(self.ylim[0]) + 2, self.width - self.col_w / 2, self.coordY(self.ylim[0]) + 2, parent=self.item) lineItem.setPen(QPen(QColor('black'))) lineItem.setZValue(10) all_vals = list(range(0, len(self.values), self.x_inter_values)) if (len(self.values) - 1) % self.x_inter_values: all_vals += [len(self.values) - 1] hp_x = [] if self.hp: for x in list(range(0, len(self.values))): if self.x_values[x] in self.hp: hp_x.append(x) if not x in all_vals: all_vals += [x] all_vals.sort() for x in all_vals: lineItem = QGraphicsLineItem(0, self.coordY(self.ylim[0]) + 2, 0, self.coordY(self.ylim[0]) + 6, parent=self.item) lineItem.setX(x * self.col_w + self.col_w / 2) lineItem.setPen(QPen(QColor('black'))) lineItem.setZValue(10) if x in hp_x: text = QGraphicsSimpleTextItem("*" + str(self.x_values[x])) qfont = QFont("Arial", self.fsize - 1) #qfont.setBold(True) text.setFont(qfont) else: text = QGraphicsSimpleTextItem(" " + str(self.x_values[x])) text.setFont(QFont("Arial", self.fsize - 1)) text.rotate(-90) text.setParentItem(self.item) text.setZValue(10) tw = text.boundingRect().width() th = text.boundingRect().height() # Center text according to masterItem size text.setPos(x * self.col_w - th / 2 + self.col_w / 2, tw + self.coordY(self.ylim[0]) + 7) def draw_y_axis(self): lineItem = QGraphicsLineItem(0, self.coordY(self.ylim[0]), 0, self.coordY(self.ylim[1]), parent=self.item) lineItem.setPen(QPen(QColor('black'))) lineItem.setZValue(10) max_w = 0 for y in set(self.hlines + list(self.ylim)): if y in list(self.ylim): lineItem = QGraphicsLineItem(0, self.coordY(y), -5, self.coordY(y), parent=self.item) lineItem.setPen(QPen(QColor('black'))) lineItem.setZValue(10) text = QGraphicsSimpleTextItem(str(y)) text.setFont(QFont("Arial", self.fsize - 2)) text.setParentItem(self.item) tw = text.boundingRect().width() max_w = tw if tw > max_w else max_w th = text.boundingRect().height() # Center text according to masterItem size text.setPos(-tw - 5, self.coordY(y) - th / 2) else: text = QGraphicsSimpleTextItem(str(y)) text.setFont(QFont("Arial", self.fsize - 4)) text.setParentItem(self.item) tw = text.boundingRect().width() max_w = tw if tw > max_w else max_w th = text.boundingRect().height() # Center text according to masterItem size text.setPos(self.width + 5, self.coordY(y) - th / 2) if self.ylabel: text = QGraphicsSimpleTextItem(self.ylabel) text.setFont(QFont("Arial", self.fsize - 1)) text.setParentItem(self.item) text.rotate(-90) tw = text.boundingRect().width() th = text.boundingRect().height() # Center text according to masterItem size text.setPos(-th - 5 - max_w, tw / 2 + self.coordY(sum(self.ylim) / 2)) def set_sticks_color(self): stick_colors = [] y_values = self.values for y_val in y_values: if y_val >= 0.99: stick_colors.append("red") elif y_val >= 0.90: stick_colors.append("orange") elif y_val >= 0.80: stick_colors.append("#EFDB00") else: stick_colors.append("gray") self.colors = stick_colors def draw_colored_boxes(self, h): stick_colors = self.colors num_col_red = stick_colors.count("red") num_col_orange = stick_colors.count("orange") num_col_yellow = stick_colors.count("#EFDB00") num_col_gray = stick_colors.count("gray") colored_box_h = self.height + h - 5 if num_col_red: rect_red = QGraphicsRectItem(self.col_w * 0 - 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_red, colored_box_h) qpen = QPen(QColor('red')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_red.setPen(qpen) rect_red.setBrush(QColor("#FFD1D1")) rect_red.setZValue(-1) rect_red.setParentItem(self.item) #rect_red.setOpacity(0.5) if num_col_orange: rect_orange = QGraphicsRectItem(self.col_w * num_col_red + 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_orange - 2, colored_box_h) qpen = QPen(QColor('orange')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_orange.setPen(qpen) rect_orange.setBrush(QColor("#FFE3B0")) rect_orange.setZValue(-1) rect_orange.setParentItem(self.item) #rect_orange.setOpacity(0.5) if num_col_yellow: rect_yellow = QGraphicsRectItem( self.col_w * (num_col_orange + num_col_red) + 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_yellow - 2, colored_box_h) qpen = QPen(QColor('#EFDB00')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_yellow.setPen(qpen) rect_yellow.setBrush(QColor("#FBFFA5")) rect_yellow.setZValue(-1) rect_yellow.setParentItem(self.item) #rect_yellow.setOpacity(0.5) if num_col_gray: rect_gray = QGraphicsRectItem( self.col_w * (num_col_orange + num_col_red + num_col_yellow) + 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_gray, colored_box_h) qpen = QPen(QColor('gray')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_gray.setPen(qpen) rect_gray.setBrush(QColor("#E2E2E2")) rect_gray.setZValue(-1) rect_gray.setParentItem(self.item) #rect_gray.setOpacity(0.5) def update_items(self): self.item = QGraphicsRectItem(-30, 0, self.width + 40, self.height + 70) #self.item.setPen(QPen(QColor('gray'))) self.item.setPen(QPen(QColor('white'))) try: put_colored_boxes = self.put_colored_boxes except AttributeError: put_colored_boxes = (False, 0) if put_colored_boxes[0]: (_, tree_h) = put_colored_boxes self.draw_colored_boxes(tree_h) # draw lines for line, col in zip(self.hlines, self.hlines_col): self.draw_hlines(line, col) # draw plot width = self.col_w for i, val in enumerate(self.values): self.draw_fun(width * i + self.col_w / 2, val, i) # draw error bars if self.errors: for i in range(len(self.errors)): self.draw_errors(width * i + self.col_w / 2, i) # draw x axis self.draw_x_axis() # draw y axis self.draw_y_axis() # put header self.write_header()
def draw_colored_boxes(self, h): stick_colors = self.colors num_col_red = stick_colors.count("red") num_col_orange = stick_colors.count("orange") num_col_yellow = stick_colors.count("#EFDB00") num_col_gray = stick_colors.count("gray") colored_box_h = self.height + h - 5 if num_col_red: rect_red = QGraphicsRectItem(self.col_w * 0 - 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_red, colored_box_h) qpen = QPen(QColor('red')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_red.setPen(qpen) rect_red.setBrush(QColor("#FFD1D1")) rect_red.setZValue(-1) rect_red.setParentItem(self.item) #rect_red.setOpacity(0.5) if num_col_orange: rect_orange = QGraphicsRectItem(self.col_w * num_col_red + 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_orange - 2, colored_box_h) qpen = QPen(QColor('orange')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_orange.setPen(qpen) rect_orange.setBrush(QColor("#FFE3B0")) rect_orange.setZValue(-1) rect_orange.setParentItem(self.item) #rect_orange.setOpacity(0.5) if num_col_yellow: rect_yellow = QGraphicsRectItem( self.col_w * (num_col_orange + num_col_red) + 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_yellow - 2, colored_box_h) qpen = QPen(QColor('#EFDB00')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_yellow.setPen(qpen) rect_yellow.setBrush(QColor("#FBFFA5")) rect_yellow.setZValue(-1) rect_yellow.setParentItem(self.item) #rect_yellow.setOpacity(0.5) if num_col_gray: rect_gray = QGraphicsRectItem( self.col_w * (num_col_orange + num_col_red + num_col_yellow) + 1, self.coordY(self.ylim[1]) - 5, self.col_w * num_col_gray, colored_box_h) qpen = QPen(QColor('gray')) qpen.setWidth(1) qpen.setStyle( 5) # dash line : http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum rect_gray.setPen(qpen) rect_gray.setBrush(QColor("#E2E2E2")) rect_gray.setZValue(-1) rect_gray.setParentItem(self.item)
def setAxisIntersect(self, intersect): self._axisIntersect = intersect #print "SkeletonsLayer(axis=%d) is updating intersect=%d" % (self._axis, self._axisIntersect) nodes, eIntersected, ePlane = self._3d._skeletons.intersect( self._axis, self._axisIntersect) #update existing items toRemove = [] for node, item in self._node2view.iteritems(): if node.pos[self._axis] != self._axisIntersect: self._scene.removeItem(item) toRemove.append(node) elif node.pointF(self._axis) != item.pos(): item.setPos(self._scene.data2scene.map(node.pointF( self._axis))) if node.isSelected() != item.isSelected(): item.setSelected(node.isSelected()) assert item.isSelected() == node.isSelected() i = 0 newSize = [0, 0] for j in range(3): if j == self._axis: continue newSize[i] = node.shape[j] i += 1 newRectF = QRectF(0, 0, *newSize) newRectF = self._scene.data2scene.mapRect(newRectF) item.setRect( QRectF(-newRectF.width() / 2.0, -newRectF.height() / 2.0, newRectF.width(), newRectF.height())) for r in toRemove: del self._node2view[r] #add new views for nodes for n in nodes: if n in self._node2view: continue pos2D = list(n.pos) del pos2D[self._axis] shape2D = n.shape2D(self._axis) itm = QGraphicsSkeletonNode(shape2D, skeletons=self._3d._skeletons, node=n) itm.setPos(self._scene.data2scene.map(QPointF(*pos2D))) itm.setSelected(n.isSelected()) self._scene.addItem(itm) self._node2view[n] = itm for itm in self._edge2view.values(): self._scene.removeItem(itm) self._edge2view = dict() for e in ePlane: l = QLineF(e[0].pointF(), e[1].pointF()) c1 = e[0].color() c2 = e[1].color() mixColor = QColor((c1.red() + c2.red()) / 2, (c1.green() + c2.green()) / 2, (c1.blue() + c2.blue()) / 2) line = QGraphicsLineItem(self._scene.data2scene.map(l)) line.setPen(QPen(mixColor)) self._scene.addItem(line) self._edge2view[e] = line for theEdge, e in eIntersected: c1 = theEdge[0].color() c2 = theEdge[1].color() mixColor = QColor((c1.red() + c2.red()) / 2, (c1.green() + c2.green()) / 2, (c1.blue() + c2.blue()) / 2) nodeSize = 6 p = QGraphicsRectItem(-nodeSize / 2, -nodeSize / 2, nodeSize, nodeSize) pos2D = list(e) del pos2D[self._axis] p.setPos(self._scene.data2scene.map(QPointF(*pos2D))) p.setPen(QPen(mixColor)) self._scene.addItem(p) self._edge2view[e] = p
def __init__(self, world): QGraphicsPolygonItem.__init__(self) ############################# ### Build graph ############################# graph = pydot.Dot() graph.set_node_defaults(color = 'red', fontcolor = 'red', label = '\<orphan\>') graph.set('overlap', 'prism') # build adjacency graph from world for area in world.areas: # create node for each room node = pydot.Node(area.id) node.set( 'label', area.name ) if area == world.player.currentArea: node.set( 'color', 'blue' ) node.set( 'fontcolor', 'blue' ) else: node.set( 'color', 'black' ) node.set( 'fontcolor', 'black' ) graph.add_node(node) # link to adjacent rooms for feature in area.features: for action in feature.actions: finalEvent = None for event in action.events: if type(event) == events.PlayerMoveEvent: finalEvent = pydot.Edge( src=area.id, dst=event.properties['destination'] ) if finalEvent is not None: graph.add_edge( finalEvent ) ################################ ### Generate SVG from graph ################################ ps = graph.create_svg(prog='neato') ######################################### ### Build graphics items from SVG ######################################### # build xml tree ns = {'svg': 'http://www.w3.org/2000/svg'} doc = ET.fromstring(ps) # grab the root node properties rootNode = doc.xpath('/svg:svg/svg:g[1]', namespaces=ns)[0] polygon = rootNode.xpath('./svg:polygon', namespaces=ns)[0] pointStr = polygon.xpath('./@points', namespaces=ns)[0] penColor = QString(polygon.xpath('./@stroke', namespaces=ns)[0]) fillColor = QString(polygon.xpath('./@fill', namespaces=ns)[0]) # parse root polygon path path = QPolygonF() for pair in pointStr.split(' '): dims = pair.split(',') point = QPointF( float(dims[0]), float(dims[1]) ) path.append(point) self.setPolygon(path) # fill in root node colors if QColor.isValidColor(penColor): self.setPen( QColor(penColor) ) if QColor.isValidColor(fillColor): self.setBrush( QColor(fillColor) ) # build each graph node for xmlNode in rootNode.xpath('./svg:g', namespaces=ns): group = QGraphicsRectItem(self) group.setPen( Qt.transparent ) group.setBrush( Qt.transparent ) if xmlNode.attrib['class'] == 'node': # find the area object name = xmlNode.xpath('./svg:title', namespaces=ns)[0].text group.setData( 0, QString(world.areas[world.areaLookup[name]].id) ) # get the ellipse info ellipseNode = xmlNode.xpath('./svg:ellipse', namespaces=ns)[0] elProps = { k: float(ellipseNode.attrib[k]) for k in ['cx', 'cy', 'rx', 'ry']} rect = QRectF( elProps['cx']-elProps['rx'], elProps['cy']-elProps['ry'], 2*elProps['rx'], 2*elProps['ry']) penColor = QString(ellipseNode.attrib['stroke']) ellipseItem = QGraphicsEllipseItem(rect, group) if QColor.isValidColor(penColor): ellipseItem.setPen( QColor(penColor) ) # get the text info textNode = xmlNode.xpath('./svg:text', namespaces=ns)[0] text = textNode.text textItem = QGraphicsTextItem(text, group) penColor = textNode.attrib.get('fill', 'black') nodePoint = QPointF(float(textNode.attrib['x']), float(textNode.attrib['y'])) textItem.setPos( nodePoint - textItem.boundingRect().center() + QPointF(0.0,-4.0)) if QColor.isValidColor(penColor): textItem.setDefaultTextColor( QColor(penColor) ) group.setRect( ellipseItem.boundingRect() ) group.setFlags( QGraphicsRectItem.ItemIsSelectable ) elif xmlNode.attrib['class'] == 'edge': # parse the line portion of the arrow line = xmlNode.xpath('./svg:path', namespaces=ns)[0] path = QPainterPath() # pull info from xml file linePath = line.attrib['d'] lineColor = line.attrib['stroke'] # parse path coords points = re.findall( '(-?\d+\.\d+),(-?\d+\.\d+)', linePath ) if len(points) != 4: continue startPoint = QPointF( float(points[0][0]), float(points[0][1]) ) path.moveTo(startPoint) curvePoints = [] for pointCoord in points[1:]: curvePoints.append( QPointF(float(pointCoord[0]), float(pointCoord[1])) ) path.cubicTo( curvePoints[0], curvePoints[1], curvePoints[2] ) # construct path item pathItem = QGraphicsPathItem(path, group) if QColor.isValidColor(lineColor): pathItem.setPen( QColor(lineColor) ) polyNode = xmlNode.xpath('./svg:polygon', namespaces=ns)[0] # pull info from xml file pointStr = polyNode.xpath('./@points', namespaces=ns)[0] penColor = QString(polyNode.xpath('./@stroke', namespaces=ns)[0]) fillColor = QString(polyNode.xpath('./@fill', namespaces=ns)[0]) # parse polygon path path = QPolygonF() for pair in pointStr.split(' '): dims = pair.split(',') point = QPointF( float(dims[0]), float(dims[1]) ) path.append(point) # construct polygon item polygonItem = QGraphicsPolygonItem(path, group) if QColor.isValidColor(penColor): polygonItem.setPen( QColor(penColor) ) if QColor.isValidColor(fillColor): polygonItem.setBrush( QColor(fillColor) ) group.setRect( pathItem.boundingRect() and polygonItem.boundingRect() )
class MJScene(QGraphicsScene): """our scene with a potential Qt bug fix""" def __init__(self): QGraphicsScene.__init__(self) self.__disableFocusRect = False self._focusBoard = None self.focusRect = QGraphicsRectItem() pen = QPen(QColor(Qt.blue)) pen.setWidth(6) self.focusRect.setPen(pen) self.addItem(self.focusRect) self.focusRect.setZValue(ZValues.marker) self.focusRect.hide() def __focusRectVisible(self): """should we show it?""" game = InternalParameters.field.game board = self._focusBoard return bool(not self.__disableFocusRect and board and board.hasFocus and board.focusTile and game and not game.autoPlay) @apply def disableFocusRect(): # pylint: disable=E0202 """suppress focusrect""" def fget(self): # pylint: disable=W0212 return self.__disableFocusRect def fset(self, value): # pylint: disable=W0212 # always place or hide, even if value does not change self.__disableFocusRect = value if value: self.focusRect.hide() else: self.placeFocusRect() return property(**locals()) @apply def focusBoard(): # pylint: disable=E0202 """get / set the board that has its focusRect shown""" def fget(self): # pylint: disable=W0212 return self._focusBoard def fset(self, board): # pylint: disable=W0212 self._focusBoard = board focusTile = board.focusTile if board else None if focusTile: focusTile.graphics.setFocus() self.placeFocusRect() self.focusRect.setVisible(self.__focusRectVisible()) return property(**locals()) def placeFocusRect(self): """show a blue rect around tile""" board = self._focusBoard if isAlive(board) and self.__focusRectVisible(): rect = board.tileFaceRect() rect.setWidth(rect.width()*board.focusRectWidth()) self.focusRect.setRect(rect) self.focusRect.setPos(board.focusTile.graphics.pos()) self.focusRect.setRotation(board.sceneRotation()) self.focusRect.setScale(board.scale()) self.focusRect.show() else: self.focusRect.hide() def graphicsTileItems(self): """returns all GraphicsTileItems in the scene""" return (x for x in self.items() if isinstance(x, GraphicsTileItem)) def nonTiles(self): """returns all other items in the scene""" return (x for x in self.items() if not isinstance(x, GraphicsTileItem)) def removeTiles(self): """remove all tiles from scene""" for item in self.graphicsTileItems(): self.removeItem(item) self.focusRect.hide()
class List90Face(faces.StaticItemFace): """Static text Face object :param l: List of element to be drawn :param fsize: Font size, e.g. 10,12,6, (default=10) :param fgcolor: Foreground font color. RGB code or color name in :data:`SVG_COLORS` :param bgcolor: Background font color. RGB code or color name in :data:`SVG_COLORS` """ def __init__(self, l, ftype="Courier", fstyle="normal", fsize=10, fgcolor="black", bgcolor="white", col_w=14.0, rotation=90): self.liste = l self.ftype = ftype self.fgcolor = fgcolor self.bgcolor = bgcolor self.fsize = fsize self.row_h = float(self.fsize + 1) self.col_w = col_w self.width = 0 self.rot = rotation self.fstyle = fstyle self.coeff_h = max([len(str(x)) for x in self.liste]) super(List90Face, self).__init__(None) def __repr__(self): return "Text Face [%s] (%s)" % (self._text, hex(self.__hash__())) def get_text(self): return self._text def update_items(self): self.item = QGraphicsRectItem( 0, 0, self.width, self.row_h * self.coeff_h) seq_width = 0 nopen = QPen(Qt.NoPen) self.item.setPen(nopen) font = QFont(self.ftype, self.fsize) if self.fstyle == "italic": font.setStyle(QFont.StyleItalic) elif self.fstyle == "oblique": font.setStyle(QFont.StyleOblique) rect_cls = QGraphicsRectItem for i, val in enumerate(self.liste): width = self.col_w height = self.row_h * len(str(val)) + 1 rectitem = rect_cls(0, 0, width, height, parent=self.item) rectitem.setX(seq_width) # to give correct X to children item rectitem.setBrush(QBrush(QColor(self.bgcolor))) rectitem.setPen(nopen) # write letter if enough space in height if height >= self.fsize: text = QGraphicsSimpleTextItem(str(val), parent=rectitem) text.setFont(font) text.setBrush(QBrush(QColor(self.fgcolor))) # Center text according to rectitem size # txtw = text.boundingRect().width() txth = text.boundingRect().height() text.setRotation(self.rot) text.setX(txth) seq_width += width self.width = seq_width
def polygon_name_face(node, *args, **kwargs): """create a wedge shaped face in the style of ARB Args: width (int): size in pixels for the width of the wedge height (int): size in pixels for the height of the wedge width_percent (float): change the angle of the point of the wedge. This must be a number between 0 and 1 Returns: QGraphicsRectItem: The Qt graphics item of the polygon """ n_leaves = len(node.get_leaves()) closest_leaf_dist = node.get_closest_leaf()[1] farthest_leaf_dist = node.get_farthest_leaf()[1] base_height = 30 width = 60 height = math.log(n_leaves, 2) + base_height width_percent = closest_leaf_dist / farthest_leaf_dist #print(width, height, width_percent) points = [ (0.0, 0.0), # top left point (width, 0.0), # top right point (width * width_percent, height), # bottom right point (0.0, height), # bottom left point (0.0, 0.0) # back to the beginning ] shape = QPolygonF() for i in points: shape << QtCore.QPointF(*i) ## Creates a main master Item that will contain all other elements ## Items can be standard QGraphicsItem masterItem = QGraphicsRectItem(0, 0, width, height) # Keep a link within the item to access node info masterItem.node = node # I dont want a border around the masterItem masterItem.setPen(QPen(QtCore.Qt.NoPen)) polygon = QGraphicsPolygonItem(shape, masterItem) # Make the wedge grey in color polygon.setBrush(QBrush(QColor( '#D3D3D3'))) # Print the name of the node # Center text according to masterItem size center = masterItem.boundingRect().center() text = QGraphicsSimpleTextItem(node.name) text.setParentItem(polygon) tw = text.boundingRect().width() th = text.boundingRect().height() text.setPos(center.x() + tw/2, center.y() - th/2) # this is a hack to prevent the name being printed twice # we set the node name to blank after we write it with the QGraphicsSimpleTextItem # it must be set to a blank string for it not to be printed later node.name = '' # print the number of collapsed leaves in the polygon leaves_count_text = QGraphicsSimpleTextItem('('+str(n_leaves)+')') leaves_count_text.setParentItem(polygon) leaves_count_text.setFont(QFont('Veranda', 6)) leaves_count_text.setPos(masterItem.boundingRect().x() + 5, center.y() - leaves_count_text.boundingRect().height()/2) polygon.setPos(0, masterItem.boundingRect().y()/1.5) return masterItem
class RepeatLegendItem(QGraphicsRectItem): Type = 70000 + 7 def __init__(self, color, parent=None): super(RepeatLegendItem, self).__init__(parent) # NOTE: need this distinction for cache mode based on # the Qt version otherwise rendering is broken if NO_ITEM_CACHING: self.setCacheMode(QGraphicsItem.NoCache) else: self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setZValue(1) self.setFlag(QGraphicsItem.ItemIsMovable) self._position = self.pos() self.penColor = color self.itemHeight = 20 self.itemWidth = 40 self._pen = QPen() self._pen.setWidthF(3.0) self._pen.setColor(self.penColor) self.setPen(self._pen) self.setRect(0, 0, self.itemWidth, self.itemHeight) self.setAcceptsHoverEvents(True) self._outline = None @property def height(self): return self.itemHeight @property def width(self): return self.itemWidth @property def color(self): return self.penColor @color.setter def color(self, newColor): self.penColor = newColor self._pen.setColor(self.penColor) self.setPen(self._pen) def hoverEnterEvent(self, event): """ Stuff related to hover enter events. For now we just show a rectangular outline. """ if not self._outline: self._outline = QGraphicsRectItem(\ self.boundingRect().adjusted(-1,-1,1,1), self) highlightColor = QColor(Qt.blue) highlightColor.setAlpha(30) self._outline.setBrush(highlightColor) highlightPen = QPen(Qt.blue) highlightPen.setWidth(2) self._outline.setPen(highlightPen) else: self._outline.show() def hoverLeaveEvent(self, event): """ Stuff related to hover leave events. For now we just show a rectangular outline. """ self._outline.hide() def mousePressEvent(self, event): """ We reimplement this function to store the position of the item when a user issues a mouse press. """ self._position = self.pos() if (event.modifiers() & Qt.ControlModifier): QApplication.setOverrideCursor(QCursor(Qt.SizeAllCursor)) else: event.ignore() return QGraphicsRectItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ We reimplement this function to check if its position has changed since the last mouse click. If yes we let the canvas know so it can store the action as a Redo/Undo event. """ QApplication.restoreOverrideCursor() # this is needed for undo/redo if self._position != self.pos(): self.scene().canvas_item_position_changed(self, self._position, self.pos()) return QGraphicsRectItem.mouseReleaseEvent(self, event)
def setAxisIntersect(self, intersect): self._axisIntersect = intersect #print "SkeletonsLayer(axis=%d) is updating intersect=%d" % (self._axis, self._axisIntersect) nodes, eIntersected, ePlane = self._3d._skeletons.intersect(self._axis, self._axisIntersect) #update existing items toRemove = [] for node, item in self._node2view.iteritems(): if node.pos[self._axis] != self._axisIntersect: self._scene.removeItem(item) toRemove.append(node) elif node.pointF(self._axis) != item.pos(): item.setPos( self._scene.data2scene.map( node.pointF(self._axis) ) ) if node.isSelected() != item.isSelected(): item.setSelected(node.isSelected()) assert item.isSelected() == node.isSelected() i = 0 newSize = [0,0] for j in range(3): if j == self._axis: continue newSize[i] = node.shape[j] i += 1 newRectF = QRectF(0,0,*newSize) newRectF = self._scene.data2scene.mapRect(newRectF) item.setRect(QRectF(-newRectF.width()/2.0, -newRectF.height()/2.0, newRectF.width(), newRectF.height())) for r in toRemove: del self._node2view[r] #add new views for nodes for n in nodes: if n in self._node2view: continue pos2D = list(n.pos) del pos2D[self._axis] shape2D = n.shape2D(self._axis) itm = QGraphicsSkeletonNode(shape2D, skeletons=self._3d._skeletons, node = n) itm.setPos(self._scene.data2scene.map(QPointF(*pos2D))) itm.setSelected(n.isSelected()) self._scene.addItem(itm) self._node2view[n] = itm for itm in self._edge2view.values(): self._scene.removeItem(itm) self._edge2view = dict() for e in ePlane: l = QLineF(e[0].pointF(), e[1].pointF()) c1 = e[0].color() c2 = e[1].color() mixColor = QColor( (c1.red()+c2.red())/2, (c1.green()+c2.green())/2, (c1.blue()+c2.blue())/2 ) line = QGraphicsLineItem(self._scene.data2scene.map(l)) line.setPen(QPen(mixColor)) self._scene.addItem(line) self._edge2view[e] = line for theEdge, e in eIntersected: c1 = theEdge[0].color() c2 = theEdge[1].color() mixColor = QColor( (c1.red()+c2.red())/2, (c1.green()+c2.green())/2, (c1.blue()+c2.blue())/2 ) nodeSize = 6 p = QGraphicsRectItem(-nodeSize/2, -nodeSize/2, nodeSize, nodeSize) pos2D = list(e) del pos2D[self._axis] p.setPos(self._scene.data2scene.map(QPointF(*pos2D))) p.setPen(QPen(mixColor)) self._scene.addItem(p) self._edge2view[e] = p
class PatternLegendText(QGraphicsTextItem): Type = 70000 + 3 def __init__(self, text, itemID=0, parent=None): super(PatternLegendText, self).__init__(text, parent) # NOTE: need this distinction for cache mode based on # the Qt version otherwise rendering is broken if NO_ITEM_CACHING: self.setCacheMode(QGraphicsItem.NoCache) else: self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.itemID = itemID self.setZValue(1) self.setFlag(QGraphicsItem.ItemIsMovable) self.setTextInteractionFlags(Qt.TextEditorInteraction) self._position = self.pos() self._outline = None def hoverEnterEvent(self, event): """ Stuff related to hover enter events. For now we just show a rectangular outline. """ if not self._outline: self._outline = QGraphicsRectItem(self.boundingRect(), self) highlightColor = QColor(Qt.blue) highlightColor.setAlpha(30) self._outline.setBrush(highlightColor) highlightPen = QPen(Qt.blue) highlightPen.setWidth(2) self._outline.setPen(highlightPen) else: self._outline.show() def hoverLeaveEvent(self, event): """ Stuff related to hover leave events. For now we just show a rectangular outline. """ self._outline.hide() def keyPressEvent(self, event): """ Stuff to do during key press events. For now we have to adjust the outline box. """ QGraphicsTextItem.keyPressEvent(self, event) self.adjust_size() def adjust_size(self): """ This function takes care of changing the size of the outline rectangle, e.g., when text is added or removed or during font size changes. """ if self._outline: self._outline.setRect(self.boundingRect()) def mousePressEvent(self, event): """ We reimplement this function to store the position of the item when a user issues a mouse press. """ self._position = self.pos() if (event.modifiers() & Qt.ControlModifier): QApplication.setOverrideCursor(QCursor(Qt.SizeAllCursor)) self.setTextInteractionFlags(Qt.NoTextInteraction) return QGraphicsTextItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ We reimplement this function to check if its position has changed since the last mouse click. If yes we let the canvas know so it can store the action as a Redo/Undo event. """ self.setTextInteractionFlags(Qt.TextEditorInteraction) QApplication.restoreOverrideCursor() # this is needed for undo/redo if self._position != self.pos(): self.scene().canvas_item_position_changed(self, self._position, self.pos()) return QGraphicsTextItem.mouseReleaseEvent(self, event)
class OWLegendGradient(QGraphicsObject): gradient_width = 20 def __init__(self, palette, values, parent): QGraphicsObject.__init__(self, parent) self.parent = parent self.palette = palette self.values = values self.legend = parent self.label_items = [QGraphicsTextItem(text, self) for text in values] for i in self.label_items: i.setTextWidth(50) self.rect = QRectF() self.gradient_item = QGraphicsRectItem(self) self.gradient = QLinearGradient() self.gradient.setStops([(v*0.1, self.palette[v*0.1]) for v in range(11) ]) self.orientation = Qt.Horizontal self.set_orientation(Qt.Vertical) def set_orientation(self, orientation): if self.orientation == orientation: return self.orientation = orientation if self.orientation == Qt.Vertical: height = max([item.boundingRect().height() for item in self.label_items]) total_height = height * max(5, len(self.label_items)) interval = (total_height - self.label_items[-1].boundingRect().height()) / (len(self.label_items) -1) self.gradient_item.setRect(10, 0, self.gradient_width, total_height) self.gradient.setStart(10, 0) self.gradient.setFinalStop(10, total_height) self.gradient_item.setBrush(QBrush(self.gradient)) self.gradient_item.setPen(QPen(Qt.NoPen)) y = 0 x = 30 for item in self.label_items: move_item_xy(item, x, y, self.parent.graph.animate_plot) y += interval self.rect = QRectF(10, 0, self.gradient_width + max([item.boundingRect().width() for item in self.label_items]), self.label_items[0].boundingRect().height() * max(5, len(self.label_items))) else: width = 50 height = max([item.boundingRect().height() for item in self.label_items]) total_width = width * max(5, len(self.label_items)) interval = (total_width - self.label_items[-1].boundingRect().width()) / (len(self.label_items) -1) self.gradient_item.setRect(0, 0, total_width, self.gradient_width) self.gradient.setStart(0, 0) self.gradient.setFinalStop(total_width, 0) self.gradient_item.setBrush(QBrush(self.gradient)) self.gradient_item.setPen(QPen(Qt.NoPen)) x = 0 y = 30 for item in self.label_items: move_item_xy(item, x, y, self.parent.graph.animate_plot) x += interval self.rect = QRectF(0, 0, total_width, self.gradient_width + height) def boundingRect(self): return getattr(self, 'rect', QRectF()) def paint(self, painter, option, widget): pass
class PatternLegendItem(QGraphicsSvgItem): Type = 70000 + 2 def __init__(self, unitDim, width, height, defaultSymbol, defaultColor = QColor(Qt.white), zValue = 1, parent = None): super(PatternLegendItem, self).__init__(parent) # NOTE: need this distinction for cache mode based on # the Qt version otherwise rendering is broken if NO_ITEM_CACHING: self.setCacheMode(QGraphicsItem.NoCache) else: self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setZValue(zValue) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.origin = QPointF(0.0, 0.0) self.unitDim = unitDim self.width = width self.height = height self.size = QSizeF(self.unitDim.width() * width, self.unitDim.height() * height) self.color = defaultColor self.symbol = None self._set_symbol(defaultSymbol) self._penSize = 1.0 self._pen = QPen() self._pen.setWidthF(self._penSize) self._pen.setJoinStyle(Qt.MiterJoin) self._pen.setColor(Qt.black) self.setAcceptsHoverEvents(True) self._outline = None def hoverEnterEvent(self, event): """ Stuff related to hover enter events. For now we just show a rectangular outline. """ if not self._outline: self._outline = QGraphicsRectItem(\ self.boundingRect().adjusted(-1,-1,1,1), self) highlightColor = QColor(Qt.blue) highlightColor.setAlpha(30) self._outline.setBrush(highlightColor) highlightPen = QPen(Qt.blue) highlightPen.setWidth(2) self._outline.setPen(highlightPen) else: self._outline.show() def hoverLeaveEvent(self, event): """ Stuff related to hover leave events. For now we just show a rectangular outline. """ self._outline.hide() def mousePressEvent(self, event): """ We reimplement this function to store the position of the item when a user issues a mouse press. """ self._position = self.pos() if (event.modifiers() & Qt.ControlModifier): QApplication.setOverrideCursor(QCursor(Qt.SizeAllCursor)) else: event.ignore() return QGraphicsSvgItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ We reimplement this function to check if its position has changed since the last mouse click. If yes we let the canvas know so it can store the action as a Redo/Undo event. """ QApplication.restoreOverrideCursor() # this is needed for redo/undo if self._position != self.pos(): self.scene().canvas_item_position_changed(self, self._position, self.pos()) return QGraphicsSvgItem.mouseReleaseEvent(self, event) def change_geometry(self, newDim): """ This slot changes the unit dimensions of the item. """ self.unitDim = newDim self.size = QSizeF(self.unitDim.width() * self.width, self.unitDim.height() * self.height) @property def name(self): """ Return the name of the knitting symbol we contain. """ return self.symbol["name"] def _set_symbol(self, newSymbol): """ Adds a new svg image of a knitting symbol to the scene. """ self.symbol = newSymbol svgPath = newSymbol["svgPath"] if not self.renderer().load(svgPath): errorMessage = ("PatternLegendItem._set_symbol: failed to load " "symbol %s" % svgPath) logger.error(errorMessage) return # apply color if present if "backgroundColor" in newSymbol: self.color = QColor(newSymbol["backgroundColor"]) def boundingRect(self): """ Return the bounding rectangle of the item. """ halfPen = self._penSize * 0.5 return QRectF(self.origin, self.size).adjusted(halfPen, halfPen, halfPen, halfPen) def paint(self, painter, option, widget): """ Paint ourselves. """ painter.setPen(self._pen) brush = QBrush(self.color) painter.setBrush(brush) halfPen = self._penSize * 0.5 painter.drawRect(\ QRectF(self.origin, self.size).adjusted(halfPen, halfPen, halfPen, halfPen)) self.renderer().render(painter, QRectF(self.origin, self.size))
class RectangleSelectionAction(UserInteraction): """ Select items in the scene using a Rectangle selection """ def __init__(self, document, *args, **kwargs): UserInteraction.__init__(self, document, *args, **kwargs) # The initial selection at drag start self.initial_selection = None # Selection when last updated in a mouseMoveEvent self.last_selection = None # A selection rect (`QRectF`) self.selection_rect = None # Keyboard modifiers self.modifiers = 0 def mousePressEvent(self, event): pos = event.scenePos() any_item = self.scene.item_at(pos) if not any_item and event.button() & Qt.LeftButton: self.modifiers = event.modifiers() self.selection_rect = QRectF(pos, QSizeF(0, 0)) self.rect_item = QGraphicsRectItem( self.selection_rect.normalized()) self.rect_item.setPen( QPen(QBrush(QColor(51, 153, 255, 192)), 0.4, Qt.SolidLine, Qt.RoundCap)) self.rect_item.setBrush(QBrush(QColor(168, 202, 236, 192))) self.rect_item.setZValue(-100) # Clear the focus if necessary. if not self.scene.stickyFocus(): self.scene.clearFocus() if not self.modifiers & Qt.ControlModifier: self.scene.clearSelection() event.accept() return True else: self.cancel(self.ErrorReason) return False def mouseMoveEvent(self, event): if not self.rect_item.scene(): # Add the rect item to the scene when the mouse moves. self.scene.addItem(self.rect_item) self.update_selection(event) return True def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.initial_selection is None: # A single click. self.scene.clearSelection() else: self.update_selection(event) self.end() return True def update_selection(self, event): """ Update the selection rectangle from a QGraphicsSceneMouseEvent `event` instance. """ if self.initial_selection is None: self.initial_selection = set(self.scene.selectedItems()) self.last_selection = self.initial_selection pos = event.scenePos() self.selection_rect = QRectF(self.selection_rect.topLeft(), pos) # Make sure the rect_item does not cause the scene rect to grow. rect = self._bound_selection_rect(self.selection_rect.normalized()) # Need that 0.5 constant otherwise the sceneRect will still # grow (anti-aliasing correction by QGraphicsScene?) pw = self.rect_item.pen().width() + 0.5 self.rect_item.setRect(rect.adjusted(pw, pw, -pw, -pw)) selected = self.scene.items(self.selection_rect.normalized(), Qt.IntersectsItemShape, Qt.AscendingOrder) selected = set([item for item in selected if \ item.flags() & Qt.ItemIsSelectable]) if self.modifiers & Qt.ControlModifier: for item in selected | self.last_selection | \ self.initial_selection: item.setSelected((item in selected) ^ (item in self.initial_selection)) else: for item in selected.union(self.last_selection): item.setSelected(item in selected) self.last_selection = set(self.scene.selectedItems()) def end(self): self.initial_selection = None self.last_selection = None self.modifiers = 0 self.rect_item.hide() if self.rect_item.scene() is not None: self.scene.removeItem(self.rect_item) UserInteraction.end(self) def viewport_rect(self): """ Return the bounding rect of the document's viewport on the scene. """ view = self.document.view() vsize = view.viewport().size() viewportrect = QRect(0, 0, vsize.width(), vsize.height()) return view.mapToScene(viewportrect).boundingRect() def _bound_selection_rect(self, rect): """ Bound the selection `rect` to a sensible size. """ srect = self.scene.sceneRect() vrect = self.viewport_rect() maxrect = srect.united(vrect) return rect.intersected(maxrect)
class RepeatLegendItem(QGraphicsRectItem): Type = 70000 + 7 def __init__(self, color, parent = None): super(RepeatLegendItem, self).__init__(parent) # NOTE: need this distinction for cache mode based on # the Qt version otherwise rendering is broken if NO_ITEM_CACHING: self.setCacheMode(QGraphicsItem.NoCache) else: self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setZValue(1) self.setFlag(QGraphicsItem.ItemIsMovable) self._position = self.pos() self.penColor = color self.itemHeight = 20 self.itemWidth = 40 self._pen = QPen() self._pen.setWidthF(3.0) self._pen.setColor(self.penColor) self.setPen(self._pen) self.setRect(0, 0, self.itemWidth, self.itemHeight) self.setAcceptsHoverEvents(True) self._outline = None @property def height(self): return self.itemHeight @property def width(self): return self.itemWidth @property def color(self): return self.penColor @color.setter def color(self, newColor): self.penColor = newColor self._pen.setColor(self.penColor) self.setPen(self._pen) def hoverEnterEvent(self, event): """ Stuff related to hover enter events. For now we just show a rectangular outline. """ if not self._outline: self._outline = QGraphicsRectItem(\ self.boundingRect().adjusted(-1,-1,1,1), self) highlightColor = QColor(Qt.blue) highlightColor.setAlpha(30) self._outline.setBrush(highlightColor) highlightPen = QPen(Qt.blue) highlightPen.setWidth(2) self._outline.setPen(highlightPen) else: self._outline.show() def hoverLeaveEvent(self, event): """ Stuff related to hover leave events. For now we just show a rectangular outline. """ self._outline.hide() def mousePressEvent(self, event): """ We reimplement this function to store the position of the item when a user issues a mouse press. """ self._position = self.pos() if (event.modifiers() & Qt.ControlModifier): QApplication.setOverrideCursor(QCursor(Qt.SizeAllCursor)) else: event.ignore() return QGraphicsRectItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ We reimplement this function to check if its position has changed since the last mouse click. If yes we let the canvas know so it can store the action as a Redo/Undo event. """ QApplication.restoreOverrideCursor() # this is needed for undo/redo if self._position != self.pos(): self.scene().canvas_item_position_changed(self, self._position, self.pos()) return QGraphicsRectItem.mouseReleaseEvent(self, event)
def draw_fun(self, x, y, val, col_width=10, col_height=10, color="gray"): color = get_corr_color(val) rect = QGraphicsRectItem(x, y, col_width, col_height, parent=self.item) rect.setPen(QPen(QColor('black'))) rect.setBrush(QColor(color))
class PatternLegendItem(QGraphicsSvgItem): Type = 70000 + 2 def __init__(self, unitDim, width, height, defaultSymbol, itemID=0, defaultColor=QColor(Qt.white), zValue=1, parent=None): super(PatternLegendItem, self).__init__(parent) # NOTE: need this distinction for cache mode based on # the Qt version otherwise rendering is broken if NO_ITEM_CACHING: self.setCacheMode(QGraphicsItem.NoCache) else: self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.itemID = itemID self.setZValue(zValue) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.origin = QPointF(0.0, 0.0) self.unitDim = unitDim self.width = width self.height = height self.size = QSizeF(self.unitDim.width() * width, self.unitDim.height() * height) self.color = defaultColor self.symbol = None self._set_symbol(defaultSymbol) self._penSize = 1.0 self._pen = QPen() self._pen.setWidthF(self._penSize) self._pen.setJoinStyle(Qt.MiterJoin) self._pen.setColor(Qt.black) self.setAcceptsHoverEvents(True) self._outline = None def hoverEnterEvent(self, event): """ Stuff related to hover enter events. For now we just show a rectangular outline. """ if not self._outline: self._outline = QGraphicsRectItem(\ self.boundingRect().adjusted(-1,-1,1,1), self) highlightColor = QColor(Qt.blue) highlightColor.setAlpha(30) self._outline.setBrush(highlightColor) highlightPen = QPen(Qt.blue) highlightPen.setWidth(2) self._outline.setPen(highlightPen) else: self._outline.show() def hoverLeaveEvent(self, event): """ Stuff related to hover leave events. For now we just show a rectangular outline. """ self._outline.hide() def mousePressEvent(self, event): """ We reimplement this function to store the position of the item when a user issues a mouse press. """ self._position = self.pos() if (event.modifiers() & Qt.ControlModifier): QApplication.setOverrideCursor(QCursor(Qt.SizeAllCursor)) else: event.ignore() return QGraphicsSvgItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ We reimplement this function to check if its position has changed since the last mouse click. If yes we let the canvas know so it can store the action as a Redo/Undo event. """ QApplication.restoreOverrideCursor() # this is needed for redo/undo if self._position != self.pos(): self.scene().canvas_item_position_changed(self, self._position, self.pos()) return QGraphicsSvgItem.mouseReleaseEvent(self, event) def change_geometry(self, newDim): """ This slot changes the unit dimensions of the item. """ self.unitDim = newDim self.size = QSizeF(self.unitDim.width() * self.width, self.unitDim.height() * self.height) @property def name(self): """ Return the name of the knitting symbol we contain. """ return self.symbol["name"] def _set_symbol(self, newSymbol): """ Adds a new svg image of a knitting symbol to the scene. """ self.symbol = newSymbol svgPath = newSymbol["svgPath"] if not self.renderer().load(svgPath): errorMessage = ("PatternLegendItem._set_symbol: failed to load " "symbol %s" % svgPath) logger.error(errorMessage) return # apply color if present if "backgroundColor" in newSymbol: self.color = QColor(newSymbol["backgroundColor"]) def boundingRect(self): """ Return the bounding rectangle of the item. """ halfPen = self._penSize * 0.5 return QRectF(self.origin, self.size).adjusted(halfPen, halfPen, halfPen, halfPen) def paint(self, painter, option, widget): """ Paint ourselves. """ painter.setPen(self._pen) brush = QBrush(self.color) painter.setBrush(brush) halfPen = self._penSize * 0.5 painter.drawRect(\ QRectF(self.origin, self.size).adjusted(halfPen, halfPen, halfPen, halfPen)) self.renderer().render(painter, QRectF(self.origin, self.size))
class ImageScene2D(QGraphicsScene): """ The 2D scene description of a tiled image generated by evaluating an overlay stack, together with a 2D cursor. """ axesChanged = pyqtSignal(int, bool) @property def stackedImageSources(self): return self._stackedImageSources @stackedImageSources.setter def stackedImageSources(self, s): self._stackedImageSources = s s.sizeChanged.connect(self._onSizeChanged) @property def showTileOutlines(self): return self._showTileOutlines @showTileOutlines.setter def showTileOutlines(self, show): self._showTileOutlines = show self.invalidate() @property def showTileProgress(self): return self._showTileProgress @showTileProgress.setter def showTileProgress(self, show): self._showTileProgress = show self._dirtyIndicator.setVisible(show) def resetAxes(self, finish=True): # rotation is in range(4) and indicates in which corner of the # view the origin lies. 0 = top left, 1 = top right, etc. self._rotation = 0 self._swapped = self._swappedDefault # whether axes are swapped self._newAxes() self._setSceneRect() self.scene2data, isInvertible = self.data2scene.inverted() assert isInvertible if finish: self._finishViewMatrixChange() def _newAxes(self): """Given self._rotation and self._swapped, calculates and sets the appropriate data2scene transformation. """ # TODO: this function works, but it is not elegant. There must # be a simpler way to calculate the appropriate tranformation. w, h = self.dataShape assert self._rotation in range(0, 4) # unlike self._rotation, the local variable 'rotation' # indicates how many times to rotate clockwise after swapping # axes. # t1 : do axis swap t1 = QTransform() if self._swapped: t1 = QTransform(0, 1, 0, 1, 0, 0, 0, 0, 1) h, w = w, h # t2 : do rotation t2 = QTransform() t2.rotate(self._rotation * 90) # t3: shift to re-center rot2trans = {0 : (0, 0), 1 : (h, 0), 2 : (w, h), 3 : (0, w)} trans = rot2trans[self._rotation] t3 = QTransform.fromTranslate(*trans) self.data2scene = t1 * t2 * t3 if self._tileProvider: self._tileProvider.axesSwapped = self._swapped self.axesChanged.emit(self._rotation, self._swapped) def rot90(self, transform, rect, direction): """ direction: left ==> -1, right ==> +1""" assert direction in [-1, 1] self._rotation = (self._rotation + direction) % 4 self._newAxes() def swapAxes(self, transform): self._swapped = not self._swapped self._newAxes() def _onRotateLeft(self): self.rot90(self.data2scene, self.sceneRect(), -1) self._finishViewMatrixChange() def _onRotateRight(self): self.rot90(self.data2scene, self.sceneRect(), 1) self._finishViewMatrixChange() def _onSwapAxes(self): self.swapAxes(self.data2scene) self._finishViewMatrixChange() def _finishViewMatrixChange(self): self.scene2data, isInvertible = self.data2scene.inverted() self._setSceneRect() self._tiling.data2scene = self.data2scene self._tileProvider._onSizeChanged() QGraphicsScene.invalidate(self, self.sceneRect()) @property def sceneShape(self): return (self.sceneRect().width(), self.sceneRect().height()) def _setSceneRect(self): w, h = self.dataShape rect = self.data2scene.mapRect(QRect(0, 0, w, h)) sw, sh = rect.width(), rect.height() self.setSceneRect(0, 0, sw, sh) #this property represent a parent to QGraphicsItems which should #be clipped to the data, such as temporary capped lines for brushing. #This works around ilastik issue #516. self.dataRect = QGraphicsRectItem(0,0,sw,sh) self.dataRect.setPen(QPen(QColor(0,0,0,0))) self.dataRect.setFlag(QGraphicsItem.ItemClipsChildrenToShape) self.addItem(self.dataRect) @property def dataShape(self): """ The shape of the scene in QGraphicsView's coordinate system. """ return self._dataShape @dataShape.setter def dataShape(self, value): """ Set the size of the scene in QGraphicsView's coordinate system. dataShape -- (widthX, widthY), where the origin of the coordinate system is in the upper left corner of the screen and 'x' points right and 'y' points down """ assert len(value) == 2 self._dataShape = value self.reset() self._finishViewMatrixChange() def setCacheSize(self, cache_size): if cache_size != self._tileProvider._cache_size: self._tileProvider = TileProvider(self._tiling, self._stackedImageSources, cache_size=cache_size) self._tileProvider.sceneRectChanged.connect(self.invalidateViewports) def cacheSize(self): return self._tileProvider._cache_size def setPrefetchingEnabled(self, enable): self._prefetching_enabled = enable def setPreemptiveFetchNumber(self, n): if n > self.cacheSize() - 1: self._n_preemptive = self.cacheSize() - 1 else: self._n_preemptive = n def preemptiveFetchNumber(self): return self._n_preemptive def invalidateViewports(self, sceneRectF): '''Call invalidate on the intersection of all observing viewport-rects and rectF.''' sceneRectF = sceneRectF if sceneRectF.isValid() else self.sceneRect() for view in self.views(): QGraphicsScene.invalidate(self, sceneRectF.intersected(view.viewportRect())) def reset(self): """Reset rotations, tiling, etc. Called when first initialized and when the underlying data changes. """ self.resetAxes(finish=False) self._tiling = Tiling(self._dataShape, self.data2scene, name=self.name) self._brushingLayer = TiledImageLayer(self._tiling) if self._tileProvider: self._tileProvider.notifyThreadsToStop() # prevent ref cycle self._tileProvider = TileProvider(self._tiling, self._stackedImageSources) self._tileProvider.sceneRectChanged.connect(self.invalidateViewports) if self._dirtyIndicator: self.removeItem(self._dirtyIndicator) del self._dirtyIndicator self._dirtyIndicator = DirtyIndicator(self._tiling) self.addItem(self._dirtyIndicator) def __init__(self, posModel, along, preemptive_fetch_number=5, parent=None, name="Unnamed Scene", swapped_default=False): """ * preemptive_fetch_number -- number of prefetched slices; 0 turns the feature off * swapped_default -- whether axes should be swapped by default. """ QGraphicsScene.__init__(self, parent=parent) self._along = along self._posModel = posModel self._dataShape = (0, 0) self._dataRect = None #A QGraphicsRectItem (or None) self._offsetX = 0 self._offsetY = 0 self.name = name self._stackedImageSources = StackedImageSources(LayerStackModel()) self._showTileOutlines = False self._showTileProgress = True self._tileProvider = None self._dirtyIndicator = None self._prefetching_enabled = False self._swappedDefault = swapped_default self.reset() # BowWave preemptive caching self.setPreemptiveFetchNumber(preemptive_fetch_number) self._course = (1,1) # (along, pos or neg direction) self._time = self._posModel.time self._channel = self._posModel.channel self._posModel.timeChanged.connect(self._onTimeChanged) self._posModel.channelChanged.connect(self._onChannelChanged) self._posModel.slicingPositionChanged.connect(self._onSlicingPositionChanged) self._allTilesCompleteEvent = threading.Event() def __del__(self): if self._tileProvider: self._tileProvider.notifyThreadsToStop() self.joinRendering() def _onSizeChanged(self): self._brushingLayer = TiledImageLayer(self._tiling) def drawForeground(self, painter, rect): if self._tiling is None: return tile_nos = self._tiling.intersected(rect) for tileId in tile_nos: p = self._brushingLayer[tileId] if p.dataVer == p.imgVer: continue p.paint(painter) #access to the underlying image patch is serialized ## draw tile outlines if self._showTileOutlines: # Dashed black line pen = QPen() pen.setDashPattern([5,5]) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) # Dashed white line # (offset to occupy the spaces in the dashed black line) pen = QPen() pen.setDashPattern([5,5]) pen.setDashOffset(5) pen.setColor(QColor(Qt.white)) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) def indicateSlicingPositionSettled(self, settled): if self._showTileProgress: self._dirtyIndicator.setVisible(settled) def drawBackground(self, painter, sceneRectF): if self._tileProvider is None: return tiles = self._tileProvider.getTiles(sceneRectF) allComplete = True for tile in tiles: #We always draw the tile, even though it might not be up-to-date #In ilastik's live mode, the user sees the old result while adding #new brush strokes on top #See also ilastik issue #132 and tests/lazy_test.py if tile.qimg is not None: painter.drawImage(tile.rectF, tile.qimg) if tile.progress < 1.0: allComplete = False if self._showTileProgress: self._dirtyIndicator.setTileProgress(tile.id, tile.progress) if allComplete: self._allTilesCompleteEvent.set() else: self._allTilesCompleteEvent.clear() # preemptive fetching if self._prefetching_enabled: for through in self._bowWave(self._n_preemptive): self._tileProvider.prefetch(sceneRectF, through) def joinRendering(self): return self._tileProvider.join() def joinRenderingAllTiles(self): """ Wait until all tiles in the scene have been 100% rendered. Note: This is useful for testing only. If called from the GUI thread, the GUI thread will block until all tiles are rendered! """ # If this is the main thread, keep repainting (otherwise we'll deadlock). if threading.current_thread().name == "MainThread": finished = False sceneRectF = self.views()[0].viewportRect() while not finished: finished = True tiles = self._tileProvider.getTiles(sceneRectF) for tile in tiles: finished &= tile.progress >= 1.0 else: self._allTilesCompleteEvent.wait() def _bowWave(self, n): shape5d = self._posModel.shape5D sl5d = self._posModel.slicingPos5D through = [sl5d[self._along[i]] for i in xrange(3)] t_max = [shape5d[self._along[i]] for i in xrange(3)] BowWave = [] a = self._course[0] for d in xrange(1,n+1): m = through[a] + d * self._course[1] if m < t_max[a] and m >= 0: t = list(through) t[a] = m BowWave.append(tuple(t)) return BowWave def _onSlicingPositionChanged(self, new, old): if (new[self._along[1] - 1] - old[self._along[1] - 1]) < 0: self._course = (1, -1) else: self._course = (1, 1) def _onChannelChanged(self, new): if (new - self._channel) < 0: self._course = (2, -1) else: self._course = (2, 1) self._channel = new def _onTimeChanged(self, new): if (new - self._time) < 0: self._course = (0, -1) else: self._course = (0, 1) self._time = new
class ImageScene2D(QGraphicsScene): """ The 2D scene description of a tiled image generated by evaluating an overlay stack, together with a 2D cursor. """ axesChanged = pyqtSignal(int, bool) dirtyChanged = pyqtSignal() @property def is_swapped(self): """ Indicates whether the dimensions are swapped swapping the axis will swap the dimensions and rotating the roi will swap the dimensions :return: bool """ return bool(self._swapped) != bool(self._rotation % 2) # xor @property def stackedImageSources(self): return self._stackedImageSources @stackedImageSources.setter def stackedImageSources(self, s): self._stackedImageSources = s @property def showTileOutlines(self): return self._showTileOutlines @showTileOutlines.setter def showTileOutlines(self, show): self._showTileOutlines = show self.invalidate() @property def showTileProgress(self): return self._showTileProgress @showTileProgress.setter def showTileProgress(self, show): self._showTileProgress = show self._dirtyIndicator.setVisible(show) def resetAxes(self, finish=True): # rotation is in range(4) and indicates in which corner of the # view the origin lies. 0 = top left, 1 = top right, etc. self._rotation = 0 self._swapped = self._swappedDefault # whether axes are swapped self._newAxes() self._setSceneRect() self.scene2data, isInvertible = self.data2scene.inverted() assert isInvertible if finish: self._finishViewMatrixChange() def _newAxes(self): """Given self._rotation and self._swapped, calculates and sets the appropriate data2scene transformation. """ # TODO: this function works, but it is not elegant. There must # be a simpler way to calculate the appropriate transformation. w, h = self.dataShape assert self._rotation in range(0, 4) # unlike self._rotation, the local variable 'rotation' # indicates how many times to rotate clockwise after swapping # axes. # t1 : do axis swap t1 = QTransform() if self._swapped: t1 = QTransform(0, 1, 0, 1, 0, 0, 0, 0, 1) h, w = w, h # t2 : do rotation t2 = QTransform() t2.rotate(self._rotation * 90) # t3: shift to re-center rot2trans = {0: (0, 0), 1: (h, 0), 2: (w, h), 3: (0, w)} trans = rot2trans[self._rotation] t3 = QTransform.fromTranslate(*trans) self.data2scene = t1 * t2 * t3 if self._tileProvider: self._tileProvider.axesSwapped = self._swapped self.axesChanged.emit(self._rotation, self._swapped) def rot90(self, direction): """ direction: left ==> -1, right ==> +1""" assert direction in [-1, 1] self._rotation = (self._rotation + direction) % 4 self._newAxes() def swapAxes(self, transform): self._swapped = not self._swapped self._newAxes() def _onRotateLeft(self): self.rot90(-1) self._finishViewMatrixChange() def _onRotateRight(self): self.rot90(1) self._finishViewMatrixChange() def _onSwapAxes(self): self.swapAxes(self.data2scene) self._finishViewMatrixChange() def _finishViewMatrixChange(self): self.scene2data, isInvertible = self.data2scene.inverted() self._setSceneRect() self._tiling.data2scene = self.data2scene self._tileProvider._onSizeChanged() QGraphicsScene.invalidate(self, self.sceneRect()) @property def sceneShape(self): return (self.sceneRect().width(), self.sceneRect().height()) def _setSceneRect(self): w, h = self.dataShape rect = self.data2scene.mapRect(QRect(0, 0, w, h)) sw, sh = rect.width(), rect.height() self.setSceneRect(0, 0, sw, sh) if self._dataRectItem is not None: self.removeItem(self._dataRectItem) #this property represent a parent to QGraphicsItems which should #be clipped to the data, such as temporary capped lines for brushing. #This works around ilastik issue #516. self._dataRectItem = QGraphicsRectItem(0, 0, sw, sh) self._dataRectItem.setPen(QPen(QColor(0, 0, 0, 0))) self._dataRectItem.setFlag(QGraphicsItem.ItemClipsChildrenToShape) self.addItem(self._dataRectItem) @property def dataRectItem(self): return self._dataRectItem @property def dataShape(self): """ The shape of the scene in QGraphicsView's coordinate system. """ return self._dataShape @dataShape.setter def dataShape(self, value): """ Set the size of the scene in QGraphicsView's coordinate system. dataShape -- (widthX, widthY), where the origin of the coordinate system is in the upper left corner of the screen and 'x' points right and 'y' points down """ assert len(value) == 2 self._dataShape = value self.reset() self._finishViewMatrixChange() def setCacheSize(self, cache_size): self._tileProvider.set_cache_size(cache_size) def cacheSize(self): return self._tileProvider.cache_size def setTileWidth(self, tileWidth): self._tileWidth = tileWidth PreferencesManager().set("ImageScene2D", "tileWidth", tileWidth) def tileWidth(self): return self._tileWidth def setPrefetchingEnabled(self, enable): self._prefetching_enabled = enable def setPreemptiveFetchNumber(self, n): if n > self.cacheSize() - 1: self._n_preemptive = self.cacheSize() - 1 else: self._n_preemptive = n def preemptiveFetchNumber(self): return self._n_preemptive def invalidateViewports(self, sceneRectF): '''Call invalidate on the intersection of all observing viewport-rects and rectF.''' sceneRectF = sceneRectF if sceneRectF.isValid() else self.sceneRect() for view in self.views(): QGraphicsScene.invalidate( self, sceneRectF.intersected(view.viewportRect())) def reset(self): """Reset rotations, tiling, etc. Called when first initialized and when the underlying data changes. """ self.resetAxes(finish=False) self._tiling = Tiling(self._dataShape, self.data2scene, name=self.name, blockSize=self.tileWidth()) self._tileProvider = TileProvider(self._tiling, self._stackedImageSources) self._tileProvider.sceneRectChanged.connect(self.invalidateViewports) if self._dirtyIndicator: self.removeItem(self._dirtyIndicator) del self._dirtyIndicator self._dirtyIndicator = DirtyIndicator(self._tiling) self.addItem(self._dirtyIndicator) self._dirtyIndicator.setVisible(False) def mouseMoveEvent(self, event): """ Normally our base class (QGraphicsScene) distributes mouse events to the various QGraphicsItems in the scene. But when the mouse is being dragged, it only sends events to the one object that was under the mouse when the button was first pressed. Here, we forward all events to QGraphicsItems on the drag path, even if they're just brushed by the mouse incidentally. """ super(ImageScene2D, self).mouseMoveEvent(event) if not event.isAccepted() and event.buttons() != Qt.NoButton: if self.last_drag_pos is None: self.last_drag_pos = event.scenePos() # As a special feature, find the item and send it this event. path = QPainterPath(self.last_drag_pos) path.lineTo(event.scenePos()) items = self.items(path) for item in items: item.mouseMoveEvent(event) self.last_drag_pos = event.scenePos() else: self.last_drag_pos = None def mousePressEvent(self, event): """ By default, our base class (QGraphicsScene) only sends mouse press events to the top-most item under the mouse. When labeling edges, we want the edge label layer to accept mouse events, even if it isn't on top. Therefore, we send events to all items under the mouse, until the event is accepted. """ super(ImageScene2D, self).mouseMoveEvent(event) if not event.isAccepted(): items = self.items(event.scenePos()) for item in items: item.mousePressEvent(event) if event.isAccepted(): break def __init__(self, posModel, along, preemptive_fetch_number=5, parent=None, name="Unnamed Scene", swapped_default=False): """ * preemptive_fetch_number -- number of prefetched slices; 0 turns the feature off * swapped_default -- whether axes should be swapped by default. """ QGraphicsScene.__init__(self, parent=parent) self._along = along self._posModel = posModel # QGraphicsItems can change this if they are in a state that should temporarily forbid brushing # (For example, when the slice intersection marker is in 'draggable' state.) self.allow_brushing = True self._dataShape = (0, 0) self._dataRectItem = None #A QGraphicsRectItem (or None) self._offsetX = 0 self._offsetY = 0 self.name = name self._tileWidth = PreferencesManager().get("ImageScene2D", "tileWidth", default=512) self._stackedImageSources = StackedImageSources(LayerStackModel()) self._showTileOutlines = False # FIXME: We don't show the red 'progress pies' because they look terrible. # If we could fix their timing, maybe it would be worth it. self._showTileProgress = False self._tileProvider = None self._dirtyIndicator = None self._prefetching_enabled = False self._swappedDefault = swapped_default self.reset() # BowWave preemptive caching self.setPreemptiveFetchNumber(preemptive_fetch_number) self._course = (1, 1) # (along, pos or neg direction) self._time = self._posModel.time self._channel = self._posModel.channel self._posModel.timeChanged.connect(self._onTimeChanged) self._posModel.channelChanged.connect(self._onChannelChanged) self._posModel.slicingPositionChanged.connect( self._onSlicingPositionChanged) self._allTilesCompleteEvent = threading.Event() self.dirty = False # We manually keep track of the tile-wise QGraphicsItems that # we've added to the scene in this dict, otherwise we would need # to use O(N) lookups for every tile by calling QGraphicsScene.items() self.tile_graphicsitems = defaultdict( set) # [Tile.id] -> set(QGraphicsItems) self.last_drag_pos = None # See mouseMoveEvent() def drawForeground(self, painter, rect): if self._tiling is None: return if self._showTileOutlines: tile_nos = self._tiling.intersected(rect) for tileId in tile_nos: ## draw tile outlines # Dashed black line pen = QPen() pen.setDashPattern([5, 5]) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) # Dashed white line # (offset to occupy the spaces in the dashed black line) pen = QPen() pen.setDashPattern([5, 5]) pen.setDashOffset(5) pen.setColor(QColor(Qt.white)) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) def indicateSlicingPositionSettled(self, settled): if self._showTileProgress: self._dirtyIndicator.setVisible(settled) def drawBackground(self, painter, sceneRectF): if self._tileProvider is None: return # FIXME: For some strange reason, drawBackground is called with # a much larger sceneRectF than necessasry sometimes. # This can happen after panSlicingViews(), for instance. # Somehow, the QGraphicsScene gets confused about how much area # it needs to draw immediately after the ImageView's scrollbar is panned. # As a workaround, we manually check the amount of the scene that needs to be drawn, # instead of relying on the above sceneRectF parameter to be correct. if self.views(): sceneRectF = self.views()[0].viewportRect().intersected(sceneRectF) if not sceneRectF.isValid(): return tiles = self._tileProvider.getTiles(sceneRectF) allComplete = True for tile in tiles: #We always draw the tile, even though it might not be up-to-date #In ilastik's live mode, the user sees the old result while adding #new brush strokes on top #See also ilastik issue #132 and tests/lazy_test.py if tile.qimg is not None: painter.drawImage(tile.rectF, tile.qimg) # The tile also contains a list of any QGraphicsItems that were produced by the layers. # If there are any new ones, add them to the scene. new_items = set( tile.qgraphicsitems) - self.tile_graphicsitems[tile.id] obsolete_items = self.tile_graphicsitems[tile.id] - set( tile.qgraphicsitems) for g_item in obsolete_items: self.tile_graphicsitems[tile.id].remove(g_item) self.removeItem(g_item) for g_item in new_items: self.tile_graphicsitems[tile.id].add(g_item) self.addItem(g_item) if tile.progress < 1.0: allComplete = False if self._showTileProgress: self._dirtyIndicator.setTileProgress(tile.id, tile.progress) if allComplete: if self.dirty: self.dirty = False self.dirtyChanged.emit() self._allTilesCompleteEvent.set() else: if not self.dirty: self.dirty = True self.dirtyChanged.emit() self._allTilesCompleteEvent.clear() # preemptive fetching if self._prefetching_enabled: upcoming_through_slices = self._bowWave(self._n_preemptive) for through in upcoming_through_slices: self._tileProvider.prefetch(sceneRectF, through, layer_indexes=None) def triggerPrefetch(self, layer_indexes, time_range='current', spatial_axis_range='current', sceneRectF=None): """ Trigger a one-time prefetch for the given set of layers. TODO: I'm not 100% sure what happens here for layers with multiple channels. layer_indexes: list-of-ints, or None, which means 'all visible'. time_range: (start_time, stop_time) spatial_axis_range: (start_slice, stop_slice), meaning Z/Y/X depending on our projection (self.along) sceneRectF: Used to determine which tiles to request. An invalid QRectF results in all tiles getting refreshed (visible or not). """ # Process parameters sceneRectF = sceneRectF or QRectF() if time_range == 'current': time_range = (self._posModel.slicingPos5D[0], self._posModel.slicingPos5D[0] + 1) elif time_range == 'all': time_range = (0, self._posModel.shape5D[0]) else: assert len(time_range) == 2 assert time_range[0] >= 0 and time_range[ 1] < self._posModel.shape5D[0] spatial_axis = self._along[1] if spatial_axis_range == 'current': spatial_axis_range = (self._posModel.slicingPos5D[spatial_axis], self._posModel.slicingPos5D[spatial_axis] + 1) elif spatial_axis_range == 'all': spatial_axis_range = (0, self._posModel.shape5D[spatial_axis]) else: assert len(spatial_axis_range) == 2 assert 0 <= spatial_axis_range[0] < self._posModel.shape5D[ spatial_axis] assert 0 < spatial_axis_range[1] <= self._posModel.shape5D[ spatial_axis] # Construct list of 'through' coordinates through_list = [] for t in range(*time_range): for s in range(*spatial_axis_range): through_list.append((t, s)) # Make sure the tile cache is big enough to hold the prefetched data. if self._tileProvider.cache_size < len(through_list): self._tileProvider.set_cache_size(len(through_list)) # Trigger prefetches for through in through_list: self._tileProvider.prefetch(sceneRectF, through, layer_indexes) def joinRenderingAllTiles(self, viewport_only=True, rect=None): """ Wait until all tiles in the scene have been 100% rendered. If sceneRectF is None, use the viewport rect. If sceneRectF is an invalid QRectF(), then wait for all tiles. Note: If called from the GUI thread, the GUI thread will block until all tiles are rendered! """ # If this is the main thread, keep repainting (otherwise we'll deadlock). if threading.current_thread().name == "MainThread": if viewport_only: sceneRectF = self.views()[0].viewportRect() else: if rect is None or not isinstance(rect, QRectF): sceneRectF = QRectF( ) # invalid QRectF means 'get all tiles' else: sceneRectF = rect self._tileProvider.waitForTiles(sceneRectF) else: self._allTilesCompleteEvent.wait() def _bowWave(self, n): through = [ self._posModel.slicingPos5D[axis] for axis in self._along[:-1] ] t_max = [self._posModel.shape5D[axis] for axis in self._along[:-1]] BowWave = [] a = self._course[0] for d in xrange(1, n + 1): m = through[a] + d * self._course[1] if m < t_max[a] and m >= 0: t = list(through) t[a] = m BowWave.append(tuple(t)) return BowWave def _onSlicingPositionChanged(self, new, old): if (new[self._along[1] - 1] - old[self._along[1] - 1]) < 0: self._course = (1, -1) else: self._course = (1, 1) def _onChannelChanged(self, new): if (new - self._channel) < 0: self._course = (2, -1) else: self._course = (2, 1) self._channel = new def _onTimeChanged(self, new): if (new - self._time) < 0: self._course = (0, -1) else: self._course = (0, 1) self._time = new
class ImageScene2D(QGraphicsScene): """ The 2D scene description of a tiled image generated by evaluating an overlay stack, together with a 2D cursor. """ axesChanged = pyqtSignal(int, bool) dirtyChanged = pyqtSignal() @property def stackedImageSources(self): return self._stackedImageSources @stackedImageSources.setter def stackedImageSources(self, s): self._stackedImageSources = s s.sizeChanged.connect(self._onSizeChanged) @property def showTileOutlines(self): return self._showTileOutlines @showTileOutlines.setter def showTileOutlines(self, show): self._showTileOutlines = show self.invalidate() @property def showTileProgress(self): return self._showTileProgress @showTileProgress.setter def showTileProgress(self, show): self._showTileProgress = show self._dirtyIndicator.setVisible(show) def resetAxes(self, finish=True): # rotation is in range(4) and indicates in which corner of the # view the origin lies. 0 = top left, 1 = top right, etc. self._rotation = 0 self._swapped = self._swappedDefault # whether axes are swapped self._newAxes() self._setSceneRect() self.scene2data, isInvertible = self.data2scene.inverted() assert isInvertible if finish: self._finishViewMatrixChange() def _newAxes(self): """Given self._rotation and self._swapped, calculates and sets the appropriate data2scene transformation. """ # TODO: this function works, but it is not elegant. There must # be a simpler way to calculate the appropriate tranformation. w, h = self.dataShape assert self._rotation in range(0, 4) # unlike self._rotation, the local variable 'rotation' # indicates how many times to rotate clockwise after swapping # axes. # t1 : do axis swap t1 = QTransform() if self._swapped: t1 = QTransform(0, 1, 0, 1, 0, 0, 0, 0, 1) h, w = w, h # t2 : do rotation t2 = QTransform() t2.rotate(self._rotation * 90) # t3: shift to re-center rot2trans = {0: (0, 0), 1: (h, 0), 2: (w, h), 3: (0, w)} trans = rot2trans[self._rotation] t3 = QTransform.fromTranslate(*trans) self.data2scene = t1 * t2 * t3 if self._tileProvider: self._tileProvider.axesSwapped = self._swapped self.axesChanged.emit(self._rotation, self._swapped) def rot90(self, transform, rect, direction): """ direction: left ==> -1, right ==> +1""" assert direction in [-1, 1] self._rotation = (self._rotation + direction) % 4 self._newAxes() def swapAxes(self, transform): self._swapped = not self._swapped self._newAxes() def _onRotateLeft(self): self.rot90(self.data2scene, self.sceneRect(), -1) self._finishViewMatrixChange() def _onRotateRight(self): self.rot90(self.data2scene, self.sceneRect(), 1) self._finishViewMatrixChange() def _onSwapAxes(self): self.swapAxes(self.data2scene) self._finishViewMatrixChange() def _finishViewMatrixChange(self): self.scene2data, isInvertible = self.data2scene.inverted() self._setSceneRect() self._tiling.data2scene = self.data2scene self._tileProvider._onSizeChanged() QGraphicsScene.invalidate(self, self.sceneRect()) @property def sceneShape(self): return (self.sceneRect().width(), self.sceneRect().height()) def _setSceneRect(self): w, h = self.dataShape rect = self.data2scene.mapRect(QRect(0, 0, w, h)) sw, sh = rect.width(), rect.height() self.setSceneRect(0, 0, sw, sh) #this property represent a parent to QGraphicsItems which should #be clipped to the data, such as temporary capped lines for brushing. #This works around ilastik issue #516. self.dataRect = QGraphicsRectItem(0, 0, sw, sh) self.dataRect.setPen(QPen(QColor(0, 0, 0, 0))) self.dataRect.setFlag(QGraphicsItem.ItemClipsChildrenToShape) self.addItem(self.dataRect) @property def dataShape(self): """ The shape of the scene in QGraphicsView's coordinate system. """ return self._dataShape @dataShape.setter def dataShape(self, value): """ Set the size of the scene in QGraphicsView's coordinate system. dataShape -- (widthX, widthY), where the origin of the coordinate system is in the upper left corner of the screen and 'x' points right and 'y' points down """ assert len(value) == 2 self._dataShape = value self.reset() self._finishViewMatrixChange() def setCacheSize(self, cache_size): if cache_size != self._tileProvider._cache_size: self._tileProvider = TileProvider(self._tiling, self._stackedImageSources, cache_size=cache_size) self._tileProvider.sceneRectChanged.connect( self.invalidateViewports) def cacheSize(self): return self._tileProvider._cache_size def setPrefetchingEnabled(self, enable): self._prefetching_enabled = enable def setPreemptiveFetchNumber(self, n): if n > self.cacheSize() - 1: self._n_preemptive = self.cacheSize() - 1 else: self._n_preemptive = n def preemptiveFetchNumber(self): return self._n_preemptive def invalidateViewports(self, sceneRectF): '''Call invalidate on the intersection of all observing viewport-rects and rectF.''' sceneRectF = sceneRectF if sceneRectF.isValid() else self.sceneRect() for view in self.views(): QGraphicsScene.invalidate( self, sceneRectF.intersected(view.viewportRect())) def reset(self): """Reset rotations, tiling, etc. Called when first initialized and when the underlying data changes. """ self.resetAxes(finish=False) self._tiling = Tiling(self._dataShape, self.data2scene, name=self.name) self._brushingLayer = TiledImageLayer(self._tiling) self._tileProvider = TileProvider(self._tiling, self._stackedImageSources) self._tileProvider.sceneRectChanged.connect(self.invalidateViewports) if self._dirtyIndicator: self.removeItem(self._dirtyIndicator) del self._dirtyIndicator self._dirtyIndicator = DirtyIndicator(self._tiling) self.addItem(self._dirtyIndicator) self._dirtyIndicator.setVisible(False) def __init__(self, posModel, along, preemptive_fetch_number=5, parent=None, name="Unnamed Scene", swapped_default=False): """ * preemptive_fetch_number -- number of prefetched slices; 0 turns the feature off * swapped_default -- whether axes should be swapped by default. """ QGraphicsScene.__init__(self, parent=parent) self._along = along self._posModel = posModel # QGraphicsItems can change this if they are in a state that should temporarily forbid brushing # (For example, when the slice intersection marker is in 'draggable' state.) self.allow_brushing = True self._dataShape = (0, 0) self._dataRect = None #A QGraphicsRectItem (or None) self._offsetX = 0 self._offsetY = 0 self.name = name self._stackedImageSources = StackedImageSources(LayerStackModel()) self._showTileOutlines = False # FIXME: We don't show the red 'progress pies' because they look terrible. # If we could fix their timing, maybe it would be worth it. self._showTileProgress = False self._tileProvider = None self._dirtyIndicator = None self._prefetching_enabled = False self._swappedDefault = swapped_default self.reset() # BowWave preemptive caching self.setPreemptiveFetchNumber(preemptive_fetch_number) self._course = (1, 1) # (along, pos or neg direction) self._time = self._posModel.time self._channel = self._posModel.channel self._posModel.timeChanged.connect(self._onTimeChanged) self._posModel.channelChanged.connect(self._onChannelChanged) self._posModel.slicingPositionChanged.connect( self._onSlicingPositionChanged) self._allTilesCompleteEvent = threading.Event() self.dirty = False def _onSizeChanged(self): self._brushingLayer = TiledImageLayer(self._tiling) def drawForeground(self, painter, rect): if self._tiling is None: return tile_nos = self._tiling.intersected(rect) for tileId in tile_nos: p = self._brushingLayer[tileId] if p.dataVer == p.imgVer: continue p.paint( painter) #access to the underlying image patch is serialized ## draw tile outlines if self._showTileOutlines: # Dashed black line pen = QPen() pen.setDashPattern([5, 5]) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) # Dashed white line # (offset to occupy the spaces in the dashed black line) pen = QPen() pen.setDashPattern([5, 5]) pen.setDashOffset(5) pen.setColor(QColor(Qt.white)) painter.setPen(pen) painter.drawRect(self._tiling.imageRects[tileId]) def indicateSlicingPositionSettled(self, settled): if self._showTileProgress: self._dirtyIndicator.setVisible(settled) def drawBackground(self, painter, sceneRectF): if self._tileProvider is None: return tiles = self._tileProvider.getTiles(sceneRectF) allComplete = True for tile in tiles: #We always draw the tile, even though it might not be up-to-date #In ilastik's live mode, the user sees the old result while adding #new brush strokes on top #See also ilastik issue #132 and tests/lazy_test.py if tile.qimg is not None: painter.drawImage(tile.rectF, tile.qimg) if tile.progress < 1.0: allComplete = False if self._showTileProgress: self._dirtyIndicator.setTileProgress(tile.id, tile.progress) if allComplete: if self.dirty: self.dirty = False self.dirtyChanged.emit() self._allTilesCompleteEvent.set() else: if not self.dirty: self.dirty = True self.dirtyChanged.emit() self._allTilesCompleteEvent.clear() # preemptive fetching if self._prefetching_enabled: for through in self._bowWave(self._n_preemptive): self._tileProvider.prefetch(sceneRectF, through) def joinRenderingAllTiles(self, viewport_only=True): """ Wait until all tiles in the scene have been 100% rendered. If sceneRectF is None, use the viewport rect. If sceneRectF is an invalid QRectF(), then wait for all tiles. Note: This is useful for testing only. If called from the GUI thread, the GUI thread will block until all tiles are rendered! """ # If this is the main thread, keep repainting (otherwise we'll deadlock). if threading.current_thread().name == "MainThread": if viewport_only: sceneRectF = self.views()[0].viewportRect() else: sceneRectF = QRectF() # invalid QRectF means 'get all tiles' self._tileProvider.waitForTiles(sceneRectF) else: self._allTilesCompleteEvent.wait() def _bowWave(self, n): shape5d = self._posModel.shape5D sl5d = self._posModel.slicingPos5D through = [sl5d[self._along[i]] for i in xrange(3)] t_max = [shape5d[self._along[i]] for i in xrange(3)] BowWave = [] a = self._course[0] for d in xrange(1, n + 1): m = through[a] + d * self._course[1] if m < t_max[a] and m >= 0: t = list(through) t[a] = m BowWave.append(tuple(t)) return BowWave def _onSlicingPositionChanged(self, new, old): if (new[self._along[1] - 1] - old[self._along[1] - 1]) < 0: self._course = (1, -1) else: self._course = (1, 1) def _onChannelChanged(self, new): if (new - self._channel) < 0: self._course = (2, -1) else: self._course = (2, 1) self._channel = new def _onTimeChanged(self, new): if (new - self._time) < 0: self._course = (0, -1) else: self._course = (0, 1) self._time = new
class RectangleSelectionAction(UserInteraction): """ Select items in the scene using a Rectangle selection """ def __init__(self, document, *args, **kwargs): UserInteraction.__init__(self, document, *args, **kwargs) # The initial selection at drag start self.initial_selection = None # Selection when last updated in a mouseMoveEvent self.last_selection = None # A selection rect (`QRectF`) self.selection_rect = None # Keyboard modifiers self.modifiers = 0 def mousePressEvent(self, event): pos = event.scenePos() any_item = self.scene.item_at(pos) if not any_item and event.button() & Qt.LeftButton: self.modifiers = event.modifiers() self.selection_rect = QRectF(pos, QSizeF(0, 0)) self.rect_item = QGraphicsRectItem( self.selection_rect.normalized() ) self.rect_item.setPen( QPen(QBrush(QColor(51, 153, 255, 192)), 0.4, Qt.SolidLine, Qt.RoundCap) ) self.rect_item.setBrush( QBrush(QColor(168, 202, 236, 192)) ) self.rect_item.setZValue(-100) # Clear the focus if necessary. if not self.scene.stickyFocus(): self.scene.clearFocus() if not self.modifiers & Qt.ControlModifier: self.scene.clearSelection() event.accept() return True else: self.cancel(self.ErrorReason) return False def mouseMoveEvent(self, event): if not self.rect_item.scene(): # Add the rect item to the scene when the mouse moves. self.scene.addItem(self.rect_item) self.update_selection(event) return True def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.initial_selection is None: # A single click. self.scene.clearSelection() else: self.update_selection(event) self.end() return True def update_selection(self, event): """ Update the selection rectangle from a QGraphicsSceneMouseEvent `event` instance. """ if self.initial_selection is None: self.initial_selection = set(self.scene.selectedItems()) self.last_selection = self.initial_selection pos = event.scenePos() self.selection_rect = QRectF(self.selection_rect.topLeft(), pos) # Make sure the rect_item does not cause the scene rect to grow. rect = self._bound_selection_rect(self.selection_rect.normalized()) # Need that 0.5 constant otherwise the sceneRect will still # grow (anti-aliasing correction by QGraphicsScene?) pw = self.rect_item.pen().width() + 0.5 self.rect_item.setRect(rect.adjusted(pw, pw, -pw, -pw)) selected = self.scene.items(self.selection_rect.normalized(), Qt.IntersectsItemShape, Qt.AscendingOrder) selected = set([item for item in selected if \ item.flags() & Qt.ItemIsSelectable]) if self.modifiers & Qt.ControlModifier: for item in selected | self.last_selection | \ self.initial_selection: item.setSelected( (item in selected) ^ (item in self.initial_selection) ) else: for item in selected.union(self.last_selection): item.setSelected(item in selected) self.last_selection = set(self.scene.selectedItems()) def end(self): self.initial_selection = None self.last_selection = None self.modifiers = 0 self.rect_item.hide() if self.rect_item.scene() is not None: self.scene.removeItem(self.rect_item) UserInteraction.end(self) def viewport_rect(self): """ Return the bounding rect of the document's viewport on the scene. """ view = self.document.view() vsize = view.viewport().size() viewportrect = QRect(0, 0, vsize.width(), vsize.height()) return view.mapToScene(viewportrect).boundingRect() def _bound_selection_rect(self, rect): """ Bound the selection `rect` to a sensible size. """ srect = self.scene.sceneRect() vrect = self.viewport_rect() maxrect = srect.united(vrect) return rect.intersected(maxrect)
class SequenceScoreFace(StaticItemFace): def __init__(self, dict_values_pcoc, col_height=10, col_width=10, fsize=9): faces.Face.__init__(self) self.type = "item" self.item = None self.dict_values_pcoc = dict_values_pcoc self.nb_values = len(dict_values_pcoc.values()[0]) self.col_w = float(col_width) self.col_h = float(col_height) self.fsize = fsize self.nb_models = len(dict_values_pcoc.keys()) self.height = (self.nb_models + 1) * self.col_h self.width = self.nb_values * self.col_w self.x_axis = False self.hp = [] def draw_fun(self, x, y, val, col_width=10, col_height=10, color="gray"): color = get_corr_color(val) rect = QGraphicsRectItem(x, y, col_width, col_height, parent=self.item) rect.setPen(QPen(QColor('black'))) rect.setBrush(QColor(color)) def draw_legend(self): legend_h = self.height * ((self.nb_models - 1) / float(self.nb_models)) if legend_h < 35: legend_h = 35 legend_rect = QGraphicsRectItem(-20, 0, 10, legend_h, parent=self.item) x0 = -20 n_cat = 6. for y, str_y in [(1, 1), (1 / n_cat * 5, 0.99), (1 / n_cat * 4, 0.9), (1 / n_cat * 3, 0.8), (1 / n_cat * 2, 0.7), (1 / n_cat * 1, 0.5), (1 / n_cat * 0, 0)]: y_stick = legend_h - y * legend_h lineItem = QGraphicsLineItem(x0 - 5, y_stick, x0, y_stick, parent=self.item) lineItem.setPen(QPen(QColor('black'))) text = QGraphicsSimpleTextItem(str(str_y)) text.setFont(QFont("Arial", self.fsize - 4)) text.setParentItem(self.item) tw = text.boundingRect().width() th = text.boundingRect().height() # Center text according to masterItem size text.setPos(x0 - tw - 7, y_stick - th / 2) for (y1, y2, c) in [(1, 1 / n_cat * 5, 0.99), (1 / n_cat * 5, 1 / n_cat * 4, 0.9), (1 / n_cat * 4, 1 / n_cat * 3, 0.8), (1 / n_cat * 3, 1 / n_cat * 2, 0.7), (1 / n_cat * 2, 1 / n_cat * 1, 0.5), (1 / n_cat * 1, 1 / n_cat * 0, 0)]: y1_stick = legend_h - y1 * legend_h y2_stick = legend_h - y2 * legend_h self.draw_fun(x0, y1_stick, c, col_width=10, col_height=y2_stick - y1_stick) def draw_x_axis(self): y0 = self.nb_models * self.col_h + 5 lineItem = QGraphicsLineItem(self.col_w / 2, y0, self.width - self.col_w / 2, y0, parent=self.item) lineItem.setPen(QPen(QColor('black'))) lineItem.setZValue(10) all_vals = list(range(0, self.nb_values, self.x_inter_values)) if (self.nb_values - 1) % self.x_inter_values: all_vals += [self.nb_values - 1] hp_x = [] if self.hp: for x in list(range(0, self.nb_values)): if self.x_values[x] in self.hp: hp_x.append(x) if not x in all_vals: all_vals += [x] all_vals.sort() for x in all_vals: lineItem = QGraphicsLineItem(0, y0, 0, y0 + 6, parent=self.item) lineItem.setX(x * self.col_w + self.col_w / 2) lineItem.setPen(QPen(QColor('black'))) lineItem.setZValue(10) if x in hp_x: text = QGraphicsSimpleTextItem("*" + str(self.x_values[x])) qfont = QFont("Arial", self.fsize - 1) #qfont.setBold(True) text.setFont(qfont) else: text = QGraphicsSimpleTextItem(" " + str(self.x_values[x])) text.setFont(QFont("Arial", self.fsize - 1)) text.rotate(-90) text.setParentItem(self.item) text.setZValue(10) tw = text.boundingRect().width() th = text.boundingRect().height() # Center text according to masterItem size text.setPos(x * self.col_w - th / 2 + self.col_w / 2, tw + y0 + 7) def update_items(self): rect_h = self.height if self.x_axis: rect_h += 30 self.item = QGraphicsRectItem(0, 0, self.width + 40, rect_h) self.item.setPen(QPen(QColor('white'))) #X axis if self.x_axis: self.draw_x_axis() # Legend self.draw_legend() # Y axes and colo rect yi = -1 for model in ["PCOC", "PC", "OC", "Topological", "Identical"]: if self.dict_values_pcoc.has_key(model): yi += 1 y = yi * self.col_w # Y axes ## Stick yaxis = (yi + 0.5) * self.col_w lineItem = QGraphicsLineItem(self.width, yaxis, self.width + 5, yaxis, parent=self.item) lineItem.setPen(QPen(QColor('black'))) ## Text text = QGraphicsSimpleTextItem(model) text.setFont(QFont("Arial", self.fsize - 2)) text.setParentItem(self.item) tw = text.boundingRect().width() th = text.boundingRect().height() ## Center text according to masterItem size text.setPos(self.width + 5, yaxis - th / 2) # Color rect for each model values = self.dict_values_pcoc[model] for i, val in enumerate(values): self.draw_fun(i * self.col_w, y, val, col_width=self.col_w)