from __future__ import annotations from copy import deepcopy from typing import Any from bedit_core.models import Component, ComponentID, ConnectionID, Document, GraphImplementation, ID, ParameterID, PortID from bedit_core.serialization.schema import document_from_data, document_to_data from bedit_gui.documents import Document as GuiDocument from bedit_gui.models import Icon, ShapeID FORMAT_VERSION = 1 COMPONENTS_MIME = "application/x-bedit-components+json" def export_components(document: GuiDocument, components: list[Component]) -> dict[str, Any]: roots = {document.component_id(component): component for component in components} component_ids = _all_component_ids(roots) icons = {} for component_id in component_ids: icon = document.stored_component_icon(component_id) if icon is not None: icons[component_id] = icon 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]]: if payload.get("format_version") != FORMAT_VERSION or payload.get("type") != "components": raise ValueError("unsupported component clipboard format") components = payload.get("components") if not isinstance(components, dict): raise TypeError("component clipboard payload must contain a components object") clipboard_document = document_from_data({"format_version": 1, "id": str(ID()), "name": "Clipboard", "root": components, "metadata": None}) component_map: dict[ComponentID, ComponentID] = {} port_map: dict[PortID, PortID] = {} remapped = _remap_components(clipboard_document.root, component_map, port_map) icon_data = payload.get("icons", {}) if not isinstance(icon_data, dict): raise TypeError("component clipboard icons must be an object") icons: dict[ComponentID, Icon] = {} for old_id, data in icon_data.items(): new_component_id = component_map.get(ComponentID(old_id)) if new_component_id is None or not isinstance(data, dict): continue icon = Icon.from_data(data) icon.shapes = {ShapeID(): shape for shape in icon.shapes.values()} icon.port_positions = {port_map[port_id]: position for port_id, position in icon.port_positions.items() if port_id in port_map} icons[new_component_id] = icon return remapped, icons def _remap_components(components: dict[ComponentID, Component], component_map: dict[ComponentID, ComponentID], port_map: dict[PortID, PortID]) -> dict[ComponentID, Component]: remapped: dict[ComponentID, Component] = {} for old_component_id, original in components.items(): component = deepcopy(original) new_component_id = ComponentID() component_map[old_component_id] = new_component_id component.interface.ports = {_new_port_id(old_id, port_map): port for old_id, port in component.interface.ports.items()} component.parameters = {ParameterID(): parameter for parameter in component.parameters.values()} if isinstance(component.implementation, GraphImplementation): graph = component.implementation.graph graph.components = _remap_components(graph.components, component_map, port_map) graph.connections = {ConnectionID(): connection for connection in graph.connections.values()} for connection in graph.connections.values(): connection.source = port_map.get(connection.source, connection.source) connection.target = port_map.get(connection.target, connection.target) remapped[new_component_id] = component return remapped def _new_port_id(old_id: PortID, port_map: dict[PortID, PortID]) -> PortID: new_id = PortID() port_map[old_id] = new_id return new_id def _all_component_ids(components: dict[ComponentID, Component]) -> list[ComponentID]: component_ids: list[ComponentID] = [] for component_id, component in components.items(): component_ids.append(component_id) if isinstance(component.implementation, GraphImplementation): component_ids.extend(_all_component_ids(component.implementation.graph.components)) return component_ids