new text mode editor
This commit is contained in:
@@ -121,6 +121,9 @@ pyside6-uic --from-imports ui/shape_options_dialog.ui \
|
|||||||
|
|
||||||
pyside6-uic --from-imports ui/icon_editor_dialog.ui \
|
pyside6-uic --from-imports ui/icon_editor_dialog.ui \
|
||||||
-o src/bedit/gui/generated/ui_icon_editor_dialog.py
|
-o src/bedit/gui/generated/ui_icon_editor_dialog.py
|
||||||
|
|
||||||
|
pyside6-uic --from-imports ui/text_definition_editor.ui \
|
||||||
|
-o src/bedit/gui/generated/ui_text_definition_editor.py
|
||||||
```
|
```
|
||||||
|
|
||||||
When adding a promoted/custom widget in Designer, its header must use the real
|
When adding a promoted/custom widget in Designer, its header must use the real
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class LibraryDocument:
|
|||||||
|
|
||||||
|
|
||||||
def bundled_library_path() -> Path:
|
def bundled_library_path() -> Path:
|
||||||
return Path(__file__).resolve().parents[1] / "data" / "libraries" / "example.json"
|
return Path(__file__).resolve().parents[1] / "data" / "libraries" / "default.json"
|
||||||
|
|
||||||
|
|
||||||
def default_library_paths() -> list[str]:
|
def default_library_paths() -> list[str]:
|
||||||
|
|||||||
@@ -152,6 +152,30 @@ class Connection:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Parameter:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
type: str = "real"
|
||||||
|
value: str = "0"
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, str]:
|
||||||
|
return {"id": self.id, "name": self.name, "type": self.type, "value": self.value}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any]) -> "Parameter":
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("Each text component parameter must be an object")
|
||||||
|
if "id" not in data:
|
||||||
|
raise ValueError("Each text component parameter must have an ID")
|
||||||
|
return cls(
|
||||||
|
id=str(data["id"]),
|
||||||
|
name=str(data.get("name", "")),
|
||||||
|
type=str(data.get("type", "real")),
|
||||||
|
value=str(data.get("value", "0")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Annotation:
|
class Annotation:
|
||||||
id: str
|
id: str
|
||||||
@@ -271,6 +295,19 @@ class Component:
|
|||||||
kind = str(implementation.get("kind", "graph"))
|
kind = str(implementation.get("kind", "graph"))
|
||||||
if kind not in {"graph", "text"}:
|
if kind not in {"graph", "text"}:
|
||||||
raise ValueError(f"Unknown component implementation kind: {kind}")
|
raise ValueError(f"Unknown component implementation kind: {kind}")
|
||||||
|
source: dict[str, Any] = {}
|
||||||
|
if kind == "text":
|
||||||
|
raw_source = implementation.get("source", {})
|
||||||
|
equations = raw_source.get("equations", "")
|
||||||
|
parameters = raw_source.get("parameters", [])
|
||||||
|
if not isinstance(equations, str):
|
||||||
|
raise ValueError("Text component equations must be a string")
|
||||||
|
if not isinstance(parameters, list):
|
||||||
|
raise ValueError("Text component parameters must be a list")
|
||||||
|
source = {
|
||||||
|
"equations": equations,
|
||||||
|
"parameters": [Parameter.from_dict(item).to_dict() for item in parameters],
|
||||||
|
}
|
||||||
return cls(
|
return cls(
|
||||||
id=str(data["id"]),
|
id=str(data["id"]),
|
||||||
name=str(data.get("name", "Unnamed")),
|
name=str(data.get("name", "Unnamed")),
|
||||||
@@ -284,7 +321,7 @@ class Component:
|
|||||||
show_subtree_in_library=bool(data.get("library", {}).get("showSubtree", True)),
|
show_subtree_in_library=bool(data.get("library", {}).get("showSubtree", True)),
|
||||||
implementation_kind=kind,
|
implementation_kind=kind,
|
||||||
graph=Graph.from_dict(implementation.get("graph")) if kind == "graph" else Graph(),
|
graph=Graph.from_dict(implementation.get("graph")) if kind == "graph" else Graph(),
|
||||||
source=dict(implementation.get("source", {})) if kind == "text" else {},
|
source=source,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -348,6 +385,9 @@ class GraphDocument:
|
|||||||
|
|
||||||
def validate(self) -> None:
|
def validate(self) -> None:
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
|
root_names = [component.name for component in self.roots.values()]
|
||||||
|
if len(set(root_names)) != len(root_names):
|
||||||
|
raise ValueError("Root component names must be unique")
|
||||||
for component in self.all_components():
|
for component in self.all_components():
|
||||||
if component.id in seen:
|
if component.id in seen:
|
||||||
raise ValueError(f"Duplicate component ID: {component.id}")
|
raise ValueError(f"Duplicate component ID: {component.id}")
|
||||||
@@ -358,6 +398,11 @@ class GraphDocument:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_graph(owner: Component) -> None:
|
def _validate_graph(owner: Component) -> None:
|
||||||
|
child_names = [component.name for component in owner.graph.blocks.values()]
|
||||||
|
if len(set(child_names)) != len(child_names):
|
||||||
|
raise ValueError(
|
||||||
|
f"Component names inside {owner.name!r} must be unique"
|
||||||
|
)
|
||||||
input_ids = {port.id for port in owner.inputs}
|
input_ids = {port.id for port in owner.inputs}
|
||||||
output_ids = {port.id for port in owner.outputs}
|
output_ids = {port.id for port in owner.outputs}
|
||||||
if len(input_ids) != len(owner.inputs) or len(output_ids) != len(owner.outputs):
|
if len(input_ids) != len(owner.inputs) or len(output_ids) != len(owner.outputs):
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
"format": "bedit-document",
|
"format": "bedit-document",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "Example"
|
"name": "Untitled"
|
||||||
},
|
},
|
||||||
"roots": [
|
"roots": [
|
||||||
{
|
{
|
||||||
"id": "bf714517-e7b2-4f4b-8b53-55642e3beb28",
|
"id": "7ad651ef-6f76-4064-81d3-3fd6dd4d2918",
|
||||||
"name": "A",
|
"name": "gain",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
@@ -16,15 +16,15 @@
|
|||||||
"interface": {
|
"interface": {
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"id": "port-5c5d4695",
|
"id": "port-df34ce84",
|
||||||
"name": "Port 1",
|
"name": "in",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"iconPosition": {
|
"iconPosition": {
|
||||||
"x": 32.0,
|
"x": 64.0,
|
||||||
"y": 64.0
|
"y": 64.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -33,16 +33,16 @@
|
|||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"id": "port-de109124",
|
"id": "port-fe9e6486",
|
||||||
"name": "Port 2",
|
"name": "out",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"iconPosition": {
|
"iconPosition": {
|
||||||
"x": 96.0,
|
"x": 88.0,
|
||||||
"y": 64.0
|
"y": 40.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "signal"
|
"type": "signal"
|
||||||
@@ -77,31 +77,40 @@
|
|||||||
"y": 40.0,
|
"y": 40.0,
|
||||||
"width": 48.0,
|
"width": 48.0,
|
||||||
"height": 48.0,
|
"height": 48.0,
|
||||||
"text": "A",
|
"text": "K",
|
||||||
"color": "#303030",
|
"color": "#00007f",
|
||||||
"fontSize": 12.0,
|
"fontSize": 24.0,
|
||||||
"lineStyle": "solid",
|
"lineStyle": "solid",
|
||||||
"lineWidth": 1.5,
|
"lineWidth": 1.5,
|
||||||
"stroke": "#303030",
|
"stroke": "#00007f",
|
||||||
"fill": "#ffffff"
|
"fill": "#ffffff"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"properties": {},
|
"properties": {
|
||||||
|
"showName": false
|
||||||
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"showSubtree": true
|
"showSubtree": true
|
||||||
},
|
},
|
||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
"source": {
|
"source": {
|
||||||
"equations": [],
|
"equations": "out = k*in;",
|
||||||
"parameters": {}
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "parameter-61a43a86",
|
||||||
|
"name": "k",
|
||||||
|
"type": "real",
|
||||||
|
"value": "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "73c9d0c0-293d-4a55-8b89-a1f095dfa75f",
|
"id": "12b21b11-8828-4a90-a561-19491dc9632e",
|
||||||
"name": "B",
|
"name": "differentiate",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
@@ -110,31 +119,16 @@
|
|||||||
"interface": {
|
"interface": {
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"id": "port-4f732b3e",
|
"id": "port-1cbabc8f",
|
||||||
"name": "Port 1",
|
"name": "in",
|
||||||
"position": {
|
"position": {
|
||||||
"x": -176.0,
|
"x": 0.0,
|
||||||
"y": -144.0
|
"y": 0.0
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"iconPosition": {
|
"iconPosition": {
|
||||||
"x": 32.0,
|
"x": 64.0,
|
||||||
"y": 48.0
|
"y": 64.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"
|
"type": "signal"
|
||||||
@@ -142,16 +136,16 @@
|
|||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"id": "port-b68679b2",
|
"id": "port-4eeea4e7",
|
||||||
"name": "Port 3",
|
"name": "out",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 128.0,
|
"x": 0.0,
|
||||||
"y": -80.0
|
"y": 0.0
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"iconPosition": {
|
"iconPosition": {
|
||||||
"x": 96.0,
|
"x": 88.0,
|
||||||
"y": 64.0
|
"y": 40.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "signal"
|
"type": "signal"
|
||||||
@@ -162,7 +156,7 @@
|
|||||||
"shape": "rectangle",
|
"shape": "rectangle",
|
||||||
"fill": "#f4f4f4",
|
"fill": "#f4f4f4",
|
||||||
"border": "#303030",
|
"border": "#303030",
|
||||||
"text": "Graph",
|
"text": "Text",
|
||||||
"size": {
|
"size": {
|
||||||
"width": 128.0,
|
"width": 128.0,
|
||||||
"height": 128.0
|
"height": 128.0
|
||||||
@@ -186,25 +180,34 @@
|
|||||||
"y": 40.0,
|
"y": 40.0,
|
||||||
"width": 48.0,
|
"width": 48.0,
|
||||||
"height": 48.0,
|
"height": 48.0,
|
||||||
"text": "B",
|
"text": "d/dt",
|
||||||
"color": "#303030",
|
"color": "#00007f",
|
||||||
"fontSize": 12.0,
|
"fontSize": 18.0,
|
||||||
"lineStyle": "solid",
|
"lineStyle": "solid",
|
||||||
"lineWidth": 1.5,
|
"lineWidth": 1.5,
|
||||||
"stroke": "#303030",
|
"stroke": "#00007f",
|
||||||
"fill": "#ffffff"
|
"fill": "#ffffff"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"properties": {},
|
"properties": {
|
||||||
|
"showName": false
|
||||||
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"showSubtree": true
|
"showSubtree": true
|
||||||
},
|
},
|
||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "graph",
|
"kind": "text",
|
||||||
"graph": {
|
"source": {
|
||||||
"blocks": [],
|
"equations": "initial out = initial;\nout = der(in);",
|
||||||
"connections": []
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "parameter-331e38be",
|
||||||
|
"name": "initial",
|
||||||
|
"type": "real",
|
||||||
|
"value": "0"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,3 +322,14 @@ class EditTextDefinitionCommand(QUndoCommand):
|
|||||||
|
|
||||||
def undo(self) -> None:
|
def undo(self) -> None:
|
||||||
self.controller._set_text_definition(self.component_id, self.old)
|
self.controller._set_text_definition(self.component_id, self.old)
|
||||||
|
|
||||||
|
def id(self) -> int:
|
||||||
|
return 1001
|
||||||
|
|
||||||
|
def mergeWith(self, other: QUndoCommand) -> bool: # noqa: N802
|
||||||
|
if not isinstance(other, EditTextDefinitionCommand):
|
||||||
|
return False
|
||||||
|
if other.component_id != self.component_id:
|
||||||
|
return False
|
||||||
|
self.new = other.new
|
||||||
|
return True
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from bedit.core.model import (
|
|||||||
Endpoint,
|
Endpoint,
|
||||||
GraphDocument,
|
GraphDocument,
|
||||||
Icon,
|
Icon,
|
||||||
|
Parameter,
|
||||||
Port,
|
Port,
|
||||||
clone_component,
|
clone_component,
|
||||||
)
|
)
|
||||||
@@ -47,6 +48,7 @@ class DocumentController(QObject):
|
|||||||
componentMoved = Signal(str, QPointF)
|
componentMoved = Signal(str, QPointF)
|
||||||
componentRotated = Signal(str, float)
|
componentRotated = Signal(str, float)
|
||||||
componentPropertiesChanged = Signal(str)
|
componentPropertiesChanged = Signal(str)
|
||||||
|
textDefinitionChanged = Signal(str)
|
||||||
connectionAdded = Signal(str)
|
connectionAdded = Signal(str)
|
||||||
connectionRemoved = Signal(str)
|
connectionRemoved = Signal(str)
|
||||||
graphItemChanged = Signal(str, str)
|
graphItemChanged = Signal(str, str)
|
||||||
@@ -153,12 +155,13 @@ class DocumentController(QObject):
|
|||||||
if self.document is None:
|
if self.document is None:
|
||||||
raise ValueError("Open or create a document first")
|
raise ValueError("Open or create a document first")
|
||||||
number = len(self.document.roots) + 1
|
number = len(self.document.roots) + 1
|
||||||
|
base_name = f"New {'Graph' if kind == 'graph' else 'Text'} Block "
|
||||||
component = Component(
|
component = Component(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
name=f"New {'Graph' if kind == 'graph' else 'Text'} Block {number}",
|
name=self._available_component_name(base_name, self.document.roots.values(), number),
|
||||||
implementation_kind=kind,
|
implementation_kind=kind,
|
||||||
icon=Icon(text="Graph" if kind == "graph" else "Text"),
|
icon=Icon(text="Graph" if kind == "graph" else "Text"),
|
||||||
source={"equations": [], "parameters": {}} if kind == "text" else {},
|
source={"equations": "", "parameters": []} if kind == "text" else {},
|
||||||
)
|
)
|
||||||
self.undo_stack.push(AddComponentCommand(self, None, component))
|
self.undo_stack.push(AddComponentCommand(self, None, component))
|
||||||
self.activate_component(component.id)
|
self.activate_component(component.id)
|
||||||
@@ -171,12 +174,13 @@ class DocumentController(QObject):
|
|||||||
if owner is None or owner.implementation_kind != "graph":
|
if owner is None or owner.implementation_kind != "graph":
|
||||||
raise ValueError("Children can only be added to graph components")
|
raise ValueError("Children can only be added to graph components")
|
||||||
number = len(owner.graph.blocks) + 1
|
number = len(owner.graph.blocks) + 1
|
||||||
|
base_name = f"New {'Graph' if kind == 'graph' else 'Text'} Block "
|
||||||
component = Component(
|
component = Component(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
name=f"New {'Graph' if kind == 'graph' else 'Text'} Block {number}",
|
name=self._available_component_name(base_name, owner.graph.blocks.values(), number),
|
||||||
implementation_kind=kind,
|
implementation_kind=kind,
|
||||||
icon=Icon(text="Graph" if kind == "graph" else "Text"),
|
icon=Icon(text="Graph" if kind == "graph" else "Text"),
|
||||||
source={"equations": [], "parameters": {}} if kind == "text" else {},
|
source={"equations": "", "parameters": []} if kind == "text" else {},
|
||||||
)
|
)
|
||||||
self.undo_stack.push(AddComponentCommand(self, owner_id, component))
|
self.undo_stack.push(AddComponentCommand(self, owner_id, component))
|
||||||
return component.id
|
return component.id
|
||||||
@@ -211,6 +215,9 @@ class DocumentController(QObject):
|
|||||||
if self.active_component is None or self.active_component.implementation_kind != "graph":
|
if self.active_component is None or self.active_component.implementation_kind != "graph":
|
||||||
raise ValueError("Open a graph component before placing components")
|
raise ValueError("Open a graph component before placing components")
|
||||||
component = clone_component(source)
|
component = clone_component(source)
|
||||||
|
component.name = self._available_component_name(
|
||||||
|
source.name, self.active_component.graph.blocks.values(), 0
|
||||||
|
)
|
||||||
component.x, component.y = position.x(), position.y()
|
component.x, component.y = position.x(), position.y()
|
||||||
self.undo_stack.push(AddComponentCommand(self, self.active_component_id, component))
|
self.undo_stack.push(AddComponentCommand(self, self.active_component_id, component))
|
||||||
return component.id
|
return component.id
|
||||||
@@ -470,7 +477,8 @@ class DocumentController(QObject):
|
|||||||
self,
|
self,
|
||||||
inputs: list[Port],
|
inputs: list[Port],
|
||||||
outputs: list[Port],
|
outputs: list[Port],
|
||||||
source: dict,
|
equations: str,
|
||||||
|
parameters: list[Parameter],
|
||||||
) -> None:
|
) -> None:
|
||||||
component = self.active_component
|
component = self.active_component
|
||||||
if component is None or component.implementation_kind != "text":
|
if component is None or component.implementation_kind != "text":
|
||||||
@@ -479,6 +487,13 @@ class DocumentController(QObject):
|
|||||||
output_ids = [port.id for port in outputs]
|
output_ids = [port.id for port in outputs]
|
||||||
if len(set(input_ids)) != len(input_ids) or len(set(output_ids)) != len(output_ids):
|
if len(set(input_ids)) != len(input_ids) or len(set(output_ids)) != len(output_ids):
|
||||||
raise ValueError("Input and output IDs must be unique")
|
raise ValueError("Input and output IDs must be unique")
|
||||||
|
if any(not port.name.strip() for port in (*inputs, *outputs)):
|
||||||
|
raise ValueError("Every port must have a name")
|
||||||
|
parameter_ids = [parameter.id for parameter in parameters]
|
||||||
|
if len(set(parameter_ids)) != len(parameter_ids):
|
||||||
|
raise ValueError("Parameter IDs must be unique")
|
||||||
|
if any(not parameter.name.strip() for parameter in parameters):
|
||||||
|
raise ValueError("Every parameter must have a name")
|
||||||
if self.document is not None:
|
if self.document is not None:
|
||||||
parent = self.document.find_parent(component.id)
|
parent = self.document.find_parent(component.id)
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
@@ -505,7 +520,10 @@ class DocumentController(QObject):
|
|||||||
new = {
|
new = {
|
||||||
"inputs": [port.to_dict() for port in inputs],
|
"inputs": [port.to_dict() for port in inputs],
|
||||||
"outputs": [port.to_dict() for port in outputs],
|
"outputs": [port.to_dict() for port in outputs],
|
||||||
"source": deepcopy(source),
|
"source": {
|
||||||
|
"equations": equations,
|
||||||
|
"parameters": [parameter.to_dict() for parameter in parameters],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if old != new:
|
if old != new:
|
||||||
self.undo_stack.push(EditTextDefinitionCommand(self, component.id, old, new))
|
self.undo_stack.push(EditTextDefinitionCommand(self, component.id, old, new))
|
||||||
@@ -525,6 +543,9 @@ class DocumentController(QObject):
|
|||||||
component = self.document.find_component(component_id)
|
component = self.document.find_component(component_id)
|
||||||
if component is None:
|
if component is None:
|
||||||
return
|
return
|
||||||
|
siblings = self._component_siblings(component_id)
|
||||||
|
if any(item.id != component_id and item.name == name for item in siblings):
|
||||||
|
raise ValueError(f"A component named {name!r} already exists at this level")
|
||||||
old = {
|
old = {
|
||||||
"name": component.name,
|
"name": component.name,
|
||||||
"icon": component.icon.to_dict(),
|
"icon": component.icon.to_dict(),
|
||||||
@@ -726,10 +747,13 @@ class DocumentController(QObject):
|
|||||||
pairs = [(source, clone_component(source)) for source in source_components]
|
pairs = [(source, clone_component(source)) for source in source_components]
|
||||||
id_map = {source.id: clone.id for source, clone in pairs}
|
id_map = {source.id: clone.id for source, clone in pairs}
|
||||||
blocks = {}
|
blocks = {}
|
||||||
for _source, clone in pairs:
|
used = list(owner.graph.blocks.values())
|
||||||
|
for source, clone in pairs:
|
||||||
|
clone.name = self._available_component_name(source.name, used, 0)
|
||||||
clone.x += offset.x()
|
clone.x += offset.x()
|
||||||
clone.y += offset.y()
|
clone.y += offset.y()
|
||||||
blocks[clone.id] = clone
|
blocks[clone.id] = clone
|
||||||
|
used.append(clone)
|
||||||
connections = {}
|
connections = {}
|
||||||
for source in source_connections:
|
for source in source_connections:
|
||||||
if source.source.block not in id_map or source.target.block not in id_map:
|
if source.source.block not in id_map or source.target.block not in id_map:
|
||||||
@@ -759,6 +783,26 @@ class DocumentController(QObject):
|
|||||||
raise ValueError("The containing component is no longer in the document")
|
raise ValueError("The containing component is no longer in the document")
|
||||||
return owner.graph
|
return owner.graph
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _available_component_name(
|
||||||
|
base: str, components, start: int = 0
|
||||||
|
) -> str:
|
||||||
|
used = {component.name for component in components}
|
||||||
|
number = start
|
||||||
|
while f"{base}{number}" in used:
|
||||||
|
number += 1
|
||||||
|
return f"{base}{number}"
|
||||||
|
|
||||||
|
def _component_siblings(self, component_id: str):
|
||||||
|
if self.document is None:
|
||||||
|
return ()
|
||||||
|
parent = self.document.find_parent(component_id)
|
||||||
|
return (
|
||||||
|
parent.graph.blocks.values()
|
||||||
|
if parent is not None
|
||||||
|
else self.document.roots.values()
|
||||||
|
)
|
||||||
|
|
||||||
def _insert_component(self, owner_id: str | None, component: Component) -> None:
|
def _insert_component(self, owner_id: str | None, component: Component) -> None:
|
||||||
if self.document is None:
|
if self.document is None:
|
||||||
raise ValueError("There is no open document")
|
raise ValueError("There is no open document")
|
||||||
@@ -1039,5 +1083,4 @@ class DocumentController(QObject):
|
|||||||
component.source = deepcopy(values["source"])
|
component.source = deepcopy(values["source"])
|
||||||
self.interfaceChanged.emit()
|
self.interfaceChanged.emit()
|
||||||
self.documentReset.emit()
|
self.documentReset.emit()
|
||||||
if component_id == self.active_component_id:
|
self.textDefinitionChanged.emit(component_id)
|
||||||
self.activeGraphChanged.emit()
|
|
||||||
|
|||||||
1
BEdit/src/bedit/gui/editors/__init__.py
Normal file
1
BEdit/src/bedit/gui/editors/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Reusable editing widgets."""
|
||||||
193
BEdit/src/bedit/gui/editors/text_definition.py
Normal file
193
BEdit/src/bedit/gui/editors/text_definition.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from PySide6.QtCore import Qt, Signal
|
||||||
|
from PySide6.QtWidgets import QComboBox, QHeaderView, QTableWidgetItem, QWidget
|
||||||
|
|
||||||
|
from bedit.core.model import Parameter, Port
|
||||||
|
from bedit.core.port_types import PortTypeRegistry
|
||||||
|
from bedit.gui.generated.ui_text_definition_editor import Ui_TextDefinitionEditor
|
||||||
|
|
||||||
|
|
||||||
|
ID_ROLE = Qt.ItemDataRole.UserRole
|
||||||
|
PROPERTIES_ROLE = Qt.ItemDataRole.UserRole + 1
|
||||||
|
|
||||||
|
|
||||||
|
class TextDefinitionEditor(QWidget):
|
||||||
|
"""Editor for a text component's equations, ports, and parameters."""
|
||||||
|
|
||||||
|
modifiedChanged = Signal(bool)
|
||||||
|
definitionEdited = Signal()
|
||||||
|
|
||||||
|
def __init__(self, parent=None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.ui = Ui_TextDefinitionEditor()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self._modified = False
|
||||||
|
self._loading = False
|
||||||
|
self.ui.portsTable.horizontalHeader().setSectionResizeMode(
|
||||||
|
QHeaderView.ResizeMode.Stretch
|
||||||
|
)
|
||||||
|
self.ui.parametersTable.horizontalHeader().setSectionResizeMode(
|
||||||
|
QHeaderView.ResizeMode.Stretch
|
||||||
|
)
|
||||||
|
self.ui.columnSplitter.setSizes([560, 340])
|
||||||
|
self.ui.definitionSplitter.setSizes([300, 300])
|
||||||
|
self.ui.equationsEdit.textChanged.connect(self._mark_modified)
|
||||||
|
self.ui.portsTable.cellChanged.connect(self._mark_modified)
|
||||||
|
self.ui.parametersTable.cellChanged.connect(self._mark_modified)
|
||||||
|
self.ui.addPortButton.clicked.connect(self.add_port)
|
||||||
|
self.ui.removePortButton.clicked.connect(self.remove_port)
|
||||||
|
self.ui.addParameterButton.clicked.connect(self.add_parameter)
|
||||||
|
self.ui.removeParameterButton.clicked.connect(self.remove_parameter)
|
||||||
|
self.ui.portsTable.itemSelectionChanged.connect(self._update_buttons)
|
||||||
|
self.ui.parametersTable.itemSelectionChanged.connect(self._update_buttons)
|
||||||
|
self._update_buttons()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_modified(self) -> bool:
|
||||||
|
return self._modified
|
||||||
|
|
||||||
|
@property
|
||||||
|
def equations(self) -> str:
|
||||||
|
return self.ui.equationsEdit.toPlainText()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ports(self) -> tuple[list[Port], list[Port]]:
|
||||||
|
inputs: list[Port] = []
|
||||||
|
outputs: list[Port] = []
|
||||||
|
for row in range(self.ui.portsTable.rowCount()):
|
||||||
|
name_item = self.ui.portsTable.item(row, 0)
|
||||||
|
type_combo = self.ui.portsTable.cellWidget(row, 1)
|
||||||
|
orientation_combo = self.ui.portsTable.cellWidget(row, 2)
|
||||||
|
port = Port(
|
||||||
|
id=name_item.data(ID_ROLE),
|
||||||
|
name=name_item.text().strip(),
|
||||||
|
type=type_combo.currentData(),
|
||||||
|
properties=deepcopy(name_item.data(PROPERTIES_ROLE) or {}),
|
||||||
|
)
|
||||||
|
target = inputs if orientation_combo.currentData() == "input" else outputs
|
||||||
|
target.append(port)
|
||||||
|
return inputs, outputs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parameters(self) -> list[Parameter]:
|
||||||
|
table = self.ui.parametersTable
|
||||||
|
return [
|
||||||
|
Parameter(
|
||||||
|
id=table.item(row, 0).data(ID_ROLE),
|
||||||
|
name=table.item(row, 0).text().strip(),
|
||||||
|
type=table.item(row, 1).text().strip(),
|
||||||
|
value=table.item(row, 2).text(),
|
||||||
|
)
|
||||||
|
for row in range(table.rowCount())
|
||||||
|
]
|
||||||
|
|
||||||
|
def set_definition(
|
||||||
|
self,
|
||||||
|
equations: str,
|
||||||
|
inputs: list[Port],
|
||||||
|
outputs: list[Port],
|
||||||
|
parameters: list[Parameter],
|
||||||
|
) -> None:
|
||||||
|
self._loading = True
|
||||||
|
self.ui.equationsEdit.setPlainText(equations)
|
||||||
|
self.ui.portsTable.setRowCount(0)
|
||||||
|
for port in inputs:
|
||||||
|
self._append_port(port, "input")
|
||||||
|
for port in outputs:
|
||||||
|
self._append_port(port, "output")
|
||||||
|
self.ui.parametersTable.setRowCount(0)
|
||||||
|
for parameter in parameters:
|
||||||
|
self._append_parameter(parameter)
|
||||||
|
self._loading = False
|
||||||
|
self.set_modified(False)
|
||||||
|
self._update_buttons()
|
||||||
|
|
||||||
|
def set_modified(self, modified: bool) -> None:
|
||||||
|
if self._modified != modified:
|
||||||
|
self._modified = modified
|
||||||
|
self.modifiedChanged.emit(modified)
|
||||||
|
|
||||||
|
def _mark_modified(self, *_args) -> None:
|
||||||
|
if not self._loading:
|
||||||
|
self.set_modified(True)
|
||||||
|
self.definitionEdited.emit()
|
||||||
|
|
||||||
|
def _new_combo(self, values: list[tuple[str, str]], current: str) -> QComboBox:
|
||||||
|
combo = QComboBox(self)
|
||||||
|
for label, value in values:
|
||||||
|
combo.addItem(label, value)
|
||||||
|
combo.setCurrentIndex(max(0, combo.findData(current)))
|
||||||
|
combo.currentIndexChanged.connect(self._mark_modified)
|
||||||
|
return combo
|
||||||
|
|
||||||
|
def _append_port(self, port: Port, orientation: str) -> None:
|
||||||
|
table = self.ui.portsTable
|
||||||
|
row = table.rowCount()
|
||||||
|
table.insertRow(row)
|
||||||
|
name = QTableWidgetItem(port.name)
|
||||||
|
name.setData(ID_ROLE, port.id)
|
||||||
|
name.setData(PROPERTIES_ROLE, deepcopy(port.properties))
|
||||||
|
table.setItem(row, 0, name)
|
||||||
|
types = [(item.display_name, item.id) for item in PortTypeRegistry.all()]
|
||||||
|
table.setCellWidget(row, 1, self._new_combo(types, port.type))
|
||||||
|
table.setCellWidget(
|
||||||
|
row,
|
||||||
|
2,
|
||||||
|
self._new_combo([("Input", "input"), ("Output", "output")], orientation),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _append_parameter(self, parameter: Parameter) -> None:
|
||||||
|
table = self.ui.parametersTable
|
||||||
|
row = table.rowCount()
|
||||||
|
table.insertRow(row)
|
||||||
|
name = QTableWidgetItem(parameter.name)
|
||||||
|
name.setData(ID_ROLE, parameter.id)
|
||||||
|
table.setItem(row, 0, name)
|
||||||
|
table.setItem(row, 1, QTableWidgetItem(parameter.type))
|
||||||
|
table.setItem(row, 2, QTableWidgetItem(parameter.value))
|
||||||
|
|
||||||
|
def add_port(self) -> None:
|
||||||
|
port = Port(
|
||||||
|
id=f"port-{uuid4().hex[:8]}",
|
||||||
|
name=f"Port {self.ui.portsTable.rowCount() + 1}",
|
||||||
|
type="signal",
|
||||||
|
properties={"iconPosition": {"x": 0.0, "y": 0.0}},
|
||||||
|
)
|
||||||
|
self._loading = True
|
||||||
|
self._append_port(port, "input")
|
||||||
|
self._loading = False
|
||||||
|
self.ui.portsTable.selectRow(self.ui.portsTable.rowCount() - 1)
|
||||||
|
self._mark_modified()
|
||||||
|
|
||||||
|
def remove_port(self) -> None:
|
||||||
|
row = self.ui.portsTable.currentRow()
|
||||||
|
if row >= 0:
|
||||||
|
self.ui.portsTable.removeRow(row)
|
||||||
|
self._mark_modified()
|
||||||
|
self._update_buttons()
|
||||||
|
|
||||||
|
def add_parameter(self) -> None:
|
||||||
|
parameter = Parameter(
|
||||||
|
id=f"parameter-{uuid4().hex[:8]}",
|
||||||
|
name=f"Parameter {self.ui.parametersTable.rowCount() + 1}",
|
||||||
|
)
|
||||||
|
self._loading = True
|
||||||
|
self._append_parameter(parameter)
|
||||||
|
self._loading = False
|
||||||
|
self.ui.parametersTable.selectRow(self.ui.parametersTable.rowCount() - 1)
|
||||||
|
self._mark_modified()
|
||||||
|
|
||||||
|
def remove_parameter(self) -> None:
|
||||||
|
row = self.ui.parametersTable.currentRow()
|
||||||
|
if row >= 0:
|
||||||
|
self.ui.parametersTable.removeRow(row)
|
||||||
|
self._mark_modified()
|
||||||
|
self._update_buttons()
|
||||||
|
|
||||||
|
def _update_buttons(self) -> None:
|
||||||
|
self.ui.removePortButton.setEnabled(self.ui.portsTable.currentRow() >= 0)
|
||||||
|
self.ui.removeParameterButton.setEnabled(
|
||||||
|
self.ui.parametersTable.currentRow() >= 0
|
||||||
|
)
|
||||||
@@ -18,10 +18,11 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
|||||||
QTransform)
|
QTransform)
|
||||||
from PySide6.QtWidgets import (QApplication, QDockWidget, QFrame, QHBoxLayout,
|
from PySide6.QtWidgets import (QApplication, QDockWidget, QFrame, QHBoxLayout,
|
||||||
QHeaderView, QLabel, QMainWindow, QMenu,
|
QHeaderView, QLabel, QMainWindow, QMenu,
|
||||||
QMenuBar, QPlainTextEdit, QPushButton, QSizePolicy,
|
QMenuBar, QSizePolicy, QSpacerItem, QSplitter,
|
||||||
QSpacerItem, QSplitter, QStackedWidget, QToolBar,
|
QStackedWidget, QToolBar, QToolButton, QTreeView,
|
||||||
QToolButton, QTreeView, QVBoxLayout, QWidget)
|
QVBoxLayout, QWidget)
|
||||||
|
|
||||||
|
from bedit.gui.editors.text_definition import TextDefinitionEditor
|
||||||
from bedit.gui.graphics.workspace import GraphWorkspaceView
|
from bedit.gui.graphics.workspace import GraphWorkspaceView
|
||||||
from . import resources_rc
|
from . import resources_rc
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ class Ui_MainWindow(object):
|
|||||||
icon5 = QIcon()
|
icon5 = QIcon()
|
||||||
icon5.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(icon5)
|
self.actionOpen.setIcon(icon5)
|
||||||
|
self.actionReloadLibraries = QAction(MainWindow)
|
||||||
|
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
|
||||||
self.actionSave = QAction(MainWindow)
|
self.actionSave = QAction(MainWindow)
|
||||||
self.actionSave.setObjectName(u"actionSave")
|
self.actionSave.setObjectName(u"actionSave")
|
||||||
icon6 = QIcon()
|
icon6 = QIcon()
|
||||||
@@ -217,12 +220,6 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.workspaceHeaderLayout.addItem(self.workspaceHeaderSpacer)
|
self.workspaceHeaderLayout.addItem(self.workspaceHeaderSpacer)
|
||||||
|
|
||||||
self.applyJsonButton = QPushButton(self.workspaceHeader)
|
|
||||||
self.applyJsonButton.setObjectName(u"applyJsonButton")
|
|
||||||
self.applyJsonButton.setVisible(False)
|
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.applyJsonButton)
|
|
||||||
|
|
||||||
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
self.pointerToolButton = QToolButton(self.workspaceHeader)
|
||||||
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
||||||
icon15 = QIcon()
|
icon15 = QIcon()
|
||||||
@@ -291,18 +288,17 @@ class Ui_MainWindow(object):
|
|||||||
self.graphPageLayout.addWidget(self.graphView)
|
self.graphPageLayout.addWidget(self.graphView)
|
||||||
|
|
||||||
self.workspaceStack.addWidget(self.graphPage)
|
self.workspaceStack.addWidget(self.graphPage)
|
||||||
self.jsonPage = QWidget()
|
self.textPage = QWidget()
|
||||||
self.jsonPage.setObjectName(u"jsonPage")
|
self.textPage.setObjectName(u"textPage")
|
||||||
self.jsonPageLayout = QVBoxLayout(self.jsonPage)
|
self.textPageLayout = QVBoxLayout(self.textPage)
|
||||||
self.jsonPageLayout.setObjectName(u"jsonPageLayout")
|
self.textPageLayout.setObjectName(u"textPageLayout")
|
||||||
self.jsonPageLayout.setContentsMargins(0, 0, 0, 0)
|
self.textPageLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.jsonEditor = QPlainTextEdit(self.jsonPage)
|
self.textDefinitionEditor = TextDefinitionEditor(self.textPage)
|
||||||
self.jsonEditor.setObjectName(u"jsonEditor")
|
self.textDefinitionEditor.setObjectName(u"textDefinitionEditor")
|
||||||
self.jsonEditor.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
|
|
||||||
|
|
||||||
self.jsonPageLayout.addWidget(self.jsonEditor)
|
self.textPageLayout.addWidget(self.textDefinitionEditor)
|
||||||
|
|
||||||
self.workspaceStack.addWidget(self.jsonPage)
|
self.workspaceStack.addWidget(self.textPage)
|
||||||
self.emptyPage = QWidget()
|
self.emptyPage = QWidget()
|
||||||
self.emptyPage.setObjectName(u"emptyPage")
|
self.emptyPage.setObjectName(u"emptyPage")
|
||||||
self.emptyPage.setStyleSheet(u"background-color: #9a9a9a;")
|
self.emptyPage.setStyleSheet(u"background-color: #9a9a9a;")
|
||||||
@@ -364,6 +360,7 @@ class Ui_MainWindow(object):
|
|||||||
self.menubar.addAction(self.menuHelp.menuAction())
|
self.menubar.addAction(self.menuHelp.menuAction())
|
||||||
self.menuFile.addAction(self.actionNew)
|
self.menuFile.addAction(self.actionNew)
|
||||||
self.menuFile.addAction(self.actionOpen)
|
self.menuFile.addAction(self.actionOpen)
|
||||||
|
self.menuFile.addAction(self.actionReloadLibraries)
|
||||||
self.menuFile.addSeparator()
|
self.menuFile.addSeparator()
|
||||||
self.menuFile.addAction(self.actionSave)
|
self.menuFile.addAction(self.actionSave)
|
||||||
self.menuFile.addAction(self.actionSaveAs)
|
self.menuFile.addAction(self.actionSaveAs)
|
||||||
@@ -439,6 +436,13 @@ class Ui_MainWindow(object):
|
|||||||
#endif // QT_CONFIG(statustip)
|
#endif // QT_CONFIG(statustip)
|
||||||
#if QT_CONFIG(shortcut)
|
#if QT_CONFIG(shortcut)
|
||||||
self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
|
self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.actionReloadLibraries.setText(QCoreApplication.translate("MainWindow", u"Reload &Libraries", None))
|
||||||
|
#if QT_CONFIG(statustip)
|
||||||
|
self.actionReloadLibraries.setStatusTip(QCoreApplication.translate("MainWindow", u"Reload configured library files from disk", None))
|
||||||
|
#endif // QT_CONFIG(statustip)
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.actionReloadLibraries.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
|
||||||
#endif // QT_CONFIG(shortcut)
|
#endif // QT_CONFIG(shortcut)
|
||||||
self.actionSave.setText(QCoreApplication.translate("MainWindow", u"&Save", None))
|
self.actionSave.setText(QCoreApplication.translate("MainWindow", u"&Save", None))
|
||||||
#if QT_CONFIG(statustip)
|
#if QT_CONFIG(statustip)
|
||||||
@@ -505,7 +509,6 @@ class Ui_MainWindow(object):
|
|||||||
self.navigateDownButton.setText(QCoreApplication.translate("MainWindow", u"Down", None))
|
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.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)
|
#if QT_CONFIG(tooltip)
|
||||||
@@ -524,7 +527,6 @@ class Ui_MainWindow(object):
|
|||||||
self.rotateToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise", None))
|
self.rotateToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None))
|
self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None))
|
||||||
self.jsonEditor.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Component JSON", None))
|
|
||||||
self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None))
|
self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None))
|
||||||
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))
|
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))
|
||||||
self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"&Edit", None))
|
self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"&Edit", None))
|
||||||
|
|||||||
163
BEdit/src/bedit/gui/generated/ui_text_definition_editor.py
Normal file
163
BEdit/src/bedit/gui/generated/ui_text_definition_editor.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Form generated from reading UI file 'text_definition_editor.ui'
|
||||||
|
##
|
||||||
|
## Created by: Qt User Interface Compiler version 6.11.1
|
||||||
|
##
|
||||||
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||||
|
QMetaObject, QObject, QPoint, QRect,
|
||||||
|
QSize, QTime, QUrl, Qt)
|
||||||
|
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||||
|
QFont, QFontDatabase, QGradient, QIcon,
|
||||||
|
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||||
|
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||||
|
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QGroupBox, QHBoxLayout,
|
||||||
|
QHeaderView, QPlainTextEdit, QPushButton, QSizePolicy,
|
||||||
|
QSpacerItem, QSplitter, QTableWidget, QTableWidgetItem,
|
||||||
|
QVBoxLayout, QWidget)
|
||||||
|
|
||||||
|
class Ui_TextDefinitionEditor(object):
|
||||||
|
def setupUi(self, TextDefinitionEditor):
|
||||||
|
if not TextDefinitionEditor.objectName():
|
||||||
|
TextDefinitionEditor.setObjectName(u"TextDefinitionEditor")
|
||||||
|
TextDefinitionEditor.resize(900, 600)
|
||||||
|
self.editorLayout = QHBoxLayout(TextDefinitionEditor)
|
||||||
|
self.editorLayout.setObjectName(u"editorLayout")
|
||||||
|
self.editorLayout.setContentsMargins(6, 6, 6, 6)
|
||||||
|
self.columnSplitter = QSplitter(TextDefinitionEditor)
|
||||||
|
self.columnSplitter.setObjectName(u"columnSplitter")
|
||||||
|
self.columnSplitter.setOrientation(Qt.Orientation.Horizontal)
|
||||||
|
self.columnSplitter.setChildrenCollapsible(False)
|
||||||
|
self.equationsGroup = QGroupBox(self.columnSplitter)
|
||||||
|
self.equationsGroup.setObjectName(u"equationsGroup")
|
||||||
|
self.equationsLayout = QVBoxLayout(self.equationsGroup)
|
||||||
|
self.equationsLayout.setObjectName(u"equationsLayout")
|
||||||
|
self.equationsEdit = QPlainTextEdit(self.equationsGroup)
|
||||||
|
self.equationsEdit.setObjectName(u"equationsEdit")
|
||||||
|
self.equationsEdit.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
|
||||||
|
|
||||||
|
self.equationsLayout.addWidget(self.equationsEdit)
|
||||||
|
|
||||||
|
self.columnSplitter.addWidget(self.equationsGroup)
|
||||||
|
self.definitionSplitter = QSplitter(self.columnSplitter)
|
||||||
|
self.definitionSplitter.setObjectName(u"definitionSplitter")
|
||||||
|
self.definitionSplitter.setOrientation(Qt.Orientation.Vertical)
|
||||||
|
self.definitionSplitter.setChildrenCollapsible(False)
|
||||||
|
self.portsGroup = QGroupBox(self.definitionSplitter)
|
||||||
|
self.portsGroup.setObjectName(u"portsGroup")
|
||||||
|
self.portsLayout = QVBoxLayout(self.portsGroup)
|
||||||
|
self.portsLayout.setObjectName(u"portsLayout")
|
||||||
|
self.portsTable = QTableWidget(self.portsGroup)
|
||||||
|
if (self.portsTable.columnCount() < 3):
|
||||||
|
self.portsTable.setColumnCount(3)
|
||||||
|
__qtablewidgetitem = QTableWidgetItem()
|
||||||
|
self.portsTable.setHorizontalHeaderItem(0, __qtablewidgetitem)
|
||||||
|
__qtablewidgetitem1 = QTableWidgetItem()
|
||||||
|
self.portsTable.setHorizontalHeaderItem(1, __qtablewidgetitem1)
|
||||||
|
__qtablewidgetitem2 = QTableWidgetItem()
|
||||||
|
self.portsTable.setHorizontalHeaderItem(2, __qtablewidgetitem2)
|
||||||
|
self.portsTable.setObjectName(u"portsTable")
|
||||||
|
self.portsTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
|
self.portsTable.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||||
|
self.portsTable.setColumnCount(3)
|
||||||
|
|
||||||
|
self.portsLayout.addWidget(self.portsTable)
|
||||||
|
|
||||||
|
self.portButtonsLayout = QHBoxLayout()
|
||||||
|
self.portButtonsLayout.setObjectName(u"portButtonsLayout")
|
||||||
|
self.addPortButton = QPushButton(self.portsGroup)
|
||||||
|
self.addPortButton.setObjectName(u"addPortButton")
|
||||||
|
|
||||||
|
self.portButtonsLayout.addWidget(self.addPortButton)
|
||||||
|
|
||||||
|
self.removePortButton = QPushButton(self.portsGroup)
|
||||||
|
self.removePortButton.setObjectName(u"removePortButton")
|
||||||
|
|
||||||
|
self.portButtonsLayout.addWidget(self.removePortButton)
|
||||||
|
|
||||||
|
self.portButtonSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
|
|
||||||
|
self.portButtonsLayout.addItem(self.portButtonSpacer)
|
||||||
|
|
||||||
|
|
||||||
|
self.portsLayout.addLayout(self.portButtonsLayout)
|
||||||
|
|
||||||
|
self.definitionSplitter.addWidget(self.portsGroup)
|
||||||
|
self.parametersGroup = QGroupBox(self.definitionSplitter)
|
||||||
|
self.parametersGroup.setObjectName(u"parametersGroup")
|
||||||
|
self.parametersLayout = QVBoxLayout(self.parametersGroup)
|
||||||
|
self.parametersLayout.setObjectName(u"parametersLayout")
|
||||||
|
self.parametersTable = QTableWidget(self.parametersGroup)
|
||||||
|
if (self.parametersTable.columnCount() < 3):
|
||||||
|
self.parametersTable.setColumnCount(3)
|
||||||
|
__qtablewidgetitem3 = QTableWidgetItem()
|
||||||
|
self.parametersTable.setHorizontalHeaderItem(0, __qtablewidgetitem3)
|
||||||
|
__qtablewidgetitem4 = QTableWidgetItem()
|
||||||
|
self.parametersTable.setHorizontalHeaderItem(1, __qtablewidgetitem4)
|
||||||
|
__qtablewidgetitem5 = QTableWidgetItem()
|
||||||
|
self.parametersTable.setHorizontalHeaderItem(2, __qtablewidgetitem5)
|
||||||
|
self.parametersTable.setObjectName(u"parametersTable")
|
||||||
|
self.parametersTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
|
self.parametersTable.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||||
|
self.parametersTable.setColumnCount(3)
|
||||||
|
|
||||||
|
self.parametersLayout.addWidget(self.parametersTable)
|
||||||
|
|
||||||
|
self.parameterButtonsLayout = QHBoxLayout()
|
||||||
|
self.parameterButtonsLayout.setObjectName(u"parameterButtonsLayout")
|
||||||
|
self.addParameterButton = QPushButton(self.parametersGroup)
|
||||||
|
self.addParameterButton.setObjectName(u"addParameterButton")
|
||||||
|
|
||||||
|
self.parameterButtonsLayout.addWidget(self.addParameterButton)
|
||||||
|
|
||||||
|
self.removeParameterButton = QPushButton(self.parametersGroup)
|
||||||
|
self.removeParameterButton.setObjectName(u"removeParameterButton")
|
||||||
|
|
||||||
|
self.parameterButtonsLayout.addWidget(self.removeParameterButton)
|
||||||
|
|
||||||
|
self.parameterButtonSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
|
|
||||||
|
self.parameterButtonsLayout.addItem(self.parameterButtonSpacer)
|
||||||
|
|
||||||
|
|
||||||
|
self.parametersLayout.addLayout(self.parameterButtonsLayout)
|
||||||
|
|
||||||
|
self.definitionSplitter.addWidget(self.parametersGroup)
|
||||||
|
self.columnSplitter.addWidget(self.definitionSplitter)
|
||||||
|
|
||||||
|
self.editorLayout.addWidget(self.columnSplitter)
|
||||||
|
|
||||||
|
|
||||||
|
self.retranslateUi(TextDefinitionEditor)
|
||||||
|
|
||||||
|
QMetaObject.connectSlotsByName(TextDefinitionEditor)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
|
def retranslateUi(self, TextDefinitionEditor):
|
||||||
|
self.equationsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Equations", None))
|
||||||
|
self.equationsEdit.setPlaceholderText(QCoreApplication.translate("TextDefinitionEditor", u"Enter equations here\u2026", None))
|
||||||
|
self.portsGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Ports", None))
|
||||||
|
___qtablewidgetitem = self.portsTable.horizontalHeaderItem(0)
|
||||||
|
___qtablewidgetitem.setText(QCoreApplication.translate("TextDefinitionEditor", u"Name", None))
|
||||||
|
___qtablewidgetitem1 = self.portsTable.horizontalHeaderItem(1)
|
||||||
|
___qtablewidgetitem1.setText(QCoreApplication.translate("TextDefinitionEditor", u"Type", None))
|
||||||
|
___qtablewidgetitem2 = self.portsTable.horizontalHeaderItem(2)
|
||||||
|
___qtablewidgetitem2.setText(QCoreApplication.translate("TextDefinitionEditor", u"Orientation", None))
|
||||||
|
self.addPortButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Add Port", None))
|
||||||
|
self.removePortButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Remove Port", None))
|
||||||
|
self.parametersGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Parameters", None))
|
||||||
|
___qtablewidgetitem3 = self.parametersTable.horizontalHeaderItem(0)
|
||||||
|
___qtablewidgetitem3.setText(QCoreApplication.translate("TextDefinitionEditor", u"Name", None))
|
||||||
|
___qtablewidgetitem4 = self.parametersTable.horizontalHeaderItem(1)
|
||||||
|
___qtablewidgetitem4.setText(QCoreApplication.translate("TextDefinitionEditor", u"Type", None))
|
||||||
|
___qtablewidgetitem5 = self.parametersTable.horizontalHeaderItem(2)
|
||||||
|
___qtablewidgetitem5.setText(QCoreApplication.translate("TextDefinitionEditor", u"Value", None))
|
||||||
|
self.addParameterButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Add Parameter", None))
|
||||||
|
self.removeParameterButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Remove Parameter", None))
|
||||||
|
pass
|
||||||
|
# retranslateUi
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ from PySide6.QtWidgets import (
|
|||||||
QTabWidget,
|
QTabWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from bedit.core.model import Component, Port
|
from bedit.core.model import Component, Parameter
|
||||||
from bedit.core.serializer import JsonDocumentSerializer
|
from bedit.core.serializer import JsonDocumentSerializer
|
||||||
from bedit.gui.controllers.document import DocumentController
|
from bedit.gui.controllers.document import DocumentController
|
||||||
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
|
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
|
||||||
@@ -40,6 +39,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui = Ui_MainWindow()
|
self.ui = Ui_MainWindow()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.settings = application_settings()
|
self.settings = application_settings()
|
||||||
|
self._applying_text_definition = False
|
||||||
|
|
||||||
self.libraries = LibraryRepository(self)
|
self.libraries = LibraryRepository(self)
|
||||||
self.document_controller = DocumentController(self)
|
self.document_controller = DocumentController(self)
|
||||||
@@ -108,12 +108,18 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.lineToolButton.clicked.connect(lambda: self.set_graph_tool("line"))
|
self.ui.lineToolButton.clicked.connect(lambda: self.set_graph_tool("line"))
|
||||||
self.ui.textToolButton.clicked.connect(lambda: self.set_graph_tool("text"))
|
self.ui.textToolButton.clicked.connect(lambda: self.set_graph_tool("text"))
|
||||||
self.ui.rotateToolButton.clicked.connect(self.ui.graphView.rotate_selected)
|
self.ui.rotateToolButton.clicked.connect(self.ui.graphView.rotate_selected)
|
||||||
self.ui.applyJsonButton.clicked.connect(self.apply_json)
|
self.ui.textDefinitionEditor.definitionEdited.connect(
|
||||||
|
self.apply_text_definition
|
||||||
|
)
|
||||||
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
||||||
|
self.document_controller.textDefinitionChanged.connect(
|
||||||
|
self._text_definition_changed
|
||||||
|
)
|
||||||
|
|
||||||
def _connect_actions(self) -> None:
|
def _connect_actions(self) -> None:
|
||||||
self.ui.actionNew.triggered.connect(self.new_document)
|
self.ui.actionNew.triggered.connect(self.new_document)
|
||||||
self.ui.actionOpen.triggered.connect(self.open_document)
|
self.ui.actionOpen.triggered.connect(self.open_document)
|
||||||
|
self.ui.actionReloadLibraries.triggered.connect(self.reload_libraries)
|
||||||
self.ui.actionSave.triggered.connect(self.save_document)
|
self.ui.actionSave.triggered.connect(self.save_document)
|
||||||
self.ui.actionSaveAs.triggered.connect(self.save_document_as)
|
self.ui.actionSaveAs.triggered.connect(self.save_document_as)
|
||||||
self.ui.actionClose.triggered.connect(self.close_document)
|
self.ui.actionClose.triggered.connect(self.close_document)
|
||||||
@@ -189,7 +195,6 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.navigateUpButton.setEnabled(False)
|
self.ui.navigateUpButton.setEnabled(False)
|
||||||
self.ui.workspaceModeLabel.setText("")
|
self.ui.workspaceModeLabel.setText("")
|
||||||
self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage)
|
self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage)
|
||||||
self.ui.applyJsonButton.setVisible(False)
|
|
||||||
self._set_graph_controls_visible(False)
|
self._set_graph_controls_visible(False)
|
||||||
self._update_edit_actions()
|
self._update_edit_actions()
|
||||||
return
|
return
|
||||||
@@ -199,13 +204,14 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
is_graph = component.implementation_kind == "graph"
|
is_graph = component.implementation_kind == "graph"
|
||||||
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
|
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
|
||||||
self.ui.workspaceStack.setCurrentWidget(self.ui.graphPage if is_graph else self.ui.jsonPage)
|
self.ui.workspaceStack.setCurrentWidget(
|
||||||
self.ui.applyJsonButton.setVisible(not is_graph)
|
self.ui.graphPage if is_graph else self.ui.textPage
|
||||||
|
)
|
||||||
self._set_graph_controls_visible(is_graph)
|
self._set_graph_controls_visible(is_graph)
|
||||||
if is_graph:
|
if is_graph:
|
||||||
self.set_graph_tool("pointer")
|
self.set_graph_tool("pointer")
|
||||||
else:
|
else:
|
||||||
self._load_source_json()
|
self._load_text_definition()
|
||||||
self._update_edit_actions()
|
self._update_edit_actions()
|
||||||
|
|
||||||
def _update_edit_actions(self) -> None:
|
def _update_edit_actions(self) -> None:
|
||||||
@@ -259,62 +265,70 @@ class MainWindow(QMainWindow):
|
|||||||
"text": self.ui.textToolButton,
|
"text": self.ui.textToolButton,
|
||||||
}[mode].setChecked(True)
|
}[mode].setChecked(True)
|
||||||
|
|
||||||
def _load_source_json(self) -> None:
|
def _load_text_definition(self) -> None:
|
||||||
component = self.document_controller.active_component
|
component = self.document_controller.active_component
|
||||||
if component is None:
|
if component is None:
|
||||||
return
|
return
|
||||||
text = json.dumps(
|
self.ui.textDefinitionEditor.set_definition(
|
||||||
{
|
component.source.get("equations", ""),
|
||||||
"inputs": [port.to_dict() for port in component.inputs],
|
component.inputs,
|
||||||
"outputs": [port.to_dict() for port in component.outputs],
|
component.outputs,
|
||||||
"source": component.source,
|
[
|
||||||
},
|
Parameter.from_dict(parameter)
|
||||||
indent=2,
|
for parameter in component.source.get("parameters", [])
|
||||||
|
],
|
||||||
)
|
)
|
||||||
self.ui.jsonEditor.setPlainText(text)
|
|
||||||
self.ui.jsonEditor.document().setModified(False)
|
|
||||||
|
|
||||||
def _resolve_source_edits(self) -> bool:
|
def _resolve_source_edits(self) -> bool:
|
||||||
component = self.document_controller.active_component
|
component = self.document_controller.active_component
|
||||||
if (
|
if (
|
||||||
component is None
|
component is None
|
||||||
or component.implementation_kind != "text"
|
or component.implementation_kind != "text"
|
||||||
or not self.ui.jsonEditor.document().isModified()
|
or not self.ui.textDefinitionEditor.is_modified
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
answer = QMessageBox.question(
|
answer = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Apply text component changes?",
|
"Apply text component changes?",
|
||||||
"The text component has unapplied input, output, or source changes.",
|
"The text component has unapplied equation, port, or parameter changes.",
|
||||||
QMessageBox.StandardButton.Apply
|
QMessageBox.StandardButton.Apply
|
||||||
| QMessageBox.StandardButton.Discard
|
| QMessageBox.StandardButton.Discard
|
||||||
| QMessageBox.StandardButton.Cancel,
|
| QMessageBox.StandardButton.Cancel,
|
||||||
)
|
)
|
||||||
if answer == QMessageBox.StandardButton.Apply:
|
if answer == QMessageBox.StandardButton.Apply:
|
||||||
return self.apply_json()
|
return self.apply_text_definition()
|
||||||
return answer == QMessageBox.StandardButton.Discard
|
return answer == QMessageBox.StandardButton.Discard
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def apply_json(self) -> bool:
|
def apply_text_definition(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = json.loads(self.ui.jsonEditor.toPlainText())
|
inputs, outputs = self.ui.textDefinitionEditor.ports
|
||||||
if not isinstance(data, dict):
|
self._applying_text_definition = True
|
||||||
raise ValueError("The text component JSON must be an object")
|
try:
|
||||||
if not isinstance(data.get("inputs"), list):
|
self.document_controller.replace_active_text_definition(
|
||||||
raise ValueError("'inputs' must be a list")
|
inputs,
|
||||||
if not isinstance(data.get("outputs"), list):
|
outputs,
|
||||||
raise ValueError("'outputs' must be a list")
|
self.ui.textDefinitionEditor.equations,
|
||||||
if not isinstance(data.get("source"), dict):
|
self.ui.textDefinitionEditor.parameters,
|
||||||
raise ValueError("'source' must be an object")
|
)
|
||||||
inputs = [Port.from_dict(item) for item in data["inputs"]]
|
finally:
|
||||||
outputs = [Port.from_dict(item) for item in data["outputs"]]
|
self._applying_text_definition = False
|
||||||
self.document_controller.replace_active_text_definition(inputs, outputs, data["source"])
|
except (TypeError, ValueError) as error:
|
||||||
except (TypeError, ValueError, json.JSONDecodeError) as error:
|
QMessageBox.critical(self, "Invalid text component", str(error))
|
||||||
QMessageBox.critical(self, "Invalid text component JSON", str(error))
|
|
||||||
return False
|
return False
|
||||||
self._load_source_json()
|
self.ui.textDefinitionEditor.set_modified(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def _text_definition_changed(self, component_id: str) -> None:
|
||||||
|
component = self.document_controller.active_component
|
||||||
|
if (
|
||||||
|
component is not None
|
||||||
|
and component.id == component_id
|
||||||
|
and not self._applying_text_definition
|
||||||
|
):
|
||||||
|
self._load_text_definition()
|
||||||
|
|
||||||
def _maybe_save(self) -> bool:
|
def _maybe_save(self) -> bool:
|
||||||
if self.document_controller.document is None:
|
if self.document_controller.document is None:
|
||||||
return True
|
return True
|
||||||
@@ -436,9 +450,9 @@ class MainWindow(QMainWindow):
|
|||||||
ports_action = menu.addAction("Port Options…")
|
ports_action = menu.addAction("Port Options…")
|
||||||
delete_action = menu.addAction("Delete")
|
delete_action = menu.addAction("Delete")
|
||||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||||
if selected is graph_action:
|
if graph_action is not None and selected is graph_action:
|
||||||
self.document_controller.add_child(component_id, "graph")
|
self.document_controller.add_child(component_id, "graph")
|
||||||
elif selected is text_action:
|
elif text_action is not None and selected is text_action:
|
||||||
self.document_controller.add_child(component_id, "text")
|
self.document_controller.add_child(component_id, "text")
|
||||||
elif selected is options_action:
|
elif selected is options_action:
|
||||||
self.show_component_options(component_id)
|
self.show_component_options(component_id)
|
||||||
@@ -521,15 +535,18 @@ class MainWindow(QMainWindow):
|
|||||||
return
|
return
|
||||||
dialog = ComponentOptionsDialog(component, self)
|
dialog = ComponentOptionsDialog(component, self)
|
||||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||||
self.document_controller.edit_component_appearance(
|
try:
|
||||||
component_id,
|
self.document_controller.edit_component_appearance(
|
||||||
dialog.ui.nameEdit.text().strip(),
|
component_id,
|
||||||
dialog.edited_icon,
|
dialog.ui.nameEdit.text().strip(),
|
||||||
dialog.edited_inputs,
|
dialog.edited_icon,
|
||||||
dialog.edited_outputs,
|
dialog.edited_inputs,
|
||||||
dialog.ui.showSubtreeCheckBox.isChecked(),
|
dialog.edited_outputs,
|
||||||
dialog.ui.showNameCheckBox.isChecked(),
|
dialog.ui.showSubtreeCheckBox.isChecked(),
|
||||||
)
|
dialog.ui.showNameCheckBox.isChecked(),
|
||||||
|
)
|
||||||
|
except ValueError as error:
|
||||||
|
QMessageBox.warning(self, "Cannot rename component", str(error))
|
||||||
|
|
||||||
@Slot(str, str)
|
@Slot(str, str)
|
||||||
def show_port_options(self, port_id: str, direction: str) -> None:
|
def show_port_options(self, port_id: str, direction: str) -> None:
|
||||||
|
|||||||
@@ -264,16 +264,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<widget class="QToolButton" name="pointerToolButton">
|
<widget class="QToolButton" name="pointerToolButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -397,8 +387,8 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="jsonPage">
|
<widget class="QWidget" name="textPage">
|
||||||
<layout class="QVBoxLayout" name="jsonPageLayout">
|
<layout class="QVBoxLayout" name="textPageLayout">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
@@ -412,14 +402,7 @@
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="jsonEditor">
|
<widget class="TextDefinitionEditor" name="textDefinitionEditor" native="true"/>
|
||||||
<property name="lineWrapMode">
|
|
||||||
<enum>QPlainTextEdit::LineWrapMode::NoWrap</enum>
|
|
||||||
</property>
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Component JSON</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@@ -466,6 +449,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="actionNew"/>
|
<addaction name="actionNew"/>
|
||||||
<addaction name="actionOpen"/>
|
<addaction name="actionOpen"/>
|
||||||
|
<addaction name="actionReloadLibraries"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionSave"/>
|
<addaction name="actionSave"/>
|
||||||
<addaction name="actionSaveAs"/>
|
<addaction name="actionSaveAs"/>
|
||||||
@@ -667,6 +651,17 @@
|
|||||||
<string>Ctrl+O</string>
|
<string>Ctrl+O</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionReloadLibraries">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reload &Libraries</string>
|
||||||
|
</property>
|
||||||
|
<property name="statusTip">
|
||||||
|
<string>Reload configured library files from disk</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>F5</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionSave">
|
<action name="actionSave">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../resources/resources.qrc">
|
<iconset resource="../resources/resources.qrc">
|
||||||
@@ -806,6 +801,12 @@
|
|||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>TextDefinitionEditor</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>bedit.gui.editors.text_definition</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>GraphWorkspaceView</class>
|
<class>GraphWorkspaceView</class>
|
||||||
<extends>QGraphicsView</extends>
|
<extends>QGraphicsView</extends>
|
||||||
|
|||||||
45
BEdit/ui/text_definition_editor.ui
Normal file
45
BEdit/ui/text_definition_editor.ui
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TextDefinitionEditor</class>
|
||||||
|
<widget class="QWidget" name="TextDefinitionEditor">
|
||||||
|
<property name="geometry"><rect><x>0</x><y>0</y><width>900</width><height>600</height></rect></property>
|
||||||
|
<layout class="QHBoxLayout" name="editorLayout">
|
||||||
|
<property name="leftMargin"><number>6</number></property>
|
||||||
|
<property name="topMargin"><number>6</number></property>
|
||||||
|
<property name="rightMargin"><number>6</number></property>
|
||||||
|
<property name="bottomMargin"><number>6</number></property>
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="columnSplitter">
|
||||||
|
<property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property>
|
||||||
|
<property name="childrenCollapsible"><bool>false</bool></property>
|
||||||
|
<widget class="QGroupBox" name="equationsGroup">
|
||||||
|
<property name="title"><string>Equations</string></property>
|
||||||
|
<layout class="QVBoxLayout" name="equationsLayout">
|
||||||
|
<item><widget class="QPlainTextEdit" name="equationsEdit"><property name="lineWrapMode"><enum>QPlainTextEdit::LineWrapMode::NoWrap</enum></property><property name="placeholderText"><string>Enter equations here…</string></property></widget></item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QSplitter" name="definitionSplitter">
|
||||||
|
<property name="orientation"><enum>Qt::Orientation::Vertical</enum></property>
|
||||||
|
<property name="childrenCollapsible"><bool>false</bool></property>
|
||||||
|
<widget class="QGroupBox" name="portsGroup">
|
||||||
|
<property name="title"><string>Ports</string></property>
|
||||||
|
<layout class="QVBoxLayout" name="portsLayout">
|
||||||
|
<item><widget class="QTableWidget" name="portsTable"><property name="selectionBehavior"><enum>QAbstractItemView::SelectionBehavior::SelectRows</enum></property><property name="selectionMode"><enum>QAbstractItemView::SelectionMode::SingleSelection</enum></property><property name="columnCount"><number>3</number></property><column><property name="text"><string>Name</string></property></column><column><property name="text"><string>Type</string></property></column><column><property name="text"><string>Orientation</string></property></column></widget></item>
|
||||||
|
<item><layout class="QHBoxLayout" name="portButtonsLayout"><item><widget class="QPushButton" name="addPortButton"><property name="text"><string>Add Port</string></property></widget></item><item><widget class="QPushButton" name="removePortButton"><property name="text"><string>Remove Port</string></property></widget></item><item><spacer name="portButtonSpacer"><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></layout></item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QGroupBox" name="parametersGroup">
|
||||||
|
<property name="title"><string>Parameters</string></property>
|
||||||
|
<layout class="QVBoxLayout" name="parametersLayout">
|
||||||
|
<item><widget class="QTableWidget" name="parametersTable"><property name="selectionBehavior"><enum>QAbstractItemView::SelectionBehavior::SelectRows</enum></property><property name="selectionMode"><enum>QAbstractItemView::SelectionMode::SingleSelection</enum></property><property name="columnCount"><number>3</number></property><column><property name="text"><string>Name</string></property></column><column><property name="text"><string>Type</string></property></column><column><property name="text"><string>Value</string></property></column></widget></item>
|
||||||
|
<item><layout class="QHBoxLayout" name="parameterButtonsLayout"><item><widget class="QPushButton" name="addParameterButton"><property name="text"><string>Add Parameter</string></property></widget></item><item><widget class="QPushButton" name="removeParameterButton"><property name="text"><string>Remove Parameter</string></property></widget></item><item><spacer name="parameterButtonSpacer"><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></layout></item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"roots": [
|
"roots": [
|
||||||
{
|
{
|
||||||
"id": "fd375563-d8cb-4c7b-b686-cd6f1edbb654",
|
"id": "b53c8186-0926-46b1-80ec-51a22ac5e0c6",
|
||||||
"name": "New Graph Block 1",
|
"name": "New Graph Block 1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
@@ -58,242 +58,8 @@
|
|||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "graph",
|
"kind": "graph",
|
||||||
"graph": {
|
"graph": {
|
||||||
"blocks": [
|
"blocks": [],
|
||||||
{
|
"connections": [],
|
||||||
"id": "ffca7e20-ca0f-4872-985a-715dcd985052",
|
|
||||||
"name": "test A",
|
|
||||||
"position": {
|
|
||||||
"x": -224.0,
|
|
||||||
"y": -96.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": {
|
|
||||||
"showName": true,
|
|
||||||
"nameLabelPosition": {
|
|
||||||
"x": 48.0,
|
|
||||||
"y": 104.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "text",
|
|
||||||
"source": {
|
|
||||||
"equations": [],
|
|
||||||
"parameters": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "571fd107-61ad-496a-8270-014e5697fcd1",
|
|
||||||
"name": "test B",
|
|
||||||
"position": {
|
|
||||||
"x": 96.0,
|
|
||||||
"y": -160.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": {
|
|
||||||
"showName": true
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "graph",
|
|
||||||
"graph": {
|
|
||||||
"blocks": [],
|
|
||||||
"connections": [],
|
|
||||||
"annotations": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"connections": [
|
|
||||||
{
|
|
||||||
"id": "df5ecb33-5451-4295-b68d-20886d86f0f4",
|
|
||||||
"source": {
|
|
||||||
"block": "ffca7e20-ca0f-4872-985a-715dcd985052",
|
|
||||||
"port": "port-de109124"
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"block": "571fd107-61ad-496a-8270-014e5697fcd1",
|
|
||||||
"port": "port-4f732b3e"
|
|
||||||
},
|
|
||||||
"name": "conn",
|
|
||||||
"properties": {
|
|
||||||
"waypoints": [],
|
|
||||||
"showName": true,
|
|
||||||
"nameLabelPosition": {
|
|
||||||
"x": -8.0,
|
|
||||||
"y": -56.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"annotations": []
|
"annotations": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user