Better drawing and added more icons
This commit is contained in:
@@ -8,8 +8,11 @@ from PySide6.QtGui import QUndoStack
|
||||
from bedit.gui.controllers.commands import (
|
||||
AddComponentCommand,
|
||||
AddConnectionCommand,
|
||||
AddAnnotationCommand,
|
||||
AddInterfacePortCommand,
|
||||
DeleteSelectionCommand,
|
||||
DeleteAnnotationsCommand,
|
||||
EditGraphItemCommand,
|
||||
EditTextDefinitionCommand,
|
||||
EditComponentAppearanceCommand,
|
||||
MoveComponentCommand,
|
||||
@@ -21,6 +24,7 @@ from bedit.gui.controllers.commands import (
|
||||
RotateComponentsCommand,
|
||||
)
|
||||
from bedit.core.model import (
|
||||
Annotation,
|
||||
Component,
|
||||
Connection,
|
||||
Endpoint,
|
||||
@@ -43,6 +47,9 @@ class DocumentController(QObject):
|
||||
componentRotated = Signal(str, float)
|
||||
connectionAdded = Signal(str)
|
||||
connectionRemoved = Signal(str)
|
||||
graphItemChanged = Signal(str, str)
|
||||
annotationAdded = Signal(str)
|
||||
annotationRemoved = Signal(str)
|
||||
interfaceChanged = Signal()
|
||||
filePathChanged = Signal(object)
|
||||
modifiedChanged = Signal(bool)
|
||||
@@ -221,16 +228,14 @@ class DocumentController(QObject):
|
||||
if (component := self.active_component.graph.blocks.get(component_id)) is not None
|
||||
}
|
||||
if rotations:
|
||||
self.undo_stack.push(
|
||||
RotateComponentsCommand(self, self.active_component_id, rotations)
|
||||
)
|
||||
self.undo_stack.push(RotateComponentsCommand(self, self.active_component_id, rotations))
|
||||
|
||||
def connect(
|
||||
self,
|
||||
source: Endpoint,
|
||||
target: Endpoint,
|
||||
*,
|
||||
routing: str = "spline",
|
||||
routing: str = "angled",
|
||||
waypoints: list[QPointF] | None = None,
|
||||
) -> str:
|
||||
if self.active_component_id is None:
|
||||
@@ -240,9 +245,7 @@ class DocumentController(QObject):
|
||||
if source_port is None or target_port is None:
|
||||
raise ValueError("A connection endpoint no longer exists")
|
||||
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
|
||||
raise ValueError(
|
||||
f"Cannot connect {source_port.type!r} to {target_port.type!r}"
|
||||
)
|
||||
raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}")
|
||||
if routing not in {"direct", "angled", "spline"}:
|
||||
raise ValueError(f"Unknown connection routing: {routing}")
|
||||
connection = Connection(
|
||||
@@ -251,16 +254,154 @@ class DocumentController(QObject):
|
||||
target,
|
||||
properties={
|
||||
"routing": routing,
|
||||
"waypoints": [
|
||||
{"x": point.x(), "y": point.y()} for point in (waypoints or [])
|
||||
],
|
||||
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
|
||||
},
|
||||
)
|
||||
self.undo_stack.push(
|
||||
AddConnectionCommand(self, self.active_component_id, connection)
|
||||
)
|
||||
self.undo_stack.push(AddConnectionCommand(self, self.active_component_id, connection))
|
||||
return connection.id
|
||||
|
||||
def add_annotation(
|
||||
self,
|
||||
kind: str,
|
||||
start: QPointF,
|
||||
end: QPointF,
|
||||
*,
|
||||
text: str = "",
|
||||
routing: str = "angled",
|
||||
waypoints: list[QPointF] | None = None,
|
||||
) -> str:
|
||||
if self.active_component_id is None:
|
||||
raise ValueError("There is no active graph")
|
||||
if kind != "line":
|
||||
left, right = sorted((start.x(), end.x()))
|
||||
top, bottom = sorted((start.y(), end.y()))
|
||||
start, end = QPointF(left, top), QPointF(right, bottom)
|
||||
style = {
|
||||
"stroke": "#303030",
|
||||
"lineWidth": 1.5,
|
||||
"lineStyle": "solid",
|
||||
"fill": "none" if kind in {"line", "text"} else "#dbeafe",
|
||||
}
|
||||
if kind == "box":
|
||||
style["cornerRadius"] = 0.0
|
||||
if kind == "text":
|
||||
style.update({"fontSize": 12.0, "color": "#202020"})
|
||||
annotation = Annotation(
|
||||
id=str(uuid4()),
|
||||
kind=kind,
|
||||
x=start.x(),
|
||||
y=start.y(),
|
||||
width=end.x() - start.x(),
|
||||
height=end.y() - start.y(),
|
||||
text=text,
|
||||
layer=-1,
|
||||
properties={
|
||||
**style,
|
||||
"routing": routing,
|
||||
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
|
||||
},
|
||||
)
|
||||
self.undo_stack.push(AddAnnotationCommand(self, self.active_component_id, annotation))
|
||||
return annotation.id
|
||||
|
||||
def set_route_waypoints(
|
||||
self, item_kind: str, item_id: str, waypoints: list[QPointF], routing: str | None = None
|
||||
) -> None:
|
||||
item = (
|
||||
self.active_graph.connections
|
||||
if item_kind == "connection"
|
||||
else self.active_graph.annotations
|
||||
).get(item_id)
|
||||
if item is None or self.active_component_id is None:
|
||||
return
|
||||
old = deepcopy(item.properties)
|
||||
new = deepcopy(old)
|
||||
if routing is not None:
|
||||
new["routing"] = routing
|
||||
new["waypoints"] = [{"x": point.x(), "y": point.y()} for point in waypoints]
|
||||
if old != new:
|
||||
self.undo_stack.push(
|
||||
EditGraphItemCommand(
|
||||
self, self.active_component_id, item_kind, item_id, old, new, "Edit line nodes"
|
||||
)
|
||||
)
|
||||
|
||||
def set_annotation_geometry(self, annotation_id: str, old: dict, new: dict) -> None:
|
||||
if old != new and self.active_component_id is not None:
|
||||
self.undo_stack.push(
|
||||
EditGraphItemCommand(
|
||||
self,
|
||||
self.active_component_id,
|
||||
"annotation_geometry",
|
||||
annotation_id,
|
||||
old,
|
||||
new,
|
||||
"Move annotation",
|
||||
)
|
||||
)
|
||||
|
||||
def edit_annotation(self, annotation_id: str, values: dict) -> None:
|
||||
annotation = self.active_graph.annotations.get(annotation_id)
|
||||
if annotation is None or self.active_component_id is None:
|
||||
return
|
||||
old = annotation.to_dict()
|
||||
if old != values:
|
||||
self.undo_stack.push(
|
||||
EditGraphItemCommand(
|
||||
self,
|
||||
self.active_component_id,
|
||||
"annotation_data",
|
||||
annotation_id,
|
||||
old,
|
||||
deepcopy(values),
|
||||
"Edit shape",
|
||||
)
|
||||
)
|
||||
|
||||
def reorder_annotations(self, annotation_ids: set[str], operation: str) -> None:
|
||||
if not annotation_ids or self.active_component_id is None:
|
||||
return
|
||||
graph = self.active_graph
|
||||
layers = [item.layer for item in graph.annotations.values()]
|
||||
minimum, maximum = min(layers, default=-1), max(layers, default=1)
|
||||
for annotation_id in annotation_ids:
|
||||
item = graph.annotations.get(annotation_id)
|
||||
if item is None:
|
||||
continue
|
||||
old = {"layer": item.layer}
|
||||
forward = item.layer + 1
|
||||
backward = item.layer - 1
|
||||
if forward == 0:
|
||||
forward = 1
|
||||
if backward == 0:
|
||||
backward = -1
|
||||
layer = {
|
||||
"forward": forward,
|
||||
"backward": backward,
|
||||
"front": max(1, maximum + 1),
|
||||
"back": min(-1, minimum - 1),
|
||||
}[operation]
|
||||
self.undo_stack.push(
|
||||
EditGraphItemCommand(
|
||||
self,
|
||||
self.active_component_id,
|
||||
"annotation_layer",
|
||||
annotation_id,
|
||||
old,
|
||||
{"layer": layer},
|
||||
"Reorder annotation",
|
||||
)
|
||||
)
|
||||
|
||||
def delete_annotations(self, annotation_ids: set[str]) -> None:
|
||||
items = {
|
||||
key: self.active_graph.annotations[key]
|
||||
for key in annotation_ids
|
||||
if key in self.active_graph.annotations
|
||||
}
|
||||
if items and self.active_component_id is not None:
|
||||
self.undo_stack.push(DeleteAnnotationsCommand(self, self.active_component_id, items))
|
||||
|
||||
def _port_for_endpoint(self, endpoint: Endpoint, role: str) -> Port | None:
|
||||
owner = self.active_component
|
||||
if owner is None:
|
||||
@@ -272,7 +413,9 @@ class DocumentController(QObject):
|
||||
if component is None:
|
||||
return None
|
||||
ports = component.outputs if role == "source" else component.inputs
|
||||
return next((port for port in ports if port.id == (endpoint.interface or endpoint.port)), None)
|
||||
return next(
|
||||
(port for port in ports if port.id == (endpoint.interface or endpoint.port)), None
|
||||
)
|
||||
|
||||
def connection_port_type(self, connection: Connection) -> str:
|
||||
port = self._port_for_endpoint(connection.source, "source")
|
||||
@@ -289,9 +432,7 @@ class DocumentController(QObject):
|
||||
x=position.x(),
|
||||
y=position.y(),
|
||||
)
|
||||
self.undo_stack.push(
|
||||
AddInterfacePortCommand(self, component.id, direction, port)
|
||||
)
|
||||
self.undo_stack.push(AddInterfacePortCommand(self, component.id, direction, port))
|
||||
return port.id
|
||||
|
||||
def move_interface_port(self, port_id: str, old: QPointF, new: QPointF) -> None:
|
||||
@@ -350,11 +491,17 @@ class DocumentController(QObject):
|
||||
parent = self.document.find_parent(component.id)
|
||||
if parent is not None:
|
||||
for connection in parent.graph.connections.values():
|
||||
if connection.target.block == component.id and connection.target.port not in input_ids:
|
||||
if (
|
||||
connection.target.block == component.id
|
||||
and connection.target.port not in input_ids
|
||||
):
|
||||
raise ValueError(
|
||||
f"Input {connection.target.port!r} is still connected in the containing graph"
|
||||
)
|
||||
if connection.source.block == component.id and connection.source.port not in output_ids:
|
||||
if (
|
||||
connection.source.block == component.id
|
||||
and connection.source.port not in output_ids
|
||||
):
|
||||
raise ValueError(
|
||||
f"Output {connection.source.port!r} is still connected in the containing graph"
|
||||
)
|
||||
@@ -422,9 +569,15 @@ class DocumentController(QObject):
|
||||
parent = self.document.find_parent(component_id)
|
||||
if parent is not None:
|
||||
for connection in parent.graph.connections.values():
|
||||
if connection.target.block == component_id and connection.target.port not in input_ids:
|
||||
if (
|
||||
connection.target.block == component_id
|
||||
and connection.target.port not in input_ids
|
||||
):
|
||||
raise ValueError("An input cannot be removed or reoriented while connected")
|
||||
if connection.source.block == component_id and connection.source.port not in output_ids:
|
||||
if (
|
||||
connection.source.block == component_id
|
||||
and connection.source.port not in output_ids
|
||||
):
|
||||
raise ValueError("An output cannot be removed or reoriented while connected")
|
||||
for connection in component.graph.connections.values():
|
||||
if connection.source.interface and connection.source.interface not in input_ids:
|
||||
@@ -471,7 +624,9 @@ class DocumentController(QObject):
|
||||
or connection.target.interface in output_ids
|
||||
):
|
||||
all_connection_ids.add(connection.id)
|
||||
blocks = {block_id: graph.blocks[block_id] for block_id in block_ids if block_id in graph.blocks}
|
||||
blocks = {
|
||||
block_id: graph.blocks[block_id] for block_id in block_ids if block_id in graph.blocks
|
||||
}
|
||||
connections = {
|
||||
connection_id: graph.connections[connection_id]
|
||||
for connection_id in all_connection_ids
|
||||
@@ -526,9 +681,7 @@ class DocumentController(QObject):
|
||||
)
|
||||
connections[connection.id] = connection
|
||||
if blocks:
|
||||
self.undo_stack.push(
|
||||
PasteSelectionCommand(self, owner.id, blocks, connections)
|
||||
)
|
||||
self.undo_stack.push(PasteSelectionCommand(self, owner.id, blocks, connections))
|
||||
return list(blocks)
|
||||
|
||||
def _graph_for(self, owner_id: str):
|
||||
@@ -571,9 +724,7 @@ class DocumentController(QObject):
|
||||
self.componentMoved.emit(component_id, position)
|
||||
self.documentReset.emit()
|
||||
|
||||
def _rotate_component(
|
||||
self, owner_id: str, component_id: str, rotation: float
|
||||
) -> None:
|
||||
def _rotate_component(self, owner_id: str, component_id: str, rotation: float) -> None:
|
||||
component = self._graph_for(owner_id).blocks.get(component_id)
|
||||
if component is None:
|
||||
return
|
||||
@@ -593,6 +744,46 @@ class DocumentController(QObject):
|
||||
self.connectionRemoved.emit(connection_id)
|
||||
self.documentReset.emit()
|
||||
|
||||
def _insert_annotation(self, owner_id: str, annotation: Annotation) -> None:
|
||||
self._graph_for(owner_id).annotations[annotation.id] = annotation
|
||||
if owner_id == self.active_component_id:
|
||||
self.annotationAdded.emit(annotation.id)
|
||||
self.documentReset.emit()
|
||||
|
||||
def _remove_annotation(self, owner_id: str, annotation_id: str) -> None:
|
||||
self._graph_for(owner_id).annotations.pop(annotation_id, None)
|
||||
if owner_id == self.active_component_id:
|
||||
self.annotationRemoved.emit(annotation_id)
|
||||
self.documentReset.emit()
|
||||
|
||||
def _set_graph_item_data(
|
||||
self, owner_id: str, item_kind: str, item_id: str, values: dict
|
||||
) -> None:
|
||||
graph = self._graph_for(owner_id)
|
||||
if item_kind == "connection":
|
||||
item = graph.connections.get(item_id)
|
||||
if item is not None:
|
||||
item.properties = deepcopy(values)
|
||||
else:
|
||||
item = graph.annotations.get(item_id)
|
||||
if item is not None:
|
||||
if item_kind == "annotation_geometry":
|
||||
item.x, item.y = float(values["x"]), float(values["y"])
|
||||
item.width, item.height = float(values["width"]), float(values["height"])
|
||||
elif item_kind == "annotation_layer":
|
||||
item.layer = int(values["layer"])
|
||||
elif item_kind == "annotation_data":
|
||||
replacement = Annotation.from_dict(values)
|
||||
item.kind = replacement.kind
|
||||
item.x, item.y = replacement.x, replacement.y
|
||||
item.width, item.height = replacement.width, replacement.height
|
||||
item.text, item.layer = replacement.text, replacement.layer
|
||||
item.properties = replacement.properties
|
||||
else:
|
||||
item.properties = deepcopy(values)
|
||||
if owner_id == self.active_component_id:
|
||||
self.graphItemChanged.emit(item_kind, item_id)
|
||||
|
||||
def _replace_component(self, old_id: str, replacement: Component) -> None:
|
||||
if self.document is None:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user