def adjustGeometry(self): # type: () -> None """ Adjust the widget geometry to exactly fit the arrow inside while preserving the arrow path scene geometry. """ # local system coordinate geom = self.geometry().translated(-self.pos()) line = self.__line arrow_rect = self.__arrowItem.shape().boundingRect() if geom.isNull() and not line.isNull(): geom = QRectF(0, 0, 1, 1) if not (geom.contains(arrow_rect)): geom = geom.united(arrow_rect) geom = geom.intersected(arrow_rect) diff = geom.topLeft() line = QLineF(line.p1() - diff, line.p2() - diff) geom.translate(self.pos()) self.setGeometry(geom) self.setLine(line)
def setLine(self, line): """ Set the arrow base line (a `QLineF` in object coordinates). """ if self.__line != line: self.__line = QLineF(line) # local item coordinate system geom = self.geometry().translated(-self.pos()) if geom.isNull() and not line.isNull(): geom = QRectF(0, 0, 1, 1) arrow_shape = arrow_path_concave(line, self.lineWidth()) arrow_rect = arrow_shape.boundingRect() if not (geom.contains(arrow_rect)): geom = geom.united(arrow_rect) if self.__autoAdjustGeometry: # Shrink the geometry if required. geom = geom.intersected(arrow_rect) # topLeft can move changing the local coordinates. diff = geom.topLeft() line = QLineF(line.p1() - diff, line.p2() - diff) self.__arrowItem.setLine(line) self.__arrowShadowBase.setLine(line) self.__line = line # parent item coordinate system geom.translate(self.pos()) self.setGeometry(geom)
def setLine(self, line): # type: (QLineF) -> None """ Set the arrow base line (a `QLineF` in object coordinates). """ if self.__line != line: self.__line = QLineF(line) # local item coordinate system geom = self.geometry().translated(-self.pos()) if geom.isNull() and not line.isNull(): geom = QRectF(0, 0, 1, 1) arrow_shape = arrow_path_concave(line, self.lineWidth()) arrow_rect = arrow_shape.boundingRect() if not (geom.contains(arrow_rect)): geom = geom.united(arrow_rect) if self.__autoAdjustGeometry: # Shrink the geometry if required. geom = geom.intersected(arrow_rect) # topLeft can move changing the local coordinates. diff = geom.topLeft() line = QLineF(line.p1() - diff, line.p2() - diff) self.__arrowItem.setLine(line) self.__arrowShadowBase.setLine(line) self.__line = line # parent item coordinate system geom.translate(self.pos()) self.setGeometry(geom)
def __select_range(self, p1: QPointF, p2: QPointF): rect = QRectF(p1, p2).normalized() self.__selection.append((rect.topLeft().x(), rect.topRight().x())) sel_rect_item = SelectionRect(rect) self.addItem(sel_rect_item) self.__selection_rect_items.append(sel_rect_item)
def qgraphicsview_map_rect_from_scene(view: QGraphicsView, rect: QRectF) -> QPolygonF: """Like QGraphicsView.mapFromScene(QRectF) but returning a QPolygonF (without rounding). """ tr = view.viewportTransform() p1 = tr.map(rect.topLeft()) p2 = tr.map(rect.topRight()) p3 = tr.map(rect.bottomRight()) p4 = tr.map(rect.bottomLeft()) return QPolygonF([p1, p2, p3, p4])
def select_by_rectangle(self, rect: QRectF): if self.bar_item is None: return x0, x1 = sorted((rect.topLeft().x(), rect.bottomRight().x())) y0, y1 = sorted((rect.topLeft().y(), rect.bottomRight().y())) x = self.bar_item.opts["x"] height = self.bar_item.opts["height"] d = self.bar_width / 2 # positive bars mask = (x0 <= x + d) & (x1 >= x - d) & (y0 <= height) & (y1 > 0) # negative bars mask |= (x0 <= x + d) & (x1 >= x - d) & (y0 <= 0) & (y1 > height) self.select_by_indices(list(np.flatnonzero(mask)))
def updateScaleBox(self, p1, p2): """ Overload to use ViewBox.mapToView instead of mapRectFromParent mapRectFromParent (from Qt) uses QTransform.invert() which has floating-point issues and can't invert the matrix with large coefficients. ViewBox.mapToView uses invertQTransform from pyqtgraph. This code, except for first three lines, are copied from the overloaded method. """ p1 = self.mapToView(p1) p2 = self.mapToView(p2) r = QRectF(p1, p2) self.rbScaleBox.setPos(r.topLeft()) tr = QTransform.fromScale(r.width(), r.height()) self.rbScaleBox.setTransform(tr) self.rbScaleBox.show()
def updateScaleBox(self, p1, p2): """ Overload to use ViewBox.mapToView instead of mapRectFromParent mapRectFromParent (from Qt) uses QTransform.invert() which has floating-point issues and can't invert the matrix with large coefficients. ViewBox.mapToView uses invertQTransform from pyqtgraph. This code, except for first three lines, are copied from the overloaded method. """ p1 = self.mapToView(p1) p2 = self.mapToView(p2) r = QRectF(p1, p2) self.rbScaleBox.setPos(r.topLeft()) self.rbScaleBox.resetTransform() self.rbScaleBox.scale(r.width(), r.height()) self.rbScaleBox.show()
def adjustGeometry(self): """ Adjust the widget geometry to exactly fit the arrow inside while preserving the arrow path scene geometry. """ # local system coordinate geom = self.geometry().translated(-self.pos()) line = self.__line arrow_rect = self.__arrowItem.shape().boundingRect() if geom.isNull() and not line.isNull(): geom = QRectF(0, 0, 1, 1) if not (geom.contains(arrow_rect)): geom = geom.united(arrow_rect) geom = geom.intersected(arrow_rect) diff = geom.topLeft() line = QLineF(line.p1() - diff, line.p2() - diff) geom.translate(self.pos()) self.setGeometry(geom) self.setLine(line)
class SelectTool(DataTool): cursor = Qt.ArrowCursor def __init__(self, parent, plot): super().__init__(parent, plot) self._item = None self._start_pos = None self._selection_rect = None self._mouse_dragging = False self._delete_action = QAction("Delete", self, shortcutContext=Qt.WindowShortcut) self._delete_action.setShortcuts( [QKeySequence.Delete, QKeySequence("Backspace")]) self._delete_action.triggered.connect(self.delete) def setSelectionRect(self, rect): if self._selection_rect != rect: self._selection_rect = QRectF(rect) self._item.setRect(self._selection_rect) def selectionRect(self): return self._item.rect() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: pos = self.mapToPlot(event.pos()) if self._item.isVisible(): if self.selectionRect().contains(pos): # Allow the event to propagate to the item. event.setAccepted(False) self._item.setCursor(Qt.ClosedHandCursor) return False self._mouse_dragging = True self._start_pos = pos self._item.setVisible(True) self._plot.addItem(self._item) self.setSelectionRect(QRectF(pos, pos)) event.accept() return True return super().mousePressEvent(event) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: pos = self.mapToPlot(event.pos()) self.setSelectionRect(QRectF(self._start_pos, pos).normalized()) event.accept() return True return super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: pos = self.mapToPlot(event.pos()) self.setSelectionRect(QRectF(self._start_pos, pos).normalized()) event.accept() self.issueCommand.emit(SelectRegion(self.selectionRect())) self._item.setCursor(Qt.OpenHandCursor) self._mouse_dragging = False return True return super().mouseReleaseEvent(event) def activate(self): if self._item is None: self._item = _RectROI((0, 0), (0, 0), pen=(25, 25, 25)) self._item.setAcceptedMouseButtons(Qt.LeftButton) self._item.setVisible(False) self._item.setCursor(Qt.OpenHandCursor) self._item.sigRegionChanged.connect(self._on_region_changed) self._item.sigRegionChangeStarted.connect( self._on_region_change_started) self._item.sigRegionChangeFinished.connect( self._on_region_change_finished) self._plot.addItem(self._item) self._mouse_dragging = False self._plot.addAction(self._delete_action) def deactivate(self): self.reset() self._plot.removeAction(self._delete_action) def reset(self): self.setSelectionRect(QRectF()) self._item.setVisible(False) self._mouse_dragging = False def delete(self): if not self._mouse_dragging and self._item.isVisible(): self.issueCommand.emit(DeleteSelection()) self.reset() def _on_region_changed(self): if not self._mouse_dragging: newrect = self._item.rect() delta = newrect.topLeft() - self._selection_rect.topLeft() self._selection_rect = newrect self.issueCommand.emit(MoveSelection(delta)) def _on_region_change_started(self): if not self._mouse_dragging: self.editingStarted.emit() def _on_region_change_finished(self): if not self._mouse_dragging: self.editingFinished.emit()
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 SelectTool(DataTool): cursor = Qt.ArrowCursor def __init__(self, parent, plot): super().__init__(parent, plot) self._item = None self._start_pos = None self._selection_rect = None self._mouse_dragging = False self._delete_action = QAction( "Delete", self, shortcutContext=Qt.WindowShortcut ) self._delete_action.setShortcuts([QKeySequence.Delete, QKeySequence("Backspace")]) self._delete_action.triggered.connect(self.delete) def setSelectionRect(self, rect): if self._selection_rect != rect: self._selection_rect = QRectF(rect) self._item.setRect(self._selection_rect) def selectionRect(self): return self._item.rect() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: pos = self.mapToPlot(event.pos()) if self._item.isVisible(): if self.selectionRect().contains(pos): # Allow the event to propagate to the item. event.setAccepted(False) self._item.setCursor(Qt.ClosedHandCursor) return False self._mouse_dragging = True self._start_pos = pos self._item.setVisible(True) self._plot.addItem(self._item) self.setSelectionRect(QRectF(pos, pos)) event.accept() return True else: return super().mousePressEvent(event) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: pos = self.mapToPlot(event.pos()) self.setSelectionRect(QRectF(self._start_pos, pos).normalized()) event.accept() return True else: return super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: pos = self.mapToPlot(event.pos()) self.setSelectionRect(QRectF(self._start_pos, pos).normalized()) event.accept() self.issueCommand.emit(SelectRegion(self.selectionRect())) self._item.setCursor(Qt.OpenHandCursor) self._mouse_dragging = False return True else: return super().mouseReleaseEvent(event) def activate(self): if self._item is None: self._item = _RectROI((0, 0), (0, 0), pen=(25, 25, 25)) self._item.setAcceptedMouseButtons(Qt.LeftButton) self._item.setVisible(False) self._item.setCursor(Qt.OpenHandCursor) self._item.sigRegionChanged.connect(self._on_region_changed) self._item.sigRegionChangeStarted.connect( self._on_region_change_started) self._item.sigRegionChangeFinished.connect( self._on_region_change_finished) self._plot.addItem(self._item) self._mouse_dragging = False self._plot.addAction(self._delete_action) def deactivate(self): self._reset() self._plot.removeAction(self._delete_action) def _reset(self): self.setSelectionRect(QRectF()) self._item.setVisible(False) self._mouse_dragging = False def delete(self): if not self._mouse_dragging and self._item.isVisible(): self.issueCommand.emit(DeleteSelection()) self._reset() def _on_region_changed(self): if not self._mouse_dragging: newrect = self._item.rect() delta = newrect.topLeft() - self._selection_rect.topLeft() self._selection_rect = newrect self.issueCommand.emit(MoveSelection(delta)) def _on_region_change_started(self): if not self._mouse_dragging: self.editingStarted.emit() def _on_region_change_finished(self): if not self._mouse_dragging: self.editingFinished.emit()
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)
def update_rect(self, p1: QPointF, p2: QPointF): rect = QRectF(p1, p2) self.setPos(rect.topLeft()) trans = QTransform.fromScale(rect.width(), rect.height()) self.setTransform(trans)