class VLine(ChartItem): """ Simple vertical line without vertical bounds. :param parent: Parent item. """ def __init__(self, parent=None): super(VLine, self).__init__(parent) self.chartItemFlags = ChartItemFlags.FLAG_NO_AUTO_RANGE self._x = None self._label = None self._pen = QPen(QBrush(Qt.green), 1.0, Qt.SolidLine) self._pen.setCosmetic(True) self._brush = QBrush(QColor(255, 255, 255, 0)) finfo = np.finfo(np.float32) self.b_rect = QRectF(0, finfo.min / 2.0, 0, finfo.max) @property def label(self): return self._label def setX(self, x, label=None, color=QColor(Qt.red)): """ Sets the Vline's x value. :param x: abscissa value. :param label: :param color: """ self._x = x self.setPos(QPointF(self._x, 0)) if label is not None: self._label = label self._color = color self._pen = QPen(QBrush(color), 1.0, Qt.SolidLine) self._pen.setCosmetic(True) self._brush = QBrush(QColor(255, 255, 255, 0)) def visibleRangeChanged(self, rect): b = min(rect.bottom(), rect.top()) self.b_rect = QRectF(0, b - 10, 0, rect.height() + 20) self.prepareGeometryChange() def paint(self, p=QPainter(), o=QStyleOptionGraphicsItem(), widget=None): # p.setRenderHint(QPainter.Antialiasing) p.setPen(self._pen) p.setBrush(self._brush) p.drawLine( QLineF(QPointF(0, self.b_rect.bottom()), QPointF(0, self.b_rect.top()))) super(VLine, self).paint(p, o, widget) def __del__(self): _log.debug("Finalize VLine {}".format(self))
class HLine(ChartItem): """ Simple horizontal line without horizontal bounds. :param parent: Parent item. """ def __init__(self, parent=None): super(HLine, self).__init__(parent) self.chartItemFlags = ChartItemFlags.FLAG_NO_AUTO_RANGE self._y = None self._label = None self._pen = QPen(QBrush(Qt.green), 1.0, Qt.SolidLine) self._pen.setCosmetic(True) self._brush = QBrush(QColor(255, 255, 255, 0)) finfo = np.finfo(np.float32) self.b_rect = QRectF(finfo.min / 2.0, 0, finfo.max, 0) @property def label(self): return self._label def setY(self, y, label=None, color=QColor(Qt.red)): """ Sets the Hline's y value :param y: Ordinate value :param label: :param color: """ self._y = y self.setPos(QPointF(0, self._y)) if label is not None: self._label = label self._color = color self._pen = QPen(QBrush(color), 1.0, Qt.SolidLine) self._pen.setCosmetic(True) self._brush = QBrush(QColor(255, 255, 255, 0)) def visibleRangeChanged(self, rect): b = min(rect.left(), rect.right()) self.b_rect = QRectF(b - 10, 0, rect.width() + 20, 0) self.prepareGeometryChange() def paint(self, p=QPainter(), o=QStyleOptionGraphicsItem(), widget=None): # p.setRenderHint(QPainter.N) p.setPen(self._pen) p.setBrush(self._brush) p.drawLine( QLineF(QPointF(self.b_rect.left(), 0), QPointF(self.b_rect.right(), 0))) super(HLine, self).paint(p, o, widget) def __del__(self): _log.debug("Finalize HLine {}".format(self))
def addMark(self, new_mark: VideoSliderMark, update: bool = True): """Adds a marked value to the slider. Args: new_mark: value to mark update: Whether to redraw slider with new mark. Returns: None. """ # check if mark is within slider range if new_mark.val > self._val_max: return if new_mark.val < self._val_min: return self._marks.add(new_mark) v_top_pad = self._header_height + 1 v_bottom_pad = 1 v_top_pad += new_mark.top_pad v_bottom_pad += new_mark.bottom_pad width = new_mark.visual_width v_offset = v_top_pad height = new_mark.get_height(container_height=self.box_rect.height() - self._header_height) color = new_mark.QColor pen = QPen(color, 0.5) pen.setCosmetic(True) brush = QBrush(color) if new_mark.filled else QBrush() line = self.scene.addRect(-width // 2, v_offset, width, height, pen, brush) self._mark_items[new_mark] = line if new_mark.mark_type == "tick": # Show tick mark behind other slider marks self._mark_items[new_mark].setZValue(0) # Add a text label to show in header area mark_label_text = ( # sci notation if large f"{new_mark.val + self.tick_index_offset:g}") self._mark_labels[new_mark] = self.scene.addSimpleText( mark_label_text, self._base_font) else: # Show in front of tick marks and behind track lines self._mark_items[new_mark].setZValue(1) if update: self._update_visual_positions()
def makePen(color, lineWidth=1.0, lineStyle=Qt.SolidLine, cosmetic=True): """ Creates and returns a pen. :param color: Any value, the `QColor <http://doc.qt.io/qt-4.8/qcolor.html#QColor>`_ constructor takes as argument :param lineWidth: line width :param lineStyle: see `http://doc.qt.io/qt-4.8/qt.html#PenStyle-enum` :param cosmetic: When set to true the pens with is transformation invariant :return: The constructed pen :rtype: `QPen <http://doc.qt.io/qt-4.8/qpen.html>`_ """ # qColor = QColor(color) pen = QPen(color, lineWidth, lineStyle) pen.setCosmetic(cosmetic) return pen
def __init__(self, data, color=Qt.green, parent=None): if ("size_x" in data.index) and ("size_y" in data.index): size_x = data["size_x"] size_y = data["size_y"] else: size_x = size_y = data["size"] super().__init__(data["x"] - size_x + 0.5, data["y"] - size_y + 0.5, 2 * size_x, 2 * size_y, parent) pen = QPen() pen.setWidthF(1.25) pen.setCosmetic(True) pen.setColor(color) self.setPen(pen) ttStr = "\n".join([ "{}: {:.2f}".format(k, v) for k, v in data.items() if k != "frame" ]) self.setToolTip(ttStr)
def showTracks(self): # clear self.pathitems self.clearTracks() frame = self.window.imageview.currentIndex if frame<len(self.tracks_by_frame): tracks = self.tracks_by_frame[frame] pen = QPen(Qt.green, .4) pen.setCosmetic(True) pen.setWidth(2) for track_idx in tracks: pathitem = QGraphicsPathItem(self.window.imageview.view) pathitem.setPen(pen) self.window.imageview.view.addItem(pathitem) self.pathitems.append(pathitem) pts = self.txy_pts[self.tracks[track_idx]] x = pts[:, 1]+.5; y = pts[:,2]+.5 path = QPainterPath(QPointF(x[0],y[0])) for i in np.arange(1, len(pts)): path.lineTo(QPointF(x[i],y[i])) pathitem.setPath(path)
def cosmetic_pen(color, width=2): pen = QPen(QColor.fromRgbF(*color)) pen.setCosmetic(True) pen.setWidth(width) return pen
class BaseSymbolIcon(QWidget): """ Base class to be used for all the Symbol Icon widgets. This class holds most of the properties to be exposed and takes care of 90% of the drawing code needed. Parameters ---------- parent : QWidget The parent widget for this widget. """ def __init__(self, parent=None): self._brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) self._original_brush = None self._rotation = 0 self._pen_style = Qt.SolidLine self._pen = QPen(self._pen_style) self._pen.setCosmetic(True) self._pen_width = 1.0 self._pen_color = QColor(0, 0, 0) self._pen.setWidthF(self._pen_width) self._pen.setColor(self._pen_color) self._original_pen_style = self._pen_style self._original_pen_color = self._pen_color super(BaseSymbolIcon, self).__init__(parent) self.setObjectName("icon") def minimumSizeHint(self): return QSize(32, 32) def paintEvent(self, event): """ Paint events are sent to widgets that need to update themselves, for instance when part of a widget is exposed because a covering widget was moved. This method handles the painting with parameters from the stylesheet, configures the brush, pen and calls ```draw_icon``` so the specifics can be performed for each of the drawing classes. Parameters ---------- event : QPaintEvent """ opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) painter.setClipping(True) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) painter.setRenderHint(QPainter.Antialiasing) x = event.rect().x() y = event.rect().y() w = event.rect().width() h = event.rect().height() painter.translate(w / 2.0, h / 2.0) painter.rotate(self._rotation) painter.translate(-w / 2.0, -h / 2.0) painter.translate(self._pen_width / 2.0, self._pen_width / 2.0) painter.scale(w - self._pen_width, h - self._pen_width) painter.translate(x, y) painter.setBrush(self._brush) painter.setPen(self._pen) self.draw_icon(painter) QWidget.paintEvent(self, event) def draw_icon(self, painter): """ Method responsible for the drawing of the icon part of the paintEvent. This method must be implemented by the symbol icon widgets to include their specific drawings. Parameters ---------- painter : QPainter """ raise NotImplementedError("draw_icon must be implemented by subclass") @Property(QBrush) def brush(self): """ PyQT Property for the brush object to be used when coloring the drawing Returns ------- QBrush """ return self._brush @brush.setter def brush(self, new_brush): """ PyQT Property for the brush object to be used when coloring the drawing Parameters ---------- new_brush : QBrush """ if new_brush != self._brush: self._brush = new_brush self.update() @Property(Qt.PenStyle) def penStyle(self): """ PyQT Property for the pen style to be used when drawing the border Returns ------- int Index at Qt.PenStyle enum """ return self._pen_style @penStyle.setter def penStyle(self, new_style): """ PyQT Property for the pen style to be used when drawing the border Parameters ---------- new_style : int Index at Qt.PenStyle enum """ if new_style != self._pen_style: self._pen_style = new_style self._pen.setStyle(new_style) self.update() @Property(QColor) def penColor(self): """ PyQT Property for the pen color to be used when drawing the border Returns ------- QColor """ return self._pen_color @penColor.setter def penColor(self, new_color): """ PyQT Property for the pen color to be used when drawing the border Parameters ---------- new_color : QColor """ if new_color != self._pen_color: self._pen_color = new_color self._pen.setColor(new_color) self.update() @Property(float) def penWidth(self): """ PyQT Property for the pen width to be used when drawing the border Returns ------- float """ return self._pen_width @penWidth.setter def penWidth(self, new_width): """ PyQT Property for the pen width to be used when drawing the border Parameters ---------- new_width : float """ if new_width < 0: return if new_width != self._pen_width: self._pen_width = new_width self._pen.setWidth(self._pen_width) self.update() @Property(float) def rotation(self): """ PyQt Property for the rotation. This rotates the icon coordinate system clockwise. Returns ------- angle : float The angle in degrees """ return self._rotation @rotation.setter def rotation(self, angle): """ PyQt Property for the rotation. This rotates the icon coordinate system clockwise. Parameters ---------- angle : float The angle in degrees """ self._rotation = angle self.update()
class InteractiveVerticalLine(ChartWidgetItem): """ Vertical Line which can be moved by mouse interaction. Usefull for timelines. """ positionChange = Signal(object) def __init__(self, parent=None): super(InteractiveVerticalLine, self).__init__(parent) self.setFlags( QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges ) self.chartItemFlags = ChartItemFlags.FLAG_NO_AUTO_RANGE self.setZValue(1e6) self.setAcceptHoverEvents(True) self._x = None self._label = None self._pen = QPen(QBrush(Qt.green), 1.0, Qt.SolidLine) self._pen.setCosmetic(True) self._brush = QBrush(QColor(255, 255, 255, 0)) @property def label(self): return self._label def setX(self, x, label=None, color=None): """ Sets the line's x value. :param x: abscissa value. :param label: :param color: """ if label is not None: self._label = label if color is not None: self._color = color self._pen = QPen(QBrush(color), 1.0, Qt.SolidLine) self._pen.setCosmetic(True) # self._brush = QBrush(QColor(255, 255, 255, 0)) if self._x != x: self._x = x self.setPos(QPointF(self._x, 0)) def visibleRangeChanged(self, rect): t = self.parentItem().transform() bb = 4 / t.m11() _log.debug("{}, {} -> {}".format(rect.bottom(), rect.height(), bb)) b = min(rect.bottom(), rect.top()) self.b_rect = QRectF(-bb, b, 2 * bb, rect.height()) self.prepareGeometryChange() def paint(self, p=QPainter(), o=QStyleOptionGraphicsItem(), widget=None): # p.setRenderHint(QPainter.Antialiasing) p.setPen(self._pen) p.setBrush(self._brush) p.drawLine( QLineF(QPointF(0, self.b_rect.bottom()), QPointF(0, self.b_rect.top())) ) p.setPen(Qt.transparent) p.drawRect(self.b_rect) if CONFIG.debug: p.setPen(Qt.yellow) p.drawRect(self.b_rect) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: old_pos = self.pos() # value = value #.toPointF() e = InteractiveChangeEvent() e.position = value self.positionChange.emit(e) return QPointF(value.x(), old_pos.y()) return super(InteractiveVerticalLine, self).itemChange(change, value) def hoverEnterEvent(self, e): super(InteractiveVerticalLine, self).hoverEnterEvent(e) self._brush = QBrush(QColor(255, 255, 255, 40)) self.update() def hoverLeaveEvent(self, e): super(InteractiveVerticalLine, self).hoverLeaveEvent(e) self._brush = QBrush(QColor(255, 255, 255, 0)) self.update() def __del__(self): _log.debug("Finalize Interactive vLine {}".format(self))
class BaseSymbolIcon(QWidget): """ Base class to be used for all the Symbol Icon widgets. This class holds most of the properties to be exposed and takes care of 90% of the drawing code needed. Parameters ---------- parent : QWidget The parent widget for this widget. """ clicked = Signal() def __init__(self, parent=None): self._brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) self._original_brush = None self._rotation = 0 self._pen_style = Qt.SolidLine self._pen = QPen(self._pen_style) self._pen.setCosmetic(True) self._pen_width = 1.0 self._pen_color = QColor(0, 0, 0) self._pen.setWidthF(self._pen_width) self._pen.setColor(self._pen_color) self._original_pen_style = self._pen_style self._original_pen_color = self._pen_color super(BaseSymbolIcon, self).__init__(parent=parent) self.setObjectName("icon") if not is_qt_designer(): # We should install the Event Filter only if we are running # and not at the Designer self.installEventFilter(self) def eventFilter(self, obj, event): """ EventFilter to redirect "middle click" to :meth:`.show_address_tooltip` """ # Override the eventFilter to capture all middle mouse button events, # and show a tooltip if needed. if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.MiddleButton: self.show_state_channel(event) return True return False def show_state_channel(self, event): """ Show the State Channel Tooltip and copy address to clipboard This is intended to replicate the behavior of the "middle click" from EDM. If the parent is not PCDSSymbolBase and does not have a valid State Channel nothing will be displayed. """ from ..vacuum.base import PCDSSymbolBase p = find_ancestor_for_widget(self, PCDSSymbolBase) if not p: return state_suffix = getattr(p, '_state_suffix', None) if not state_suffix: return addr = "{}{}".format(p.channelsPrefix, state_suffix) QToolTip.showText(event.globalPos(), addr) # If the address has a protocol, strip it out before putting it on the # clipboard. copy_text = remove_protocol(addr) clipboard = QApplication.clipboard() clipboard.setText(copy_text) event = QEvent(QEvent.Clipboard) QApplication.instance().sendEvent(clipboard, event) def minimumSizeHint(self): return QSize(32, 32) def paintEvent(self, event): """ Paint events are sent to widgets that need to update themselves, for instance when part of a widget is exposed because a covering widget was moved. This method handles the painting with parameters from the stylesheet, configures the brush, pen and calls ```draw_icon``` so the specifics can be performed for each of the drawing classes. Parameters ---------- event : QPaintEvent """ opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) painter.setClipping(True) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) painter.setRenderHint(QPainter.Antialiasing) w = self.width() h = self.height() painter.translate(w / 2.0, h / 2.0) painter.rotate(self._rotation) painter.translate(-w / 2.0, -h / 2.0) painter.translate(self._pen_width / 2.0, self._pen_width / 2.0) painter.scale(w - self._pen_width, h - self._pen_width) painter.translate(0, 0) painter.setBrush(self._brush) painter.setPen(self._pen) self.draw_icon(painter) QWidget.paintEvent(self, event) def draw_icon(self, painter): """ Method responsible for the drawing of the icon part of the paintEvent. This method must be implemented by the symbol icon widgets to include their specific drawings. Parameters ---------- painter : QPainter """ raise NotImplementedError("draw_icon must be implemented by subclass") @Property(QBrush) def brush(self): """ PyQT Property for the brush object to be used when coloring the drawing Returns ------- QBrush """ return self._brush @brush.setter def brush(self, new_brush): """ PyQT Property for the brush object to be used when coloring the drawing Parameters ---------- new_brush : QBrush """ if new_brush != self._brush: self._brush = new_brush self.update() @Property(Qt.PenStyle) def penStyle(self): """ PyQT Property for the pen style to be used when drawing the border Returns ------- int Index at Qt.PenStyle enum """ return self._pen_style @penStyle.setter def penStyle(self, new_style): """ PyQT Property for the pen style to be used when drawing the border Parameters ---------- new_style : int Index at Qt.PenStyle enum """ if new_style != self._pen_style: self._pen_style = new_style self._pen.setStyle(new_style) self.update() @Property(QColor) def penColor(self): """ PyQT Property for the pen color to be used when drawing the border Returns ------- QColor """ return self._pen_color @penColor.setter def penColor(self, new_color): """ PyQT Property for the pen color to be used when drawing the border Parameters ---------- new_color : QColor """ if new_color != self._pen_color: self._pen_color = new_color self._pen.setColor(new_color) self.update() @Property(float) def penWidth(self): """ PyQT Property for the pen width to be used when drawing the border Returns ------- float """ return self._pen_width @penWidth.setter def penWidth(self, new_width): """ PyQT Property for the pen width to be used when drawing the border Parameters ---------- new_width : float """ if new_width < 0: return if new_width != self._pen_width: self._pen_width = new_width self._pen.setWidth(self._pen_width) self.update() @Property(float) def rotation(self): """ PyQt Property for the rotation. This rotates the icon coordinate system clockwise. Returns ------- angle : float The angle in degrees """ return self._rotation @rotation.setter def rotation(self, angle): """ PyQt Property for the rotation. This rotates the icon coordinate system clockwise. Parameters ---------- angle : float The angle in degrees """ self._rotation = angle self.update() def mousePressEvent(self, evt): """Clicking the icon fires the ``clicked`` signal""" # Default Qt reaction to the icon press super().mousePressEvent(evt) if evt.button() == Qt.LeftButton: self.clicked.emit()
class MicroViewScene(QGraphicsScene): def __init__(self, parent): super().__init__(parent) self._imageItem = ImageGraphicsItem() self.addItem(self._imageItem) self._roiMode = False self._drawingRoi = False self.roiPen = QPen() self.roiPen.setWidthF(1.25) self.roiPen.setCosmetic(True) self.roiPen.setColor(Qt.yellow) self._roiPolygon = QPolygonF() self._roiItem = QGraphicsPolygonItem(self._roiPolygon) self._roiItem.setPen(self.roiPen) self.addItem(self._roiItem) def setImage(self, img): if isinstance(img, QImage): img = QPixmap.fromImage(img) self._imageItem.setPixmap(img) @Property(ImageGraphicsItem) def imageItem(self): return self._imageItem def enableRoiMode(self, enable): if enable == self._roiMode: return if enable: self._roiPolygon = QPolygonF() self._roiItem.setPolygon(self._roiPolygon) self._tempRoiPolygon = QPolygonF(self._roiPolygon) self._tempRoiPolygon.append(QPointF()) if not enable: self._roiItem.setPolygon(self._roiPolygon) self.roiChanged.emit(self._roiPolygon) self._roiMode = enable self.roiModeChanged.emit(enable) roiModeChanged = Signal(bool) @Property(bool, fset=enableRoiMode) def roiMode(self): return self._roiMode def setRoi(self, roi): if self._roiPolygon == roi: return self._roiPolygon = roi self._roiItem.setPolygon(self._roiPolygon) self.roiChanged.emit(roi) roiChanged = Signal(QPolygonF) @Property(QPolygonF, fset=setRoi, doc="Polygon describing the region of interest (ROI)") def roi(self): return self._roiPolygon def _appendPointToRoi(self, pos, polygon, replace_last=False): br = self._imageItem.boundingRect() topLeft = br.topLeft() bottomRight = br.bottomRight() # Make sure we stay inside the image boundaries xInBr = max(topLeft.x(), pos.x()) xInBr = min(bottomRight.x(), xInBr) yInBr = max(topLeft.y(), pos.y()) yInBr = min(bottomRight.y(), yInBr) pointInBr = QPointF(xInBr, yInBr) if replace_last: polygon[-1] = pointInBr else: polygon.append(pointInBr) def mousePressEvent(self, event): super().mousePressEvent(event) if not self._roiMode: return self._appendPointToRoi(event.scenePos(), self._roiPolygon, False) self._roiItem.setPolygon(self._roiPolygon) self._tempRoiPolygon = QPolygonF(self._roiPolygon) self._tempRoiPolygon.append(QPointF()) def mouseMoveEvent(self, event): super().mouseMoveEvent(event) if not self._roiMode: return self._appendPointToRoi(event.scenePos(), self._tempRoiPolygon, True) self._roiItem.setPolygon(self._tempRoiPolygon) def mouseDoubleClickEvent(self, event): super().mouseDoubleClickEvent(event) # the first click of the double click is a normal mousePressEvent, # thus the current point has already been added. Simply exit ROI mode self.roiMode = False
def __init__( self, orientation=-1, # for compatibility with QSlider min=0, max=1, val=0, marks=None, *args, **kwargs, ): super(VideoSlider, self).__init__(*args, **kwargs) self.scene = QtWidgets.QGraphicsScene() self.setScene(self.scene) self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setMouseTracking(True) self._get_val_tooltip = None self.tick_index_offset = 1 self.zoom_factor = 1 self._header_label_height = 20 self._header_graph_height = 40 self._header_height = self._header_label_height # room for frame labels self._min_height = 19 + self._header_height self._base_font = QtGui.QFont() self._base_font.setPixelSize(10) self._tick_marks = [] # Add border rect outline_rect = QtCore.QRectF(0, 0, 200, self._min_height - 3) self.box_rect = outline_rect # self.outlineBox = self.scene.addRect(outline_rect) # self.outlineBox.setPen(QPen(QColor("black", alpha=0))) # Add drag handle rect self._handle_width = 6 handle_rect = QtCore.QRectF(0, self._handle_top, self._handle_width, self._handle_height) self.setMinimumHeight(self._min_height) self.setMaximumHeight(self._min_height) self.handle = self.scene.addRect( QtCore.QRectF(0, self._handle_top, self._handle_width, self._handle_height)) self.handle.setPen(QPen(QColor(80, 80, 80))) self.handle.setBrush(QColor(128, 128, 128, 128)) # Add (hidden) rect to highlight selection self.select_box = self.scene.addRect( QtCore.QRectF(0, 1, 0, outline_rect.height() - 2)) self.select_box.setPen(QPen(QColor(80, 80, 255))) self.select_box.setBrush(QColor(80, 80, 255, 128)) self.select_box.hide() self.zoom_box = self.scene.addRect( QtCore.QRectF(0, 1, 0, outline_rect.height() - 2)) self.zoom_box.setPen(QPen(QColor(80, 80, 80, 64))) self.zoom_box.setBrush(QColor(80, 80, 80, 64)) self.zoom_box.hide() self.scene.setBackgroundBrush(QBrush(QColor(200, 200, 200))) self.clearSelection() self.setEnabled(True) self.setMinimum(min) self.setMaximum(max) self.setValue(val) self.setMarks(marks) pen = QPen(QColor(80, 80, 255), 0.5) pen.setCosmetic(True) self.poly = self.scene.addPath(QPainterPath(), pen, self.select_box.brush()) self.headerSeries = dict() self._draw_header()
class RectangularRegion(ChartItem): """ Rectangular overlay, that (optionally) can be resized by handles. TODO: * Notification framework * Modifiers to allow for more flexible mouse interactions """ def __init__(self, x, y, width=1, height=1, rotation=0, parent=None): """ Constructor. :param x: Initial x position of ROIs center. :param y: Initial y position of ROIs center. :param width: Initial width :param height: Initial height :param rotation: Initial rotation in radians :param parent: Optional parent item. """ super(RectangularRegion, self).__init__(parent) self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemSendsGeometryChanges) # self.setHandlesChildEvents(False) # self.setFiltersChildEvents(True) self.__in_move = False self._path = QPainterPath() self.pen = QPen(QBrush(QColor(161, 0, 161, 255)), 1.0, Qt.SolidLine) self.pen.setCosmetic(True) self.brush = QBrush(QColor(255, 255, 255, 40)) self.state = RoiState() self.state.pos = QPointF(x, y) self.state.w0 = width self.state.w1 = width self.state.h0 = height self.state.h1 = height self.state.rotation = rotation self.setPos(self.state.pos) self.updatePath() self._handles = [] @property def handles(self): """ Returns all handles which are attached to the ROI. :return: list """ return self._handles def addHandle(self, handle): """ Adds a handle to the ROI. :param handle: Resize or rotate handle """ handle.setParentItem(self) # self.addToGroup(handle) self.handles.append(handle) handle.updatePosition() def removeHandle(self, handle): """ Removes the given handle from the ROI. :param handle: Handle :return: """ if handle not in self.handles: _log.warning("Handle not part of ROI.") return self.handles.remove(handle) # self.removeFromGroup(handle) self.scene().removeItem(handle) def boundingRect(self): return self._path.boundingRect() def updatePath(self): p0 = Vec2(self.state.w0, self.state.h0) p1 = Vec2(-self.state.w1, self.state.h0) p2 = Vec2(-self.state.w1, -self.state.h1) p3 = Vec2(self.state.w0, -self.state.h1) p0t = p0.rotate(self.state.rotation) p1t = p1.rotate(self.state.rotation) p2t = p2.rotate(self.state.rotation) p3t = p3.rotate(self.state.rotation) self._path = QPainterPath() self._path.moveTo(p0t.x, p0t.y) self._path.lineTo(p1t.x, p1t.y) self._path.lineTo(p2t.x, p2t.y) self._path.lineTo(p3t.x, p3t.y) self._path.closeSubpath() self.prepareGeometryChange() def paint(self, p=QPainter(), o=QStyleOptionGraphicsItem(), widget=None): p.setRenderHint(QPainter.Antialiasing) p.setPen(self.pen) p.setBrush(self.brush) p.drawPath(self._path) p.setBrush(Qt.transparent) p.drawLine(QLineF(-0.5, 0, 0.5, 0)) p.drawLine(QLineF(0, -0.5, 0, 0.5)) p.drawEllipse(QPointF(0, 0), 0.25, 0.25) def mousePressEvent(self, e): # !!!F*****g leason learned, call super to avoid weird jumps in position!!! super(RectangularRegion, self).mousePressEvent(e) self.__in_move = True def mouseReleaseEvent(self, e): super(RectangularRegion, self).mouseReleaseEvent(e) self.__in_move = False def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange and self.__in_move: old_pos = self.pos() new_pos = value # .toPointF() delta = old_pos - new_pos _log.debug("Delta: {}".format(delta)) self.state.pos = new_pos value = new_pos for handle in self.handles: handle.updatePosition() return super(RectangularRegion, self).itemChange(change, value) def rotation(self): """ Override takes rotation from the state instead of items transformation. :return: rotation in degrees """ return self.state.rotation * 180 / np.pi def setRotation(self, degree): """ Override to propagate rotation to state instead of applying it to the items transform :param degree: Rotation in degrees """ self.state.rotation = (degree % 360) * np.pi / 180.0 for h in self.handles: h.updatePosition() self.updatePath()