Basic document handling
This commit is contained in:
4
BEdit/src/bedit/workspace/__init__.py
Normal file
4
BEdit/src/bedit/workspace/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from bedit.workspace.view import GraphWorkspaceView
|
||||
|
||||
__all__ = ["GraphWorkspaceView"]
|
||||
|
||||
547
BEdit/src/bedit/workspace/view.py
Normal file
547
BEdit/src/bedit/workspace/view.py
Normal file
@@ -0,0 +1,547 @@
|
||||
import json
|
||||
|
||||
from PySide6.QtCore import QMimeData, QPointF, QRectF, Qt, Signal
|
||||
from PySide6.QtGui import (
|
||||
QColor,
|
||||
QDragEnterEvent,
|
||||
QDropEvent,
|
||||
QMouseEvent,
|
||||
QPainter,
|
||||
QPainterPath,
|
||||
QPen,
|
||||
QTransform,
|
||||
)
|
||||
from PySide6.QtWidgets import (
|
||||
QGraphicsEllipseItem,
|
||||
QGraphicsItem,
|
||||
QGraphicsObject,
|
||||
QGraphicsPathItem,
|
||||
QGraphicsScene,
|
||||
QGraphicsSceneContextMenuEvent,
|
||||
QGraphicsSceneMouseEvent,
|
||||
QGraphicsView,
|
||||
QApplication,
|
||||
QMenu,
|
||||
QStyleOptionGraphicsItem,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from bedit.document.controller import DocumentController
|
||||
from bedit.document.model import Component, Connection, Endpoint, Port
|
||||
from bedit.library.tree_model import COMPONENT_MIME_TYPE
|
||||
|
||||
|
||||
SELECTION_MIME_TYPE = "application/x-bedit-selection"
|
||||
|
||||
|
||||
class ConnectionPortItem(QGraphicsEllipseItem):
|
||||
def __init__(self, endpoint: Endpoint, role: str, label: str, parent: QGraphicsItem) -> None:
|
||||
super().__init__(-6, -6, 12, 12, parent)
|
||||
self.endpoint = endpoint
|
||||
self.role = role
|
||||
self.setBrush(QColor("#ffffff"))
|
||||
self.setPen(QPen(QColor("#303030"), 1.5))
|
||||
self.setZValue(2)
|
||||
self.setToolTip(label)
|
||||
|
||||
|
||||
class ComponentGraphicsItem(QGraphicsObject):
|
||||
WIDTH = 120.0
|
||||
HEIGHT = 72.0
|
||||
|
||||
def __init__(self, component: Component, controller: DocumentController) -> None:
|
||||
super().__init__()
|
||||
self.component_id = component.id
|
||||
self.component = component
|
||||
self.controller = controller
|
||||
self.drag_start = QPointF()
|
||||
self.setFlags(
|
||||
QGraphicsItem.GraphicsItemFlag.ItemIsMovable
|
||||
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||
)
|
||||
self.input_ports = self._create_ports(component.inputs, "target", 0.0)
|
||||
self.output_ports = self._create_ports(component.outputs, "source", self.WIDTH)
|
||||
self.setTransformOriginPoint(self.WIDTH / 2, self.HEIGHT / 2)
|
||||
self.setRotation(component.rotation)
|
||||
|
||||
def _create_ports(self, ports, role: str, x: float) -> dict[str, ConnectionPortItem]:
|
||||
result = {}
|
||||
spacing = self.HEIGHT / (len(ports) + 1)
|
||||
for index, port in enumerate(ports, start=1):
|
||||
endpoint = Endpoint(block=self.component_id, port=port.id)
|
||||
item = ConnectionPortItem(endpoint, role, port.name, self)
|
||||
item.setPos(x, spacing * index)
|
||||
result[port.id] = item
|
||||
return result
|
||||
|
||||
def boundingRect(self) -> QRectF: # noqa: N802
|
||||
return QRectF(0, 0, self.WIDTH, self.HEIGHT)
|
||||
|
||||
def paint(
|
||||
self,
|
||||
painter: QPainter,
|
||||
option: QStyleOptionGraphicsItem,
|
||||
widget: QWidget | None = None,
|
||||
) -> None:
|
||||
del option, widget
|
||||
icon = self.component.icon
|
||||
fill = QColor("#dbeafe") if self.isSelected() else QColor(icon.fill)
|
||||
painter.setBrush(fill)
|
||||
painter.setPen(QPen(QColor(icon.border), 1.5))
|
||||
if icon.shape == "ellipse":
|
||||
painter.drawEllipse(self.boundingRect())
|
||||
else:
|
||||
painter.drawRoundedRect(self.boundingRect(), 5, 5)
|
||||
painter.setPen(QColor("#202020"))
|
||||
painter.drawText(
|
||||
self.boundingRect(),
|
||||
Qt.AlignmentFlag.AlignCenter,
|
||||
icon.text or self.component.name,
|
||||
)
|
||||
|
||||
def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
self.controller.activate_component(self.component_id)
|
||||
event.accept()
|
||||
|
||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||
if not self.isSelected():
|
||||
scene = self.scene()
|
||||
if scene is not None:
|
||||
scene.clearSelection()
|
||||
self.setSelected(True)
|
||||
menu = QMenu()
|
||||
options_action = menu.addAction("Component Options…")
|
||||
if menu.exec(event.screenPos()) is options_action:
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.componentOptionsRequested.emit(self.component_id)
|
||||
event.accept()
|
||||
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
self.drag_start = self.pos()
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
super().mouseReleaseEvent(event)
|
||||
self.controller.move_component(self.component_id, self.drag_start, self.pos())
|
||||
|
||||
def itemChange(self, change, value): # noqa: N802
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.update_connections_for_block(self.component_id)
|
||||
return super().itemChange(change, value)
|
||||
|
||||
|
||||
class InterfaceTerminalItem(QGraphicsObject):
|
||||
WIDTH = 110.0
|
||||
HEIGHT = 36.0
|
||||
|
||||
def __init__(self, port: Port, direction: str, controller: DocumentController) -> None:
|
||||
super().__init__()
|
||||
self.port = port
|
||||
self.direction = direction
|
||||
self.controller = controller
|
||||
self.drag_start = QPointF()
|
||||
role = "source" if direction == "input" else "target"
|
||||
self.connection_port = ConnectionPortItem(
|
||||
Endpoint(interface=port.id), role, port.name, self
|
||||
)
|
||||
connection_x = self.WIDTH if direction == "input" else 0.0
|
||||
self.connection_port.setPos(connection_x, self.HEIGHT / 2)
|
||||
self.setFlags(
|
||||
QGraphicsItem.GraphicsItemFlag.ItemIsMovable
|
||||
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||
)
|
||||
self.setToolTip(f"Component {direction}: {port.name}")
|
||||
|
||||
def boundingRect(self) -> QRectF: # noqa: N802
|
||||
return QRectF(0, 0, self.WIDTH, self.HEIGHT)
|
||||
|
||||
def paint(
|
||||
self,
|
||||
painter: QPainter,
|
||||
option: QStyleOptionGraphicsItem,
|
||||
widget: QWidget | None = None,
|
||||
) -> None:
|
||||
del option, widget
|
||||
painter.setBrush(QColor("#e5e7eb"))
|
||||
painter.setPen(QPen(QColor("#4b5563"), 1.5))
|
||||
painter.drawRoundedRect(self.boundingRect(), 4, 4)
|
||||
painter.setPen(QColor("#202020"))
|
||||
marker = "IN" if self.direction == "input" else "OUT"
|
||||
painter.drawText(
|
||||
self.boundingRect().adjusted(8, 0, -8, 0),
|
||||
Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft,
|
||||
f"{marker} {self.port.name}",
|
||||
)
|
||||
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
self.drag_start = self.pos()
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||
menu = QMenu()
|
||||
options_action = menu.addAction(f"{self.direction.title()} Options…")
|
||||
if menu.exec(event.screenPos()) is options_action:
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.portOptionsRequested.emit(self.port.id, self.direction)
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
super().mouseReleaseEvent(event)
|
||||
self.controller.move_interface_port(self.port.id, self.drag_start, self.pos())
|
||||
|
||||
def itemChange(self, change, value): # noqa: N802
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.update_connections_for_interface(self.port.id)
|
||||
return super().itemChange(change, value)
|
||||
|
||||
|
||||
class ConnectionGraphicsItem(QGraphicsPathItem):
|
||||
def __init__(self, connection_id: str, name: str = "") -> None:
|
||||
super().__init__()
|
||||
self.connection_id = connection_id
|
||||
self.name = name
|
||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
||||
self._update_pen()
|
||||
self.setZValue(-1)
|
||||
self.setToolTip(name or "Connection")
|
||||
|
||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||
menu = QMenu()
|
||||
options_action = menu.addAction("Connection Options…")
|
||||
if menu.exec(event.screenPos()) is options_action:
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.connectionOptionsRequested.emit(self.connection_id)
|
||||
event.accept()
|
||||
|
||||
def itemChange(self, change, value): # noqa: N802
|
||||
result = super().itemChange(change, value)
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
||||
self._update_pen()
|
||||
return result
|
||||
|
||||
def _update_pen(self) -> None:
|
||||
self.setPen(
|
||||
QPen(
|
||||
QColor("#f59e0b") if self.isSelected() else QColor("#285f9e"),
|
||||
4.0 if self.isSelected() else 2.5,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class GraphScene(QGraphicsScene):
|
||||
componentOptionsRequested = Signal(str)
|
||||
portOptionsRequested = Signal(str, str)
|
||||
connectionOptionsRequested = Signal(str)
|
||||
|
||||
def __init__(self, controller: DocumentController, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.controller = controller
|
||||
self.component_items: dict[str, ComponentGraphicsItem] = {}
|
||||
self.input_items: dict[str, InterfaceTerminalItem] = {}
|
||||
self.output_items: dict[str, InterfaceTerminalItem] = {}
|
||||
self.connection_items: dict[str, ConnectionGraphicsItem] = {}
|
||||
self.pending_source: ConnectionPortItem | None = None
|
||||
self.setSceneRect(-2000, -2000, 4000, 4000)
|
||||
|
||||
controller.documentReset.connect(self.rebuild)
|
||||
controller.activeGraphChanged.connect(self.rebuild)
|
||||
controller.componentMoved.connect(self.set_component_position)
|
||||
controller.componentRotated.connect(self.set_component_rotation)
|
||||
self.rebuild()
|
||||
|
||||
def rebuild(self) -> None:
|
||||
self.clear()
|
||||
self.component_items.clear()
|
||||
self.input_items.clear()
|
||||
self.output_items.clear()
|
||||
self.connection_items.clear()
|
||||
self.pending_source = None
|
||||
owner = self.controller.active_component
|
||||
if owner is None or owner.implementation_kind != "graph":
|
||||
return
|
||||
for port in owner.inputs:
|
||||
item = InterfaceTerminalItem(port, "input", self.controller)
|
||||
self.addItem(item)
|
||||
item.setPos(port.x, port.y)
|
||||
self.input_items[port.id] = item
|
||||
for port in owner.outputs:
|
||||
item = InterfaceTerminalItem(port, "output", self.controller)
|
||||
self.addItem(item)
|
||||
item.setPos(port.x, port.y)
|
||||
self.output_items[port.id] = item
|
||||
for component in owner.graph.blocks.values():
|
||||
item = ComponentGraphicsItem(component, self.controller)
|
||||
self.addItem(item)
|
||||
item.setPos(component.x, component.y)
|
||||
self.component_items[component.id] = item
|
||||
for connection in owner.graph.connections.values():
|
||||
item = ConnectionGraphicsItem(connection.id, connection.name)
|
||||
self.addItem(item)
|
||||
self.connection_items[connection.id] = item
|
||||
self.update_connection(connection.id)
|
||||
|
||||
def set_component_position(self, component_id: str, position: QPointF) -> None:
|
||||
item = self.component_items.get(component_id)
|
||||
if item is not None and item.pos() != position:
|
||||
item.setPos(position)
|
||||
|
||||
def set_component_rotation(self, component_id: str, rotation: float) -> None:
|
||||
item = self.component_items.get(component_id)
|
||||
if item is not None:
|
||||
item.setRotation(rotation)
|
||||
self.update_connections_for_block(component_id)
|
||||
|
||||
def update_connections_for_block(self, component_id: str) -> None:
|
||||
for connection in self.controller.active_graph.connections.values():
|
||||
if component_id in (connection.source.block, connection.target.block):
|
||||
self.update_connection(connection.id)
|
||||
|
||||
def update_connections_for_interface(self, port_id: str) -> None:
|
||||
for connection in self.controller.active_graph.connections.values():
|
||||
if port_id in (connection.source.interface, connection.target.interface):
|
||||
self.update_connection(connection.id)
|
||||
|
||||
def drawBackground(self, painter: QPainter, rect: QRectF) -> None: # noqa: N802
|
||||
painter.fillRect(rect, QColor("#e4e4e4"))
|
||||
if self.controller.document is None or self.controller.active_component is None:
|
||||
return
|
||||
spacing = 32
|
||||
left = int(rect.left()) - (int(rect.left()) % spacing)
|
||||
top = int(rect.top()) - (int(rect.top()) % spacing)
|
||||
painter.setPen(QPen(QColor("#b8b8b8"), 1))
|
||||
for x in range(left, int(rect.right()) + spacing, spacing):
|
||||
for y in range(top, int(rect.bottom()) + spacing, spacing):
|
||||
painter.drawPoint(x, y)
|
||||
|
||||
def _endpoint_item(self, endpoint: Endpoint, role: str) -> ConnectionPortItem | None:
|
||||
if endpoint.interface is not None:
|
||||
terminals = self.input_items if role == "source" else self.output_items
|
||||
terminal = terminals.get(endpoint.interface)
|
||||
return terminal.connection_port if terminal else None
|
||||
component = self.component_items.get(endpoint.block or "")
|
||||
if component is None:
|
||||
return None
|
||||
ports = component.output_ports if role == "source" else component.input_ports
|
||||
return ports.get(endpoint.port or "")
|
||||
|
||||
def update_connection(self, connection_id: str) -> None:
|
||||
connection = self.controller.active_graph.connections.get(connection_id)
|
||||
graphics = self.connection_items.get(connection_id)
|
||||
if connection is None or graphics is None:
|
||||
return
|
||||
source = self._endpoint_item(connection.source, "source")
|
||||
target = self._endpoint_item(connection.target, "target")
|
||||
if source is None or target is None:
|
||||
return
|
||||
start, end = source.scenePos(), target.scenePos()
|
||||
distance = max(50.0, abs(end.x() - start.x()) * 0.5)
|
||||
path = QPainterPath(start)
|
||||
path.cubicTo(start + QPointF(distance, 0), end - QPointF(distance, 0), end)
|
||||
graphics.setPath(path)
|
||||
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
item = self.itemAt(event.scenePos(), QTransform())
|
||||
if isinstance(item, ConnectionPortItem):
|
||||
if item.role == "source":
|
||||
self._clear_pending_source()
|
||||
self.pending_source = item
|
||||
item.setBrush(QColor("#f5b642"))
|
||||
elif self.pending_source is not None:
|
||||
if self.pending_source.endpoint != item.endpoint:
|
||||
self.controller.connect(self.pending_source.endpoint, item.endpoint)
|
||||
self._clear_pending_source()
|
||||
event.accept()
|
||||
return
|
||||
self._clear_pending_source()
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def _clear_pending_source(self) -> None:
|
||||
if self.pending_source is not None:
|
||||
self.pending_source.setBrush(QColor("#ffffff"))
|
||||
self.pending_source = None
|
||||
|
||||
|
||||
class GraphWorkspaceView(QGraphicsView):
|
||||
toolUsed = Signal()
|
||||
componentOptionsRequested = Signal(str)
|
||||
portOptionsRequested = Signal(str, str)
|
||||
connectionOptionsRequested = Signal(str)
|
||||
selectionAvailabilityChanged = Signal(bool)
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.controller: DocumentController | None = None
|
||||
self.tool_mode = "pointer"
|
||||
self.paste_count = 0
|
||||
self.setAcceptDrops(True)
|
||||
self.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
|
||||
self.setBackgroundBrush(QColor("#9a9a9a"))
|
||||
|
||||
def set_model(self, controller: DocumentController) -> None:
|
||||
self.controller = controller
|
||||
scene = GraphScene(controller, self)
|
||||
scene.componentOptionsRequested.connect(self.componentOptionsRequested)
|
||||
scene.portOptionsRequested.connect(self.portOptionsRequested)
|
||||
scene.connectionOptionsRequested.connect(self.connectionOptionsRequested)
|
||||
scene.selectionChanged.connect(
|
||||
lambda: self.selectionAvailabilityChanged.emit(bool(scene.selectedItems()))
|
||||
)
|
||||
self.setScene(scene)
|
||||
|
||||
def select_all(self) -> None:
|
||||
scene = self.scene()
|
||||
if scene is None:
|
||||
return
|
||||
for item in scene.items():
|
||||
if item.flags() & QGraphicsItem.GraphicsItemFlag.ItemIsSelectable:
|
||||
item.setSelected(True)
|
||||
|
||||
def delete_selected(self) -> None:
|
||||
if self.controller is None or not isinstance(self.scene(), GraphScene):
|
||||
return
|
||||
blocks: set[str] = set()
|
||||
connections: set[str] = set()
|
||||
inputs: set[str] = set()
|
||||
outputs: set[str] = set()
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, ComponentGraphicsItem):
|
||||
blocks.add(item.component_id)
|
||||
elif isinstance(item, ConnectionGraphicsItem):
|
||||
connections.add(item.connection_id)
|
||||
elif isinstance(item, InterfaceTerminalItem):
|
||||
(inputs if item.direction == "input" else outputs).add(item.port.id)
|
||||
self.controller.delete_selection(blocks, connections, inputs, outputs)
|
||||
|
||||
def has_selected_components(self) -> bool:
|
||||
scene = self.scene()
|
||||
return bool(
|
||||
scene
|
||||
and any(isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems())
|
||||
)
|
||||
|
||||
def rotate_selected(self) -> None:
|
||||
if self.controller is None or self.scene() is None:
|
||||
return
|
||||
component_ids = {
|
||||
item.component_id
|
||||
for item in self.scene().selectedItems()
|
||||
if isinstance(item, ComponentGraphicsItem)
|
||||
}
|
||||
self.controller.rotate_components(component_ids)
|
||||
|
||||
def copy_selection(self) -> bool:
|
||||
if self.controller is None or not isinstance(self.scene(), GraphScene):
|
||||
return False
|
||||
selected_ids = {
|
||||
item.component_id
|
||||
for item in self.scene().selectedItems()
|
||||
if isinstance(item, ComponentGraphicsItem)
|
||||
}
|
||||
if not selected_ids:
|
||||
return False
|
||||
graph = self.controller.active_graph
|
||||
components = [graph.blocks[component_id].to_dict() for component_id in selected_ids]
|
||||
connections = [
|
||||
connection.to_dict()
|
||||
for connection in graph.connections.values()
|
||||
if connection.source.block in selected_ids and connection.target.block in selected_ids
|
||||
]
|
||||
mime_data = QMimeData()
|
||||
mime_data.setData(
|
||||
SELECTION_MIME_TYPE,
|
||||
json.dumps({"components": components, "connections": connections}).encode("utf-8"),
|
||||
)
|
||||
QApplication.clipboard().setMimeData(mime_data)
|
||||
self.paste_count = 0
|
||||
return True
|
||||
|
||||
def cut_selection(self) -> None:
|
||||
if self.copy_selection():
|
||||
self.delete_selected()
|
||||
|
||||
def paste_selection(self) -> None:
|
||||
if self.controller is None:
|
||||
return
|
||||
mime_data = QApplication.clipboard().mimeData()
|
||||
if not mime_data.hasFormat(SELECTION_MIME_TYPE):
|
||||
return
|
||||
try:
|
||||
payload = json.loads(bytes(mime_data.data(SELECTION_MIME_TYPE)).decode("utf-8"))
|
||||
components = [Component.from_dict(item) for item in payload.get("components", [])]
|
||||
connections = [Connection.from_dict(item) for item in payload.get("connections", [])]
|
||||
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
|
||||
return
|
||||
self.paste_count += 1
|
||||
new_ids = self.controller.paste_selection(
|
||||
components,
|
||||
connections,
|
||||
QPointF(32 * self.paste_count, 32 * self.paste_count),
|
||||
)
|
||||
scene = self.scene()
|
||||
if isinstance(scene, GraphScene):
|
||||
scene.clearSelection()
|
||||
for component_id in new_ids:
|
||||
item = scene.component_items.get(component_id)
|
||||
if item is not None:
|
||||
item.setSelected(True)
|
||||
|
||||
def set_tool_mode(self, mode: str) -> None:
|
||||
self.tool_mode = mode
|
||||
self.setDragMode(
|
||||
QGraphicsView.DragMode.RubberBandDrag
|
||||
if mode == "pointer"
|
||||
else QGraphicsView.DragMode.NoDrag
|
||||
)
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent) -> None: # noqa: N802
|
||||
if (
|
||||
self.controller is not None
|
||||
and self.tool_mode in {"input", "output"}
|
||||
and event.button() == Qt.MouseButton.LeftButton
|
||||
):
|
||||
self.controller.add_interface_port(self.tool_mode, self.mapToScene(event.position().toPoint()))
|
||||
self.toolUsed.emit()
|
||||
event.accept()
|
||||
return
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def dragEnterEvent(self, event: QDragEnterEvent) -> None: # noqa: N802
|
||||
if (
|
||||
event.mimeData().hasFormat(COMPONENT_MIME_TYPE)
|
||||
and self.controller is not None
|
||||
and self.controller.active_component is not None
|
||||
and self.controller.active_component.implementation_kind == "graph"
|
||||
):
|
||||
event.acceptProposedAction()
|
||||
return
|
||||
super().dragEnterEvent(event)
|
||||
|
||||
def dragMoveEvent(self, event) -> None: # noqa: N802
|
||||
if (
|
||||
event.mimeData().hasFormat(COMPONENT_MIME_TYPE)
|
||||
and self.controller is not None
|
||||
and self.controller.active_component is not None
|
||||
and self.controller.active_component.implementation_kind == "graph"
|
||||
):
|
||||
event.acceptProposedAction()
|
||||
return
|
||||
super().dragMoveEvent(event)
|
||||
|
||||
def dropEvent(self, event: QDropEvent) -> None: # noqa: N802
|
||||
if self.controller is None or not event.mimeData().hasFormat(COMPONENT_MIME_TYPE):
|
||||
super().dropEvent(event)
|
||||
return
|
||||
data = json.loads(bytes(event.mimeData().data(COMPONENT_MIME_TYPE)).decode("utf-8"))
|
||||
source = Component.from_dict(data)
|
||||
self.controller.add_component_copy(source, self.mapToScene(event.position().toPoint()))
|
||||
event.acceptProposedAction()
|
||||
Reference in New Issue
Block a user