"""CanvasView: zoomable/pannable QGraphicsView that accepts node drops.""" from __future__ import annotations from PyQt6.QtCore import Qt from PyQt6.QtGui import QPainter from PyQt6.QtWidgets import QGraphicsView from ..panels.node_panel import NODE_MIME MIN_SCALE = 0.15 MAX_SCALE = 6.0 class CanvasView(QGraphicsView): def __init__(self, scene, parent=None): super().__init__(scene, parent) self.setRenderHints( QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter) self.setAcceptDrops(True) self._scale = 1.0 self._panning = False # -- zoom ------------------------------------------------------------- def wheelEvent(self, event): factor = 1.2 if event.angleDelta().y() > 0 else 1 / 1.2 new_scale = self._scale * factor if new_scale < MIN_SCALE or new_scale > MAX_SCALE: return self._scale = new_scale self.scale(factor, factor) def reset_zoom(self): self.resetTransform() self._scale = 1.0 def fit_to_contents(self): rect = self.scene().itemsBoundingRect() if rect.isNull(): return self.fitInView(rect.adjusted(-40, -40, 40, 40), Qt.AspectRatioMode.KeepAspectRatio) self._scale = self.transform().m11() # -- panning (space or middle mouse) ---------------------------------- def keyPressEvent(self, event): if event.key() == Qt.Key.Key_Space and not self._panning: self._panning = True self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) super().keyPressEvent(event) def keyReleaseEvent(self, event): if event.key() == Qt.Key.Key_Space: self._panning = False self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) super().keyReleaseEvent(event) def mousePressEvent(self, event): if event.button() == Qt.MouseButton.MiddleButton: self._panning = True self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # forward as a left press so ScrollHandDrag engages from PyQt6.QtGui import QMouseEvent fake = QMouseEvent(event.type(), event.position(), event.globalPosition(), Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, event.modifiers()) super().mousePressEvent(fake) return super().mousePressEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.MouseButton.MiddleButton and self._panning: self._panning = False self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) return super().mouseReleaseEvent(event) # -- drops from the node panel --------------------------------------- def dragEnterEvent(self, event): if event.mimeData().hasFormat(NODE_MIME): event.acceptProposedAction() else: super().dragEnterEvent(event) def dragMoveEvent(self, event): if event.mimeData().hasFormat(NODE_MIME): event.acceptProposedAction() else: super().dragMoveEvent(event) def dropEvent(self, event): if event.mimeData().hasFormat(NODE_MIME): key = bytes(event.mimeData().data(NODE_MIME)).decode("utf-8") scene_pos = self.mapToScene(event.position().toPoint()) self.scene().create_node(key, scene_pos.x(), scene_pos.y()) event.acceptProposedAction() else: super().dropEvent(event)