def _handle_chart_hover(self, point: QPointF) -> None: # Transform to the data coordinate-space of the chart. # See https://stackoverflow.com/a/44078533 scene_pos = self._chart_view.mapToScene(point.toPoint()) chart_item_pos = self._chart.mapFromScene(scene_pos) series_point = self._chart.mapToValue(chart_item_pos) series_x = series_point.x() self.chart_hover_pos.emit(scene_pos) # TODO smooth to a fraction of the data space extent, # rather than an absolute magnitude. abs_dx = abs(series_x - self._last_chart_x) if abs_dx >= 0.1: self._last_chart_x = series_x self.selected_solar_mult.emit(series_x)
class ChipSceneViewer(QGraphicsView): selectionChanged = Signal(list) def __init__(self): super().__init__() scene = QGraphicsScene() self.setScene(scene) self.setRenderHint(QPainter.Antialiasing) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setMouseTracking(True) self._offset = QPointF(0, 0) self._zoom = 0 self.backgroundColor = QColor(40, 40, 40) self.gridSpacing = QSizeF(60, 60) self.gridThickness = 1 self.gridColor = QColor(100, 100, 100) self.gridZoomThreshold = -1.5 self.showGrid = True self.selectionBoxStrokeColor = QColor(52, 222, 235) self.selectionBoxFillColor = QColor(52, 222, 235, 50) self.selectionBoxThickness = 2 self._editing = True self._hoveredItems: List[ChipItem] = [] self._selectedItems: List[ChipItem] = [] self._sceneItems: Set[ChipItem] = set() self._boxSelectionRectAnchor = QPointF() self._currentCursorPosition = QPointF() self.selectionBox = self.scene().addRect(QRectF(), QPen(self.selectionBoxStrokeColor, self.selectionBoxThickness), QBrush(self.selectionBoxFillColor)) self.selectionBox.setVisible(False) self._state = State.IDLE self.UpdateView() def SetEditing(self, editing: bool): self._editing = editing self.showGrid = editing for item in self._sceneItems: item.SetEditDisplay(editing) if not editing: self.DeselectAll() def GetSelectedItems(self): return self._selectedItems def AddItem(self, item: ChipItem): if item not in self._sceneItems: self._sceneItems.add(item) item.Move(QPointF()) item.onRemoved.connect(self.RemoveItem) self.scene().addItem(item.GraphicsObject()) item.SetEditDisplay(self._editing) return item def RemoveItem(self, item: ChipItem): self.DeselectItem(item) if item in self._hoveredItems: self._hoveredItems.remove(item) if item in self._sceneItems: self._sceneItems.remove(item) self.scene().removeItem(item.GraphicsObject()) def GetItems(self): return self._sceneItems def RemoveAll(self): self._hoveredItems.clear() self._selectedItems.clear() for item in self._sceneItems.copy(): self.RemoveItem(item) def SelectItem(self, item: ChipItem): if item not in self._selectedItems: item.SetSelected(True) self._selectedItems.append(item) def ToggleSelectItem(self, item: ChipItem): if item in self._selectedItems: self.DeselectItem(item) else: self.SelectItem(item) def DeselectItem(self, item: ChipItem): if item in self._selectedItems: item.SetSelected(False) self._selectedItems.remove(item) def DeselectAll(self): for item in self._selectedItems.copy(): self.DeselectItem(item) def Recenter(self): itemsRect = QRectF() for item in self._sceneItems: itemsRect = itemsRect.united(item.GraphicsObject().boundingRect().translated(item.GraphicsObject().pos())) self._offset = itemsRect.center() self.UpdateView() def CenterItem(self, item: ChipItem): QApplication.processEvents() sceneCenter = self.mapToScene(self.rect().center()) currentCenter = item.GraphicsObject().sceneBoundingRect().center() delta = sceneCenter - currentCenter item.Move(delta) def UpdateView(self): matrix = QTransform() matrix.scale(2 ** self._zoom, 2 ** self._zoom) self.setTransform(matrix) self.setSceneRect(QRectF(self._offset.x(), self._offset.y(), 10, 10)) self.UpdateSelectionBox() self.UpdateHoveredItems() @staticmethod def GetSelectionMode(): if QApplication.keyboardModifiers() == Qt.ShiftModifier: return SelectionMode.MODIFY return SelectionMode.NORMAL def CreateSelectionRect(self) -> QRectF: cursorScene = self.mapToScene(self._currentCursorPosition.toPoint()) if self._state is State.SELECTING: selectionRect = QRectF(0, 0, abs(self._boxSelectionRectAnchor.x() - cursorScene.x()), abs(self._boxSelectionRectAnchor.y() - cursorScene.y())) selectionRect.moveCenter((cursorScene + self._boxSelectionRectAnchor) / 2.0) else: selectionRect = QRectF(cursorScene, QSizeF()) return selectionRect def UpdateHoveredItems(self): if self._state is State.PANNING: return if not self._editing or self._state is State.MOVING: hoveredChipItems = [] else: # What are we hovering over in the scene hoveredGraphicsItems = [item for item in self.scene().items(self.selectionBox.sceneBoundingRect())] hoveredChipItems = [item for item in self._sceneItems if item.GraphicsObject() in hoveredGraphicsItems] # Make sure we maintain the found order hoveredChipItems.sort(key=lambda x: hoveredGraphicsItems.index(x.GraphicsObject())) if self._state is not State.SELECTING: hoveredChipItems = hoveredChipItems[:1] for item in hoveredChipItems: if item not in self._hoveredItems and item.CanSelect(): item.SetHovered(True) for item in self._hoveredItems: if item not in hoveredChipItems: item.SetHovered(False) self._hoveredItems = hoveredChipItems def UpdateSelectionBox(self): if self._state == State.SELECTING: self.selectionBox.setVisible(True) self.selectionBox.prepareGeometryChange() else: self.selectionBox.setVisible(False) self.selectionBox.setRect(self.CreateSelectionRect()) def drawBackground(self, painter: QPainter, rect: QRectF): currentColor = self.backgroundBrush().color() if currentColor != self.backgroundColor: self.setBackgroundBrush(QBrush(self.backgroundColor)) super().drawBackground(painter, rect) if self._zoom <= self.gridZoomThreshold or not self.showGrid: return painter.setPen(QPen(self.gridColor, self.gridThickness)) lines = [] if self.gridSpacing.width() > 0: xStart = rect.left() - rect.left() % self.gridSpacing.width() while xStart <= rect.right(): line = QLineF(xStart, rect.bottom(), xStart, rect.top()) lines.append(line) xStart = xStart + self.gridSpacing.width() if self.gridSpacing.height() > 0: yStart = rect.top() - rect.top() % self.gridSpacing.height() while yStart <= rect.bottom(): line = QLineF(rect.left(), yStart, rect.right(), yStart) lines.append(line) yStart = yStart + self.gridSpacing.height() painter.drawLines(lines) def wheelEvent(self, event: QWheelEvent): numSteps = float(event.angleDelta().y()) / 1000 oldWorldPos = self.mapToScene(self._currentCursorPosition.toPoint()) self._zoom = min(self._zoom + numSteps, 0) self.UpdateView() newWorldPos = self.mapToScene(self._currentCursorPosition.toPoint()) delta = newWorldPos - oldWorldPos self._offset -= delta self.UpdateView() def mousePressEvent(self, event): if self._state != State.IDLE: return if event.button() == Qt.RightButton: self._state = State.PANNING elif event.button() == Qt.LeftButton and self._editing: if len(self._hoveredItems) == 0: self._state = State.SELECTING self._boxSelectionRectAnchor = self.mapToScene(self._currentCursorPosition.toPoint()) else: hoveredItem = self._hoveredItems[0] if self.GetSelectionMode() is SelectionMode.MODIFY: self.ToggleSelectItem(hoveredItem) self.selectionChanged.emit(self._selectedItems) else: if hoveredItem not in self._selectedItems: self.DeselectAll() self.SelectItem(hoveredItem) self.selectionChanged.emit(self._selectedItems) if hoveredItem.CanMove(self.mapToScene(self._currentCursorPosition.toPoint())): self._state = State.MOVING self.UpdateView() super().mousePressEvent(event) def mouseMoveEvent(self, event: QMouseEvent): # Update position movement newCursorPosition = event.localPos() if self._currentCursorPosition is None: deltaScene = QPointF() else: deltaScene = (self.mapToScene(newCursorPosition.toPoint()) - self.mapToScene(self._currentCursorPosition.toPoint())) self._currentCursorPosition = newCursorPosition if self._state is State.PANNING: self._offset -= deltaScene self.UpdateView() elif self._state is State.MOVING: for selectedItem in self._selectedItems: selectedItem.Move(deltaScene) self.UpdateView() super().mouseMoveEvent(event) def mouseReleaseEvent(self, event: QMouseEvent): super().mouseReleaseEvent(event) if self._state == State.PANNING: if event.button() == Qt.RightButton: self._state = State.IDLE elif self._state == State.SELECTING: if event.button() == Qt.LeftButton: self._state = State.IDLE if self.GetSelectionMode() is SelectionMode.MODIFY: for item in self._hoveredItems: self.ToggleSelectItem(item) else: self.DeselectAll() for item in self._hoveredItems: self.SelectItem(item) self.selectionChanged.emit(self._selectedItems) elif self._state == State.MOVING: if event.button() == Qt.LeftButton: self._state = State.IDLE self.UpdateView() super().mouseReleaseEvent(event) def keyReleaseEvent(self, event: QKeyEvent): if event.key() == Qt.Key.Key_Delete: self.DeleteSelected() if event.key() == Qt.Key.Key_D and event.modifiers() == Qt.Modifier.CTRL: self.DuplicateSelected() super().keyReleaseEvent(event) def DeleteSelected(self): for item in self.GetSelectedItems().copy(): if item.CanDelete(): item.RequestDelete() def DuplicateSelected(self): newItems = [] for item in self.GetSelectedItems(): if item.CanDuplicate(): newItem = item.Duplicate() newItem.Move(QPointF(50, 50)) self.AddItem(newItem) newItems.append(newItem) if newItems: self.DeselectAll() [self.SelectItem(item) for item in newItems] self.selectionChanged.emit(self.GetSelectedItems())