Better drawing and added more icons
@@ -66,6 +66,11 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
|
|||||||
- Connection appearance is configured per port type in
|
- Connection appearance is configured per port type in
|
||||||
`gui/graphics/connection_styles.py`, including color, width, pen style, and
|
`gui/graphics/connection_styles.py`, including color, width, pen style, and
|
||||||
source/target arrowheads. Do not scatter those constants through painters.
|
source/target arrowheads. Do not scatter those constants through painters.
|
||||||
|
- Graph annotations are `box`, `line`, or `text` objects in `Graph.annotations`.
|
||||||
|
They use integer layers below or above graph layer 0. Annotation lines reuse
|
||||||
|
connection routing and absolute `properties.waypoints` semantics. Graph
|
||||||
|
annotations and icon elements share `ShapeOptionsDialog` and `shape_pen` so
|
||||||
|
their style fields and rendering must remain aligned.
|
||||||
- Icon editing uses a fixed 128×128 coordinate space.
|
- Icon editing uses a fixed 128×128 coordinate space.
|
||||||
- The visible/selectable component hitbox is calculated from vector elements,
|
- The visible/selectable component hitbox is calculated from vector elements,
|
||||||
not from the complete 128×128 icon canvas.
|
not from the complete 128×128 icon canvas.
|
||||||
|
|||||||
@@ -112,26 +112,36 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
|
|||||||
standard Cut/Copy/Paste shortcuts to duplicate selected component groups.
|
standard Cut/Copy/Paste shortcuts to duplicate selected component groups.
|
||||||
- Switch the graph header to **Connect**, choose Direct, Angled, or Spline, then
|
- Switch the graph header to **Connect**, choose Direct, Angled, or Spline, then
|
||||||
click an output and input. Angled connections accept intermediate corner
|
click an output and input. Angled connections accept intermediate corner
|
||||||
clicks; right-click cancels an unfinished connection.
|
clicks, use right-angle segments, and snap to the graph snapping grid.
|
||||||
- Double-click a graph component to open its owned subgraph; use **Up** to return.
|
Right-click cancels an unfinished connection.
|
||||||
|
- Select a routed connection to reveal its draggable nodes. Right-click a line
|
||||||
|
to add a node, or right-click a node to delete it.
|
||||||
|
- Use **Box**, **Line**, and **Text** to add persistent graph annotations. Lines
|
||||||
|
share the Direct, Angled, and Spline routing controls. Annotation context menus
|
||||||
|
provide the same shape styling as the icon editor and can move annotations
|
||||||
|
through integer layers below or above graph layer 0.
|
||||||
|
- Double-click a graph component or select it and use **Down** to open its owned
|
||||||
|
subgraph; use **Up** to return.
|
||||||
- Graph components show **Pointer**, **Input**, and **Output** tools. Select an
|
- Graph components show **Pointer**, **Input**, and **Output** tools. Select an
|
||||||
interface tool and click the canvas to add a visible internal terminal and a
|
interface tool and click the canvas to add a visible internal terminal and a
|
||||||
corresponding external block port. Interface terminals can be moved afterward.
|
corresponding external block port. Interface terminals can be moved afterward.
|
||||||
- Right-click a component on the canvas or in Current Document to edit its name,
|
- Right-click a component on the canvas or in Current Document to edit its name,
|
||||||
icon shape, icon text, fill color, and border color. The same dialog can hide
|
icon shape, icon text, fill color, and border color. The same dialog can hide
|
||||||
that component's contained subtree from the Libraries tree.
|
that component's contained subtree from the Libraries tree.
|
||||||
|
- The icon editor uses Pointer and click-drag drawing tools. Its toolbar and
|
||||||
|
mouse wheel provide zoom in, zoom out, and fit-to-canvas controls.
|
||||||
- Double-click a text component to edit its input list, output list, and
|
- Double-click a text component to edit its input list, output list, and
|
||||||
`implementation.source` JSON.
|
`implementation.source` JSON.
|
||||||
- Right-click any graph component under Current Document to add nested graph or
|
- Right-click any graph component under Current Document to add nested graph or
|
||||||
text blocks. Any current-document component can also be deleted there.
|
text blocks. Any current-document component can also be deleted there.
|
||||||
- The active document hierarchy has its own Document panel; the Libraries panel
|
- The left navigator presents the current Document and external Libraries as tabs.
|
||||||
contains only configured external libraries.
|
- Select one or more blocks and press `Ctrl+R`, or use **Rotate** in the graph header, to
|
||||||
- Select one or more blocks and press `Ctrl+R`, or use the Transform toolbar, to
|
|
||||||
rotate them clockwise by 90 degrees. Rotation is saved and supports undo/redo.
|
rotate them clockwise by 90 degrees. Rotation is saved and supports undo/redo.
|
||||||
**Apply JSON** updates that source and participates in undo/redo.
|
**Apply JSON** updates that source and participates in undo/redo.
|
||||||
- File → Save writes the complete recursive document to JSON.
|
- File → Save writes the complete recursive document to JSON.
|
||||||
- File → Close Document removes the active document and returns to an empty
|
- File → Close Document removes the active document and returns to an empty
|
||||||
workspace. An open graph uses a light gray, 32-unit dotted canvas.
|
workspace. An open graph uses a light gray grid whose visible and snapping
|
||||||
|
spacing are configured separately.
|
||||||
- Edit → Settings → Libraries accepts document files or folders of JSON files.
|
- Edit → Settings → Libraries accepts document files or folders of JSON files.
|
||||||
|
|
||||||
Every component owns its ports, declarative icon, properties, and child graph:
|
Every component owns its ports, declarative icon, properties, and child graph:
|
||||||
|
|||||||
BIN
BEdit/resources/icons/arrow-down.png
Normal file
|
After Width: | Height: | Size: 1006 B |
BIN
BEdit/resources/icons/arrow-up.png
Normal file
|
After Width: | Height: | Size: 927 B |
BIN
BEdit/resources/icons/draw-bezier-curves.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
BEdit/resources/icons/draw-rectangle.png
Normal file
|
After Width: | Height: | Size: 658 B |
BIN
BEdit/resources/icons/draw-text.png
Normal file
|
After Width: | Height: | Size: 598 B |
BIN
BEdit/resources/icons/edit-select.png
Normal file
|
After Width: | Height: | Size: 991 B |
BIN
BEdit/resources/icons/zoom-in.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
BEdit/resources/icons/zoom-original.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
BEdit/resources/icons/zoom-out.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,14 +1,25 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="icons">
|
<qresource prefix="icons">
|
||||||
|
<file>icons/arrow-down.png</file>
|
||||||
|
<file>icons/configure.png</file>
|
||||||
|
<file>icons/arrow-up.png</file>
|
||||||
|
<file>icons/document-new.png</file>
|
||||||
|
<file>icons/document-open.png</file>
|
||||||
|
<file>icons/document-save-as.png</file>
|
||||||
|
<file>icons/document-save.png</file>
|
||||||
|
<file>icons/draw-bezier-curves.png</file>
|
||||||
|
<file>icons/draw-rectangle.png</file>
|
||||||
|
<file>icons/draw-text.png</file>
|
||||||
|
<file>icons/edit-copy.png</file>
|
||||||
|
<file>icons/edit-cut.png</file>
|
||||||
<file>icons/edit-paste.png</file>
|
<file>icons/edit-paste.png</file>
|
||||||
<file>icons/edit-redo.png</file>
|
<file>icons/edit-redo.png</file>
|
||||||
<file>icons/edit-cut.png</file>
|
<file>icons/edit-select.png</file>
|
||||||
<file>icons/edit-copy.png</file>
|
|
||||||
<file>icons/edit-undo.png</file>
|
<file>icons/edit-undo.png</file>
|
||||||
<file>icons/document-save.png</file>
|
<file>icons/media-playback-start.png</file>
|
||||||
<file>icons/document-save-as.png</file>
|
|
||||||
<file>icons/document-open.png</file>
|
|
||||||
<file>icons/document-new.png</file>
|
|
||||||
<file>icons/transform-rotate.png</file>
|
<file>icons/transform-rotate.png</file>
|
||||||
|
<file>icons/zoom-in.png</file>
|
||||||
|
<file>icons/zoom-original.png</file>
|
||||||
|
<file>icons/zoom-out.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -51,18 +51,33 @@ class Icon:
|
|||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if not self.elements:
|
if not self.elements:
|
||||||
self.elements = [{
|
self.elements = [
|
||||||
"type": "ellipse" if self.shape == "ellipse" else "rectangle",
|
{
|
||||||
"x": 32.0, "y": 32.0, "width": 64.0, "height": 64.0,
|
"type": "ellipse" if self.shape == "ellipse" else "rectangle",
|
||||||
"fill": self.fill, "stroke": self.border, "lineWidth": 1.5,
|
"x": 32.0,
|
||||||
"lineStyle": "solid", "cornerRadius": 5.0,
|
"y": 32.0,
|
||||||
}]
|
"width": 64.0,
|
||||||
|
"height": 64.0,
|
||||||
|
"fill": self.fill,
|
||||||
|
"stroke": self.border,
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"cornerRadius": 5.0,
|
||||||
|
}
|
||||||
|
]
|
||||||
if self.text:
|
if self.text:
|
||||||
self.elements.append({
|
self.elements.append(
|
||||||
"type": "text", "x": 40.0, "y": 40.0,
|
{
|
||||||
"width": 48.0, "height": 48.0,
|
"type": "text",
|
||||||
"text": self.text, "color": "#202020", "fontSize": 12.0,
|
"x": 40.0,
|
||||||
})
|
"y": 40.0,
|
||||||
|
"width": 48.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"text": self.text,
|
||||||
|
"color": "#202020",
|
||||||
|
"fontSize": 12.0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
@@ -137,15 +152,57 @@ class Connection:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Annotation:
|
||||||
|
id: str
|
||||||
|
kind: str
|
||||||
|
x: float = 0.0
|
||||||
|
y: float = 0.0
|
||||||
|
width: float = 0.0
|
||||||
|
height: float = 0.0
|
||||||
|
text: str = ""
|
||||||
|
layer: int = -1
|
||||||
|
properties: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"kind": self.kind,
|
||||||
|
"position": {"x": self.x, "y": self.y},
|
||||||
|
"size": {"width": self.width, "height": self.height},
|
||||||
|
"text": self.text,
|
||||||
|
"layer": self.layer,
|
||||||
|
"properties": deepcopy(self.properties),
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any]) -> "Annotation":
|
||||||
|
position = data.get("position", {})
|
||||||
|
size = data.get("size", {})
|
||||||
|
return cls(
|
||||||
|
id=str(data["id"]),
|
||||||
|
kind=str(data["kind"]),
|
||||||
|
x=float(position.get("x", 0)),
|
||||||
|
y=float(position.get("y", 0)),
|
||||||
|
width=float(size.get("width", 0)),
|
||||||
|
height=float(size.get("height", 0)),
|
||||||
|
text=str(data.get("text", "")),
|
||||||
|
layer=int(data.get("layer", -1)),
|
||||||
|
properties=deepcopy(data.get("properties", {})),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Graph:
|
class Graph:
|
||||||
blocks: dict[str, Component] = field(default_factory=dict)
|
blocks: dict[str, Component] = field(default_factory=dict)
|
||||||
connections: dict[str, Connection] = field(default_factory=dict)
|
connections: dict[str, Connection] = field(default_factory=dict)
|
||||||
|
annotations: dict[str, Annotation] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"blocks": [block.to_dict() for block in self.blocks.values()],
|
"blocks": [block.to_dict() for block in self.blocks.values()],
|
||||||
"connections": [connection.to_dict() for connection in self.connections.values()],
|
"connections": [connection.to_dict() for connection in self.connections.values()],
|
||||||
|
"annotations": [item.to_dict() for item in self.annotations.values()],
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -153,13 +210,19 @@ class Graph:
|
|||||||
data = data or {}
|
data = data or {}
|
||||||
blocks = [Component.from_dict(item) for item in data.get("blocks", [])]
|
blocks = [Component.from_dict(item) for item in data.get("blocks", [])]
|
||||||
connections = [Connection.from_dict(item) for item in data.get("connections", [])]
|
connections = [Connection.from_dict(item) for item in data.get("connections", [])]
|
||||||
|
annotations = [Annotation.from_dict(item) for item in data.get("annotations", [])]
|
||||||
if len({block.id for block in blocks}) != len(blocks):
|
if len({block.id for block in blocks}) != len(blocks):
|
||||||
raise ValueError("A graph contains duplicate component IDs")
|
raise ValueError("A graph contains duplicate component IDs")
|
||||||
if len({connection.id for connection in connections}) != len(connections):
|
if len({connection.id for connection in connections}) != len(connections):
|
||||||
raise ValueError("A graph contains duplicate connection IDs")
|
raise ValueError("A graph contains duplicate connection IDs")
|
||||||
|
if len({item.id for item in annotations}) != len(annotations):
|
||||||
|
raise ValueError("A graph contains duplicate annotation IDs")
|
||||||
|
if any(item.kind not in {"box", "line", "text"} for item in annotations):
|
||||||
|
raise ValueError("A graph contains an unknown annotation kind")
|
||||||
return cls(
|
return cls(
|
||||||
blocks={block.id: block for block in blocks},
|
blocks={block.id: block for block in blocks},
|
||||||
connections={connection.id: connection for connection in connections},
|
connections={connection.id: connection for connection in connections},
|
||||||
|
annotations={item.id: item for item in annotations},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -349,14 +412,35 @@ def clone_component(source: Component) -> Component:
|
|||||||
for connection in current.graph.connections.values()
|
for connection in current.graph.connections.values()
|
||||||
for new_id in [str(uuid4())]
|
for new_id in [str(uuid4())]
|
||||||
},
|
},
|
||||||
|
annotations={
|
||||||
|
new_id: Annotation(
|
||||||
|
new_id,
|
||||||
|
annotation.kind,
|
||||||
|
annotation.x,
|
||||||
|
annotation.y,
|
||||||
|
annotation.width,
|
||||||
|
annotation.height,
|
||||||
|
annotation.text,
|
||||||
|
annotation.layer,
|
||||||
|
deepcopy(annotation.properties),
|
||||||
|
)
|
||||||
|
for annotation in current.graph.annotations.values()
|
||||||
|
for new_id in [str(uuid4())]
|
||||||
|
},
|
||||||
)
|
)
|
||||||
return Component(
|
return Component(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
name=current.name,
|
name=current.name,
|
||||||
x=current.x,
|
x=current.x,
|
||||||
y=current.y,
|
y=current.y,
|
||||||
inputs=[Port(port.id, port.name, port.x, port.y, deepcopy(port.properties), port.type) for port in current.inputs],
|
inputs=[
|
||||||
outputs=[Port(port.id, port.name, port.x, port.y, deepcopy(port.properties), port.type) for port in current.outputs],
|
Port(port.id, port.name, port.x, port.y, deepcopy(port.properties), port.type)
|
||||||
|
for port in current.inputs
|
||||||
|
],
|
||||||
|
outputs=[
|
||||||
|
Port(port.id, port.name, port.x, port.y, deepcopy(port.properties), port.type)
|
||||||
|
for port in current.outputs
|
||||||
|
],
|
||||||
icon=Icon.from_dict(current.icon.to_dict()),
|
icon=Icon.from_dict(current.icon.to_dict()),
|
||||||
properties=deepcopy(current.properties),
|
properties=deepcopy(current.properties),
|
||||||
implementation_kind=current.implementation_kind,
|
implementation_kind=current.implementation_kind,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from PySide6.QtCore import QPointF
|
from PySide6.QtCore import QPointF
|
||||||
from PySide6.QtGui import QUndoCommand
|
from PySide6.QtGui import QUndoCommand
|
||||||
|
|
||||||
from bedit.core.model import Component, Connection, Port
|
from bedit.core.model import Annotation, Component, Connection, Port
|
||||||
|
|
||||||
|
|
||||||
class AddComponentCommand(QUndoCommand):
|
class AddComponentCommand(QUndoCommand):
|
||||||
@@ -76,6 +76,55 @@ class AddConnectionCommand(QUndoCommand):
|
|||||||
self.controller._remove_connection(self.owner_id, self.connection.id)
|
self.controller._remove_connection(self.owner_id, self.connection.id)
|
||||||
|
|
||||||
|
|
||||||
|
class AddAnnotationCommand(QUndoCommand):
|
||||||
|
def __init__(self, controller, owner_id: str, annotation: Annotation) -> None:
|
||||||
|
super().__init__(f"Draw {annotation.kind}")
|
||||||
|
self.controller, self.owner_id, self.annotation = controller, owner_id, annotation
|
||||||
|
|
||||||
|
def redo(self) -> None:
|
||||||
|
self.controller._insert_annotation(self.owner_id, self.annotation)
|
||||||
|
|
||||||
|
def undo(self) -> None:
|
||||||
|
self.controller._remove_annotation(self.owner_id, self.annotation.id)
|
||||||
|
|
||||||
|
|
||||||
|
class EditGraphItemCommand(QUndoCommand):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
controller,
|
||||||
|
owner_id: str,
|
||||||
|
item_kind: str,
|
||||||
|
item_id: str,
|
||||||
|
old: dict,
|
||||||
|
new: dict,
|
||||||
|
text: str,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(text)
|
||||||
|
self.controller, self.owner_id = controller, owner_id
|
||||||
|
self.item_kind, self.item_id = item_kind, item_id
|
||||||
|
self.old, self.new = old, new
|
||||||
|
|
||||||
|
def redo(self) -> None:
|
||||||
|
self.controller._set_graph_item_data(self.owner_id, self.item_kind, self.item_id, self.new)
|
||||||
|
|
||||||
|
def undo(self) -> None:
|
||||||
|
self.controller._set_graph_item_data(self.owner_id, self.item_kind, self.item_id, self.old)
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteAnnotationsCommand(QUndoCommand):
|
||||||
|
def __init__(self, controller, owner_id: str, annotations: dict[str, Annotation]) -> None:
|
||||||
|
super().__init__("Delete annotations")
|
||||||
|
self.controller, self.owner_id, self.annotations = controller, owner_id, annotations
|
||||||
|
|
||||||
|
def redo(self) -> None:
|
||||||
|
for annotation_id in self.annotations:
|
||||||
|
self.controller._remove_annotation(self.owner_id, annotation_id)
|
||||||
|
|
||||||
|
def undo(self) -> None:
|
||||||
|
for annotation in self.annotations.values():
|
||||||
|
self.controller._insert_annotation(self.owner_id, annotation)
|
||||||
|
|
||||||
|
|
||||||
class ReplaceComponentCommand(QUndoCommand):
|
class ReplaceComponentCommand(QUndoCommand):
|
||||||
def __init__(self, controller, old: Component, new: Component) -> None:
|
def __init__(self, controller, old: Component, new: Component) -> None:
|
||||||
super().__init__("Apply JSON changes")
|
super().__init__("Apply JSON changes")
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ from PySide6.QtGui import QUndoStack
|
|||||||
from bedit.gui.controllers.commands import (
|
from bedit.gui.controllers.commands import (
|
||||||
AddComponentCommand,
|
AddComponentCommand,
|
||||||
AddConnectionCommand,
|
AddConnectionCommand,
|
||||||
|
AddAnnotationCommand,
|
||||||
AddInterfacePortCommand,
|
AddInterfacePortCommand,
|
||||||
DeleteSelectionCommand,
|
DeleteSelectionCommand,
|
||||||
|
DeleteAnnotationsCommand,
|
||||||
|
EditGraphItemCommand,
|
||||||
EditTextDefinitionCommand,
|
EditTextDefinitionCommand,
|
||||||
EditComponentAppearanceCommand,
|
EditComponentAppearanceCommand,
|
||||||
MoveComponentCommand,
|
MoveComponentCommand,
|
||||||
@@ -21,6 +24,7 @@ from bedit.gui.controllers.commands import (
|
|||||||
RotateComponentsCommand,
|
RotateComponentsCommand,
|
||||||
)
|
)
|
||||||
from bedit.core.model import (
|
from bedit.core.model import (
|
||||||
|
Annotation,
|
||||||
Component,
|
Component,
|
||||||
Connection,
|
Connection,
|
||||||
Endpoint,
|
Endpoint,
|
||||||
@@ -43,6 +47,9 @@ class DocumentController(QObject):
|
|||||||
componentRotated = Signal(str, float)
|
componentRotated = Signal(str, float)
|
||||||
connectionAdded = Signal(str)
|
connectionAdded = Signal(str)
|
||||||
connectionRemoved = Signal(str)
|
connectionRemoved = Signal(str)
|
||||||
|
graphItemChanged = Signal(str, str)
|
||||||
|
annotationAdded = Signal(str)
|
||||||
|
annotationRemoved = Signal(str)
|
||||||
interfaceChanged = Signal()
|
interfaceChanged = Signal()
|
||||||
filePathChanged = Signal(object)
|
filePathChanged = Signal(object)
|
||||||
modifiedChanged = Signal(bool)
|
modifiedChanged = Signal(bool)
|
||||||
@@ -221,16 +228,14 @@ class DocumentController(QObject):
|
|||||||
if (component := self.active_component.graph.blocks.get(component_id)) is not None
|
if (component := self.active_component.graph.blocks.get(component_id)) is not None
|
||||||
}
|
}
|
||||||
if rotations:
|
if rotations:
|
||||||
self.undo_stack.push(
|
self.undo_stack.push(RotateComponentsCommand(self, self.active_component_id, rotations))
|
||||||
RotateComponentsCommand(self, self.active_component_id, rotations)
|
|
||||||
)
|
|
||||||
|
|
||||||
def connect(
|
def connect(
|
||||||
self,
|
self,
|
||||||
source: Endpoint,
|
source: Endpoint,
|
||||||
target: Endpoint,
|
target: Endpoint,
|
||||||
*,
|
*,
|
||||||
routing: str = "spline",
|
routing: str = "angled",
|
||||||
waypoints: list[QPointF] | None = None,
|
waypoints: list[QPointF] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
if self.active_component_id is None:
|
if self.active_component_id is None:
|
||||||
@@ -240,9 +245,7 @@ class DocumentController(QObject):
|
|||||||
if source_port is None or target_port is None:
|
if source_port is None or target_port is None:
|
||||||
raise ValueError("A connection endpoint no longer exists")
|
raise ValueError("A connection endpoint no longer exists")
|
||||||
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
|
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
|
||||||
raise ValueError(
|
raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}")
|
||||||
f"Cannot connect {source_port.type!r} to {target_port.type!r}"
|
|
||||||
)
|
|
||||||
if routing not in {"direct", "angled", "spline"}:
|
if routing not in {"direct", "angled", "spline"}:
|
||||||
raise ValueError(f"Unknown connection routing: {routing}")
|
raise ValueError(f"Unknown connection routing: {routing}")
|
||||||
connection = Connection(
|
connection = Connection(
|
||||||
@@ -251,16 +254,154 @@ class DocumentController(QObject):
|
|||||||
target,
|
target,
|
||||||
properties={
|
properties={
|
||||||
"routing": routing,
|
"routing": routing,
|
||||||
"waypoints": [
|
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
|
||||||
{"x": point.x(), "y": point.y()} for point in (waypoints or [])
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.undo_stack.push(
|
self.undo_stack.push(AddConnectionCommand(self, self.active_component_id, connection))
|
||||||
AddConnectionCommand(self, self.active_component_id, connection)
|
|
||||||
)
|
|
||||||
return connection.id
|
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:
|
def _port_for_endpoint(self, endpoint: Endpoint, role: str) -> Port | None:
|
||||||
owner = self.active_component
|
owner = self.active_component
|
||||||
if owner is None:
|
if owner is None:
|
||||||
@@ -272,7 +413,9 @@ class DocumentController(QObject):
|
|||||||
if component is None:
|
if component is None:
|
||||||
return None
|
return None
|
||||||
ports = component.outputs if role == "source" else component.inputs
|
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:
|
def connection_port_type(self, connection: Connection) -> str:
|
||||||
port = self._port_for_endpoint(connection.source, "source")
|
port = self._port_for_endpoint(connection.source, "source")
|
||||||
@@ -289,9 +432,7 @@ class DocumentController(QObject):
|
|||||||
x=position.x(),
|
x=position.x(),
|
||||||
y=position.y(),
|
y=position.y(),
|
||||||
)
|
)
|
||||||
self.undo_stack.push(
|
self.undo_stack.push(AddInterfacePortCommand(self, component.id, direction, port))
|
||||||
AddInterfacePortCommand(self, component.id, direction, port)
|
|
||||||
)
|
|
||||||
return port.id
|
return port.id
|
||||||
|
|
||||||
def move_interface_port(self, port_id: str, old: QPointF, new: QPointF) -> None:
|
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)
|
parent = self.document.find_parent(component.id)
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
for connection in parent.graph.connections.values():
|
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(
|
raise ValueError(
|
||||||
f"Input {connection.target.port!r} is still connected in the containing graph"
|
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(
|
raise ValueError(
|
||||||
f"Output {connection.source.port!r} is still connected in the containing graph"
|
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)
|
parent = self.document.find_parent(component_id)
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
for connection in parent.graph.connections.values():
|
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")
|
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")
|
raise ValueError("An output cannot be removed or reoriented while connected")
|
||||||
for connection in component.graph.connections.values():
|
for connection in component.graph.connections.values():
|
||||||
if connection.source.interface and connection.source.interface not in input_ids:
|
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
|
or connection.target.interface in output_ids
|
||||||
):
|
):
|
||||||
all_connection_ids.add(connection.id)
|
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 = {
|
connections = {
|
||||||
connection_id: graph.connections[connection_id]
|
connection_id: graph.connections[connection_id]
|
||||||
for connection_id in all_connection_ids
|
for connection_id in all_connection_ids
|
||||||
@@ -526,9 +681,7 @@ class DocumentController(QObject):
|
|||||||
)
|
)
|
||||||
connections[connection.id] = connection
|
connections[connection.id] = connection
|
||||||
if blocks:
|
if blocks:
|
||||||
self.undo_stack.push(
|
self.undo_stack.push(PasteSelectionCommand(self, owner.id, blocks, connections))
|
||||||
PasteSelectionCommand(self, owner.id, blocks, connections)
|
|
||||||
)
|
|
||||||
return list(blocks)
|
return list(blocks)
|
||||||
|
|
||||||
def _graph_for(self, owner_id: str):
|
def _graph_for(self, owner_id: str):
|
||||||
@@ -571,9 +724,7 @@ class DocumentController(QObject):
|
|||||||
self.componentMoved.emit(component_id, position)
|
self.componentMoved.emit(component_id, position)
|
||||||
self.documentReset.emit()
|
self.documentReset.emit()
|
||||||
|
|
||||||
def _rotate_component(
|
def _rotate_component(self, owner_id: str, component_id: str, rotation: float) -> None:
|
||||||
self, owner_id: str, component_id: str, rotation: float
|
|
||||||
) -> None:
|
|
||||||
component = self._graph_for(owner_id).blocks.get(component_id)
|
component = self._graph_for(owner_id).blocks.get(component_id)
|
||||||
if component is None:
|
if component is None:
|
||||||
return
|
return
|
||||||
@@ -593,6 +744,46 @@ class DocumentController(QObject):
|
|||||||
self.connectionRemoved.emit(connection_id)
|
self.connectionRemoved.emit(connection_id)
|
||||||
self.documentReset.emit()
|
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:
|
def _replace_component(self, old_id: str, replacement: Component) -> None:
|
||||||
if self.document is None:
|
if self.document is None:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ class Ui_IconEditorDialog(object):
|
|||||||
self.dialogLayout.setObjectName(u"dialogLayout")
|
self.dialogLayout.setObjectName(u"dialogLayout")
|
||||||
self.shapeToolbarLayout = QHBoxLayout()
|
self.shapeToolbarLayout = QHBoxLayout()
|
||||||
self.shapeToolbarLayout.setObjectName(u"shapeToolbarLayout")
|
self.shapeToolbarLayout.setObjectName(u"shapeToolbarLayout")
|
||||||
|
self.pointerButton = QToolButton(IconEditorDialog)
|
||||||
|
self.pointerButton.setObjectName(u"pointerButton")
|
||||||
|
self.pointerButton.setCheckable(True)
|
||||||
|
self.pointerButton.setChecked(True)
|
||||||
|
|
||||||
|
self.shapeToolbarLayout.addWidget(self.pointerButton)
|
||||||
|
|
||||||
self.addShapeLabel = QLabel(IconEditorDialog)
|
self.addShapeLabel = QLabel(IconEditorDialog)
|
||||||
self.addShapeLabel.setObjectName(u"addShapeLabel")
|
self.addShapeLabel.setObjectName(u"addShapeLabel")
|
||||||
|
|
||||||
@@ -38,31 +45,37 @@ class Ui_IconEditorDialog(object):
|
|||||||
|
|
||||||
self.addRectangleButton = QToolButton(IconEditorDialog)
|
self.addRectangleButton = QToolButton(IconEditorDialog)
|
||||||
self.addRectangleButton.setObjectName(u"addRectangleButton")
|
self.addRectangleButton.setObjectName(u"addRectangleButton")
|
||||||
|
self.addRectangleButton.setCheckable(True)
|
||||||
|
|
||||||
self.shapeToolbarLayout.addWidget(self.addRectangleButton)
|
self.shapeToolbarLayout.addWidget(self.addRectangleButton)
|
||||||
|
|
||||||
self.addCircleButton = QToolButton(IconEditorDialog)
|
self.addCircleButton = QToolButton(IconEditorDialog)
|
||||||
self.addCircleButton.setObjectName(u"addCircleButton")
|
self.addCircleButton.setObjectName(u"addCircleButton")
|
||||||
|
self.addCircleButton.setCheckable(True)
|
||||||
|
|
||||||
self.shapeToolbarLayout.addWidget(self.addCircleButton)
|
self.shapeToolbarLayout.addWidget(self.addCircleButton)
|
||||||
|
|
||||||
self.addEllipseButton = QToolButton(IconEditorDialog)
|
self.addEllipseButton = QToolButton(IconEditorDialog)
|
||||||
self.addEllipseButton.setObjectName(u"addEllipseButton")
|
self.addEllipseButton.setObjectName(u"addEllipseButton")
|
||||||
|
self.addEllipseButton.setCheckable(True)
|
||||||
|
|
||||||
self.shapeToolbarLayout.addWidget(self.addEllipseButton)
|
self.shapeToolbarLayout.addWidget(self.addEllipseButton)
|
||||||
|
|
||||||
self.addLineButton = QToolButton(IconEditorDialog)
|
self.addLineButton = QToolButton(IconEditorDialog)
|
||||||
self.addLineButton.setObjectName(u"addLineButton")
|
self.addLineButton.setObjectName(u"addLineButton")
|
||||||
|
self.addLineButton.setCheckable(True)
|
||||||
|
|
||||||
self.shapeToolbarLayout.addWidget(self.addLineButton)
|
self.shapeToolbarLayout.addWidget(self.addLineButton)
|
||||||
|
|
||||||
self.addTriangleButton = QToolButton(IconEditorDialog)
|
self.addTriangleButton = QToolButton(IconEditorDialog)
|
||||||
self.addTriangleButton.setObjectName(u"addTriangleButton")
|
self.addTriangleButton.setObjectName(u"addTriangleButton")
|
||||||
|
self.addTriangleButton.setCheckable(True)
|
||||||
|
|
||||||
self.shapeToolbarLayout.addWidget(self.addTriangleButton)
|
self.shapeToolbarLayout.addWidget(self.addTriangleButton)
|
||||||
|
|
||||||
self.addTextButton = QToolButton(IconEditorDialog)
|
self.addTextButton = QToolButton(IconEditorDialog)
|
||||||
self.addTextButton.setObjectName(u"addTextButton")
|
self.addTextButton.setObjectName(u"addTextButton")
|
||||||
|
self.addTextButton.setCheckable(True)
|
||||||
|
|
||||||
self.shapeToolbarLayout.addWidget(self.addTextButton)
|
self.shapeToolbarLayout.addWidget(self.addTextButton)
|
||||||
|
|
||||||
@@ -70,6 +83,21 @@ class Ui_IconEditorDialog(object):
|
|||||||
|
|
||||||
self.shapeToolbarLayout.addItem(self.toolbarSpacer)
|
self.shapeToolbarLayout.addItem(self.toolbarSpacer)
|
||||||
|
|
||||||
|
self.zoomInButton = QToolButton(IconEditorDialog)
|
||||||
|
self.zoomInButton.setObjectName(u"zoomInButton")
|
||||||
|
|
||||||
|
self.shapeToolbarLayout.addWidget(self.zoomInButton)
|
||||||
|
|
||||||
|
self.zoomOutButton = QToolButton(IconEditorDialog)
|
||||||
|
self.zoomOutButton.setObjectName(u"zoomOutButton")
|
||||||
|
|
||||||
|
self.shapeToolbarLayout.addWidget(self.zoomOutButton)
|
||||||
|
|
||||||
|
self.centerButton = QToolButton(IconEditorDialog)
|
||||||
|
self.centerButton.setObjectName(u"centerButton")
|
||||||
|
|
||||||
|
self.shapeToolbarLayout.addWidget(self.centerButton)
|
||||||
|
|
||||||
self.deleteSelectedButton = QPushButton(IconEditorDialog)
|
self.deleteSelectedButton = QPushButton(IconEditorDialog)
|
||||||
self.deleteSelectedButton.setObjectName(u"deleteSelectedButton")
|
self.deleteSelectedButton.setObjectName(u"deleteSelectedButton")
|
||||||
|
|
||||||
@@ -106,6 +134,7 @@ class Ui_IconEditorDialog(object):
|
|||||||
|
|
||||||
def retranslateUi(self, IconEditorDialog):
|
def retranslateUi(self, IconEditorDialog):
|
||||||
IconEditorDialog.setWindowTitle(QCoreApplication.translate("IconEditorDialog", u"Icon Editor", None))
|
IconEditorDialog.setWindowTitle(QCoreApplication.translate("IconEditorDialog", u"Icon Editor", None))
|
||||||
|
self.pointerButton.setText(QCoreApplication.translate("IconEditorDialog", u"Pointer", None))
|
||||||
self.addShapeLabel.setText(QCoreApplication.translate("IconEditorDialog", u"Add:", None))
|
self.addShapeLabel.setText(QCoreApplication.translate("IconEditorDialog", u"Add:", None))
|
||||||
self.addRectangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Rectangle", None))
|
self.addRectangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Rectangle", None))
|
||||||
self.addCircleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Circle", None))
|
self.addCircleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Circle", None))
|
||||||
@@ -113,6 +142,18 @@ class Ui_IconEditorDialog(object):
|
|||||||
self.addLineButton.setText(QCoreApplication.translate("IconEditorDialog", u"Line", None))
|
self.addLineButton.setText(QCoreApplication.translate("IconEditorDialog", u"Line", None))
|
||||||
self.addTriangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Triangle", None))
|
self.addTriangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Triangle", None))
|
||||||
self.addTextButton.setText(QCoreApplication.translate("IconEditorDialog", u"Text", None))
|
self.addTextButton.setText(QCoreApplication.translate("IconEditorDialog", u"Text", None))
|
||||||
|
self.zoomInButton.setText(QCoreApplication.translate("IconEditorDialog", u"+", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.zoomInButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom in", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.zoomOutButton.setText(QCoreApplication.translate("IconEditorDialog", u"\u2212", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.zoomOutButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom out", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.centerButton.setText(QCoreApplication.translate("IconEditorDialog", u"Fit", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.centerButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Fit and center the icon canvas", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.deleteSelectedButton.setText(QCoreApplication.translate("IconEditorDialog", u"Delete selected", None))
|
self.deleteSelectedButton.setText(QCoreApplication.translate("IconEditorDialog", u"Delete selected", None))
|
||||||
self.portHintLabel.setText(QCoreApplication.translate("IconEditorDialog", u"Green points are inputs; red points are outputs. Drag them to place connection anchors.", None))
|
self.portHintLabel.setText(QCoreApplication.translate("IconEditorDialog", u"Green points are inputs; red points are outputs. Drag them to place connection anchors.", None))
|
||||||
# retranslateUi
|
# retranslateUi
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Ui_MainWindow(object):
|
|||||||
def setupUi(self, MainWindow):
|
def setupUi(self, MainWindow):
|
||||||
if not MainWindow.objectName():
|
if not MainWindow.objectName():
|
||||||
MainWindow.setObjectName(u"MainWindow")
|
MainWindow.setObjectName(u"MainWindow")
|
||||||
MainWindow.resize(1000, 700)
|
MainWindow.resize(1209, 777)
|
||||||
self.actionNew = QAction(MainWindow)
|
self.actionNew = QAction(MainWindow)
|
||||||
self.actionNew.setObjectName(u"actionNew")
|
self.actionNew.setObjectName(u"actionNew")
|
||||||
icon = QIcon()
|
icon = QIcon()
|
||||||
@@ -42,54 +42,63 @@ class Ui_MainWindow(object):
|
|||||||
self.actionRotateClockwise.setIcon(icon1)
|
self.actionRotateClockwise.setIcon(icon1)
|
||||||
self.actionZoomIn = QAction(MainWindow)
|
self.actionZoomIn = QAction(MainWindow)
|
||||||
self.actionZoomIn.setObjectName(u"actionZoomIn")
|
self.actionZoomIn.setObjectName(u"actionZoomIn")
|
||||||
|
icon2 = QIcon()
|
||||||
|
icon2.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionZoomIn.setIcon(icon2)
|
||||||
self.actionZoomOut = QAction(MainWindow)
|
self.actionZoomOut = QAction(MainWindow)
|
||||||
self.actionZoomOut.setObjectName(u"actionZoomOut")
|
self.actionZoomOut.setObjectName(u"actionZoomOut")
|
||||||
|
icon3 = QIcon()
|
||||||
|
icon3.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionZoomOut.setIcon(icon3)
|
||||||
self.actionCenterView = QAction(MainWindow)
|
self.actionCenterView = QAction(MainWindow)
|
||||||
self.actionCenterView.setObjectName(u"actionCenterView")
|
self.actionCenterView.setObjectName(u"actionCenterView")
|
||||||
|
icon4 = QIcon()
|
||||||
|
icon4.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionCenterView.setIcon(icon4)
|
||||||
self.actionOpen = QAction(MainWindow)
|
self.actionOpen = QAction(MainWindow)
|
||||||
self.actionOpen.setObjectName(u"actionOpen")
|
self.actionOpen.setObjectName(u"actionOpen")
|
||||||
icon2 = QIcon()
|
icon5 = QIcon()
|
||||||
icon2.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon5.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionOpen.setIcon(icon2)
|
self.actionOpen.setIcon(icon5)
|
||||||
self.actionSave = QAction(MainWindow)
|
self.actionSave = QAction(MainWindow)
|
||||||
self.actionSave.setObjectName(u"actionSave")
|
self.actionSave.setObjectName(u"actionSave")
|
||||||
icon3 = QIcon()
|
icon6 = QIcon()
|
||||||
icon3.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon6.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionSave.setIcon(icon3)
|
self.actionSave.setIcon(icon6)
|
||||||
self.actionSaveAs = QAction(MainWindow)
|
self.actionSaveAs = QAction(MainWindow)
|
||||||
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
self.actionSaveAs.setObjectName(u"actionSaveAs")
|
||||||
icon4 = QIcon()
|
icon7 = QIcon()
|
||||||
icon4.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon7.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionSaveAs.setIcon(icon4)
|
self.actionSaveAs.setIcon(icon7)
|
||||||
self.actionExit = QAction(MainWindow)
|
self.actionExit = QAction(MainWindow)
|
||||||
self.actionExit.setObjectName(u"actionExit")
|
self.actionExit.setObjectName(u"actionExit")
|
||||||
self.actionClose = QAction(MainWindow)
|
self.actionClose = QAction(MainWindow)
|
||||||
self.actionClose.setObjectName(u"actionClose")
|
self.actionClose.setObjectName(u"actionClose")
|
||||||
self.actionUndo = QAction(MainWindow)
|
self.actionUndo = QAction(MainWindow)
|
||||||
self.actionUndo.setObjectName(u"actionUndo")
|
self.actionUndo.setObjectName(u"actionUndo")
|
||||||
icon5 = QIcon()
|
icon8 = QIcon()
|
||||||
icon5.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon8.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionUndo.setIcon(icon5)
|
self.actionUndo.setIcon(icon8)
|
||||||
self.actionRedo = QAction(MainWindow)
|
self.actionRedo = QAction(MainWindow)
|
||||||
self.actionRedo.setObjectName(u"actionRedo")
|
self.actionRedo.setObjectName(u"actionRedo")
|
||||||
icon6 = QIcon()
|
icon9 = QIcon()
|
||||||
icon6.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon9.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionRedo.setIcon(icon6)
|
self.actionRedo.setIcon(icon9)
|
||||||
self.actionCut = QAction(MainWindow)
|
self.actionCut = QAction(MainWindow)
|
||||||
self.actionCut.setObjectName(u"actionCut")
|
self.actionCut.setObjectName(u"actionCut")
|
||||||
icon7 = QIcon()
|
icon10 = QIcon()
|
||||||
icon7.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon10.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionCut.setIcon(icon7)
|
self.actionCut.setIcon(icon10)
|
||||||
self.actionCopy = QAction(MainWindow)
|
self.actionCopy = QAction(MainWindow)
|
||||||
self.actionCopy.setObjectName(u"actionCopy")
|
self.actionCopy.setObjectName(u"actionCopy")
|
||||||
icon8 = QIcon()
|
icon11 = QIcon()
|
||||||
icon8.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon11.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionCopy.setIcon(icon8)
|
self.actionCopy.setIcon(icon11)
|
||||||
self.actionPaste = QAction(MainWindow)
|
self.actionPaste = QAction(MainWindow)
|
||||||
self.actionPaste.setObjectName(u"actionPaste")
|
self.actionPaste.setObjectName(u"actionPaste")
|
||||||
icon9 = QIcon()
|
icon12 = QIcon()
|
||||||
icon9.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon12.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionPaste.setIcon(icon9)
|
self.actionPaste.setIcon(icon12)
|
||||||
self.actionSelectAll = QAction(MainWindow)
|
self.actionSelectAll = QAction(MainWindow)
|
||||||
self.actionSelectAll.setObjectName(u"actionSelectAll")
|
self.actionSelectAll.setObjectName(u"actionSelectAll")
|
||||||
self.actionDelete = QAction(MainWindow)
|
self.actionDelete = QAction(MainWindow)
|
||||||
@@ -129,9 +138,9 @@ class Ui_MainWindow(object):
|
|||||||
self.librariesLayout.setContentsMargins(0, 0, 0, 0)
|
self.librariesLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.treeView = QTreeView(self.dockWidgetContents)
|
self.treeView = QTreeView(self.dockWidgetContents)
|
||||||
self.treeView.setObjectName(u"treeView")
|
self.treeView.setObjectName(u"treeView")
|
||||||
self.treeView.setIconSize(QSize(28, 28))
|
|
||||||
self.treeView.setStyleSheet(u"QTreeView::item { height: 32px; }")
|
self.treeView.setStyleSheet(u"QTreeView::item { height: 32px; }")
|
||||||
self.treeView.setAlternatingRowColors(True)
|
self.treeView.setAlternatingRowColors(True)
|
||||||
|
self.treeView.setIconSize(QSize(28, 28))
|
||||||
self.treeView.setUniformRowHeights(True)
|
self.treeView.setUniformRowHeights(True)
|
||||||
|
|
||||||
self.librariesLayout.addWidget(self.treeView)
|
self.librariesLayout.addWidget(self.treeView)
|
||||||
@@ -149,8 +158,8 @@ class Ui_MainWindow(object):
|
|||||||
self.documentPanelLayout.setContentsMargins(0, 0, 0, 0)
|
self.documentPanelLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.documentTreeView = QTreeView(self.documentDockContents)
|
self.documentTreeView = QTreeView(self.documentDockContents)
|
||||||
self.documentTreeView.setObjectName(u"documentTreeView")
|
self.documentTreeView.setObjectName(u"documentTreeView")
|
||||||
self.documentTreeView.setIconSize(QSize(16, 16))
|
|
||||||
self.documentTreeView.setAlternatingRowColors(True)
|
self.documentTreeView.setAlternatingRowColors(True)
|
||||||
|
self.documentTreeView.setIconSize(QSize(16, 16))
|
||||||
self.documentTreeView.setUniformRowHeights(True)
|
self.documentTreeView.setUniformRowHeights(True)
|
||||||
|
|
||||||
self.documentPanelLayout.addWidget(self.documentTreeView)
|
self.documentPanelLayout.addWidget(self.documentTreeView)
|
||||||
@@ -179,12 +188,18 @@ class Ui_MainWindow(object):
|
|||||||
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
|
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
|
||||||
self.navigateUpButton = QToolButton(self.workspaceHeader)
|
self.navigateUpButton = QToolButton(self.workspaceHeader)
|
||||||
self.navigateUpButton.setObjectName(u"navigateUpButton")
|
self.navigateUpButton.setObjectName(u"navigateUpButton")
|
||||||
|
icon13 = QIcon()
|
||||||
|
icon13.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.navigateUpButton.setIcon(icon13)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
||||||
|
|
||||||
self.navigateDownButton = QToolButton(self.workspaceHeader)
|
self.navigateDownButton = QToolButton(self.workspaceHeader)
|
||||||
self.navigateDownButton.setObjectName(u"navigateDownButton")
|
self.navigateDownButton.setObjectName(u"navigateDownButton")
|
||||||
self.navigateDownButton.setEnabled(False)
|
self.navigateDownButton.setEnabled(False)
|
||||||
|
icon14 = QIcon()
|
||||||
|
icon14.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.navigateDownButton.setIcon(icon14)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
|
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
|
||||||
|
|
||||||
@@ -210,6 +225,9 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
||||||
|
icon15 = QIcon()
|
||||||
|
icon15.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.pointerToolButton.setIcon(icon15)
|
||||||
self.pointerToolButton.setCheckable(True)
|
self.pointerToolButton.setCheckable(True)
|
||||||
self.pointerToolButton.setChecked(True)
|
self.pointerToolButton.setChecked(True)
|
||||||
|
|
||||||
@@ -221,6 +239,39 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
|
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
|
||||||
|
|
||||||
|
self.boxToolButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.boxToolButton.setObjectName(u"boxToolButton")
|
||||||
|
icon16 = QIcon()
|
||||||
|
icon16.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.boxToolButton.setIcon(icon16)
|
||||||
|
self.boxToolButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.boxToolButton)
|
||||||
|
|
||||||
|
self.lineToolButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.lineToolButton.setObjectName(u"lineToolButton")
|
||||||
|
icon17 = QIcon()
|
||||||
|
icon17.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.lineToolButton.setIcon(icon17)
|
||||||
|
self.lineToolButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.lineToolButton)
|
||||||
|
|
||||||
|
self.textToolButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.textToolButton.setObjectName(u"textToolButton")
|
||||||
|
icon18 = QIcon()
|
||||||
|
icon18.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.textToolButton.setIcon(icon18)
|
||||||
|
self.textToolButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.textToolButton)
|
||||||
|
|
||||||
|
self.rotateToolButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.rotateToolButton.setObjectName(u"rotateToolButton")
|
||||||
|
self.rotateToolButton.setIcon(icon1)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
|
||||||
|
|
||||||
self.routingLabel = QLabel(self.workspaceHeader)
|
self.routingLabel = QLabel(self.workspaceHeader)
|
||||||
self.routingLabel.setObjectName(u"routingLabel")
|
self.routingLabel.setObjectName(u"routingLabel")
|
||||||
|
|
||||||
@@ -235,13 +286,13 @@ class Ui_MainWindow(object):
|
|||||||
self.angledRoutingButton = QToolButton(self.workspaceHeader)
|
self.angledRoutingButton = QToolButton(self.workspaceHeader)
|
||||||
self.angledRoutingButton.setObjectName(u"angledRoutingButton")
|
self.angledRoutingButton.setObjectName(u"angledRoutingButton")
|
||||||
self.angledRoutingButton.setCheckable(True)
|
self.angledRoutingButton.setCheckable(True)
|
||||||
|
self.angledRoutingButton.setChecked(True)
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.angledRoutingButton)
|
self.workspaceHeaderLayout.addWidget(self.angledRoutingButton)
|
||||||
|
|
||||||
self.splineRoutingButton = QToolButton(self.workspaceHeader)
|
self.splineRoutingButton = QToolButton(self.workspaceHeader)
|
||||||
self.splineRoutingButton.setObjectName(u"splineRoutingButton")
|
self.splineRoutingButton.setObjectName(u"splineRoutingButton")
|
||||||
self.splineRoutingButton.setCheckable(True)
|
self.splineRoutingButton.setCheckable(True)
|
||||||
self.splineRoutingButton.setChecked(True)
|
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.splineRoutingButton)
|
self.workspaceHeaderLayout.addWidget(self.splineRoutingButton)
|
||||||
|
|
||||||
@@ -296,7 +347,7 @@ class Ui_MainWindow(object):
|
|||||||
MainWindow.setCentralWidget(self.centralwidget)
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
self.menubar = QMenuBar(MainWindow)
|
self.menubar = QMenuBar(MainWindow)
|
||||||
self.menubar.setObjectName(u"menubar")
|
self.menubar.setObjectName(u"menubar")
|
||||||
self.menubar.setGeometry(QRect(0, 0, 1000, 24))
|
self.menubar.setGeometry(QRect(0, 0, 1209, 24))
|
||||||
self.menuFile = QMenu(self.menubar)
|
self.menuFile = QMenu(self.menubar)
|
||||||
self.menuFile.setObjectName(u"menuFile")
|
self.menuFile.setObjectName(u"menuFile")
|
||||||
self.menuEdit = QMenu(self.menubar)
|
self.menuEdit = QMenu(self.menubar)
|
||||||
@@ -324,9 +375,6 @@ class Ui_MainWindow(object):
|
|||||||
self.editToolbar = QToolBar(MainWindow)
|
self.editToolbar = QToolBar(MainWindow)
|
||||||
self.editToolbar.setObjectName(u"editToolbar")
|
self.editToolbar.setObjectName(u"editToolbar")
|
||||||
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.editToolbar)
|
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.editToolbar)
|
||||||
self.transformToolbar = QToolBar(MainWindow)
|
|
||||||
self.transformToolbar.setObjectName(u"transformToolbar")
|
|
||||||
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.transformToolbar)
|
|
||||||
self.cameraToolbar = QToolBar(MainWindow)
|
self.cameraToolbar = QToolBar(MainWindow)
|
||||||
self.cameraToolbar.setObjectName(u"cameraToolbar")
|
self.cameraToolbar.setObjectName(u"cameraToolbar")
|
||||||
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.cameraToolbar)
|
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.cameraToolbar)
|
||||||
@@ -366,7 +414,6 @@ class Ui_MainWindow(object):
|
|||||||
self.editToolbar.addAction(self.actionCopy)
|
self.editToolbar.addAction(self.actionCopy)
|
||||||
self.editToolbar.addAction(self.actionCut)
|
self.editToolbar.addAction(self.actionCut)
|
||||||
self.editToolbar.addAction(self.actionPaste)
|
self.editToolbar.addAction(self.actionPaste)
|
||||||
self.transformToolbar.addAction(self.actionRotateClockwise)
|
|
||||||
self.cameraToolbar.addAction(self.actionZoomIn)
|
self.cameraToolbar.addAction(self.actionZoomIn)
|
||||||
self.cameraToolbar.addAction(self.actionZoomOut)
|
self.cameraToolbar.addAction(self.actionZoomOut)
|
||||||
self.cameraToolbar.addAction(self.actionCenterView)
|
self.cameraToolbar.addAction(self.actionCenterView)
|
||||||
@@ -469,19 +516,35 @@ class Ui_MainWindow(object):
|
|||||||
self.actionAboutQt.setText(QCoreApplication.translate("MainWindow", u"About &Qt", None))
|
self.actionAboutQt.setText(QCoreApplication.translate("MainWindow", u"About &Qt", None))
|
||||||
self.panel_libraries.setWindowTitle(QCoreApplication.translate("MainWindow", u"Libraries", None))
|
self.panel_libraries.setWindowTitle(QCoreApplication.translate("MainWindow", u"Libraries", None))
|
||||||
self.panel_document.setWindowTitle(QCoreApplication.translate("MainWindow", u"Document", None))
|
self.panel_document.setWindowTitle(QCoreApplication.translate("MainWindow", u"Document", None))
|
||||||
self.navigateUpButton.setText(QCoreApplication.translate("MainWindow", u"Up", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.navigateUpButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the containing graph", None))
|
self.navigateUpButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the containing graph", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.navigateDownButton.setText(QCoreApplication.translate("MainWindow", u"Down", None))
|
self.navigateUpButton.setText(QCoreApplication.translate("MainWindow", u"Up", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.navigateDownButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the selected block", None))
|
self.navigateDownButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the selected block", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.navigateDownButton.setText(QCoreApplication.translate("MainWindow", u"Down", None))
|
||||||
self.graphBreadcrumbLabel.setText(QCoreApplication.translate("MainWindow", u"Untitled", None))
|
self.graphBreadcrumbLabel.setText(QCoreApplication.translate("MainWindow", u"Untitled", None))
|
||||||
self.workspaceModeLabel.setText(QCoreApplication.translate("MainWindow", u"Graph", None))
|
self.workspaceModeLabel.setText(QCoreApplication.translate("MainWindow", u"Graph", None))
|
||||||
self.applyJsonButton.setText(QCoreApplication.translate("MainWindow", u"Apply JSON", None))
|
self.applyJsonButton.setText(QCoreApplication.translate("MainWindow", u"Apply JSON", None))
|
||||||
self.pointerToolButton.setText(QCoreApplication.translate("MainWindow", u"Pointer", None))
|
self.pointerToolButton.setText(QCoreApplication.translate("MainWindow", u"Pointer", None))
|
||||||
self.connectToolButton.setText(QCoreApplication.translate("MainWindow", u"Connect", None))
|
self.connectToolButton.setText(QCoreApplication.translate("MainWindow", u"Connect", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.boxToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Draw a box annotation", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.boxToolButton.setText(QCoreApplication.translate("MainWindow", u"Box", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.lineToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Draw a line annotation", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.lineToolButton.setText(QCoreApplication.translate("MainWindow", u"Line", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.textToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Add a text annotation", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.textToolButton.setText(QCoreApplication.translate("MainWindow", u"Text", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.rotateToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None))
|
||||||
self.routingLabel.setText(QCoreApplication.translate("MainWindow", u"Line:", None))
|
self.routingLabel.setText(QCoreApplication.translate("MainWindow", u"Line:", None))
|
||||||
self.directRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Direct", None))
|
self.directRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Direct", None))
|
||||||
self.angledRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Angled", None))
|
self.angledRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Angled", None))
|
||||||
@@ -496,7 +559,6 @@ class Ui_MainWindow(object):
|
|||||||
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", None))
|
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", None))
|
||||||
self.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None))
|
self.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None))
|
||||||
self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
|
self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
|
||||||
self.transformToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Transform", None))
|
|
||||||
self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None))
|
self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None))
|
||||||
# retranslateUi
|
# retranslateUi
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from PySide6.QtCore import QPointF, QRectF
|
from PySide6.QtCore import QPointF, QRectF
|
||||||
from PySide6.QtGui import QColor, QPainter, QPen
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtGui import QColor, QPainter, QPen, QWheelEvent
|
||||||
from PySide6.QtWidgets import QGraphicsView
|
from PySide6.QtWidgets import QGraphicsView
|
||||||
|
|
||||||
from bedit.gui.preferences import application_settings
|
from bedit.gui.preferences import application_settings
|
||||||
@@ -22,3 +23,25 @@ class IconCanvasView(QGraphicsView):
|
|||||||
painter.drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom()))
|
painter.drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom()))
|
||||||
for y in range(top, int(rect.bottom()) + grid, grid):
|
for y in range(top, int(rect.bottom()) + grid, grid):
|
||||||
painter.drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y))
|
painter.drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y))
|
||||||
|
|
||||||
|
def _zoom(self, factor: float) -> None:
|
||||||
|
target = self.transform().m11() * factor
|
||||||
|
if 0.25 <= target <= 16.0:
|
||||||
|
self.scale(factor, factor)
|
||||||
|
|
||||||
|
def zoom_in(self) -> None:
|
||||||
|
self._zoom(1.2)
|
||||||
|
|
||||||
|
def zoom_out(self) -> None:
|
||||||
|
self._zoom(1 / 1.2)
|
||||||
|
|
||||||
|
def center_icon(self) -> None:
|
||||||
|
if self.scene() is not None:
|
||||||
|
self.fitInView(
|
||||||
|
self.scene().sceneRect().adjusted(-10, -10, 10, 10),
|
||||||
|
Qt.AspectRatioMode.KeepAspectRatio,
|
||||||
|
)
|
||||||
|
|
||||||
|
def wheelEvent(self, event: QWheelEvent) -> None: # noqa: N802
|
||||||
|
self._zoom(1.2 if event.angleDelta().y() > 0 else 1 / 1.2)
|
||||||
|
event.accept()
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from PySide6.QtCore import QPointF, QRectF, Qt
|
from PySide6.QtCore import QPointF, QRectF, QSizeF, Qt
|
||||||
from PySide6.QtGui import QColor, QPainter, QPen, QPolygonF
|
from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPolygonF
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QColorDialog,
|
QColorDialog,
|
||||||
|
QButtonGroup,
|
||||||
QDialog,
|
QDialog,
|
||||||
QGraphicsEllipseItem,
|
QGraphicsEllipseItem,
|
||||||
QGraphicsItem,
|
QGraphicsItem,
|
||||||
QGraphicsObject,
|
QGraphicsObject,
|
||||||
|
QGraphicsPathItem,
|
||||||
QGraphicsScene,
|
QGraphicsScene,
|
||||||
QGraphicsSceneContextMenuEvent,
|
QGraphicsSceneContextMenuEvent,
|
||||||
QGraphicsSceneMouseEvent,
|
QGraphicsSceneMouseEvent,
|
||||||
@@ -19,7 +21,7 @@ from bedit.core.model import Component, Icon, Port
|
|||||||
from bedit.gui.generated.ui_icon_editor_dialog import Ui_IconEditorDialog
|
from bedit.gui.generated.ui_icon_editor_dialog import Ui_IconEditorDialog
|
||||||
from bedit.gui.generated.ui_shape_options_dialog import Ui_ShapeOptionsDialog
|
from bedit.gui.generated.ui_shape_options_dialog import Ui_ShapeOptionsDialog
|
||||||
from bedit.gui.graphics.icon_canvas import icon_grid_size
|
from bedit.gui.graphics.icon_canvas import icon_grid_size
|
||||||
from bedit.gui.graphics.icon_renderer import _pen
|
from bedit.gui.graphics.icon_renderer import shape_pen
|
||||||
|
|
||||||
|
|
||||||
def _icon_grid_size() -> int:
|
def _icon_grid_size() -> int:
|
||||||
@@ -49,6 +51,21 @@ class ResizeHandle(QGraphicsEllipseItem):
|
|||||||
if self.owner.scene()
|
if self.owner.scene()
|
||||||
else QPointF(128, 128)
|
else QPointF(128, 128)
|
||||||
)
|
)
|
||||||
|
if self.owner.element.get("type") == "line":
|
||||||
|
minimum = (
|
||||||
|
self.owner.scene().sceneRect().topLeft() - self.owner.pos()
|
||||||
|
if self.owner.scene()
|
||||||
|
else QPointF(-128, -128)
|
||||||
|
)
|
||||||
|
value = QPointF(
|
||||||
|
min(maximum.x(), max(minimum.x(), _snap(value.x()))),
|
||||||
|
min(maximum.y(), max(minimum.y(), _snap(value.y()))),
|
||||||
|
)
|
||||||
|
self.owner.prepareGeometryChange()
|
||||||
|
self.owner.element["width"] = value.x()
|
||||||
|
self.owner.element["height"] = value.y()
|
||||||
|
self.owner.update()
|
||||||
|
return super().itemChange(change, value)
|
||||||
value = QPointF(
|
value = QPointF(
|
||||||
min(maximum.x(), max(_icon_grid_size(), _snap(value.x()))),
|
min(maximum.x(), max(_icon_grid_size(), _snap(value.x()))),
|
||||||
min(maximum.y(), max(_icon_grid_size(), _snap(value.y()))),
|
min(maximum.y(), max(_icon_grid_size(), _snap(value.y()))),
|
||||||
@@ -82,9 +99,17 @@ class ShapeOptionsDialog(QDialog):
|
|||||||
self.ui.cornerRadiusSpin.setValue(float(self.element.get("cornerRadius", 0)))
|
self.ui.cornerRadiusSpin.setValue(float(self.element.get("cornerRadius", 0)))
|
||||||
self.ui.textEdit.setText(str(self.element.get("text", "Text")))
|
self.ui.textEdit.setText(str(self.element.get("text", "Text")))
|
||||||
self.ui.fontSizeSpin.setValue(float(self.element.get("fontSize", 12)))
|
self.ui.fontSizeSpin.setValue(float(self.element.get("fontSize", 12)))
|
||||||
self._show_row(self.ui.fillTypeLabel, self.ui.fillTypeCombo, self.element.get("type") != "line")
|
self._show_row(
|
||||||
self._show_row(self.ui.fillColorLabel, self.ui.fillColorButton, self.element.get("type") != "line")
|
self.ui.fillTypeLabel, self.ui.fillTypeCombo, self.element.get("type") != "line"
|
||||||
self._show_row(self.ui.cornerRadiusLabel, self.ui.cornerRadiusSpin, self.element.get("type") == "rectangle")
|
)
|
||||||
|
self._show_row(
|
||||||
|
self.ui.fillColorLabel, self.ui.fillColorButton, self.element.get("type") != "line"
|
||||||
|
)
|
||||||
|
self._show_row(
|
||||||
|
self.ui.cornerRadiusLabel,
|
||||||
|
self.ui.cornerRadiusSpin,
|
||||||
|
self.element.get("type") == "rectangle",
|
||||||
|
)
|
||||||
is_text = self.element.get("type") == "text"
|
is_text = self.element.get("type") == "text"
|
||||||
self._show_row(self.ui.textLabel, self.ui.textEdit, is_text)
|
self._show_row(self.ui.textLabel, self.ui.textEdit, is_text)
|
||||||
self._show_row(self.ui.fontSizeLabel, self.ui.fontSizeSpin, is_text)
|
self._show_row(self.ui.fontSizeLabel, self.ui.fontSizeSpin, is_text)
|
||||||
@@ -99,7 +124,9 @@ class ShapeOptionsDialog(QDialog):
|
|||||||
|
|
||||||
def _choose_color(self, current: str) -> str:
|
def _choose_color(self, current: str) -> str:
|
||||||
color = QColorDialog.getColor(
|
color = QColorDialog.getColor(
|
||||||
QColor(current), self, "Choose colour",
|
QColor(current),
|
||||||
|
self,
|
||||||
|
"Choose colour",
|
||||||
QColorDialog.ColorDialogOption.ShowAlphaChannel,
|
QColorDialog.ColorDialogOption.ShowAlphaChannel,
|
||||||
)
|
)
|
||||||
if not color.isValid():
|
if not color.isValid():
|
||||||
@@ -129,7 +156,9 @@ class ShapeOptionsDialog(QDialog):
|
|||||||
self.element["width"] = self.ui.widthSpin.value()
|
self.element["width"] = self.ui.widthSpin.value()
|
||||||
self.element["height"] = self.ui.heightSpin.value()
|
self.element["height"] = self.ui.heightSpin.value()
|
||||||
if self.element.get("type") != "line":
|
if self.element.get("type") != "line":
|
||||||
self.element["fill"] = self.fill_color if self.ui.fillTypeCombo.currentText() == "solid" else "none"
|
self.element["fill"] = (
|
||||||
|
self.fill_color if self.ui.fillTypeCombo.currentText() == "solid" else "none"
|
||||||
|
)
|
||||||
if self.element.get("type") == "rectangle":
|
if self.element.get("type") == "rectangle":
|
||||||
self.element["cornerRadius"] = self.ui.cornerRadiusSpin.value()
|
self.element["cornerRadius"] = self.ui.cornerRadiusSpin.value()
|
||||||
if self.element.get("type") == "text":
|
if self.element.get("type") == "text":
|
||||||
@@ -144,21 +173,35 @@ class ShapeItem(QGraphicsObject):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.element = element
|
self.element = element
|
||||||
self.setPos(float(element.get("x", 0)), float(element.get("y", 0)))
|
self.setPos(float(element.get("x", 0)), float(element.get("y", 0)))
|
||||||
self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
self.setFlags(
|
||||||
|
QGraphicsItem.GraphicsItemFlag.ItemIsMovable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
|
)
|
||||||
self.resize_handle = ResizeHandle(self)
|
self.resize_handle = ResizeHandle(self)
|
||||||
self.resize_handle.setPos(float(element.get("width", 20)), float(element.get("height", 20)))
|
self.resize_handle.setPos(float(element.get("width", 20)), float(element.get("height", 20)))
|
||||||
self.resize_handle.hide()
|
self.resize_handle.hide()
|
||||||
|
|
||||||
def boundingRect(self) -> QRectF: # noqa: N802
|
def boundingRect(self) -> QRectF: # noqa: N802
|
||||||
margin = max(3.0, float(self.element.get("lineWidth", 1.5)))
|
margin = max(3.0, float(self.element.get("lineWidth", 1.5)))
|
||||||
return QRectF(0, 0, float(self.element.get("width", 20)), float(self.element.get("height", 20))).adjusted(-margin, -margin, margin, margin)
|
return (
|
||||||
|
QRectF(
|
||||||
|
0, 0, float(self.element.get("width", 20)), float(self.element.get("height", 20))
|
||||||
|
)
|
||||||
|
.normalized()
|
||||||
|
.adjusted(-margin, -margin, margin, margin)
|
||||||
|
)
|
||||||
|
|
||||||
def paint(self, painter: QPainter, option, widget=None) -> None:
|
def paint(self, painter: QPainter, option, widget=None) -> None:
|
||||||
del option, widget
|
del option, widget
|
||||||
rect = QRectF(0, 0, float(self.element.get("width", 20)), float(self.element.get("height", 20)))
|
rect = QRectF(
|
||||||
painter.setPen(_pen(self.element))
|
0, 0, float(self.element.get("width", 20)), float(self.element.get("height", 20))
|
||||||
|
)
|
||||||
|
painter.setPen(shape_pen(self.element))
|
||||||
fill = self.element.get("fill", "none")
|
fill = self.element.get("fill", "none")
|
||||||
painter.setBrush(Qt.BrushStyle.NoBrush if fill in {"none", "transparent", ""} else QColor(fill))
|
painter.setBrush(
|
||||||
|
Qt.BrushStyle.NoBrush if fill in {"none", "transparent", ""} else QColor(fill)
|
||||||
|
)
|
||||||
kind = self.element.get("type")
|
kind = self.element.get("type")
|
||||||
if kind == "rectangle":
|
if kind == "rectangle":
|
||||||
radius = float(self.element.get("cornerRadius", 0))
|
radius = float(self.element.get("cornerRadius", 0))
|
||||||
@@ -168,13 +211,17 @@ class ShapeItem(QGraphicsObject):
|
|||||||
elif kind == "line":
|
elif kind == "line":
|
||||||
painter.drawLine(rect.topLeft(), rect.bottomRight())
|
painter.drawLine(rect.topLeft(), rect.bottomRight())
|
||||||
elif kind == "triangle":
|
elif kind == "triangle":
|
||||||
painter.drawPolygon(QPolygonF([QPointF(rect.center().x(), 0), rect.bottomRight(), rect.bottomLeft()]))
|
painter.drawPolygon(
|
||||||
|
QPolygonF([QPointF(rect.center().x(), 0), rect.bottomRight(), rect.bottomLeft()])
|
||||||
|
)
|
||||||
elif kind == "text":
|
elif kind == "text":
|
||||||
painter.setPen(QColor(self.element.get("color", "#202020")))
|
painter.setPen(QColor(self.element.get("color", "#202020")))
|
||||||
font = painter.font()
|
font = painter.font()
|
||||||
font.setPointSizeF(float(self.element.get("fontSize", 12)))
|
font.setPointSizeF(float(self.element.get("fontSize", 12)))
|
||||||
painter.setFont(font)
|
painter.setFont(font)
|
||||||
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, str(self.element.get("text", "Text")))
|
painter.drawText(
|
||||||
|
rect, Qt.AlignmentFlag.AlignCenter, str(self.element.get("text", "Text"))
|
||||||
|
)
|
||||||
if self.isSelected():
|
if self.isSelected():
|
||||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
painter.setPen(QPen(QColor("#2563eb"), 1, Qt.PenStyle.DashLine))
|
painter.setPen(QPen(QColor("#2563eb"), 1, Qt.PenStyle.DashLine))
|
||||||
@@ -182,23 +229,7 @@ class ShapeItem(QGraphicsObject):
|
|||||||
|
|
||||||
def itemChange(self, change, value): # noqa: N802
|
def itemChange(self, change, value): # noqa: N802
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
bounds = self.scene().sceneRect() if self.scene() else QRectF(0, 0, 128, 128)
|
value = self._bounded_position(value)
|
||||||
value = QPointF(
|
|
||||||
max(
|
|
||||||
bounds.left(),
|
|
||||||
min(
|
|
||||||
bounds.right() - float(self.element.get("width", 20)),
|
|
||||||
_snap(value.x()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
max(
|
|
||||||
bounds.top(),
|
|
||||||
min(
|
|
||||||
bounds.bottom() - float(self.element.get("height", 20)),
|
|
||||||
_snap(value.y()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
elif change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
elif change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
||||||
self.element["x"], self.element["y"] = value.x(), value.y()
|
self.element["x"], self.element["y"] = value.x(), value.y()
|
||||||
elif change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
elif change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
||||||
@@ -207,10 +238,17 @@ class ShapeItem(QGraphicsObject):
|
|||||||
|
|
||||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
super().mouseReleaseEvent(event)
|
super().mouseReleaseEvent(event)
|
||||||
|
self.setPos(self._bounded_position(self.pos()))
|
||||||
|
|
||||||
|
def _bounded_position(self, position: QPointF) -> QPointF:
|
||||||
bounds = self.scene().sceneRect() if self.scene() else QRectF(0, 0, 128, 128)
|
bounds = self.scene().sceneRect() if self.scene() else QRectF(0, 0, 128, 128)
|
||||||
self.setPos(
|
width = float(self.element.get("width", 20))
|
||||||
max(bounds.left(), min(bounds.right() - float(self.element.get("width", 20)), _snap(self.pos().x()))),
|
height = float(self.element.get("height", 20))
|
||||||
max(bounds.top(), min(bounds.bottom() - float(self.element.get("height", 20)), _snap(self.pos().y()))),
|
minimum_x, maximum_x = min(0.0, width), max(0.0, width)
|
||||||
|
minimum_y, maximum_y = min(0.0, height), max(0.0, height)
|
||||||
|
return QPointF(
|
||||||
|
max(bounds.left() - minimum_x, min(bounds.right() - maximum_x, _snap(position.x()))),
|
||||||
|
max(bounds.top() - minimum_y, min(bounds.bottom() - maximum_y, _snap(position.y()))),
|
||||||
)
|
)
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
@@ -219,11 +257,19 @@ class ShapeItem(QGraphicsObject):
|
|||||||
delete = menu.addAction("Delete Shape")
|
delete = menu.addAction("Delete Shape")
|
||||||
chosen = menu.exec(event.screenPos())
|
chosen = menu.exec(event.screenPos())
|
||||||
if chosen is options:
|
if chosen is options:
|
||||||
dialog = ShapeOptionsDialog(self.element)
|
options_element = deepcopy(self.element)
|
||||||
|
width_sign = -1 if float(options_element.get("width", 20)) < 0 else 1
|
||||||
|
height_sign = -1 if float(options_element.get("height", 20)) < 0 else 1
|
||||||
|
options_element["width"] = abs(float(options_element.get("width", 20)))
|
||||||
|
options_element["height"] = abs(float(options_element.get("height", 20)))
|
||||||
|
dialog = ShapeOptionsDialog(options_element)
|
||||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
self.element.clear()
|
self.element.clear()
|
||||||
self.element.update(dialog.element)
|
self.element.update(dialog.element)
|
||||||
|
if self.element.get("type") == "line":
|
||||||
|
self.element["width"] *= width_sign
|
||||||
|
self.element["height"] *= height_sign
|
||||||
self.resize_handle.setPos(
|
self.resize_handle.setPos(
|
||||||
float(self.element.get("width", 20)),
|
float(self.element.get("width", 20)),
|
||||||
float(self.element.get("height", 20)),
|
float(self.element.get("height", 20)),
|
||||||
@@ -244,7 +290,11 @@ class PortHandle(QGraphicsEllipseItem):
|
|||||||
self.setPen(QPen(QColor("#ffffff"), 1.5))
|
self.setPen(QPen(QColor("#ffffff"), 1.5))
|
||||||
self.setToolTip(f"{direction.title()}: {port.name} (drag to position)")
|
self.setToolTip(f"{direction.title()}: {port.name} (drag to position)")
|
||||||
self.setZValue(10)
|
self.setZValue(10)
|
||||||
self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
self.setFlags(
|
||||||
|
QGraphicsItem.GraphicsItemFlag.ItemIsMovable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
|
)
|
||||||
|
|
||||||
def itemChange(self, change, value): # noqa: N802
|
def itemChange(self, change, value): # noqa: N802
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
@@ -266,6 +316,81 @@ class PortHandle(QGraphicsEllipseItem):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IconDrawingScene(QGraphicsScene):
|
||||||
|
def __init__(self, editor: "IconEditorDialog", rect: QRectF) -> None:
|
||||||
|
super().__init__(rect, editor)
|
||||||
|
self.editor = editor
|
||||||
|
self.mode = "pointer"
|
||||||
|
self.start: QPointF | None = None
|
||||||
|
self.preview: QGraphicsPathItem | None = None
|
||||||
|
|
||||||
|
def set_mode(self, mode: str) -> None:
|
||||||
|
self.mode = mode
|
||||||
|
self._clear_preview()
|
||||||
|
|
||||||
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.mode == "pointer":
|
||||||
|
super().mousePressEvent(event)
|
||||||
|
return
|
||||||
|
point = self._bounded(event.scenePos())
|
||||||
|
if self.mode == "text":
|
||||||
|
text, accepted = QInputDialog.getText(self.editor, "Add Text", "Text:", text="Text")
|
||||||
|
if accepted:
|
||||||
|
self.editor.create_shape("text", point, point + QPointF(55, 35), text)
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
self.start = point
|
||||||
|
self.preview = QGraphicsPathItem()
|
||||||
|
self.preview.setPen(QPen(QColor("#64748b"), 1.5, Qt.PenStyle.DashLine))
|
||||||
|
self.preview.setZValue(100)
|
||||||
|
self.addItem(self.preview)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.start is None or self.preview is None:
|
||||||
|
super().mouseMoveEvent(event)
|
||||||
|
return
|
||||||
|
end = self._bounded(event.scenePos())
|
||||||
|
path = QPainterPath()
|
||||||
|
rect = QRectF(self.start, end).normalized()
|
||||||
|
if self.mode == "line":
|
||||||
|
path.moveTo(self.start)
|
||||||
|
path.lineTo(end)
|
||||||
|
elif self.mode in {"circle", "ellipse"}:
|
||||||
|
if self.mode == "circle":
|
||||||
|
side = min(rect.width(), rect.height())
|
||||||
|
rect.setSize(QSizeF(side, side))
|
||||||
|
path.addEllipse(rect)
|
||||||
|
else:
|
||||||
|
path.addRect(rect)
|
||||||
|
self.preview.setPath(path)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.start is None:
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
return
|
||||||
|
end = self._bounded(event.scenePos())
|
||||||
|
start = self.start
|
||||||
|
self._clear_preview()
|
||||||
|
if start != end:
|
||||||
|
self.editor.create_shape(self.mode, start, end)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def _bounded(self, point: QPointF) -> QPointF:
|
||||||
|
rect = self.sceneRect()
|
||||||
|
return QPointF(
|
||||||
|
min(rect.right(), max(rect.left(), _snap(point.x()))),
|
||||||
|
min(rect.bottom(), max(rect.top(), _snap(point.y()))),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _clear_preview(self) -> None:
|
||||||
|
if self.preview is not None:
|
||||||
|
self.removeItem(self.preview)
|
||||||
|
self.preview = None
|
||||||
|
self.start = None
|
||||||
|
|
||||||
|
|
||||||
class IconEditorDialog(QDialog):
|
class IconEditorDialog(QDialog):
|
||||||
def __init__(self, component: Component, parent=None) -> None:
|
def __init__(self, component: Component, parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -275,6 +400,10 @@ class IconEditorDialog(QDialog):
|
|||||||
self.icon = Icon.from_dict(component.icon.to_dict())
|
self.icon = Icon.from_dict(component.icon.to_dict())
|
||||||
self.inputs = deepcopy(component.inputs)
|
self.inputs = deepcopy(component.inputs)
|
||||||
self.outputs = deepcopy(component.outputs)
|
self.outputs = deepcopy(component.outputs)
|
||||||
|
self.tool_group = QButtonGroup(self)
|
||||||
|
self.tool_group.setExclusive(True)
|
||||||
|
self.tool_group.addButton(self.ui.pointerButton)
|
||||||
|
self.ui.pointerButton.clicked.connect(lambda: self.set_draw_mode("pointer"))
|
||||||
for button, kind in (
|
for button, kind in (
|
||||||
(self.ui.addRectangleButton, "rectangle"),
|
(self.ui.addRectangleButton, "rectangle"),
|
||||||
(self.ui.addCircleButton, "circle"),
|
(self.ui.addCircleButton, "circle"),
|
||||||
@@ -283,43 +412,70 @@ class IconEditorDialog(QDialog):
|
|||||||
(self.ui.addTriangleButton, "triangle"),
|
(self.ui.addTriangleButton, "triangle"),
|
||||||
(self.ui.addTextButton, "text"),
|
(self.ui.addTextButton, "text"),
|
||||||
):
|
):
|
||||||
button.clicked.connect(
|
self.tool_group.addButton(button)
|
||||||
lambda _checked=False, value=kind: self.add_shape(value)
|
button.clicked.connect(lambda _checked=False, value=kind: self.set_draw_mode(value))
|
||||||
)
|
|
||||||
self.ui.deleteSelectedButton.clicked.connect(self.delete_selected)
|
self.ui.deleteSelectedButton.clicked.connect(self.delete_selected)
|
||||||
self.scene = QGraphicsScene(0, 0, self.icon.width, self.icon.height, self)
|
self.scene = IconDrawingScene(self, QRectF(0, 0, self.icon.width, self.icon.height))
|
||||||
self.ui.iconView.setScene(self.scene)
|
self.ui.iconView.setScene(self.scene)
|
||||||
self.view = self.ui.iconView
|
self.view = self.ui.iconView
|
||||||
self.view.setRenderHint(QPainter.RenderHint.Antialiasing)
|
self.view.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||||
|
self.ui.zoomInButton.clicked.connect(self.view.zoom_in)
|
||||||
|
self.ui.zoomOutButton.clicked.connect(self.view.zoom_out)
|
||||||
|
self.ui.centerButton.clicked.connect(self.view.center_icon)
|
||||||
self.scene.addRect(self.scene.sceneRect(), QPen(QColor("#64748b"), 0)).setZValue(-100)
|
self.scene.addRect(self.scene.sceneRect(), QPen(QColor("#64748b"), 0)).setZValue(-100)
|
||||||
for element in self.icon.elements:
|
for element in self.icon.elements:
|
||||||
self.scene.addItem(ShapeItem(element))
|
self.scene.addItem(ShapeItem(element))
|
||||||
self._add_ports(self.inputs, "input", 0.0)
|
self._add_ports(self.inputs, "input", 0.0)
|
||||||
self._add_ports(self.outputs, "output", self.icon.width)
|
self._add_ports(self.outputs, "output", self.icon.width)
|
||||||
self.view.fitInView(self.scene.sceneRect().adjusted(-10, -10, 10, 10), Qt.AspectRatioMode.KeepAspectRatio)
|
self.view.center_icon()
|
||||||
|
|
||||||
def _add_ports(self, ports: list[Port], direction: str, default_x: float) -> None:
|
def _add_ports(self, ports: list[Port], direction: str, default_x: float) -> None:
|
||||||
spacing = self.icon.height / (len(ports) + 1)
|
spacing = self.icon.height / (len(ports) + 1)
|
||||||
for index, port in enumerate(ports, 1):
|
for index, port in enumerate(ports, 1):
|
||||||
saved = port.properties.get("iconPosition", {})
|
saved = port.properties.get("iconPosition", {})
|
||||||
position = QPointF(float(saved.get("x", default_x)), float(saved.get("y", spacing * index)))
|
position = QPointF(
|
||||||
|
float(saved.get("x", default_x)), float(saved.get("y", spacing * index))
|
||||||
|
)
|
||||||
self.scene.addItem(PortHandle(port, direction, position))
|
self.scene.addItem(PortHandle(port, direction, position))
|
||||||
|
|
||||||
def add_shape(self, kind: str) -> None:
|
def set_draw_mode(self, mode: str) -> None:
|
||||||
count = len([item for item in self.scene.items() if isinstance(item, ShapeItem)])
|
self.scene.set_mode(mode)
|
||||||
x, y = 15 + (count * 5) % 40, 15 + (count * 4) % 25
|
self.view.setDragMode(
|
||||||
element = {"type": kind, "x": x, "y": y, "width": 55, "height": 35, "fill": "#dbeafe", "stroke": "#303030", "lineWidth": 1.5, "lineStyle": "solid"}
|
self.view.DragMode.RubberBandDrag if mode == "pointer" else self.view.DragMode.NoDrag
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_shape(self, kind: str, start: QPointF, end: QPointF, text: str = "Text") -> None:
|
||||||
|
left, right = sorted((start.x(), end.x()))
|
||||||
|
top, bottom = sorted((start.y(), end.y()))
|
||||||
|
element = {
|
||||||
|
"type": kind,
|
||||||
|
"x": left,
|
||||||
|
"y": top,
|
||||||
|
"width": max(_icon_grid_size(), right - left),
|
||||||
|
"height": max(_icon_grid_size(), bottom - top),
|
||||||
|
"fill": "#dbeafe",
|
||||||
|
"stroke": "#303030",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
}
|
||||||
if kind == "circle":
|
if kind == "circle":
|
||||||
element["width"] = element["height"] = 35
|
element["width"] = element["height"] = min(element["width"], element["height"])
|
||||||
if kind == "line":
|
if kind == "line":
|
||||||
|
element["x"], element["y"] = start.x(), start.y()
|
||||||
|
element["width"], element["height"] = end.x() - start.x(), end.y() - start.y()
|
||||||
element["fill"] = "none"
|
element["fill"] = "none"
|
||||||
if kind == "rectangle":
|
if kind == "rectangle":
|
||||||
element["cornerRadius"] = 0
|
element["cornerRadius"] = 0
|
||||||
if kind == "text":
|
if kind == "text":
|
||||||
text, accepted = QInputDialog.getText(self, "Add Text", "Text:", text="Text")
|
element.update(
|
||||||
if not accepted:
|
{
|
||||||
return
|
"text": text,
|
||||||
element.update({"text": text, "fontSize": 12, "color": "#202020", "fill": "none", "lineStyle": "none"})
|
"fontSize": 12,
|
||||||
|
"color": "#202020",
|
||||||
|
"fill": "none",
|
||||||
|
"lineStyle": "none",
|
||||||
|
}
|
||||||
|
)
|
||||||
self.icon.elements.append(element)
|
self.icon.elements.append(element)
|
||||||
item = ShapeItem(element)
|
item = ShapeItem(element)
|
||||||
self.scene.addItem(item)
|
self.scene.addItem(item)
|
||||||
@@ -332,5 +488,7 @@ class IconEditorDialog(QDialog):
|
|||||||
item.element["_deleted"] = True
|
item.element["_deleted"] = True
|
||||||
|
|
||||||
def accept(self) -> None:
|
def accept(self) -> None:
|
||||||
self.icon.elements = [element for element in self.icon.elements if not element.pop("_deleted", False)]
|
self.icon.elements = [
|
||||||
|
element for element in self.icon.elements if not element.pop("_deleted", False)
|
||||||
|
]
|
||||||
super().accept()
|
super().accept()
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ def icon_bounds(icon: Icon) -> QRectF:
|
|||||||
rect = QRectF(
|
rect = QRectF(
|
||||||
float(element.get("x", 0)),
|
float(element.get("x", 0)),
|
||||||
float(element.get("y", 0)),
|
float(element.get("y", 0)),
|
||||||
max(0.0, float(element.get("width", 0))),
|
float(element.get("width", 0)),
|
||||||
max(0.0, float(element.get("height", 0))),
|
float(element.get("height", 0)),
|
||||||
)
|
).normalized()
|
||||||
bounds = rect if bounds.isNull() else bounds.united(rect)
|
bounds = rect if bounds.isNull() else bounds.united(rect)
|
||||||
return bounds if not bounds.isNull() else QRectF(32, 32, 64, 64)
|
return bounds if not bounds.isNull() else QRectF(32, 32, 64, 64)
|
||||||
|
|
||||||
|
|
||||||
def _pen(element: dict) -> QPen:
|
def shape_pen(element: dict) -> QPen:
|
||||||
styles = {
|
styles = {
|
||||||
"solid": Qt.PenStyle.SolidLine,
|
"solid": Qt.PenStyle.SolidLine,
|
||||||
"dash": Qt.PenStyle.DashLine,
|
"dash": Qt.PenStyle.DashLine,
|
||||||
@@ -51,12 +51,16 @@ def paint_icon(
|
|||||||
for element in icon.elements:
|
for element in icon.elements:
|
||||||
kind = element.get("type", "rectangle")
|
kind = element.get("type", "rectangle")
|
||||||
rect = QRectF(
|
rect = QRectF(
|
||||||
float(element.get("x", 0)), float(element.get("y", 0)),
|
float(element.get("x", 0)),
|
||||||
float(element.get("width", 20)), float(element.get("height", 20)),
|
float(element.get("y", 0)),
|
||||||
|
float(element.get("width", 20)),
|
||||||
|
float(element.get("height", 20)),
|
||||||
)
|
)
|
||||||
painter.setPen(_pen(element))
|
painter.setPen(shape_pen(element))
|
||||||
fill = element.get("fill", "#ffffff")
|
fill = element.get("fill", "#ffffff")
|
||||||
painter.setBrush(Qt.BrushStyle.NoBrush if fill in {"none", "transparent", ""} else QColor(fill))
|
painter.setBrush(
|
||||||
|
Qt.BrushStyle.NoBrush if fill in {"none", "transparent", ""} else QColor(fill)
|
||||||
|
)
|
||||||
if kind == "rectangle":
|
if kind == "rectangle":
|
||||||
radius = float(element.get("cornerRadius", 0))
|
radius = float(element.get("cornerRadius", 0))
|
||||||
painter.drawRoundedRect(rect, radius, radius)
|
painter.drawRoundedRect(rect, radius, radius)
|
||||||
@@ -65,7 +69,11 @@ def paint_icon(
|
|||||||
elif kind == "line":
|
elif kind == "line":
|
||||||
painter.drawLine(rect.topLeft(), rect.bottomRight())
|
painter.drawLine(rect.topLeft(), rect.bottomRight())
|
||||||
elif kind == "triangle":
|
elif kind == "triangle":
|
||||||
painter.drawPolygon(QPolygonF([QPointF(rect.center().x(), rect.top()), rect.bottomRight(), rect.bottomLeft()]))
|
painter.drawPolygon(
|
||||||
|
QPolygonF(
|
||||||
|
[QPointF(rect.center().x(), rect.top()), rect.bottomRight(), rect.bottomLeft()]
|
||||||
|
)
|
||||||
|
)
|
||||||
elif kind == "text":
|
elif kind == "text":
|
||||||
painter.setPen(QColor(element.get("color", element.get("stroke", "#202020"))))
|
painter.setPen(QColor(element.get("color", element.get("stroke", "#202020"))))
|
||||||
font = QFont()
|
font = QFont()
|
||||||
|
|||||||
@@ -24,15 +24,18 @@ from PySide6.QtWidgets import (
|
|||||||
QGraphicsView,
|
QGraphicsView,
|
||||||
QApplication,
|
QApplication,
|
||||||
QMenu,
|
QMenu,
|
||||||
|
QInputDialog,
|
||||||
QStyleOptionGraphicsItem,
|
QStyleOptionGraphicsItem,
|
||||||
QToolTip,
|
QToolTip,
|
||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from bedit.core.model import Component, Connection, Endpoint, Port
|
from bedit.core.model import Annotation, Component, Connection, Endpoint, Port
|
||||||
from bedit.gui.controllers.document import DocumentController
|
from bedit.gui.controllers.document import DocumentController
|
||||||
from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE
|
from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE
|
||||||
from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon
|
from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon
|
||||||
|
from bedit.gui.graphics.icon_editor import ShapeOptionsDialog
|
||||||
|
from bedit.gui.graphics.icon_renderer import shape_pen
|
||||||
from bedit.gui.graphics.connection_styles import ConnectionStyle, connection_style
|
from bedit.gui.graphics.connection_styles import ConnectionStyle, connection_style
|
||||||
from bedit.gui.preferences import application_settings
|
from bedit.gui.preferences import application_settings
|
||||||
|
|
||||||
@@ -94,7 +97,9 @@ class ComponentGraphicsItem(QGraphicsObject):
|
|||||||
position = port.properties.get("iconPosition", {})
|
position = port.properties.get("iconPosition", {})
|
||||||
item.setPos(
|
item.setPos(
|
||||||
float(position.get("x", x)) * self.WIDTH / self.component.icon.width,
|
float(position.get("x", x)) * self.WIDTH / self.component.icon.width,
|
||||||
float(position.get("y", spacing * index)) * self.HEIGHT / self.component.icon.height,
|
float(position.get("y", spacing * index))
|
||||||
|
* self.HEIGHT
|
||||||
|
/ self.component.icon.height,
|
||||||
)
|
)
|
||||||
result[port.id] = item
|
result[port.id] = item
|
||||||
return result
|
return result
|
||||||
@@ -212,6 +217,9 @@ class InterfaceTerminalItem(QGraphicsObject):
|
|||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
|
if self.index < 0:
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
options_action = menu.addAction(f"{self.direction.title()} Options…")
|
options_action = menu.addAction(f"{self.direction.title()} Options…")
|
||||||
if menu.exec(event.screenPos()) is options_action:
|
if menu.exec(event.screenPos()) is options_action:
|
||||||
@@ -253,13 +261,24 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
|||||||
self.end_direction = QPointF(1, 0)
|
self.end_direction = QPointF(1, 0)
|
||||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
||||||
self._update_pen()
|
self._update_pen()
|
||||||
self.setZValue(-1)
|
self.setZValue(0)
|
||||||
self.setToolTip(name or "Connection")
|
self.setToolTip(name or "Connection")
|
||||||
|
self.handles: list[WaypointHandle] = []
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
|
if not self.isSelected():
|
||||||
|
self.scene().clearSelection()
|
||||||
|
self.setSelected(True)
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
|
add_node_action = menu.addAction("Add Node")
|
||||||
|
menu.addSeparator()
|
||||||
options_action = menu.addAction("Connection Options…")
|
options_action = menu.addAction("Connection Options…")
|
||||||
if menu.exec(event.screenPos()) is options_action:
|
selected = menu.exec(event.screenPos())
|
||||||
|
if selected is add_node_action:
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.add_route_node("connection", self.connection_id, event.scenePos())
|
||||||
|
elif selected is options_action:
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
if isinstance(scene, GraphScene):
|
if isinstance(scene, GraphScene):
|
||||||
scene.connectionOptionsRequested.emit(self.connection_id)
|
scene.connectionOptionsRequested.emit(self.connection_id)
|
||||||
@@ -269,6 +288,7 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
|||||||
result = super().itemChange(change, value)
|
result = super().itemChange(change, value)
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
||||||
self._update_pen()
|
self._update_pen()
|
||||||
|
self._update_handles()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _update_pen(self) -> None:
|
def _update_pen(self) -> None:
|
||||||
@@ -291,6 +311,24 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
|||||||
self.start_direction = points[1] - points[0]
|
self.start_direction = points[1] - points[0]
|
||||||
self.end_direction = points[-1] - points[-2]
|
self.end_direction = points[-1] - points[-2]
|
||||||
|
|
||||||
|
def set_waypoints(self, points: list[QPointF]) -> None:
|
||||||
|
for handle in self.handles:
|
||||||
|
if handle.scene() is not None:
|
||||||
|
handle.scene().removeItem(handle)
|
||||||
|
self.handles = [
|
||||||
|
WaypointHandle("connection", self.connection_id, index, point)
|
||||||
|
for index, point in enumerate(points)
|
||||||
|
]
|
||||||
|
scene = self.scene()
|
||||||
|
if scene is not None:
|
||||||
|
for handle in self.handles:
|
||||||
|
scene.addItem(handle)
|
||||||
|
self._update_handles()
|
||||||
|
|
||||||
|
def _update_handles(self) -> None:
|
||||||
|
for handle in self.handles:
|
||||||
|
handle.setVisible(self.isSelected())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _arrow(end: QPointF, direction: QPointF, size: float) -> QPolygonF:
|
def _arrow(end: QPointF, direction: QPointF, size: float) -> QPolygonF:
|
||||||
length = max(0.001, (direction.x() ** 2 + direction.y() ** 2) ** 0.5)
|
length = max(0.001, (direction.x() ** 2 + direction.y() ** 2) ** 0.5)
|
||||||
@@ -306,7 +344,211 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
|||||||
if self.style.arrow_at_target:
|
if self.style.arrow_at_target:
|
||||||
painter.drawPolygon(self._arrow(self.end, self.end_direction, self.style.arrow_size))
|
painter.drawPolygon(self._arrow(self.end, self.end_direction, self.style.arrow_size))
|
||||||
if self.style.arrow_at_source:
|
if self.style.arrow_at_source:
|
||||||
painter.drawPolygon(self._arrow(self.start, -self.start_direction, self.style.arrow_size))
|
painter.drawPolygon(
|
||||||
|
self._arrow(self.start, -self.start_direction, self.style.arrow_size)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WaypointHandle(QGraphicsEllipseItem):
|
||||||
|
def __init__(self, item_kind: str, item_id: str, index: int, position: QPointF) -> None:
|
||||||
|
super().__init__(-5, -5, 10, 10)
|
||||||
|
self.item_kind, self.item_id, self.index = item_kind, item_id, index
|
||||||
|
self.setPos(position)
|
||||||
|
self.setBrush(QColor("#ffffff"))
|
||||||
|
self.setPen(QPen(QColor("#2563eb"), 1.5))
|
||||||
|
self.setZValue(1000)
|
||||||
|
self.setFlags(
|
||||||
|
QGraphicsItem.GraphicsItemFlag.ItemIsMovable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
|
)
|
||||||
|
self.setCursor(Qt.CursorShape.SizeAllCursor)
|
||||||
|
|
||||||
|
def itemChange(self, change, value): # noqa: N802
|
||||||
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
|
snapped = _snapped(value)
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.preview_route_node(self.item_kind, self.item_id, self.index, snapped)
|
||||||
|
return snapped
|
||||||
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.move_route_node(self.item_kind, self.item_id, self.index, self.pos())
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
|
menu = QMenu()
|
||||||
|
delete_action = menu.addAction("Delete Node")
|
||||||
|
if menu.exec(event.screenPos()) is delete_action:
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.delete_route_node(self.item_kind, self.item_id, self.index)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class AnnotationGraphicsItem(QGraphicsObject):
|
||||||
|
def __init__(self, annotation: Annotation, controller: DocumentController) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.annotation = annotation
|
||||||
|
self.annotation_id = annotation.id
|
||||||
|
self.controller = controller
|
||||||
|
self.drag_start = QPointF(annotation.x, annotation.y)
|
||||||
|
self.setPos(annotation.x, annotation.y)
|
||||||
|
self.setZValue(annotation.layer)
|
||||||
|
self.setFlags(
|
||||||
|
QGraphicsItem.GraphicsItemFlag.ItemIsMovable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||||
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
|
)
|
||||||
|
|
||||||
|
def boundingRect(self) -> QRectF: # noqa: N802
|
||||||
|
width, height = self.annotation.width, self.annotation.height
|
||||||
|
return QRectF(min(0, width), min(0, height), abs(width), abs(height)).adjusted(-4, -4, 4, 4)
|
||||||
|
|
||||||
|
def paint(self, painter: QPainter, option, widget=None) -> None:
|
||||||
|
del option, widget
|
||||||
|
rect = QRectF(0, 0, self.annotation.width, self.annotation.height).normalized()
|
||||||
|
painter.setPen(shape_pen(self.annotation.properties))
|
||||||
|
fill = self.annotation.properties.get("fill", "none")
|
||||||
|
painter.setBrush(
|
||||||
|
Qt.BrushStyle.NoBrush if fill in {"none", "transparent", ""} else QColor(fill)
|
||||||
|
)
|
||||||
|
if self.annotation.kind == "box":
|
||||||
|
radius = float(self.annotation.properties.get("cornerRadius", 0))
|
||||||
|
painter.drawRoundedRect(rect, radius, radius)
|
||||||
|
else:
|
||||||
|
painter.setPen(
|
||||||
|
QColor(
|
||||||
|
self.annotation.properties.get(
|
||||||
|
"color", self.annotation.properties.get("stroke", "#202020")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
font = painter.font()
|
||||||
|
font.setPointSizeF(float(self.annotation.properties.get("fontSize", 12)))
|
||||||
|
painter.setFont(font)
|
||||||
|
painter.drawText(
|
||||||
|
rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop, self.annotation.text
|
||||||
|
)
|
||||||
|
if self.isSelected():
|
||||||
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
|
painter.setPen(QPen(QColor("#2563eb"), 1, Qt.PenStyle.DashLine))
|
||||||
|
painter.drawRect(self.boundingRect())
|
||||||
|
|
||||||
|
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)
|
||||||
|
new_pos = _snapped(self.pos())
|
||||||
|
self.setPos(new_pos)
|
||||||
|
old = {
|
||||||
|
"x": self.drag_start.x(),
|
||||||
|
"y": self.drag_start.y(),
|
||||||
|
"width": self.annotation.width,
|
||||||
|
"height": self.annotation.height,
|
||||||
|
}
|
||||||
|
new = {**old, "x": new_pos.x(), "y": new_pos.y()}
|
||||||
|
self.controller.set_annotation_geometry(self.annotation_id, old, new)
|
||||||
|
|
||||||
|
def itemChange(self, change, value): # noqa: N802
|
||||||
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
|
return _snapped(value)
|
||||||
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
|
scene = self.scene()
|
||||||
|
if not isinstance(scene, GraphScene):
|
||||||
|
return
|
||||||
|
if not self.isSelected():
|
||||||
|
scene.clearSelection()
|
||||||
|
self.setSelected(True)
|
||||||
|
menu = QMenu()
|
||||||
|
options_action = menu.addAction("Shape Options…")
|
||||||
|
menu.addSeparator()
|
||||||
|
actions = {
|
||||||
|
menu.addAction("Bring Forward"): "forward",
|
||||||
|
menu.addAction("Send Backward"): "backward",
|
||||||
|
menu.addAction("Bring to Front"): "front",
|
||||||
|
menu.addAction("Send to Back"): "back",
|
||||||
|
}
|
||||||
|
selected = menu.exec(event.screenPos())
|
||||||
|
if selected is options_action:
|
||||||
|
scene.edit_annotation_options(self.annotation_id)
|
||||||
|
elif selected in actions:
|
||||||
|
scene.reorder_selected_annotations(actions[selected])
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class LineAnnotationGraphicsItem(QGraphicsPathItem):
|
||||||
|
def __init__(self, annotation: Annotation) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.annotation = annotation
|
||||||
|
self.annotation_id = annotation.id
|
||||||
|
self.handles: list[WaypointHandle] = []
|
||||||
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
||||||
|
self.setZValue(annotation.layer)
|
||||||
|
self._update_pen()
|
||||||
|
|
||||||
|
def _update_pen(self) -> None:
|
||||||
|
pen = shape_pen(self.annotation.properties)
|
||||||
|
if self.isSelected():
|
||||||
|
pen.setColor(QColor("#2563eb"))
|
||||||
|
pen.setWidthF(max(3.0, pen.widthF()))
|
||||||
|
self.setPen(pen)
|
||||||
|
|
||||||
|
def set_waypoints(self, points: list[QPointF], start: QPointF, end: QPointF) -> None:
|
||||||
|
for handle in self.handles:
|
||||||
|
if handle.scene() is not None:
|
||||||
|
handle.scene().removeItem(handle)
|
||||||
|
self.handles = [WaypointHandle("annotation", self.annotation_id, -1, start)]
|
||||||
|
self.handles.extend(
|
||||||
|
WaypointHandle("annotation", self.annotation_id, index, point)
|
||||||
|
for index, point in enumerate(points)
|
||||||
|
)
|
||||||
|
self.handles.append(WaypointHandle("annotation", self.annotation_id, -2, end))
|
||||||
|
scene = self.scene()
|
||||||
|
if scene is not None:
|
||||||
|
for handle in self.handles:
|
||||||
|
scene.addItem(handle)
|
||||||
|
for handle in self.handles:
|
||||||
|
handle.setVisible(self.isSelected())
|
||||||
|
|
||||||
|
def itemChange(self, change, value): # noqa: N802
|
||||||
|
result = super().itemChange(change, value)
|
||||||
|
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
||||||
|
for handle in self.handles:
|
||||||
|
handle.setVisible(self.isSelected())
|
||||||
|
self._update_pen()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
|
if not self.isSelected():
|
||||||
|
self.scene().clearSelection()
|
||||||
|
self.setSelected(True)
|
||||||
|
menu = QMenu()
|
||||||
|
add_action = menu.addAction("Add Node")
|
||||||
|
options_action = menu.addAction("Shape Options…")
|
||||||
|
menu.addSeparator()
|
||||||
|
actions = {
|
||||||
|
menu.addAction("Bring Forward"): "forward",
|
||||||
|
menu.addAction("Send Backward"): "backward",
|
||||||
|
menu.addAction("Bring to Front"): "front",
|
||||||
|
menu.addAction("Send to Back"): "back",
|
||||||
|
}
|
||||||
|
selected = menu.exec(event.screenPos())
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
if selected is add_action:
|
||||||
|
scene.add_route_node("annotation", self.annotation_id, event.scenePos())
|
||||||
|
elif selected is options_action:
|
||||||
|
scene.edit_annotation_options(self.annotation_id)
|
||||||
|
elif selected in actions:
|
||||||
|
scene.reorder_selected_annotations(actions[selected])
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class GraphScene(QGraphicsScene):
|
class GraphScene(QGraphicsScene):
|
||||||
@@ -322,17 +564,21 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.input_items: dict[str, InterfaceTerminalItem] = {}
|
self.input_items: dict[str, InterfaceTerminalItem] = {}
|
||||||
self.output_items: dict[str, InterfaceTerminalItem] = {}
|
self.output_items: dict[str, InterfaceTerminalItem] = {}
|
||||||
self.connection_items: dict[str, ConnectionGraphicsItem] = {}
|
self.connection_items: dict[str, ConnectionGraphicsItem] = {}
|
||||||
|
self.annotation_items: dict[str, QGraphicsItem] = {}
|
||||||
self.pending_source: ConnectionPortItem | None = None
|
self.pending_source: ConnectionPortItem | None = None
|
||||||
self.pending_waypoints: list[QPointF] = []
|
self.pending_waypoints: list[QPointF] = []
|
||||||
self.pending_preview: QGraphicsPathItem | None = None
|
self.pending_preview: QGraphicsPathItem | None = None
|
||||||
self.interaction_mode = "pointer"
|
self.interaction_mode = "pointer"
|
||||||
self.connection_routing = "spline"
|
self.connection_routing = "angled"
|
||||||
|
self.drawing_start: QPointF | None = None
|
||||||
|
self.drawing_waypoints: list[QPointF] = []
|
||||||
self.setSceneRect(-2000, -2000, 4000, 4000)
|
self.setSceneRect(-2000, -2000, 4000, 4000)
|
||||||
|
|
||||||
controller.documentReset.connect(self.rebuild)
|
controller.documentReset.connect(self.rebuild)
|
||||||
controller.activeGraphChanged.connect(self.rebuild)
|
controller.activeGraphChanged.connect(self.rebuild)
|
||||||
controller.componentMoved.connect(self.set_component_position)
|
controller.componentMoved.connect(self.set_component_position)
|
||||||
controller.componentRotated.connect(self.set_component_rotation)
|
controller.componentRotated.connect(self.set_component_rotation)
|
||||||
|
controller.graphItemChanged.connect(self.refresh_graph_item)
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
def rebuild(self) -> None:
|
def rebuild(self) -> None:
|
||||||
@@ -341,6 +587,7 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.input_items.clear()
|
self.input_items.clear()
|
||||||
self.output_items.clear()
|
self.output_items.clear()
|
||||||
self.connection_items.clear()
|
self.connection_items.clear()
|
||||||
|
self.annotation_items.clear()
|
||||||
self.pending_source = None
|
self.pending_source = None
|
||||||
self.pending_waypoints.clear()
|
self.pending_waypoints.clear()
|
||||||
self.pending_preview = None
|
self.pending_preview = None
|
||||||
@@ -371,6 +618,16 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.addItem(item)
|
self.addItem(item)
|
||||||
self.connection_items[connection.id] = item
|
self.connection_items[connection.id] = item
|
||||||
self.update_connection(connection.id)
|
self.update_connection(connection.id)
|
||||||
|
for annotation in owner.graph.annotations.values():
|
||||||
|
if annotation.kind == "line":
|
||||||
|
item = LineAnnotationGraphicsItem(annotation)
|
||||||
|
self.addItem(item)
|
||||||
|
self.annotation_items[annotation.id] = item
|
||||||
|
self.update_annotation(annotation.id)
|
||||||
|
else:
|
||||||
|
item = AnnotationGraphicsItem(annotation, self.controller)
|
||||||
|
self.addItem(item)
|
||||||
|
self.annotation_items[annotation.id] = item
|
||||||
|
|
||||||
def set_component_position(self, component_id: str, position: QPointF) -> None:
|
def set_component_position(self, component_id: str, position: QPointF) -> None:
|
||||||
item = self.component_items.get(component_id)
|
item = self.component_items.get(component_id)
|
||||||
@@ -383,6 +640,52 @@ class GraphScene(QGraphicsScene):
|
|||||||
item.setRotation(rotation)
|
item.setRotation(rotation)
|
||||||
self.update_connections_for_block(component_id)
|
self.update_connections_for_block(component_id)
|
||||||
|
|
||||||
|
def refresh_graph_item(self, item_kind: str, item_id: str) -> None:
|
||||||
|
if item_kind == "connection":
|
||||||
|
self.update_connection(item_id)
|
||||||
|
return
|
||||||
|
annotation = self.controller.active_graph.annotations.get(item_id)
|
||||||
|
graphics = self.annotation_items.get(item_id)
|
||||||
|
if annotation is None or graphics is None:
|
||||||
|
return
|
||||||
|
graphics.setZValue(annotation.layer)
|
||||||
|
if isinstance(graphics, LineAnnotationGraphicsItem):
|
||||||
|
graphics._update_pen()
|
||||||
|
self.update_annotation(item_id)
|
||||||
|
elif isinstance(graphics, AnnotationGraphicsItem):
|
||||||
|
graphics.prepareGeometryChange()
|
||||||
|
graphics.setPos(annotation.x, annotation.y)
|
||||||
|
graphics.update()
|
||||||
|
|
||||||
|
def edit_annotation_options(self, annotation_id: str) -> None:
|
||||||
|
annotation = self.controller.active_graph.annotations.get(annotation_id)
|
||||||
|
if annotation is None:
|
||||||
|
return
|
||||||
|
element = {
|
||||||
|
"type": {"box": "rectangle", "line": "line", "text": "text"}[annotation.kind],
|
||||||
|
"width": abs(annotation.width),
|
||||||
|
"height": abs(annotation.height),
|
||||||
|
"text": annotation.text,
|
||||||
|
**annotation.properties,
|
||||||
|
}
|
||||||
|
dialog = ShapeOptionsDialog(element)
|
||||||
|
if dialog.exec() != dialog.DialogCode.Accepted:
|
||||||
|
return
|
||||||
|
values = annotation.to_dict()
|
||||||
|
values["size"] = {
|
||||||
|
"width": dialog.element["width"] * (-1 if annotation.width < 0 else 1),
|
||||||
|
"height": dialog.element["height"] * (-1 if annotation.height < 0 else 1),
|
||||||
|
}
|
||||||
|
values["text"] = str(dialog.element.get("text", annotation.text))
|
||||||
|
values["properties"].update(
|
||||||
|
{
|
||||||
|
key: value
|
||||||
|
for key, value in dialog.element.items()
|
||||||
|
if key not in {"type", "x", "y", "width", "height", "text"}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.controller.edit_annotation(annotation_id, values)
|
||||||
|
|
||||||
def update_connections_for_block(self, component_id: str) -> None:
|
def update_connections_for_block(self, component_id: str) -> None:
|
||||||
for connection in self.controller.active_graph.connections.values():
|
for connection in self.controller.active_graph.connections.values():
|
||||||
if component_id in (connection.source.block, connection.target.block):
|
if component_id in (connection.source.block, connection.target.block):
|
||||||
@@ -418,7 +721,7 @@ class GraphScene(QGraphicsScene):
|
|||||||
if source is None or target is None:
|
if source is None or target is None:
|
||||||
return
|
return
|
||||||
start, end = source.scenePos(), target.scenePos()
|
start, end = source.scenePos(), target.scenePos()
|
||||||
routing = str(connection.properties.get("routing", "spline"))
|
routing = str(connection.properties.get("routing", "angled"))
|
||||||
waypoints = [
|
waypoints = [
|
||||||
QPointF(float(point["x"]), float(point["y"]))
|
QPointF(float(point["x"]), float(point["y"]))
|
||||||
for point in connection.properties.get("waypoints", [])
|
for point in connection.properties.get("waypoints", [])
|
||||||
@@ -426,6 +729,35 @@ class GraphScene(QGraphicsScene):
|
|||||||
]
|
]
|
||||||
path, direction_points = self._route_path(start, end, routing, waypoints)
|
path, direction_points = self._route_path(start, end, routing, waypoints)
|
||||||
graphics.set_connection_path(path, direction_points)
|
graphics.set_connection_path(path, direction_points)
|
||||||
|
graphics.set_waypoints(waypoints)
|
||||||
|
|
||||||
|
def update_annotation(self, annotation_id: str) -> None:
|
||||||
|
annotation = self.controller.active_graph.annotations.get(annotation_id)
|
||||||
|
graphics = self.annotation_items.get(annotation_id)
|
||||||
|
if annotation is None or not isinstance(graphics, LineAnnotationGraphicsItem):
|
||||||
|
return
|
||||||
|
start = QPointF(annotation.x, annotation.y)
|
||||||
|
end = start + QPointF(annotation.width, annotation.height)
|
||||||
|
waypoints = [
|
||||||
|
QPointF(float(p["x"]), float(p["y"]))
|
||||||
|
for p in annotation.properties.get("waypoints", [])
|
||||||
|
]
|
||||||
|
routing = str(annotation.properties.get("routing", "angled"))
|
||||||
|
path, points = self._route_path(start, end, routing, waypoints)
|
||||||
|
graphics.setPath(path)
|
||||||
|
graphics.set_waypoints(waypoints, start, end)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _orthogonal_points(start: QPointF, end: QPointF, waypoints: list[QPointF]) -> list[QPointF]:
|
||||||
|
points = [start]
|
||||||
|
for waypoint in [*waypoints, end]:
|
||||||
|
previous = points[-1]
|
||||||
|
if previous.x() != waypoint.x() and previous.y() != waypoint.y():
|
||||||
|
elbow = QPointF(waypoint.x(), previous.y())
|
||||||
|
points.append(elbow)
|
||||||
|
if waypoint != points[-1]:
|
||||||
|
points.append(waypoint)
|
||||||
|
return points
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _route_path(
|
def _route_path(
|
||||||
@@ -437,16 +769,23 @@ class GraphScene(QGraphicsScene):
|
|||||||
waypoints = waypoints or []
|
waypoints = waypoints or []
|
||||||
path = QPainterPath(start)
|
path = QPainterPath(start)
|
||||||
if routing == "angled":
|
if routing == "angled":
|
||||||
points = [start, *waypoints, end]
|
points = GraphScene._orthogonal_points(start, end, waypoints)
|
||||||
for point in points[1:]:
|
for point in points[1:]:
|
||||||
path.lineTo(point)
|
path.lineTo(point)
|
||||||
return path, points
|
return path, points
|
||||||
if routing == "direct":
|
if routing == "direct":
|
||||||
path.lineTo(end)
|
path.lineTo(end)
|
||||||
return path, [start, end]
|
return path, [start, end]
|
||||||
|
if waypoints:
|
||||||
|
points = [start, *waypoints, end]
|
||||||
|
for index, point in enumerate(points[1:-1], start=1):
|
||||||
|
following = points[index + 1]
|
||||||
|
midpoint = QPointF((point.x() + following.x()) / 2, (point.y() + following.y()) / 2)
|
||||||
|
path.quadTo(point, midpoint)
|
||||||
|
path.quadTo(points[-2], end)
|
||||||
|
return path, points
|
||||||
distance = max(50.0, abs(end.x() - start.x()) * 0.5)
|
distance = max(50.0, abs(end.x() - start.x()) * 0.5)
|
||||||
first_control = start + QPointF(distance, 0)
|
first_control, second_control = start + QPointF(distance, 0), end - QPointF(distance, 0)
|
||||||
second_control = end - QPointF(distance, 0)
|
|
||||||
path.cubicTo(first_control, second_control, end)
|
path.cubicTo(first_control, second_control, end)
|
||||||
return path, [start, first_control, second_control, end]
|
return path, [start, first_control, second_control, end]
|
||||||
|
|
||||||
@@ -454,12 +793,16 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.interaction_mode = mode
|
self.interaction_mode = mode
|
||||||
if mode != "connect":
|
if mode != "connect":
|
||||||
self._clear_pending_source()
|
self._clear_pending_source()
|
||||||
|
self._clear_drawing()
|
||||||
|
|
||||||
def set_connection_routing(self, routing: str) -> None:
|
def set_connection_routing(self, routing: str) -> None:
|
||||||
self.connection_routing = routing
|
self.connection_routing = routing
|
||||||
self._clear_pending_source()
|
self._clear_pending_source()
|
||||||
|
|
||||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.interaction_mode in {"box", "text", "line"}:
|
||||||
|
self._drawing_press(event)
|
||||||
|
return
|
||||||
if self.interaction_mode != "connect":
|
if self.interaction_mode != "connect":
|
||||||
self._clear_pending_source()
|
self._clear_pending_source()
|
||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
@@ -483,7 +826,15 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.pending_source.endpoint,
|
self.pending_source.endpoint,
|
||||||
item.endpoint,
|
item.endpoint,
|
||||||
routing=self.connection_routing,
|
routing=self.connection_routing,
|
||||||
waypoints=self.pending_waypoints,
|
waypoints=(
|
||||||
|
self._completed_angled_waypoints(
|
||||||
|
self.pending_source.scenePos(),
|
||||||
|
item.scenePos(),
|
||||||
|
self.pending_waypoints,
|
||||||
|
)
|
||||||
|
if self.connection_routing == "angled"
|
||||||
|
else self.pending_waypoints
|
||||||
|
),
|
||||||
)
|
)
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
QToolTip.showText(event.screenPos(), str(error))
|
QToolTip.showText(event.screenPos(), str(error))
|
||||||
@@ -500,8 +851,222 @@ class GraphScene(QGraphicsScene):
|
|||||||
self._update_preview(event.scenePos())
|
self._update_preview(event.scenePos())
|
||||||
event.accept()
|
event.accept()
|
||||||
return
|
return
|
||||||
|
if self.interaction_mode in {"box", "line"} and self.drawing_start is not None:
|
||||||
|
self._update_drawing_preview(event.scenePos())
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
super().mouseMoveEvent(event)
|
super().mouseMoveEvent(event)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.interaction_mode == "box" and self.drawing_start is not None:
|
||||||
|
end = _snapped(event.scenePos())
|
||||||
|
if end != self.drawing_start:
|
||||||
|
self.controller.add_annotation("box", self.drawing_start, end)
|
||||||
|
self._clear_drawing()
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
|
def _drawing_press(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||||
|
if event.button() == Qt.MouseButton.RightButton:
|
||||||
|
self._clear_drawing()
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
point = _snapped(event.scenePos())
|
||||||
|
if self.interaction_mode == "text":
|
||||||
|
text_value, accepted = QInputDialog.getText(None, "Add Text", "Text:")
|
||||||
|
if accepted and text_value:
|
||||||
|
self.controller.add_annotation(
|
||||||
|
"text", point, point + QPointF(160, 48), text=text_value
|
||||||
|
)
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
if self.drawing_start is None:
|
||||||
|
self.drawing_start = point
|
||||||
|
self.drawing_waypoints = []
|
||||||
|
self._create_preview()
|
||||||
|
elif self.interaction_mode == "line" and self.connection_routing == "angled":
|
||||||
|
self.drawing_waypoints.append(point)
|
||||||
|
self._update_drawing_preview(point)
|
||||||
|
elif self.interaction_mode == "line":
|
||||||
|
self.controller.add_annotation(
|
||||||
|
"line", self.drawing_start, point, routing=self.connection_routing
|
||||||
|
)
|
||||||
|
self._clear_drawing()
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.interaction_mode == "line" and self.drawing_start is not None:
|
||||||
|
end = _snapped(event.scenePos())
|
||||||
|
waypoints = (
|
||||||
|
self.drawing_waypoints[:-1]
|
||||||
|
if self.drawing_waypoints and self.drawing_waypoints[-1] == end
|
||||||
|
else self.drawing_waypoints
|
||||||
|
)
|
||||||
|
completed = self._completed_angled_waypoints(self.drawing_start, end, waypoints)
|
||||||
|
self.controller.add_annotation(
|
||||||
|
"line", self.drawing_start, end, routing="angled", waypoints=completed
|
||||||
|
)
|
||||||
|
self._clear_drawing()
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
super().mouseDoubleClickEvent(event)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _completed_angled_waypoints(
|
||||||
|
start: QPointF, end: QPointF, waypoints: list[QPointF]
|
||||||
|
) -> list[QPointF]:
|
||||||
|
return GraphScene._orthogonal_points(start, end, waypoints)[1:-1]
|
||||||
|
|
||||||
|
def _update_drawing_preview(self, cursor: QPointF) -> None:
|
||||||
|
if self.drawing_start is None or self.pending_preview is None:
|
||||||
|
return
|
||||||
|
end = _snapped(cursor)
|
||||||
|
if self.interaction_mode == "box":
|
||||||
|
path = QPainterPath()
|
||||||
|
path.addRect(QRectF(self.drawing_start, end).normalized())
|
||||||
|
else:
|
||||||
|
path, _ = self._route_path(
|
||||||
|
self.drawing_start, end, self.connection_routing, self.drawing_waypoints
|
||||||
|
)
|
||||||
|
self.pending_preview.setPath(path)
|
||||||
|
|
||||||
|
def _clear_drawing(self) -> None:
|
||||||
|
if self.pending_preview is not None and self.pending_source is None:
|
||||||
|
self.removeItem(self.pending_preview)
|
||||||
|
self.pending_preview = None
|
||||||
|
self.drawing_start = None
|
||||||
|
self.drawing_waypoints.clear()
|
||||||
|
|
||||||
|
def _route_values(self, item_kind: str, item_id: str):
|
||||||
|
item = (
|
||||||
|
self.controller.active_graph.connections
|
||||||
|
if item_kind == "connection"
|
||||||
|
else self.controller.active_graph.annotations
|
||||||
|
).get(item_id)
|
||||||
|
if item is None:
|
||||||
|
return None, []
|
||||||
|
points = [
|
||||||
|
QPointF(float(p["x"]), float(p["y"])) for p in item.properties.get("waypoints", [])
|
||||||
|
]
|
||||||
|
return item, points
|
||||||
|
|
||||||
|
def add_route_node(self, item_kind: str, item_id: str, position: QPointF) -> None:
|
||||||
|
item, points = self._route_values(item_kind, item_id)
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
snapped = _snapped(position)
|
||||||
|
if item_kind == "annotation":
|
||||||
|
start = QPointF(item.x, item.y)
|
||||||
|
end = QPointF(item.x + item.width, item.y + item.height)
|
||||||
|
else:
|
||||||
|
graphics = self.connection_items[item_id]
|
||||||
|
start, end = graphics.start, graphics.end
|
||||||
|
anchors = [start, *points, end]
|
||||||
|
|
||||||
|
def segment_distance(point: QPointF, first: QPointF, second: QPointF) -> float:
|
||||||
|
delta = second - first
|
||||||
|
length_squared = delta.x() ** 2 + delta.y() ** 2
|
||||||
|
if length_squared == 0:
|
||||||
|
return (point - first).manhattanLength()
|
||||||
|
ratio = max(
|
||||||
|
0.0,
|
||||||
|
min(
|
||||||
|
1.0,
|
||||||
|
((point.x() - first.x()) * delta.x() + (point.y() - first.y()) * delta.y())
|
||||||
|
/ length_squared,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
nearest = first + delta * ratio
|
||||||
|
return (point.x() - nearest.x()) ** 2 + (point.y() - nearest.y()) ** 2
|
||||||
|
|
||||||
|
insertion = min(
|
||||||
|
range(len(anchors) - 1),
|
||||||
|
key=lambda index: segment_distance(snapped, anchors[index], anchors[index + 1]),
|
||||||
|
)
|
||||||
|
points.insert(insertion, snapped)
|
||||||
|
routing = "angled" if item.properties.get("routing") == "direct" else None
|
||||||
|
self.controller.set_route_waypoints(item_kind, item_id, points, routing)
|
||||||
|
|
||||||
|
def move_route_node(self, item_kind: str, item_id: str, index: int, position: QPointF) -> None:
|
||||||
|
item, points = self._route_values(item_kind, item_id)
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
position = _snapped(position)
|
||||||
|
if item_kind == "annotation" and index in {-1, -2}:
|
||||||
|
old = {
|
||||||
|
"x": item.x,
|
||||||
|
"y": item.y,
|
||||||
|
"width": item.width,
|
||||||
|
"height": item.height,
|
||||||
|
}
|
||||||
|
start = position if index == -1 else QPointF(item.x, item.y)
|
||||||
|
end = position if index == -2 else QPointF(item.x + item.width, item.y + item.height)
|
||||||
|
new = {
|
||||||
|
"x": start.x(),
|
||||||
|
"y": start.y(),
|
||||||
|
"width": end.x() - start.x(),
|
||||||
|
"height": end.y() - start.y(),
|
||||||
|
}
|
||||||
|
self.controller.set_annotation_geometry(item_id, old, new)
|
||||||
|
return
|
||||||
|
if index < len(points):
|
||||||
|
points[index] = position
|
||||||
|
self.controller.set_route_waypoints(item_kind, item_id, points)
|
||||||
|
|
||||||
|
def preview_route_node(
|
||||||
|
self, item_kind: str, item_id: str, index: int, position: QPointF
|
||||||
|
) -> None:
|
||||||
|
item, points = self._route_values(item_kind, item_id)
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
if item_kind == "annotation" and index in {-1, -2}:
|
||||||
|
graphics = self.annotation_items.get(item_id)
|
||||||
|
if not isinstance(graphics, LineAnnotationGraphicsItem):
|
||||||
|
return
|
||||||
|
start = position if index == -1 else QPointF(item.x, item.y)
|
||||||
|
end = position if index == -2 else QPointF(item.x + item.width, item.y + item.height)
|
||||||
|
path, _direction_points = self._route_path(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
str(item.properties.get("routing", "angled")),
|
||||||
|
points,
|
||||||
|
)
|
||||||
|
graphics.setPath(path)
|
||||||
|
return
|
||||||
|
if index >= len(points):
|
||||||
|
return
|
||||||
|
points[index] = position
|
||||||
|
routing = str(item.properties.get("routing", "angled"))
|
||||||
|
if item_kind == "connection":
|
||||||
|
graphics = self.connection_items.get(item_id)
|
||||||
|
if graphics is None:
|
||||||
|
return
|
||||||
|
path, direction_points = self._route_path(graphics.start, graphics.end, routing, points)
|
||||||
|
graphics.set_connection_path(path, direction_points)
|
||||||
|
return
|
||||||
|
graphics = self.annotation_items.get(item_id)
|
||||||
|
if not isinstance(graphics, LineAnnotationGraphicsItem):
|
||||||
|
return
|
||||||
|
start = QPointF(item.x, item.y)
|
||||||
|
end = QPointF(item.x + item.width, item.y + item.height)
|
||||||
|
path, _direction_points = self._route_path(start, end, routing, points)
|
||||||
|
graphics.setPath(path)
|
||||||
|
|
||||||
|
def delete_route_node(self, item_kind: str, item_id: str, index: int) -> None:
|
||||||
|
_item, points = self._route_values(item_kind, item_id)
|
||||||
|
if 0 <= index < len(points):
|
||||||
|
points.pop(index)
|
||||||
|
self.controller.set_route_waypoints(item_kind, item_id, points)
|
||||||
|
|
||||||
|
def reorder_selected_annotations(self, operation: str) -> None:
|
||||||
|
ids = {
|
||||||
|
item.annotation_id
|
||||||
|
for item in self.selectedItems()
|
||||||
|
if isinstance(item, (AnnotationGraphicsItem, LineAnnotationGraphicsItem))
|
||||||
|
}
|
||||||
|
self.controller.reorder_annotations(ids, operation)
|
||||||
|
|
||||||
def _create_preview(self) -> None:
|
def _create_preview(self) -> None:
|
||||||
self.pending_preview = QGraphicsPathItem()
|
self.pending_preview = QGraphicsPathItem()
|
||||||
self.pending_preview.setPen(QPen(QColor("#64748b"), 1.5, Qt.PenStyle.DashLine))
|
self.pending_preview.setPen(QPen(QColor("#64748b"), 1.5, Qt.PenStyle.DashLine))
|
||||||
@@ -511,9 +1076,10 @@ class GraphScene(QGraphicsScene):
|
|||||||
def _update_preview(self, cursor: QPointF) -> None:
|
def _update_preview(self, cursor: QPointF) -> None:
|
||||||
if self.pending_source is None or self.pending_preview is None:
|
if self.pending_source is None or self.pending_preview is None:
|
||||||
return
|
return
|
||||||
|
preview_cursor = _snapped(cursor) if self.connection_routing == "angled" else cursor
|
||||||
path, _points = self._route_path(
|
path, _points = self._route_path(
|
||||||
self.pending_source.scenePos(),
|
self.pending_source.scenePos(),
|
||||||
cursor,
|
preview_cursor,
|
||||||
self.connection_routing,
|
self.connection_routing,
|
||||||
self.pending_waypoints,
|
self.pending_waypoints,
|
||||||
)
|
)
|
||||||
@@ -622,6 +1188,7 @@ class GraphWorkspaceView(QGraphicsView):
|
|||||||
connections: set[str] = set()
|
connections: set[str] = set()
|
||||||
inputs: set[str] = set()
|
inputs: set[str] = set()
|
||||||
outputs: set[str] = set()
|
outputs: set[str] = set()
|
||||||
|
annotations: set[str] = set()
|
||||||
for item in self.scene().selectedItems():
|
for item in self.scene().selectedItems():
|
||||||
if isinstance(item, ComponentGraphicsItem):
|
if isinstance(item, ComponentGraphicsItem):
|
||||||
blocks.add(item.component_id)
|
blocks.add(item.component_id)
|
||||||
@@ -629,32 +1196,29 @@ class GraphWorkspaceView(QGraphicsView):
|
|||||||
connections.add(item.connection_id)
|
connections.add(item.connection_id)
|
||||||
elif isinstance(item, InterfaceTerminalItem):
|
elif isinstance(item, InterfaceTerminalItem):
|
||||||
(inputs if item.direction == "input" else outputs).add(item.port.id)
|
(inputs if item.direction == "input" else outputs).add(item.port.id)
|
||||||
|
elif isinstance(item, (AnnotationGraphicsItem, LineAnnotationGraphicsItem)):
|
||||||
|
annotations.add(item.annotation_id)
|
||||||
self.controller.delete_selection(blocks, connections, inputs, outputs)
|
self.controller.delete_selection(blocks, connections, inputs, outputs)
|
||||||
|
self.controller.delete_annotations(annotations)
|
||||||
|
|
||||||
def has_selected_components(self) -> bool:
|
def has_selected_components(self) -> bool:
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
return bool(
|
return bool(
|
||||||
scene
|
scene and any(isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems())
|
||||||
and any(isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def has_single_selected_component(self) -> bool:
|
def has_single_selected_component(self) -> bool:
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
return bool(
|
return bool(
|
||||||
scene
|
scene
|
||||||
and sum(
|
and sum(isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems()) == 1
|
||||||
isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems()
|
|
||||||
)
|
|
||||||
== 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def open_selected_component(self) -> bool:
|
def open_selected_component(self) -> bool:
|
||||||
if self.controller is None or self.scene() is None:
|
if self.controller is None or self.scene() is None:
|
||||||
return False
|
return False
|
||||||
selected = [
|
selected = [
|
||||||
item
|
item for item in self.scene().selectedItems() if isinstance(item, ComponentGraphicsItem)
|
||||||
for item in self.scene().selectedItems()
|
|
||||||
if isinstance(item, ComponentGraphicsItem)
|
|
||||||
]
|
]
|
||||||
if len(selected) != 1:
|
if len(selected) != 1:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -4,7 +4,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
from PySide6.QtCore import Qt, Slot
|
from PySide6.QtCore import Qt, Slot
|
||||||
from PySide6.QtGui import QCloseEvent
|
from PySide6.QtGui import QCloseEvent
|
||||||
from PySide6.QtWidgets import QButtonGroup, QFileDialog, QMainWindow, QMenu, QMessageBox
|
from PySide6.QtWidgets import (
|
||||||
|
QButtonGroup,
|
||||||
|
QFileDialog,
|
||||||
|
QMainWindow,
|
||||||
|
QMenu,
|
||||||
|
QMessageBox,
|
||||||
|
QTabWidget,
|
||||||
|
)
|
||||||
|
|
||||||
from bedit.core.model import Component, Port
|
from bedit.core.model import Component, Port
|
||||||
from bedit.core.serializer import JsonDocumentSerializer
|
from bedit.core.serializer import JsonDocumentSerializer
|
||||||
@@ -51,11 +58,11 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.leftDockHost.show()
|
self.ui.leftDockHost.show()
|
||||||
self.ui.panel_libraries.show()
|
self.ui.panel_libraries.show()
|
||||||
self.ui.panel_document.show()
|
self.ui.panel_document.show()
|
||||||
self.ui.leftDockHost.splitDockWidget(
|
self.ui.leftDockHost.setTabPosition(
|
||||||
self.ui.panel_document,
|
Qt.DockWidgetArea.LeftDockWidgetArea, QTabWidget.TabPosition.North
|
||||||
self.ui.panel_libraries,
|
|
||||||
Qt.Orientation.Vertical,
|
|
||||||
)
|
)
|
||||||
|
self.ui.leftDockHost.tabifyDockWidget(self.ui.panel_document, self.ui.panel_libraries)
|
||||||
|
self.ui.panel_document.raise_()
|
||||||
self.ui.workspaceSplitter.setSizes([280, 720])
|
self.ui.workspaceSplitter.setSizes([280, 720])
|
||||||
self.reload_libraries()
|
self.reload_libraries()
|
||||||
self._active_graph_changed()
|
self._active_graph_changed()
|
||||||
@@ -67,22 +74,14 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.treeView.setDragEnabled(True)
|
self.ui.treeView.setDragEnabled(True)
|
||||||
self.ui.treeView.setDragDropMode(self.ui.treeView.DragDropMode.DragOnly)
|
self.ui.treeView.setDragDropMode(self.ui.treeView.DragDropMode.DragOnly)
|
||||||
self.ui.treeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self.ui.treeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.ui.treeView.customContextMenuRequested.connect(
|
self.ui.treeView.customContextMenuRequested.connect(self.show_external_library_context_menu)
|
||||||
self.show_external_library_context_menu
|
|
||||||
)
|
|
||||||
self.library_tree_model.rebuilt.connect(self.ui.treeView.expandAll)
|
self.library_tree_model.rebuilt.connect(self.ui.treeView.expandAll)
|
||||||
self.ui.documentTreeView.setModel(self.document_tree_model)
|
self.ui.documentTreeView.setModel(self.document_tree_model)
|
||||||
self.ui.documentTreeView.setHeaderHidden(True)
|
self.ui.documentTreeView.setHeaderHidden(True)
|
||||||
self.ui.documentTreeView.setDragEnabled(True)
|
self.ui.documentTreeView.setDragEnabled(True)
|
||||||
self.ui.documentTreeView.setDragDropMode(
|
self.ui.documentTreeView.setDragDropMode(self.ui.documentTreeView.DragDropMode.DragOnly)
|
||||||
self.ui.documentTreeView.DragDropMode.DragOnly
|
self.ui.documentTreeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
)
|
self.ui.documentTreeView.customContextMenuRequested.connect(self.show_library_context_menu)
|
||||||
self.ui.documentTreeView.setContextMenuPolicy(
|
|
||||||
Qt.ContextMenuPolicy.CustomContextMenu
|
|
||||||
)
|
|
||||||
self.ui.documentTreeView.customContextMenuRequested.connect(
|
|
||||||
self.show_library_context_menu
|
|
||||||
)
|
|
||||||
self.ui.documentTreeView.doubleClicked.connect(self.activate_tree_component)
|
self.ui.documentTreeView.doubleClicked.connect(self.activate_tree_component)
|
||||||
self.document_tree_model.rebuilt.connect(self.ui.documentTreeView.expandAll)
|
self.document_tree_model.rebuilt.connect(self.ui.documentTreeView.expandAll)
|
||||||
self.ui.graphView.set_model(self.document_controller)
|
self.ui.graphView.set_model(self.document_controller)
|
||||||
@@ -97,6 +96,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.mode_button_group.setExclusive(True)
|
self.mode_button_group.setExclusive(True)
|
||||||
self.mode_button_group.addButton(self.ui.pointerToolButton)
|
self.mode_button_group.addButton(self.ui.pointerToolButton)
|
||||||
self.mode_button_group.addButton(self.ui.connectToolButton)
|
self.mode_button_group.addButton(self.ui.connectToolButton)
|
||||||
|
self.mode_button_group.addButton(self.ui.boxToolButton)
|
||||||
|
self.mode_button_group.addButton(self.ui.lineToolButton)
|
||||||
|
self.mode_button_group.addButton(self.ui.textToolButton)
|
||||||
self.routing_button_group = QButtonGroup(self)
|
self.routing_button_group = QButtonGroup(self)
|
||||||
self.routing_button_group.setExclusive(True)
|
self.routing_button_group.setExclusive(True)
|
||||||
for button in (
|
for button in (
|
||||||
@@ -109,15 +111,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.navigateDownButton.clicked.connect(self.navigate_down)
|
self.ui.navigateDownButton.clicked.connect(self.navigate_down)
|
||||||
self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer"))
|
self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer"))
|
||||||
self.ui.connectToolButton.clicked.connect(lambda: self.set_graph_tool("connect"))
|
self.ui.connectToolButton.clicked.connect(lambda: self.set_graph_tool("connect"))
|
||||||
self.ui.directRoutingButton.clicked.connect(
|
self.ui.boxToolButton.clicked.connect(lambda: self.set_graph_tool("box"))
|
||||||
lambda: self.set_connection_routing("direct")
|
self.ui.lineToolButton.clicked.connect(lambda: self.set_graph_tool("line"))
|
||||||
)
|
self.ui.textToolButton.clicked.connect(lambda: self.set_graph_tool("text"))
|
||||||
self.ui.angledRoutingButton.clicked.connect(
|
self.ui.rotateToolButton.clicked.connect(self.ui.graphView.rotate_selected)
|
||||||
lambda: self.set_connection_routing("angled")
|
self.ui.directRoutingButton.clicked.connect(lambda: self.set_connection_routing("direct"))
|
||||||
)
|
self.ui.angledRoutingButton.clicked.connect(lambda: self.set_connection_routing("angled"))
|
||||||
self.ui.splineRoutingButton.clicked.connect(
|
self.ui.splineRoutingButton.clicked.connect(lambda: self.set_connection_routing("spline"))
|
||||||
lambda: self.set_connection_routing("spline")
|
|
||||||
)
|
|
||||||
self.ui.applyJsonButton.clicked.connect(self.apply_json)
|
self.ui.applyJsonButton.clicked.connect(self.apply_json)
|
||||||
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
||||||
|
|
||||||
@@ -160,7 +160,6 @@ class MainWindow(QMainWindow):
|
|||||||
for toolbar in (
|
for toolbar in (
|
||||||
self.ui.fileToolbar,
|
self.ui.fileToolbar,
|
||||||
self.ui.editToolbar,
|
self.ui.editToolbar,
|
||||||
self.ui.transformToolbar,
|
|
||||||
self.ui.cameraToolbar,
|
self.ui.cameraToolbar,
|
||||||
):
|
):
|
||||||
self.ui.menuToolbars.addAction(toolbar.toggleViewAction())
|
self.ui.menuToolbars.addAction(toolbar.toggleViewAction())
|
||||||
@@ -184,7 +183,11 @@ class MainWindow(QMainWindow):
|
|||||||
if self.document_controller.document is None:
|
if self.document_controller.document is None:
|
||||||
self.setWindowTitle("BEdit")
|
self.setWindowTitle("BEdit")
|
||||||
return
|
return
|
||||||
name = self.document_controller.file_path.name if self.document_controller.file_path else "Untitled"
|
name = (
|
||||||
|
self.document_controller.file_path.name
|
||||||
|
if self.document_controller.file_path
|
||||||
|
else "Untitled"
|
||||||
|
)
|
||||||
modified = "*" if not self.document_controller.undo_stack.isClean() else ""
|
modified = "*" if not self.document_controller.undo_stack.isClean() else ""
|
||||||
self.setWindowTitle(f"{modified}{name} — BEdit")
|
self.setWindowTitle(f"{modified}{name} — BEdit")
|
||||||
|
|
||||||
@@ -217,12 +220,17 @@ class MainWindow(QMainWindow):
|
|||||||
def _update_edit_actions(self) -> None:
|
def _update_edit_actions(self) -> None:
|
||||||
component = self.document_controller.active_component
|
component = self.document_controller.active_component
|
||||||
is_graph = component is not None and component.implementation_kind == "graph"
|
is_graph = component is not None and component.implementation_kind == "graph"
|
||||||
has_selection = bool(self.ui.graphView.scene() and self.ui.graphView.scene().selectedItems())
|
has_selection = bool(
|
||||||
|
self.ui.graphView.scene() and self.ui.graphView.scene().selectedItems()
|
||||||
|
)
|
||||||
for action in (self.ui.actionCut, self.ui.actionCopy, self.ui.actionDelete):
|
for action in (self.ui.actionCut, self.ui.actionCopy, self.ui.actionDelete):
|
||||||
action.setEnabled(is_graph and has_selection)
|
action.setEnabled(is_graph and has_selection)
|
||||||
self.ui.actionRotateClockwise.setEnabled(
|
self.ui.actionRotateClockwise.setEnabled(
|
||||||
is_graph and self.ui.graphView.has_selected_components()
|
is_graph and self.ui.graphView.has_selected_components()
|
||||||
)
|
)
|
||||||
|
self.ui.rotateToolButton.setEnabled(
|
||||||
|
is_graph and self.ui.graphView.has_selected_components()
|
||||||
|
)
|
||||||
self.ui.actionSelectAll.setEnabled(is_graph)
|
self.ui.actionSelectAll.setEnabled(is_graph)
|
||||||
self.ui.actionPaste.setEnabled(is_graph)
|
self.ui.actionPaste.setEnabled(is_graph)
|
||||||
self.ui.navigateDownButton.setEnabled(
|
self.ui.navigateDownButton.setEnabled(
|
||||||
@@ -243,6 +251,10 @@ class MainWindow(QMainWindow):
|
|||||||
for widget in (
|
for widget in (
|
||||||
self.ui.pointerToolButton,
|
self.ui.pointerToolButton,
|
||||||
self.ui.connectToolButton,
|
self.ui.connectToolButton,
|
||||||
|
self.ui.boxToolButton,
|
||||||
|
self.ui.lineToolButton,
|
||||||
|
self.ui.textToolButton,
|
||||||
|
self.ui.rotateToolButton,
|
||||||
self.ui.routingLabel,
|
self.ui.routingLabel,
|
||||||
self.ui.directRoutingButton,
|
self.ui.directRoutingButton,
|
||||||
self.ui.angledRoutingButton,
|
self.ui.angledRoutingButton,
|
||||||
@@ -252,7 +264,13 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def set_graph_tool(self, mode: str) -> None:
|
def set_graph_tool(self, mode: str) -> None:
|
||||||
self.ui.graphView.set_tool_mode(mode)
|
self.ui.graphView.set_tool_mode(mode)
|
||||||
(self.ui.pointerToolButton if mode == "pointer" else self.ui.connectToolButton).setChecked(True)
|
{
|
||||||
|
"pointer": self.ui.pointerToolButton,
|
||||||
|
"connect": self.ui.connectToolButton,
|
||||||
|
"box": self.ui.boxToolButton,
|
||||||
|
"line": self.ui.lineToolButton,
|
||||||
|
"text": self.ui.textToolButton,
|
||||||
|
}[mode].setChecked(True)
|
||||||
|
|
||||||
def set_connection_routing(self, routing: str) -> None:
|
def set_connection_routing(self, routing: str) -> None:
|
||||||
self.ui.graphView.set_connection_routing(routing)
|
self.ui.graphView.set_connection_routing(routing)
|
||||||
@@ -312,9 +330,7 @@ class MainWindow(QMainWindow):
|
|||||||
raise ValueError("'source' must be an object")
|
raise ValueError("'source' must be an object")
|
||||||
inputs = [Port.from_dict(item) for item in data["inputs"]]
|
inputs = [Port.from_dict(item) for item in data["inputs"]]
|
||||||
outputs = [Port.from_dict(item) for item in data["outputs"]]
|
outputs = [Port.from_dict(item) for item in data["outputs"]]
|
||||||
self.document_controller.replace_active_text_definition(
|
self.document_controller.replace_active_text_definition(inputs, outputs, data["source"])
|
||||||
inputs, outputs, data["source"]
|
|
||||||
)
|
|
||||||
except (TypeError, ValueError, json.JSONDecodeError) as error:
|
except (TypeError, ValueError, json.JSONDecodeError) as error:
|
||||||
QMessageBox.critical(self, "Invalid text component JSON", str(error))
|
QMessageBox.critical(self, "Invalid text component JSON", str(error))
|
||||||
return False
|
return False
|
||||||
@@ -497,9 +513,7 @@ class MainWindow(QMainWindow):
|
|||||||
try:
|
try:
|
||||||
if library is not None:
|
if library is not None:
|
||||||
library.document.validate()
|
library.document.validate()
|
||||||
JsonDocumentSerializer.save(
|
JsonDocumentSerializer.save(library.document, Path(library.source_path))
|
||||||
library.document, Path(library.source_path)
|
|
||||||
)
|
|
||||||
except (OSError, ValueError) as error:
|
except (OSError, ValueError) as error:
|
||||||
component.inputs, component.outputs = old_inputs, old_outputs
|
component.inputs, component.outputs = old_inputs, old_outputs
|
||||||
QMessageBox.warning(self, "Cannot change library ports", str(error))
|
QMessageBox.warning(self, "Cannot change library ports", str(error))
|
||||||
@@ -559,9 +573,7 @@ class MainWindow(QMainWindow):
|
|||||||
connection = owner.graph.connections.get(connection_id)
|
connection = owner.graph.connections.get(connection_id)
|
||||||
if connection is None:
|
if connection is None:
|
||||||
return
|
return
|
||||||
dialog = ItemOptionsDialog(
|
dialog = ItemOptionsDialog("Connection Options", connection.name, self, name_required=False)
|
||||||
"Connection Options", connection.name, self, name_required=False
|
|
||||||
)
|
|
||||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||||
self.document_controller.rename_connection(connection_id, dialog.name)
|
self.document_controller.rename_connection(connection_id, dialog.name)
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,18 @@
|
|||||||
<layout class="QVBoxLayout" name="dialogLayout">
|
<layout class="QVBoxLayout" name="dialogLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="shapeToolbarLayout">
|
<layout class="QHBoxLayout" name="shapeToolbarLayout">
|
||||||
|
<item><widget class="QToolButton" name="pointerButton"><property name="text"><string>Pointer</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property></widget></item>
|
||||||
<item><widget class="QLabel" name="addShapeLabel"><property name="text"><string>Add:</string></property></widget></item>
|
<item><widget class="QLabel" name="addShapeLabel"><property name="text"><string>Add:</string></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="addRectangleButton"><property name="text"><string>Rectangle</string></property></widget></item>
|
<item><widget class="QToolButton" name="addRectangleButton"><property name="text"><string>Rectangle</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="addCircleButton"><property name="text"><string>Circle</string></property></widget></item>
|
<item><widget class="QToolButton" name="addCircleButton"><property name="text"><string>Circle</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="addEllipseButton"><property name="text"><string>Ellipse</string></property></widget></item>
|
<item><widget class="QToolButton" name="addEllipseButton"><property name="text"><string>Ellipse</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="addLineButton"><property name="text"><string>Line</string></property></widget></item>
|
<item><widget class="QToolButton" name="addLineButton"><property name="text"><string>Line</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="addTriangleButton"><property name="text"><string>Triangle</string></property></widget></item>
|
<item><widget class="QToolButton" name="addTriangleButton"><property name="text"><string>Triangle</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="addTextButton"><property name="text"><string>Text</string></property></widget></item>
|
<item><widget class="QToolButton" name="addTextButton"><property name="text"><string>Text</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
<item><spacer name="toolbarSpacer"><property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
|
<item><spacer name="toolbarSpacer"><property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
|
||||||
|
<item><widget class="QToolButton" name="zoomInButton"><property name="text"><string>+</string></property><property name="toolTip"><string>Zoom in</string></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="zoomOutButton"><property name="text"><string>−</string></property><property name="toolTip"><string>Zoom out</string></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="centerButton"><property name="text"><string>Fit</string></property><property name="toolTip"><string>Fit and center the icon canvas</string></property></widget></item>
|
||||||
<item><widget class="QPushButton" name="deleteSelectedButton"><property name="text"><string>Delete selected</string></property></widget></item>
|
<item><widget class="QPushButton" name="deleteSelectedButton"><property name="text"><string>Delete selected</string></property></widget></item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1000</width>
|
<width>1209</width>
|
||||||
<height>700</height>
|
<height>777</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -83,11 +83,18 @@
|
|||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeView" name="treeView">
|
<widget class="QTreeView" name="treeView">
|
||||||
<property name="iconSize"><size><width>28</width><height>28</height></size></property>
|
<property name="styleSheet">
|
||||||
<property name="styleSheet"><string notr="true">QTreeView::item { height: 32px; }</string></property>
|
<string notr="true">QTreeView::item { height: 32px; }</string>
|
||||||
|
</property>
|
||||||
<property name="alternatingRowColors">
|
<property name="alternatingRowColors">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>28</width>
|
||||||
|
<height>28</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="uniformRowHeights">
|
<property name="uniformRowHeights">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@@ -98,22 +105,48 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<widget class="QDockWidget" name="panel_document">
|
<widget class="QDockWidget" name="panel_document">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size><width>220</width><height>91</height></size>
|
<size>
|
||||||
|
<width>220</width>
|
||||||
|
<height>91</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle"><string>Document</string></property>
|
<property name="windowTitle">
|
||||||
<attribute name="dockWidgetArea"><number>1</number></attribute>
|
<string>Document</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>1</number>
|
||||||
|
</attribute>
|
||||||
<widget class="QWidget" name="documentDockContents">
|
<widget class="QWidget" name="documentDockContents">
|
||||||
<layout class="QVBoxLayout" name="documentPanelLayout">
|
<layout class="QVBoxLayout" name="documentPanelLayout">
|
||||||
<property name="spacing"><number>0</number></property>
|
<property name="spacing">
|
||||||
<property name="leftMargin"><number>0</number></property>
|
<number>0</number>
|
||||||
<property name="topMargin"><number>0</number></property>
|
</property>
|
||||||
<property name="rightMargin"><number>0</number></property>
|
<property name="leftMargin">
|
||||||
<property name="bottomMargin"><number>0</number></property>
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeView" name="documentTreeView">
|
<widget class="QTreeView" name="documentTreeView">
|
||||||
<property name="iconSize"><size><width>16</width><height>16</height></size></property>
|
<property name="alternatingRowColors">
|
||||||
<property name="alternatingRowColors"><bool>true</bool></property>
|
<bool>true</bool>
|
||||||
<property name="uniformRowHeights"><bool>true</bool></property>
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>16</width>
|
||||||
|
<height>16</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -128,59 +161,320 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="workspaceEditorLayout">
|
<layout class="QVBoxLayout" name="workspaceEditorLayout">
|
||||||
<property name="spacing"><number>0</number></property>
|
<property name="spacing">
|
||||||
<property name="leftMargin"><number>0</number></property>
|
<number>0</number>
|
||||||
<property name="topMargin"><number>0</number></property>
|
</property>
|
||||||
<property name="rightMargin"><number>0</number></property>
|
<property name="leftMargin">
|
||||||
<property name="bottomMargin"><number>0</number></property>
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QFrame" name="workspaceHeader">
|
<widget class="QFrame" name="workspaceHeader">
|
||||||
<property name="minimumSize"><size><width>0</width><height>34</height></size></property>
|
<property name="minimumSize">
|
||||||
<property name="maximumSize"><size><width>16777215</width><height>34</height></size></property>
|
<size>
|
||||||
<property name="frameShape"><enum>QFrame::Shape::StyledPanel</enum></property>
|
<width>0</width>
|
||||||
|
<height>34</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>34</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::Shape::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
<layout class="QHBoxLayout" name="workspaceHeaderLayout">
|
<layout class="QHBoxLayout" name="workspaceHeaderLayout">
|
||||||
<property name="leftMargin"><number>6</number></property>
|
<property name="leftMargin">
|
||||||
<property name="topMargin"><number>2</number></property>
|
<number>6</number>
|
||||||
<property name="rightMargin"><number>6</number></property>
|
</property>
|
||||||
<property name="bottomMargin"><number>2</number></property>
|
<property name="topMargin">
|
||||||
<item><widget class="QToolButton" name="navigateUpButton"><property name="text"><string>Up</string></property><property name="toolTip"><string>Open the containing graph</string></property></widget></item>
|
<number>2</number>
|
||||||
<item><widget class="QToolButton" name="navigateDownButton"><property name="text"><string>Down</string></property><property name="toolTip"><string>Open the selected block</string></property><property name="enabled"><bool>false</bool></property></widget></item>
|
</property>
|
||||||
<item><widget class="QLabel" name="graphBreadcrumbLabel"><property name="text"><string>Untitled</string></property></widget></item>
|
<property name="rightMargin">
|
||||||
<item><widget class="QLabel" name="workspaceModeLabel"><property name="text"><string>Graph</string></property></widget></item>
|
<number>6</number>
|
||||||
<item><spacer name="workspaceHeaderSpacer"><property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
|
</property>
|
||||||
<item><widget class="QPushButton" name="applyJsonButton"><property name="text"><string>Apply JSON</string></property><property name="visible"><bool>false</bool></property></widget></item>
|
<property name="bottomMargin">
|
||||||
<item><widget class="QToolButton" name="pointerToolButton"><property name="text"><string>Pointer</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property></widget></item>
|
<number>2</number>
|
||||||
<item><widget class="QToolButton" name="connectToolButton"><property name="text"><string>Connect</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
</property>
|
||||||
<item><widget class="QLabel" name="routingLabel"><property name="text"><string>Line:</string></property></widget></item>
|
<item>
|
||||||
<item><widget class="QToolButton" name="directRoutingButton"><property name="text"><string>Direct</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
<widget class="QToolButton" name="navigateUpButton">
|
||||||
<item><widget class="QToolButton" name="angledRoutingButton"><property name="text"><string>Angled</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
<property name="toolTip">
|
||||||
<item><widget class="QToolButton" name="splineRoutingButton"><property name="text"><string>Spline</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property></widget></item>
|
<string>Open the containing graph</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Up</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/arrow-up.png</normaloff>:/icons/icons/arrow-up.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="navigateDownButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Open the selected block</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Down</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/arrow-down.png</normaloff>:/icons/icons/arrow-down.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="graphBreadcrumbLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Untitled</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="workspaceModeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Graph</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="workspaceHeaderSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="applyJsonButton">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Apply JSON</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="pointerToolButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Pointer</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/edit-select.png</normaloff>:/icons/icons/edit-select.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="connectToolButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Connect</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="boxToolButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Draw a box annotation</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Box</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/draw-rectangle.png</normaloff>:/icons/icons/draw-rectangle.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="lineToolButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Draw a line annotation</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Line</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/draw-bezier-curves.png</normaloff>:/icons/icons/draw-bezier-curves.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="textToolButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Add a text annotation</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Text</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/draw-text.png</normaloff>:/icons/icons/draw-text.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="rotateToolButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Rotate selected blocks clockwise</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Rotate</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/transform-rotate.png</normaloff>:/icons/icons/transform-rotate.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="routingLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Line:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="directRoutingButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Direct</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="angledRoutingButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Angled</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="splineRoutingButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Spline</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="workspaceStack">
|
<widget class="QStackedWidget" name="workspaceStack">
|
||||||
<property name="currentIndex"><number>0</number></property>
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<widget class="QWidget" name="graphPage">
|
<widget class="QWidget" name="graphPage">
|
||||||
<layout class="QVBoxLayout" name="graphPageLayout">
|
<layout class="QVBoxLayout" name="graphPageLayout">
|
||||||
<property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>0</number></property><property name="bottomMargin"><number>0</number></property>
|
<property name="leftMargin">
|
||||||
<item><widget class="GraphWorkspaceView" name="graphView"/></item>
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="GraphWorkspaceView" name="graphView"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="jsonPage">
|
<widget class="QWidget" name="jsonPage">
|
||||||
<layout class="QVBoxLayout" name="jsonPageLayout">
|
<layout class="QVBoxLayout" name="jsonPageLayout">
|
||||||
<property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>0</number></property><property name="bottomMargin"><number>0</number></property>
|
<property name="leftMargin">
|
||||||
<item><widget class="QPlainTextEdit" name="jsonEditor"><property name="lineWrapMode"><enum>QPlainTextEdit::LineWrapMode::NoWrap</enum></property><property name="placeholderText"><string>Component JSON</string></property></widget></item>
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="jsonEditor">
|
||||||
|
<property name="lineWrapMode">
|
||||||
|
<enum>QPlainTextEdit::LineWrapMode::NoWrap</enum>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Component JSON</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="emptyPage">
|
<widget class="QWidget" name="emptyPage">
|
||||||
<property name="styleSheet"><string notr="true">background-color: #9a9a9a;</string></property>
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: #9a9a9a;</string>
|
||||||
|
</property>
|
||||||
<layout class="QVBoxLayout" name="emptyPageLayout">
|
<layout class="QVBoxLayout" name="emptyPageLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="emptyWorkspaceLabel">
|
<widget class="QLabel" name="emptyWorkspaceLabel">
|
||||||
<property name="styleSheet"><string notr="true">background: transparent; color: #202020;</string></property>
|
<property name="styleSheet">
|
||||||
<property name="text"><string>No document open</string></property>
|
<string notr="true">background: transparent; color: #202020;</string>
|
||||||
<property name="alignment"><set>Qt::AlignmentFlag::AlignCenter</set></property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>No document open</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -198,7 +492,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1000</width>
|
<width>1209</width>
|
||||||
<height>24</height>
|
<height>24</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@@ -314,16 +608,16 @@
|
|||||||
<addaction name="actionCut"/>
|
<addaction name="actionCut"/>
|
||||||
<addaction name="actionPaste"/>
|
<addaction name="actionPaste"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QToolBar" name="transformToolbar">
|
|
||||||
<property name="windowTitle"><string>Transform</string></property>
|
|
||||||
<attribute name="toolBarArea"><enum>TopToolBarArea</enum></attribute>
|
|
||||||
<attribute name="toolBarBreak"><bool>false</bool></attribute>
|
|
||||||
<addaction name="actionRotateClockwise"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QToolBar" name="cameraToolbar">
|
<widget class="QToolBar" name="cameraToolbar">
|
||||||
<property name="windowTitle"><string>Camera</string></property>
|
<property name="windowTitle">
|
||||||
<attribute name="toolBarArea"><enum>TopToolBarArea</enum></attribute>
|
<string>Camera</string>
|
||||||
<attribute name="toolBarBreak"><bool>false</bool></attribute>
|
</property>
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>TopToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
<addaction name="actionZoomIn"/>
|
<addaction name="actionZoomIn"/>
|
||||||
<addaction name="actionZoomOut"/>
|
<addaction name="actionZoomOut"/>
|
||||||
<addaction name="actionCenterView"/>
|
<addaction name="actionCenterView"/>
|
||||||
@@ -348,13 +642,52 @@
|
|||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
<normaloff>:/icons/icons/transform-rotate.png</normaloff>:/icons/icons/transform-rotate.png</iconset>
|
<normaloff>:/icons/icons/transform-rotate.png</normaloff>:/icons/icons/transform-rotate.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text"><string>Rotate Clockwise</string></property>
|
<property name="text">
|
||||||
<property name="toolTip"><string>Rotate selected blocks clockwise by 90 degrees</string></property>
|
<string>Rotate Clockwise</string>
|
||||||
<property name="shortcut"><string>Ctrl+R</string></property>
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Rotate selected blocks clockwise by 90 degrees</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+R</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionZoomIn">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/zoom-in.png</normaloff>:/icons/icons/zoom-in.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Zoom In</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl++</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionZoomOut">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/zoom-out.png</normaloff>:/icons/icons/zoom-out.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Zoom Out</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+-</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCenterView">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/zoom-original.png</normaloff>:/icons/icons/zoom-original.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Center</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+0</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionZoomIn"><property name="text"><string>Zoom In</string></property><property name="shortcut"><string>Ctrl++</string></property></action>
|
|
||||||
<action name="actionZoomOut"><property name="text"><string>Zoom Out</string></property><property name="shortcut"><string>Ctrl+-</string></property></action>
|
|
||||||
<action name="actionCenterView"><property name="text"><string>Center</string></property><property name="shortcut"><string>Ctrl+0</string></property></action>
|
|
||||||
<action name="actionOpen">
|
<action name="actionOpen">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
@@ -406,8 +739,12 @@
|
|||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionClose">
|
<action name="actionClose">
|
||||||
<property name="text"><string>&Close Document</string></property>
|
<property name="text">
|
||||||
<property name="shortcut"><string>Ctrl+W</string></property>
|
<string>&Close Document</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+W</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionUndo">
|
<action name="actionUndo">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
@@ -478,8 +815,12 @@
|
|||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDelete">
|
<action name="actionDelete">
|
||||||
<property name="text"><string>&Delete</string></property>
|
<property name="text">
|
||||||
<property name="shortcut"><string>Del</string></property>
|
<string>&Delete</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Del</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAbout">
|
<action name="actionAbout">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
355
BEdit/untitled.bedit.json
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
{
|
||||||
|
"format": "bedit-document",
|
||||||
|
"version": 1,
|
||||||
|
"metadata": {
|
||||||
|
"name": "Untitled"
|
||||||
|
},
|
||||||
|
"roots": [
|
||||||
|
{
|
||||||
|
"id": "f4a9c769-d49a-46e1-90d8-a8175bf42e9c",
|
||||||
|
"name": "New Graph Block 1",
|
||||||
|
"position": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0
|
||||||
|
},
|
||||||
|
"rotation": 0.0,
|
||||||
|
"interface": {
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"shape": "rectangle",
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"border": "#303030",
|
||||||
|
"text": "Graph",
|
||||||
|
"size": {
|
||||||
|
"width": 128.0,
|
||||||
|
"height": 128.0
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 32.0,
|
||||||
|
"width": 64.0,
|
||||||
|
"height": 64.0,
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"stroke": "#303030",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"cornerRadius": 5.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"x": 40.0,
|
||||||
|
"y": 40.0,
|
||||||
|
"width": 48.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"text": "Graph",
|
||||||
|
"color": "#202020",
|
||||||
|
"fontSize": 12.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"library": {
|
||||||
|
"showSubtree": true
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"kind": "graph",
|
||||||
|
"graph": {
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "af73487a-4a2d-4908-b919-d0a2eaa75ce2",
|
||||||
|
"name": "A",
|
||||||
|
"position": {
|
||||||
|
"x": -192.0,
|
||||||
|
"y": -192.0
|
||||||
|
},
|
||||||
|
"rotation": 0.0,
|
||||||
|
"interface": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "port-5c5d4695",
|
||||||
|
"name": "Port 1",
|
||||||
|
"position": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 64.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "signal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "port-de109124",
|
||||||
|
"name": "Port 2",
|
||||||
|
"position": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 96.0,
|
||||||
|
"y": 64.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "signal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"shape": "rectangle",
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"border": "#303030",
|
||||||
|
"text": "Text",
|
||||||
|
"size": {
|
||||||
|
"width": 128.0,
|
||||||
|
"height": 128.0
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"cornerRadius": 5.0,
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"height": 64.0,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"stroke": "#303030",
|
||||||
|
"type": "rectangle",
|
||||||
|
"width": 64.0,
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 32.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#303030",
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"fontSize": 12.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"stroke": "#303030",
|
||||||
|
"text": "A",
|
||||||
|
"type": "text",
|
||||||
|
"width": 48.0,
|
||||||
|
"x": 40.0,
|
||||||
|
"y": 40.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"library": {
|
||||||
|
"showSubtree": true
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"kind": "text",
|
||||||
|
"source": {
|
||||||
|
"equations": [],
|
||||||
|
"parameters": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b89a77a1-d773-4ee3-8c3f-74377c29b4b2",
|
||||||
|
"name": "B",
|
||||||
|
"position": {
|
||||||
|
"x": 128.0,
|
||||||
|
"y": -128.0
|
||||||
|
},
|
||||||
|
"rotation": 0.0,
|
||||||
|
"interface": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "port-4f732b3e",
|
||||||
|
"name": "Port 1",
|
||||||
|
"position": {
|
||||||
|
"x": -176.0,
|
||||||
|
"y": -144.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 48.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "signal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "port-ef7c4218",
|
||||||
|
"name": "Port 2",
|
||||||
|
"position": {
|
||||||
|
"x": -176.0,
|
||||||
|
"y": -16.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 80.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "signal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "port-b68679b2",
|
||||||
|
"name": "Port 3",
|
||||||
|
"position": {
|
||||||
|
"x": 128.0,
|
||||||
|
"y": -80.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 96.0,
|
||||||
|
"y": 64.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "signal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"shape": "rectangle",
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"border": "#303030",
|
||||||
|
"text": "Graph",
|
||||||
|
"size": {
|
||||||
|
"width": 128.0,
|
||||||
|
"height": 128.0
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"cornerRadius": 5.0,
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"height": 64.0,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"stroke": "#303030",
|
||||||
|
"type": "rectangle",
|
||||||
|
"width": 64.0,
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 32.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#303030",
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"fontSize": 12.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"stroke": "#303030",
|
||||||
|
"text": "B",
|
||||||
|
"type": "text",
|
||||||
|
"width": 48.0,
|
||||||
|
"x": 40.0,
|
||||||
|
"y": 40.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {},
|
||||||
|
"library": {
|
||||||
|
"showSubtree": true
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"kind": "graph",
|
||||||
|
"graph": {
|
||||||
|
"blocks": [],
|
||||||
|
"connections": [],
|
||||||
|
"annotations": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "c59eac1f-5f24-45de-90ee-bfd039894ed9",
|
||||||
|
"source": {
|
||||||
|
"block": "af73487a-4a2d-4908-b919-d0a2eaa75ce2",
|
||||||
|
"port": "port-de109124"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"block": "b89a77a1-d773-4ee3-8c3f-74377c29b4b2",
|
||||||
|
"port": "port-4f732b3e"
|
||||||
|
},
|
||||||
|
"name": "",
|
||||||
|
"properties": {
|
||||||
|
"routing": "angled",
|
||||||
|
"waypoints": [
|
||||||
|
{
|
||||||
|
"x": -64.0,
|
||||||
|
"y": -128.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": -64.0,
|
||||||
|
"y": 64.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 64.0,
|
||||||
|
"y": 64.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 64.0,
|
||||||
|
"y": -80.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"annotations": [
|
||||||
|
{
|
||||||
|
"id": "bfe7d48b-fe54-4378-ab1b-f9b46c2efa87",
|
||||||
|
"kind": "box",
|
||||||
|
"position": {
|
||||||
|
"x": 48.0,
|
||||||
|
"y": -328.0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 160.0,
|
||||||
|
"height": 112.0
|
||||||
|
},
|
||||||
|
"text": "",
|
||||||
|
"layer": -1,
|
||||||
|
"properties": {
|
||||||
|
"stroke": "#303030",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"fill": "#dbeafe",
|
||||||
|
"cornerRadius": 0.0,
|
||||||
|
"routing": "angled",
|
||||||
|
"waypoints": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aa69f7b3-f5af-426b-9eb3-c63a268346b5",
|
||||||
|
"kind": "text",
|
||||||
|
"position": {
|
||||||
|
"x": -272.0,
|
||||||
|
"y": -216.0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 400.0,
|
||||||
|
"height": 48.0
|
||||||
|
},
|
||||||
|
"text": "This is an annotation",
|
||||||
|
"layer": -1,
|
||||||
|
"properties": {
|
||||||
|
"stroke": "#023010",
|
||||||
|
"lineWidth": 3.0,
|
||||||
|
"lineStyle": "dash",
|
||||||
|
"fill": "none",
|
||||||
|
"fontSize": 18.0,
|
||||||
|
"color": "#023010",
|
||||||
|
"routing": "angled",
|
||||||
|
"waypoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||