Library drag-in
This commit is contained in:
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"2a5bf670-c821-442b-9d46-1b07faa70722": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-32,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 64,
|
|
||||||
"height": 64,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"497b1f74-1186-471f-976a-36b07a451caf": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"2a5bf670-c821-442b-9d46-1b07faa70722": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-32,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 64,
|
|
||||||
"height": 64,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"4306701a-6b1b-4d19-b8ff-45dbfa04f2d3": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"2a5bf670-c821-442b-9d46-1b07faa70722": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-32,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 64.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"c87881f1-23b4-4a69-b586-8e3c7bf6e21e": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
],
|
|
||||||
"c62de8eb-e13b-4849-bea5-c8cc5332269e": [
|
|
||||||
16,
|
|
||||||
-32
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"2a5bf670-c821-442b-9d46-1b07faa70722": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-32,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 64,
|
|
||||||
"height": 64,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "I"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"0b9036b1-e4e4-437e-8c35-f5bb391b6cbd": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
],
|
|
||||||
"fb25f35d-4c0c-4cfa-92d4-1a18a71e2c11": [
|
|
||||||
16,
|
|
||||||
-32
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"2a5bf670-c821-442b-9d46-1b07faa70722": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-32,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 64,
|
|
||||||
"height": 64,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "R"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"0ac7d4ea-77f7-4c0d-8e37-406ef66e4740": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
],
|
|
||||||
"d1f5aab2-7bff-4b1e-8697-98fb6c3d8f02": [
|
|
||||||
16,
|
|
||||||
-32
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"2a5bf670-c821-442b-9d46-1b07faa70722": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-48,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 96.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "Se"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"5b6a8a0c-0875-402c-bd74-11d086aac372": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"shapes": {
|
|
||||||
"f1a38ee1-8a53-454d-a398-12c14032ba79": {
|
|
||||||
"layer": 0,
|
|
||||||
"type": "text",
|
|
||||||
"pos": [
|
|
||||||
-48,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"width": 96.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"color": "#000000ff",
|
|
||||||
"bold": true,
|
|
||||||
"italic": false,
|
|
||||||
"size": 64.0,
|
|
||||||
"text": "Sf"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"port_positions": {
|
|
||||||
"856b4152-e18f-46d7-889d-626cb98474aa": [
|
|
||||||
-8,
|
|
||||||
-8
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -182,6 +182,7 @@ class GraphEditorClipboardHandler(ClipboardHandler):
|
|||||||
self.editor = editor
|
self.editor = editor
|
||||||
self.clipboard = clipboard
|
self.clipboard = clipboard
|
||||||
editor.scene.selectionChanged.connect(self.availability_changed)
|
editor.scene.selectionChanged.connect(self.availability_changed)
|
||||||
|
editor.component_drop_requested.connect(self.drop_components)
|
||||||
|
|
||||||
def owns_focus(self, widget: QWidget) -> bool:
|
def owns_focus(self, widget: QWidget) -> bool:
|
||||||
return widget is self.editor or self.editor.isAncestorOf(widget)
|
return widget is self.editor or self.editor.isAncestorOf(widget)
|
||||||
@@ -214,12 +215,19 @@ class GraphEditorClipboardHandler(ClipboardHandler):
|
|||||||
payload = self.clipboard.get_json(ClipboardService.COMPONENTS_MIME)
|
payload = self.clipboard.get_json(ClipboardService.COMPONENTS_MIME)
|
||||||
if graph_component is None or not isinstance(graph_component.implementation, GraphImplementation) or payload is None:
|
if graph_component is None or not isinstance(graph_component.implementation, GraphImplementation) or payload is None:
|
||||||
return
|
return
|
||||||
|
x, y = self.editor.paste_position()
|
||||||
|
self._paste_payload(graph_component, payload, (x, y))
|
||||||
|
|
||||||
|
def drop_components(self, graph_component: Component, payload: dict, position: tuple[int, int]) -> None:
|
||||||
|
self._paste_payload(graph_component, payload, position)
|
||||||
|
|
||||||
|
def _paste_payload(self, graph_component: Component, payload: dict, position: tuple[int, int]) -> None:
|
||||||
try:
|
try:
|
||||||
components, icons = import_components(payload)
|
components, icons = import_components(payload)
|
||||||
except (TypeError, ValueError) as exc:
|
except (TypeError, ValueError) as exc:
|
||||||
QMessageBox.critical(self.editor, "Could not paste components", str(exc))
|
QMessageBox.critical(self.editor, "Could not paste components", str(exc))
|
||||||
return
|
return
|
||||||
x, y = self.editor.paste_position()
|
x, y = position
|
||||||
spacing = self.editor.snap_to_grid_size * 4
|
spacing = self.editor.snap_to_grid_size * 4
|
||||||
positions = {component_id: (x + index * spacing, y + index * spacing) for index, component_id in enumerate(components)}
|
positions = {component_id: (x + index * spacing, y + index * spacing) for index, component_id in enumerate(components)}
|
||||||
self.document.paste_graph_components(graph_component, components, icons, positions)
|
self.document.paste_graph_components(graph_component, components, icons, positions)
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
from PySide6.QtCore import QObject, QSize
|
from PySide6.QtCore import QObject, QSize, Qt
|
||||||
from PySide6.QtWidgets import QAbstractItemView, QHeaderView
|
from PySide6.QtWidgets import QAbstractItemView, QHeaderView
|
||||||
|
|
||||||
from bedit_core.models import Component, ComponentID, GraphImplementation
|
from bedit_core.models import Component, ComponentID, GraphImplementation
|
||||||
from bedit_gui.models import Icon, IconDatabase
|
from bedit_gui.models import Icon, IconDatabase
|
||||||
from bedit_gui.services.application_settings import ApplicationSettings
|
from bedit_gui.services.application_settings import ApplicationSettings
|
||||||
|
from bedit_gui.services.component_clipboard import export_component_data
|
||||||
from bedit_gui.services.libraries import load_library_documents
|
from bedit_gui.services.libraries import load_library_documents
|
||||||
from bedit_gui.utils.icon import render_fitted_icon
|
from bedit_gui.utils.icon import render_fitted_icon
|
||||||
from bedit_gui.views.main_window import MainWindow
|
from bedit_gui.views.main_window import MainWindow
|
||||||
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
|
from bedit_gui.views.models.library_tree_model import LibraryTreeModel
|
||||||
|
|
||||||
ICON_SIZE = QSize(32, 32)
|
ICON_SIZE = QSize(32, 32)
|
||||||
|
|
||||||
@@ -17,13 +18,17 @@ class LibraryController(QObject):
|
|||||||
super().__init__(window)
|
super().__init__(window)
|
||||||
self.window = window
|
self.window = window
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.model = DocumentTreeModel(editable=False)
|
self._component_sources: dict[int, tuple[ComponentID, dict[ComponentID, Icon]]] = {}
|
||||||
|
self.model = LibraryTreeModel(self._component_payload)
|
||||||
|
|
||||||
tree = window.ui.libraryTree
|
tree = window.ui.libraryTree
|
||||||
tree.setModel(self.model)
|
tree.setModel(self.model)
|
||||||
tree.setHeaderHidden(True)
|
tree.setHeaderHidden(True)
|
||||||
tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
tree.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
tree.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
|
tree.setDragEnabled(True)
|
||||||
|
tree.setDragDropMode(QAbstractItemView.DragDropMode.DragOnly)
|
||||||
|
tree.setDefaultDropAction(Qt.DropAction.CopyAction)
|
||||||
tree.setIconSize(QSize(48, 48))
|
tree.setIconSize(QSize(48, 48))
|
||||||
tree.header().setStretchLastSection(False)
|
tree.header().setStretchLastSection(False)
|
||||||
tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||||
@@ -34,6 +39,11 @@ class LibraryController(QObject):
|
|||||||
|
|
||||||
def reload(self) -> None:
|
def reload(self) -> None:
|
||||||
libraries = load_library_documents(self.settings.library_paths)
|
libraries = load_library_documents(self.settings.library_paths)
|
||||||
|
self._component_sources = {}
|
||||||
|
for library in libraries:
|
||||||
|
database = library.document.metadata.get("icon_database") if library.document.metadata is not None else None
|
||||||
|
icons = database.icons if isinstance(database, IconDatabase) else {}
|
||||||
|
self._collect_component_sources(library.document.root, icons)
|
||||||
self.model.set_documents([library.document for library in libraries])
|
self.model.set_documents([library.document for library in libraries])
|
||||||
for library in libraries:
|
for library in libraries:
|
||||||
database = library.document.metadata.get("icon_database") if library.document.metadata is not None else None
|
database = library.document.metadata.get("icon_database") if library.document.metadata is not None else None
|
||||||
@@ -41,6 +51,24 @@ class LibraryController(QObject):
|
|||||||
self._set_component_icons(library.document.root, icons)
|
self._set_component_icons(library.document.root, icons)
|
||||||
self.window.ui.libraryTree.expandAll()
|
self.window.ui.libraryTree.expandAll()
|
||||||
|
|
||||||
|
def _component_payload(self, components: list[Component]) -> dict:
|
||||||
|
roots = {}
|
||||||
|
icons = {}
|
||||||
|
for component in components:
|
||||||
|
source = self._component_sources.get(id(component))
|
||||||
|
if source is None:
|
||||||
|
continue
|
||||||
|
component_id, source_icons = source
|
||||||
|
roots[component_id] = component
|
||||||
|
icons.update(source_icons)
|
||||||
|
return export_component_data(roots, icons)
|
||||||
|
|
||||||
|
def _collect_component_sources(self, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
|
||||||
|
for component_id, component in components.items():
|
||||||
|
self._component_sources[id(component)] = (component_id, icons)
|
||||||
|
if isinstance(component.implementation, GraphImplementation):
|
||||||
|
self._collect_component_sources(component.implementation.graph.components, icons)
|
||||||
|
|
||||||
def _set_component_icons(self, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
|
def _set_component_icons(self, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
|
||||||
for component_id, component in components.items():
|
for component_id, component in components.items():
|
||||||
self.model.set_component_icon(component_id, render_fitted_icon(icons.get(component_id, Icon()), component.interface.ports, ICON_SIZE))
|
self.model.set_component_icon(component_id, render_fitted_icon(icons.get(component_id, Icon()), component.interface.ports, ICON_SIZE))
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ from typing import Any
|
|||||||
from PySide6.QtCore import QMimeData, QObject, Signal
|
from PySide6.QtCore import QMimeData, QObject, Signal
|
||||||
from PySide6.QtGui import QClipboard, QGuiApplication
|
from PySide6.QtGui import QClipboard, QGuiApplication
|
||||||
|
|
||||||
|
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
|
||||||
|
|
||||||
|
|
||||||
class ClipboardService(QObject):
|
class ClipboardService(QObject):
|
||||||
COMPONENTS_MIME = "application/x-bedit-components+json"
|
COMPONENTS_MIME = COMPONENTS_MIME
|
||||||
changed = Signal()
|
changed = Signal()
|
||||||
|
|
||||||
def __init__(self, parent: QObject | None = None) -> None:
|
def __init__(self, parent: QObject | None = None) -> None:
|
||||||
|
|||||||
@@ -9,18 +9,25 @@ from bedit_gui.documents import Document as GuiDocument
|
|||||||
from bedit_gui.models import Icon, ShapeID
|
from bedit_gui.models import Icon, ShapeID
|
||||||
|
|
||||||
FORMAT_VERSION = 1
|
FORMAT_VERSION = 1
|
||||||
|
COMPONENTS_MIME = "application/x-bedit-components+json"
|
||||||
|
|
||||||
|
|
||||||
def export_components(document: GuiDocument, components: list[Component]) -> dict[str, Any]:
|
def export_components(document: GuiDocument, components: list[Component]) -> dict[str, Any]:
|
||||||
roots = {document.component_id(component): component for component in components}
|
roots = {document.component_id(component): component for component in components}
|
||||||
serialized = document_to_data(Document(format_version=1, id=ID(), name="Clipboard", root=roots))
|
|
||||||
component_ids = _all_component_ids(roots)
|
component_ids = _all_component_ids(roots)
|
||||||
icons = {}
|
icons = {}
|
||||||
for component_id in component_ids:
|
for component_id in component_ids:
|
||||||
icon = document.stored_component_icon(component_id)
|
icon = document.stored_component_icon(component_id)
|
||||||
if icon is not None:
|
if icon is not None:
|
||||||
icons[str(component_id)] = icon.to_data()
|
icons[component_id] = icon
|
||||||
return {"format_version": FORMAT_VERSION, "type": "components", "components": serialized["root"], "icons": icons}
|
return export_component_data(roots, icons)
|
||||||
|
|
||||||
|
|
||||||
|
def export_component_data(components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> dict[str, Any]:
|
||||||
|
serialized = document_to_data(Document(format_version=1, id=ID(), name="Clipboard", root=components))
|
||||||
|
component_ids = set(_all_component_ids(components))
|
||||||
|
icon_data = {str(component_id): icon.to_data() for component_id, icon in icons.items() if component_id in component_ids}
|
||||||
|
return {"format_version": FORMAT_VERSION, "type": "components", "components": serialized["root"], "icons": icon_data}
|
||||||
|
|
||||||
|
|
||||||
def import_components(payload: dict[str, Any]) -> tuple[dict[ComponentID, Component], dict[ComponentID, Icon]]:
|
def import_components(payload: dict[str, Any]) -> tuple[dict[ComponentID, Component], dict[ComponentID, Icon]]:
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from itertools import pairwise
|
from itertools import pairwise
|
||||||
from math import hypot
|
from math import hypot
|
||||||
|
|
||||||
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
||||||
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
|
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
|
||||||
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsTextItem, QGraphicsView, QMenu, QWidget
|
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsTextItem, QGraphicsView, QMenu, QWidget
|
||||||
|
|
||||||
from bedit_core.models import BondCausality, BondConnection, BondPort, Component, ComponentID, Connection, ConnectionID, GraphImplementation, Port, PortID, SignalConnection, SignalDirection, SignalPort
|
from bedit_core.models import BondCausality, BondConnection, BondPort, Component, ComponentID, Connection, ConnectionID, GraphImplementation, Port, PortID, SignalConnection, SignalDirection, SignalPort
|
||||||
from bedit_gui.models import Graph, GraphComponentLabel, GraphConnection, Icon
|
from bedit_gui.models import Graph, GraphComponentLabel, GraphConnection, Icon
|
||||||
|
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
|
||||||
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
|
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
|
||||||
from bedit_gui.utils.icon import get_pixmap_bounding_box, render_icon
|
from bedit_gui.utils.icon import get_pixmap_bounding_box, render_icon
|
||||||
|
|
||||||
@@ -291,6 +293,7 @@ class GraphEditorWidget(QWidget):
|
|||||||
connection_points_change_requested = Signal(object, object, object, str)
|
connection_points_change_requested = Signal(object, object, object, str)
|
||||||
connection_add_requested = Signal(object, object)
|
connection_add_requested = Signal(object, object)
|
||||||
connections_delete_requested = Signal(object, object)
|
connections_delete_requested = Signal(object, object)
|
||||||
|
component_drop_requested = Signal(object, object, object)
|
||||||
|
|
||||||
def __init__(self, parent: QWidget | None = None, snap_to_grid_size: int = 4) -> None:
|
def __init__(self, parent: QWidget | None = None, snap_to_grid_size: int = 4) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -314,6 +317,7 @@ class GraphEditorWidget(QWidget):
|
|||||||
self.ui.graphicsView.setScene(self.scene)
|
self.ui.graphicsView.setScene(self.scene)
|
||||||
self.ui.graphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
self.ui.graphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
||||||
self.ui.graphicsView.viewport().installEventFilter(self)
|
self.ui.graphicsView.viewport().installEventFilter(self)
|
||||||
|
self.ui.graphicsView.viewport().setAcceptDrops(True)
|
||||||
self._mode_actions = QActionGroup(self)
|
self._mode_actions = QActionGroup(self)
|
||||||
self._mode_actions.setExclusive(True)
|
self._mode_actions.setExclusive(True)
|
||||||
self._mode_actions.addAction(self.ui.actionMouseMode)
|
self._mode_actions.addAction(self.ui.actionMouseMode)
|
||||||
@@ -736,6 +740,25 @@ class GraphEditorWidget(QWidget):
|
|||||||
self.ui.graphicsView.centerOn(bounds.center())
|
self.ui.graphicsView.centerOn(bounds.center())
|
||||||
|
|
||||||
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
||||||
|
if watched is self.ui.graphicsView.viewport() and event.type() in (QEvent.Type.DragEnter, QEvent.Type.DragMove):
|
||||||
|
assert isinstance(event, (QDragEnterEvent, QDragMoveEvent))
|
||||||
|
if self._component is not None and event.mimeData().hasFormat(COMPONENTS_MIME):
|
||||||
|
event.setDropAction(Qt.DropAction.CopyAction)
|
||||||
|
event.accept()
|
||||||
|
return True
|
||||||
|
if watched is self.ui.graphicsView.viewport() and event.type() == QEvent.Type.Drop:
|
||||||
|
assert isinstance(event, QDropEvent)
|
||||||
|
if self._component is not None and event.mimeData().hasFormat(COMPONENTS_MIME):
|
||||||
|
try:
|
||||||
|
payload = json.loads(bytes(event.mimeData().data(COMPONENTS_MIME)).decode("utf-8"))
|
||||||
|
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||||
|
return True
|
||||||
|
if isinstance(payload, dict):
|
||||||
|
position = self._snap_position(self.ui.graphicsView.mapToScene(event.position().toPoint()))
|
||||||
|
self.component_drop_requested.emit(self._component, payload, position)
|
||||||
|
event.setDropAction(Qt.DropAction.CopyAction)
|
||||||
|
event.accept()
|
||||||
|
return True
|
||||||
if watched is self.ui.graphicsView.viewport() and event.type() == QEvent.Type.MouseMove:
|
if watched is self.ui.graphicsView.viewport() and event.type() == QEvent.Type.MouseMove:
|
||||||
assert isinstance(event, QMouseEvent)
|
assert isinstance(event, QMouseEvent)
|
||||||
self._update_connection_preview(self.ui.graphicsView.mapToScene(event.position().toPoint()))
|
self._update_connection_preview(self.ui.graphicsView.mapToScene(event.position().toPoint()))
|
||||||
|
|||||||
55
src/bedit_gui/views/models/library_tree_model.py
Normal file
55
src/bedit_gui/views/models/library_tree_model.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
from PySide6.QtCore import QMimeData, QModelIndex, Qt
|
||||||
|
|
||||||
|
from bedit_core.models import Component
|
||||||
|
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
|
||||||
|
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
|
||||||
|
|
||||||
|
PayloadFactory = Callable[[list[Component]], dict]
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryTreeModel(DocumentTreeModel):
|
||||||
|
def __init__(self, payload_factory: PayloadFactory) -> None:
|
||||||
|
super().__init__(editable=False)
|
||||||
|
self._payload_factory = payload_factory
|
||||||
|
|
||||||
|
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
|
||||||
|
flags = super().flags(index)
|
||||||
|
if isinstance(self.value(index), Component):
|
||||||
|
flags |= Qt.ItemFlag.ItemIsDragEnabled
|
||||||
|
return flags
|
||||||
|
|
||||||
|
def mimeTypes(self) -> list[str]:
|
||||||
|
return [COMPONENTS_MIME]
|
||||||
|
|
||||||
|
def mimeData(self, indexes: list[QModelIndex]) -> QMimeData:
|
||||||
|
rows = [index for index in indexes if index.column() == 0 and isinstance(self.value(index), Component)]
|
||||||
|
selected = {id(self.value(index)) for index in rows}
|
||||||
|
components = []
|
||||||
|
added = set()
|
||||||
|
for index in rows:
|
||||||
|
parent = index.parent()
|
||||||
|
if any(id(self.value(parent_index)) in selected for parent_index in self._parents(parent)):
|
||||||
|
continue
|
||||||
|
component = self.value(index)
|
||||||
|
if isinstance(component, Component) and id(component) not in added:
|
||||||
|
components.append(component)
|
||||||
|
added.add(id(component))
|
||||||
|
mime = QMimeData()
|
||||||
|
if components:
|
||||||
|
mime.setData(COMPONENTS_MIME, json.dumps(self._payload_factory(components)).encode("utf-8"))
|
||||||
|
mime.setText("\n".join(component.name for component in components))
|
||||||
|
return mime
|
||||||
|
|
||||||
|
def supportedDragActions(self) -> Qt.DropAction:
|
||||||
|
return Qt.DropAction.CopyAction
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parents(index: QModelIndex):
|
||||||
|
while index.isValid():
|
||||||
|
yield index
|
||||||
|
index = index.parent()
|
||||||
@@ -374,14 +374,14 @@
|
|||||||
"connection_type": "bond",
|
"connection_type": "bond",
|
||||||
"source": "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3",
|
"source": "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3",
|
||||||
"target": "497b1f74-1186-471f-976a-36b07a451caf",
|
"target": "497b1f74-1186-471f-976a-36b07a451caf",
|
||||||
"causality": "effort_out",
|
"causality": "flow_out",
|
||||||
"undesired": false
|
"undesired": false
|
||||||
},
|
},
|
||||||
"743e35a4-e736-407d-bf34-b4afbcde5a0b": {
|
"743e35a4-e736-407d-bf34-b4afbcde5a0b": {
|
||||||
"connection_type": "bond",
|
"connection_type": "bond",
|
||||||
"source": "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3",
|
"source": "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3",
|
||||||
"target": "0ac7d4ea-77f7-4c0d-8e37-406ef66e4740",
|
"target": "0ac7d4ea-77f7-4c0d-8e37-406ef66e4740",
|
||||||
"causality": "flow_out",
|
"causality": "effort_out",
|
||||||
"undesired": false
|
"undesired": false
|
||||||
},
|
},
|
||||||
"68727b23-888b-45b4-a0f4-801ac7b39601": {
|
"68727b23-888b-45b4-a0f4-801ac7b39601": {
|
||||||
@@ -626,6 +626,10 @@
|
|||||||
"1455acad-931b-4caa-be71-aff4283507c6": [
|
"1455acad-931b-4caa-be71-aff4283507c6": [
|
||||||
64,
|
64,
|
||||||
384
|
384
|
||||||
|
],
|
||||||
|
"015a0697-69a9-43bf-9410-d2c8b44303f8": [
|
||||||
|
64,
|
||||||
|
384
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"component_labels": {
|
"component_labels": {
|
||||||
|
|||||||
Reference in New Issue
Block a user