def paintEvent(self, event): if self.__pixmap.isNull(): return sourcerect = QRect(QPoint(0, 0), self.__pixmap.size()) pixsize = QSizeF(self.__pixmap.size()) rect = self.contentsRect() pixsize.scale(QSizeF(rect.size()), Qt.KeepAspectRatio) targetrect = QRectF(QPointF(0, 0), pixsize) targetrect.moveCenter(QPointF(rect.center())) painter = QPainter(self) painter.setRenderHint(QPainter.SmoothPixmapTransform) painter.drawPixmap(targetrect, self.__pixmap, QRectF(sourcerect)) painter.end()
def __naturalsh(self): fm = QFontMetrics(self.font()) spacing = self.__spacing N = len(self.__items) width = max((fm.width(text) for text in self.__items), default=0) height = N * fm.height() + (N - 1) * spacing return QSizeF(width, height)
def boundingRect(self): if hasattr(self, "attr"): attr_rect = QRectF(QPointF(0, -self.attr_text_h), QSizeF(self.attr_text_w, self.attr_text_h)) else: attr_rect = QRectF(0, 0, 1, 1) rect = self.rect().adjusted(-5, -5, 5, 5) return rect | attr_rect
def __init__(self, widget, parent=None, max_size=150, **kwargs): self._max_size = QSizeF(max_size, max_size) # We store the offsets from the top left corner to move widget properly self.__offset_x = self.__offset_y = 0 super().__init__(widget, parent, **kwargs) self._resize_widget()
def __naturalsh(self) -> QSizeF: """Return the natural size hint (preferred sh with no constraints).""" fm = QFontMetrics(self.font()) spacing = self.__spacing N = len(self.__items) width = self.__width_for_font(self.font()) height = N * fm.height() + max(N - 1, 0) * spacing return QSizeF(width, height)
def sizeHint(self, which, constraint=QSizeF()): if which == Qt.PreferredSize: sh = self.__naturalsh() if 0 < constraint.height() < sh.height(): sh = scaled(sh, constraint, Qt.KeepAspectRatioByExpanding) return sh return super().sizeHint(which, constraint)
def sizeHint(self, which, constraint=QRectF()): # type: (Qt.SizeHint, QSizeF) -> QSizeF pw = 1. sh = QSizeF() if which == Qt.MinimumSize: sh = QSizeF(pw, pw) elif which == Qt.PreferredSize: sh = QSizeF(pw, 30.) elif which == Qt.MaximumSize: sh = QSizeF(pw, QWIDGETSIZE_MAX) if self.__orientation == Qt.Horizontal: sh = sh.transposed() return sh
def adjustSize(self): """Resize to a reasonable size. """ self.__textItem.setTextWidth(-1) self.__textItem.adjustSize() size = self.__textItem.boundingRect().size() left, top, right, bottom = self.textMargins() geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom)) self.setGeometry(geom)
def __init__(self, color, parent): super().__init__(parent) height, width = self.SIZE.height(), self.SIZE.width() self.__circle = QGraphicsEllipseItem(0, 0, height, width) self.__circle.setBrush(QBrush(color)) self.__circle.setPen(QPen(QColor(0, 0, 0, 0))) self.__circle.setParentItem(self) self._size_hint = QSizeF(self.__circle.boundingRect().size())
def pixmapTransform(self) -> QTransform: if self.__pixmap.isNull(): return QTransform() pxsize = QSizeF(self.__pixmap.size()) crect = self.contentsRect() transform = QTransform() transform = transform.translate(crect.left(), crect.top()) if self.__scaleContents: csize = scaled(pxsize, crect.size(), self.__aspectMode) else: csize = pxsize xscale = csize.width() / pxsize.width() yscale = csize.height() / pxsize.height() return transform.scale(xscale, yscale)
def paint(self, painter, option, index): # type: (QPainter, QStyleOptionViewItem, QModelIndex) -> None scene = index.data(Qt.DisplayRole) # type: Optional[QGraphicsScene] if scene is None: super().paint(painter, option, index) return painter.save() rect = QRectF(QPointF(option.rect.topLeft()), QSizeF(option.rect.size())) if option.state & QStyle.State_Selected: painter.setPen(QPen(QColor(125, 162, 206, 192))) painter.setBrush(QBrush(QColor(217, 232, 252, 192))) else: painter.setPen(QPen(QColor('#ebebeb'))) painter.drawRoundedRect(rect, 3, 3) painter.restore() painter.setRenderHint(QPainter.Antialiasing) # The sceneRect doesn't automatically shrink to fit contents, so when # drawing smaller tree, remove any excess space aroung the tree scene.setSceneRect(scene.itemsBoundingRect()) # Make sure the tree is centered in the item cell # First, figure out how much we get the bounding rect to the size of # the available painting rect scene_rect = scene.itemsBoundingRect() w_scale = option.rect.width() / scene_rect.width() h_scale = option.rect.height() / scene_rect.height() # In order to keep the aspect ratio, we use the same scale scale = min(w_scale, h_scale) # Figure out the rescaled scene width/height scene_w = scale * scene_rect.width() scene_h = scale * scene_rect.height() # Figure out how much we have to offset the rect so that the scene will # be painted in the centre of the rect offset_w = (option.rect.width() - scene_w) / 2 offset_h = (option.rect.height() - scene_h) / 2 offset = option.rect.topLeft() + QPointF(offset_w, offset_h) # Finally, we have all the data for the new rect in which to render target_rect = QRectF(offset, QSizeF(scene_w, scene_h)) scene.render(painter, target=target_rect, mode=Qt.KeepAspectRatio)
def sizeHint(self, which: Qt.SizeHint, constraint=QSizeF()) -> QSizeF: # reimplemented fm = QFontMetrics(self.font()) spacing = fm.lineSpacing() mleft, mtop, mright, mbottom = self.getContentsMargins() if self._root and which == Qt.PreferredSize: nleaves = len( [node for node in self._items.keys() if not node.branches]) base = max(10, min(spacing * 16, 250)) if self.orientation in [self.Left, self.Right]: return QSizeF(base, spacing * nleaves + mleft + mright) else: return QSizeF(spacing * nleaves + mtop + mbottom, base) elif which == Qt.MinimumSize: return QSizeF(mleft + mright + 10, mtop + mbottom + 10) else: return QSizeF()
def paint(self, painter, option, widget=0): if self._pixmap.isNull(): return rect = self.contentsRect() pixsize = QSizeF(self._pixmap.size()) aspectmode = (Qt.KeepAspectRatio if self._keepAspect else Qt.IgnoreAspectRatio) pixsize.scale(rect.size(), aspectmode) pixrect = QRectF(QPointF(0, 0), pixsize) pixrect.moveCenter(rect.center()) painter.save() painter.setPen(QPen(QColor(0, 0, 0, 50), 3)) painter.drawRoundedRect(pixrect, 2, 2) painter.setRenderHint(QPainter.SmoothPixmapTransform) source = QRectF(QPointF(0, 0), QSizeF(self._pixmap.size())) painter.drawPixmap(pixrect, self._pixmap, source) painter.restore()
class LegendGradient(QGraphicsWidget): """Gradient widget. A gradient square bar that can be used to display continuous values. Parameters ---------- palette : iterable[QColor] parent : QGraphicsWidget orientation : Qt.Orientation Notes ----- .. note:: While the gradient does support any number of colors, any more than 3 is not very readable. This should not be a problem, since Orange only implements 2 or 3 colors. """ # Default sizes (assume gradient is vertical by default) GRADIENT_WIDTH = 20 GRADIENT_HEIGHT = 150 _size_hint = QSizeF(GRADIENT_WIDTH, GRADIENT_HEIGHT) def __init__(self, palette, parent, orientation): super().__init__(parent) self.__gradient = QLinearGradient() num_colors = len(palette) for idx, stop in enumerate(palette): self.__gradient.setColorAt(idx * (1. / (num_colors - 1)), stop) # We need to tell the gradient where it's start and stop points are self.__gradient.setStart(QPointF(0, 0)) if orientation == Qt.Vertical: final_stop = QPointF(0, self.GRADIENT_HEIGHT) else: final_stop = QPointF(self.GRADIENT_HEIGHT, 0) self.__gradient.setFinalStop(final_stop) # Get the appropriate rectangle dimensions based on orientation if orientation == Qt.Vertical: width, height = self.GRADIENT_WIDTH, self.GRADIENT_HEIGHT elif orientation == Qt.Horizontal: width, height = self.GRADIENT_HEIGHT, self.GRADIENT_WIDTH self.__rect_item = QGraphicsRectItem(0, 0, width, height, self) self.__rect_item.setPen(QPen(QColor(0, 0, 0, 0))) self.__rect_item.setBrush(QBrush(self.__gradient)) self._size_hint = QSizeF(self.__rect_item.boundingRect().size()) def sizeHint(self, size_hint, size_constraint=None, *args, **kwargs): return self._size_hint
def paint(self, painter, option, widget=0): if self._pixmap.isNull(): return rect = self.contentsRect() pixsize = QSizeF(self._pixmap.size()) aspectmode = (Qt.KeepAspectRatio if self._keepAspect else Qt.IgnoreAspectRatio) if self._crop: height, width = pixsize.height(), pixsize.width() diff = abs(height - width) if height > width: y, x = diff / 2, 0 h, w = height - diff, width else: x, y = diff / 2, 0 w, h = width - diff, height source = QRectF(x, y, w, h) pixrect = QRectF(QPointF(0, 0), rect.size()) else: source = QRectF(QPointF(0, 0), pixsize) pixsize.scale(rect.size(), aspectmode) pixrect = QRectF(QPointF(0, 0), pixsize) if self._subset: painter.setOpacity(1.0) else: painter.setOpacity(0.35) pixrect.moveCenter(rect.center()) painter.save() painter.setPen(QPen(QColor(0, 0, 0, 50), 3)) painter.drawRoundedRect(pixrect, 2, 2) painter.setRenderHint(QPainter.SmoothPixmapTransform) painter.drawPixmap(pixrect, self._pixmap, source) painter.restore()
def _resizeToFit(self): widget = self.__centralWidget size = self.__viewportContentSize() vprect = self.viewport().geometry() vprect.setSize(size) margins = self.viewportMargins() vprect = vprect.marginsRemoved(margins) viewrect = self.mapToScene(vprect).boundingRect() targetsize = viewrect.size() maxsize = widget.maximumSize() minsize = widget.minimumSize() targetsize = targetsize.expandedTo(minsize).boundedTo(maxsize) sh = widget.effectiveSizeHint(Qt.PreferredSize) policy = widget.sizePolicy() vpolicy = policy.verticalPolicy() hpolicy = policy.horizontalPolicy() if not self.__fitInView: widget.resize(sh.expandedTo(minsize).boundedTo(maxsize)) return width = adjusted_size( sh.width(), targetsize.width(), minsize.width(), maxsize.width(), hpolicy) height = adjusted_size( sh.height(), targetsize.height(), minsize.height(), maxsize.height(), vpolicy) if policy.hasHeightForWidth(): constr = QSizeF(width, -1) height = adjusted_size( widget.effectiveSizeHint(Qt.PreferredSize, constr).height(), targetsize.height(), widget.effectiveSizeHint(Qt.MinimumSize, constr).height(), widget.effectiveSizeHint(Qt.MaximumSize, constr).height(), QSizePolicy.Fixed ) widget.resize(QSizeF(width, height))
def __gridlayout(self): assert self.__layoutMode == GraphicsThumbnailGrid.FixedColumnCount width = (self.size().width() - self.__columnCount * 10) / self.__columnCount height = (self.size().height() - self.__rowCount * 10) / self.__rowCount item_size = min(width, height) for item in self.__thumbnails: item.setThumbnailSize(QSizeF(item_size, item_size)) self.__relayoutGrid(self.__columnCount)
def __gridlayout(self): assert self.__layoutMode == GraphicsThumbnailGrid.FixedColumnCount width = ((self.size().width() - self.__columnCount * 10) / self.__columnCount) height = (self.size().height() - self.__rowCount * 10) / self.__rowCount for item in self.__thumbnails: label_size = item.label.height() + 1 if item.label is not None else 0 item_size = min(width, height - label_size) item.setThumbnailSize(QSizeF(item_size, item_size)) self.__relayoutGrid(self.__columnCount)
def sizeHint(self, which, constraint=QSizeF()): if which == Qt.PreferredSize: doc = self.document() textwidth = doc.textWidth() if textwidth != constraint.width(): cloned = doc.clone(self) cloned.setTextWidth(constraint.width()) sh = cloned.size() cloned.deleteLater() else: sh = doc.size() return sh else: return super().sizeHint(which, constraint)
def defaultTextGeometry(self, point): """ Return the default text geometry. Used in case the user single clicked in the scene. """ font = self.annotation_item.font() metrics = QFontMetrics(font) spacing = metrics.lineSpacing() margin = self.annotation_item.document().documentMargin() rect = QRectF(QPointF(point.x(), point.y() - spacing - margin), QSizeF(150, spacing + 2 * margin)) return rect
def sizeHint(self, which: Qt.SizeHint, constraint=QSizeF()) -> QSizeF: """Reimplemented.""" if which == Qt.PreferredSize: sh = self.__naturalsh() if self.__orientation == Qt.Vertical: if 0 < constraint.height() < sh.height(): sh = scaled(sh, constraint, Qt.KeepAspectRatioByExpanding) else: sh = sh.transposed() if 0 < constraint.width() < sh.width(): sh = scaled(sh, constraint, Qt.KeepAspectRatioByExpanding) else: sh = super().sizeHint(which, constraint) return sh
def __init__(self, graph, scene): QGraphicsObject.__init__(self) if scene: scene.addItem(self) self.graph = graph self.curves = [] self.items = {} self.attributes = [] self.point_attrs = {} self.point_vals = {} self.default_values = { PointColor: Qt.black, PointSize: 8, PointSymbol: OWPoint.Ellipse } self.box_rect = QRectF() self.setFiltersChildEvents(True) self.setFlag(self.ItemHasNoContents, True) self.mouse_down = False self._orientation = Qt.Vertical self.max_size = QSizeF() self._floating = True self._floating_animation = None self._mouse_down_pos = QPointF()
def __init__(self, parent=None, direction=Qt.LeftToRight, node=None, icon=None, iconSize=None, **args): super().__init__(parent, **args) self.setAcceptedMouseButtons(Qt.NoButton) self.__direction = direction self.setLayout(QGraphicsLinearLayout(Qt.Horizontal)) # Set the maximum size, otherwise the layout can't grow beyond its # sizeHint (and we need it to grow so the widget can grow and keep the # contents centered vertically. self.layout().setMaximumSize(QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.__iconSize = iconSize or QSize(64, 64) self.__icon = icon self.__iconItem = QGraphicsPixmapItem(self) self.__iconLayoutItem = GraphicsItemLayoutItem(item=self.__iconItem) self.__channelLayout = QGraphicsGridLayout() self.channelAnchors = [] if self.__direction == Qt.LeftToRight: self.layout().addItem(self.__iconLayoutItem) self.layout().addItem(self.__channelLayout) channel_alignemnt = Qt.AlignRight else: self.layout().addItem(self.__channelLayout) self.layout().addItem(self.__iconLayoutItem) channel_alignemnt = Qt.AlignLeft self.layout().setAlignment(self.__iconLayoutItem, Qt.AlignCenter) self.layout().setAlignment(self.__channelLayout, Qt.AlignVCenter | channel_alignemnt) self.node: Optional[SchemeNode] = None self.channels: Union[List[InputSignal], List[OutputSignal]] = [] if node is not None: self.setSchemeNode(node)
def sizeHint(self, which: Qt.SizeHint, constraint=QSizeF(-1, -1)) -> QSizeF: if which == Qt.PreferredSize: brect = self.item.boundingRect() brect = self.item.mapRectToParent(brect) scale = self.__transform size = brect.size() if scale is not None: # undo the scaling sx, sy = self.__scale size = QSizeF(float(Fraction(size.width()) / sx), float(Fraction(size.height()) / sy)) if constraint != QSizeF(-1, -1): size = scaled(size, constraint, self.__aspectMode) return size else: return QSizeF()
def setupScene(self): self.error() if self.data: attr = self.stringAttrs[self.smilesAttr] titleAttr = self.allAttrs[self.titleAttr] assert self.thumbnailView.count() == 0 size = QSizeF(self.imageSize, self.imageSize) for i, inst in enumerate(self.data): if not numpy.isfinite(inst[attr]): # skip missing continue smiles = str(inst[attr]) title = str(inst[titleAttr]) thumbnail = GraphicsThumbnailWidget(QPixmap(), title=title) thumbnail.setThumbnailSize(size) thumbnail.setToolTip(smiles) thumbnail.instance = inst self.thumbnailView.addThumbnail(thumbnail) if self.check_smiles(smiles): pixmap = self.pixmap_from_smiles(smiles) thumbnail.setPixmap(pixmap) self._successcount += 1 else: pixmap = QPixmap() thumbnail.setPixmap(pixmap) thumbnail.setToolTip(thumbnail.toolTip() + "\nInvalid SMILES") self._errcount += 1 future = Future() future.set_result(pixmap) future._reply = None self.items.append(_ImageItem(i, thumbnail, smiles, future)) if any(it.future is not None and not it.future.done() for it in self.items): self.info.setText("Retrieving...\n") else: self._updateStatus()
def __doLayout(self, rect: QRectF) -> Iterable[QRectF]: x = y = 0 rowheight = 0 width = rect.width() spacing_x, spacing_y = self.__spacing first_in_row = True rows: List[List[QRectF]] = [[]] def break_(): nonlocal x, y, rowheight, first_in_row y += rowheight + spacing_y x = 0 rowheight = 0 first_in_row = True rows.append([]) items = [ _FlowLayoutItem(item=item, geom=QRectF(), size=QSizeF()) for item in self.__items ] for flitem in items: item = flitem.item sh = item.effectiveSizeHint(Qt.PreferredSize) if x + sh.width() > width and not first_in_row: break_() r = QRectF(rect.x() + x, rect.y() + y, sh.width(), sh.height()) flitem.geom = r flitem.size = sh flitem.row = len(rows) - 1 rowheight = max(rowheight, sh.height()) x += sh.width() + spacing_x first_in_row = False rows[-1].append(flitem.geom) alignment = Qt.AlignVCenter | Qt.AlignLeft for flitem in items: row = rows[flitem.row] row_rect = reduce(QRectF.united, row, QRectF()) if row_rect.isEmpty(): continue flitem.geom = qrect_aligned_to(flitem.geom, row_rect, alignment & Qt.AlignVertical_Mask) return [fli.geom for fli in items]
def item_at(self, pos, type_or_tuple=None, buttons=0): """Return the item at `pos` that is an instance of the specified type (`type_or_tuple`). If `buttons` (`Qt.MouseButtons`) is given only return the item if it is the top level item that would accept any of the buttons (`QGraphicsItem.acceptedMouseButtons`). """ rect = QRectF(pos, QSizeF(1, 1)) items = self.items(rect) if buttons: items = itertools.dropwhile( lambda item: not item.acceptedMouseButtons() & buttons, items) items = list(items)[:1] if type_or_tuple: items = [i for i in items if isinstance(i, type_or_tuple)] return items[0] if items else None
def sizeHint(self, which, constraint=QRectF()): # type: (Qt.SizeHint, QSizeF) -> QSizeF pw = 1. sh = QSizeF() if which == Qt.MinimumSize: sh = QSizeF(pw, pw) elif which == Qt.PreferredSize: sh = QSizeF(pw, 30.) elif which == Qt.MaximumSize: sh = QSizeF(pw, QWIDGETSIZE_MAX) if self.__orientation == Qt.Horizontal: sh.transpose() # Qt4 compatible return sh
def export(self, filename=None): pw = QPdfWriter(filename) dpi = QApplication.desktop().logicalDpiX() pw.setResolution(dpi) pw.setPageMargins(QMarginsF(0, 0, 0, 0)) pw.setPageSizeMM(QSizeF(self.getTargetRect().size()) / dpi * 25.4) painter = QPainter(pw) try: self.setExportMode(True, {'antialias': True, 'background': self.background, 'painter': painter}) painter.setRenderHint(QPainter.Antialiasing, True) if QtCore.QT_VERSION >= 0x050D00: painter.setRenderHint(QPainter.LosslessImageRendering, True) self.getScene().render(painter, QRectF(self.getTargetRect()), QRectF(self.getSourceRect())) finally: self.setExportMode(False) painter.end()
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
def __init__(self, pixmap, parentItem=None, crop=False, in_subset=True, add_label=False, text="", **kwargs): super().__init__(parentItem, **kwargs) self.setFocusPolicy(Qt.StrongFocus) self._size = QSizeF() layout = QGraphicsLinearLayout(Qt.Vertical, self) layout.setSpacing(1) layout.setContentsMargins(5, 5, 5, 5) self.setContentsMargins(0, 0, 0, 0) self.pixmapWidget = GraphicsPixmapWidget(pixmap, self) self.pixmapWidget.setCrop(crop) self.pixmapWidget.setSubset(in_subset) self.selectionBrush = DEFAULT_SELECTION_BRUSH self.selectionPen = DEFAULT_SELECTION_PEN layout.addItem(self.pixmapWidget) self.label = None if add_label: l1 = ElidedLabel(text) l1.setStyleSheet("background-color: rgba(255, 255, 255, 10);") l1.setAlignment(Qt.AlignCenter) l1.setFixedHeight(16) self.label = l1 gs = QGraphicsScene() w = gs.addWidget(l1) layout.addItem(w) layout.setAlignment(self.pixmapWidget, Qt.AlignCenter) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
def sizeHint(self, which, constraint=QSizeF(-1, -1)) -> QSizeF: if which == Qt.PreferredSize: sh = QSizeF(self.__pixmap.size()) if self.__scaleContents: sh = scaled(sh, constraint, self.__aspectMode) return sh elif which == Qt.MinimumSize: if self.__scaleContents: return QSizeF(0, 0) else: return QSizeF(self.__pixmap.size()) elif which == Qt.MaximumSize: if self.__scaleContents: return QSizeF() else: return QSizeF(self.__pixmap.size()) else: # Qt.MinimumDescent return QSizeF()
def mouseReleaseEvent(self, event): QGraphicsScene.mouseReleaseEvent(self, event) if event.button() == Qt.LeftButton: modifiers = event.modifiers() path = QPainterPath() # the mouse was moved if self.selectionRect: path.addRect(self.selectionRect.rect()) self.removeItem(self.selectionRect) self.selectionRect = None # the mouse was only clicked - create a selection area of 1x1 size else: rect = QRectF(event.buttonDownScenePos(Qt.LeftButton), QSizeF(1., 1.)).intersected(self.sceneRect()) path.addRect(rect) self.setSelectionArea(path) self.selectionChanged.emit(set(self.selectedItems()), modifiers)
def __init__(self, graph, scene): QGraphicsObject.__init__(self) if scene: scene.addItem(self) self.graph = graph self.curves = [] self.items = {} self.attributes = [] self.point_attrs = {} self.point_vals = {} self.default_values = { PointColor : Qt.black, PointSize : 8, PointSymbol : OWPoint.Ellipse } self.box_rect = QRectF() self.setFiltersChildEvents(True) self.setFlag(self.ItemHasNoContents, True) self.mouse_down = False self._orientation = Qt.Vertical self.max_size = QSizeF() self._floating = True self._floating_animation = None self._mouse_down_pos = QPointF()
class OWLegend(QGraphicsObject): """ A legend for :obj:`.OWPlot`. Its items are arranged into a hierarchy by `category`. This is useful when points differ in more than one attribute. In such a case, there can be one category for point color and one for point shape. Usually the category name will be the name of the attribute, while the item's title will be the value. Arbitrary categories can be created, for an example see :meth:`.OWPlot.update_axes`, which creates a special category for unused axes. decimals .. image:: files/legend-categories.png In the image above, `type` and `milk` are categories with 7 and 2 possible values, respectively. """ def __init__(self, graph, scene): QGraphicsObject.__init__(self) if scene: scene.addItem(self) self.graph = graph self.curves = [] self.items = {} self.attributes = [] self.point_attrs = {} self.point_vals = {} self.default_values = { PointColor : Qt.black, PointSize : 8, PointSymbol : OWPoint.Ellipse } self.box_rect = QRectF() self.setFiltersChildEvents(True) self.setFlag(self.ItemHasNoContents, True) self.mouse_down = False self._orientation = Qt.Vertical self.max_size = QSizeF() self._floating = True self._floating_animation = None self._mouse_down_pos = QPointF() def clear(self): """ Removes all items from the legend """ for lst in self.items.values(): for i in lst: i.setParentItem(None) if self.scene(): self.scene().removeItem(i) self.items = {} self.update_items() def add_curve(self, curve): """ Adds a legend item with the same point symbol and name as ``curve``. If the curve's name contains the equal sign (=), it is split at that sign. The first part of the curve is a used as the category, and the second part as the value. """ i = curve.name.find('=') if i == -1: cat = '' name = curve.name else: cat = curve.name[:i] name = curve.name[i+1:] self.add_item(cat, name, curve.point_item(0, 0, 0)) def add_item(self, category, value, point): """ Adds an item with title ``value`` and point symbol ``point`` to the specified ``category``. """ if category not in self.items: self.items[category] = [OWLegendTitle(category, self)] self.items[category].append(OWLegendItem(str(value), point, self)) self.update_items() def add_color_gradient(self, title, values): if len(values) < 2: # No point in showing a gradient with less that two values return if title in self.items: self.remove_category(title) item = OWLegendGradient(self.graph.contPalette, [str(v) for v in values], self) self.items[title] = [OWLegendTitle(title, self), item] self.update_items() def remove_category(self, category): """ Removes ``category`` and all items that belong to it. """ if category not in self.items: return if self.scene(): for item in self.items[category]: self.scene().removeItem(item) del self.items[category] def update_items(self): """ Updates the legend, repositioning the items according to the legend's orientation. """ self.box_rect = QRectF() x = y = 0 for lst in self.items.values(): for item in lst: if hasattr(item, 'text_item'): item.text_item.setDefaultTextColor(self.graph.color(OWPalette.Text)) if hasattr(item, 'rect_item'): item.rect_item.setBrush(self.graph.color(OWPalette.Canvas)) if hasattr(item, 'set_orientation'): item.set_orientation(self._orientation) if self._orientation == Qt.Vertical: for lst in self.items.values(): for item in lst: if self.max_size.height() and y and y + item.boundingRect().height() > self.max_size.height(): y = 0 x = x + item.boundingRect().width() self.box_rect = self.box_rect | item.boundingRect().translated(x, y) move_item_xy(item, x, y, self.graph.animate_plot) y = y + item.boundingRect().height() elif self._orientation == Qt.Horizontal: for lst in self.items.values(): max_h = max(item.boundingRect().height() for item in lst) for item in lst: if self.max_size.width() and x and x + item.boundingRect().width() > self.max_size.width(): x = 0 y = y + max_h self.box_rect = self.box_rect | item.boundingRect().translated(x, y) move_item_xy(item, x, y, self.graph.animate_plot) x = x + item.boundingRect().width() if lst: x = 0 y = y + max_h def mouseMoveEvent(self, event): self.graph.notify_legend_moved(event.scenePos()) if self._floating: p = event.scenePos() - self._mouse_down_pos if self._floating_animation and self._floating_animation.state() == QPropertyAnimation.Running: self.set_pos_animated(p) else: self.setPos(p) event.accept() def mousePressEvent(self, event): self.setCursor(Qt.ClosedHandCursor) self.mouse_down = True self._mouse_down_pos = event.scenePos() - self.pos() event.accept() def mouseReleaseEvent(self, event): self.unsetCursor() self.mouse_down = False self._mouse_down_pos = QPointF() event.accept() def boundingRect(self): return self.box_rect def paint(self, painter, option, widget=None): pass def set_orientation(self, orientation): """ Sets the legend's orientation to ``orientation``. """ self._orientation = orientation self.update_items() def orientation(self): return self._orientation def set_pos_animated(self, pos): if (self.pos() - pos).manhattanLength() < 6 or not self.graph.animate_plot: self.setPos(pos) else: t = 250 if self._floating_animation and self._floating_animation.state() == QPropertyAnimation.Running: t = t - self._floating_animation.currentTime() self._floating_animation.stop() self._floating_animation = QPropertyAnimation(self, 'pos') self._floating_animation.setEndValue(pos) self._floating_animation.setDuration(t) self._floating_animation.start(QPropertyAnimation.KeepWhenStopped) def set_floating(self, floating, pos=None): """ If floating is ``True``, the legend can be dragged with the mouse. Otherwise, it's fixed in its position. If ``pos`` is specified, the legend is moved there. """ if floating == self._floating: return self._floating = floating if pos: if floating: self.set_pos_animated(pos - self._mouse_down_pos) else: self.set_pos_animated(pos)