diff --git a/BEdit/AGENTS.md b/BEdit/AGENTS.md
index 460fa21..64240e4 100644
--- a/BEdit/AGENTS.md
+++ b/BEdit/AGENTS.md
@@ -121,6 +121,9 @@ pyside6-uic --from-imports ui/shape_options_dialog.ui \
pyside6-uic --from-imports ui/icon_editor_dialog.ui \
-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
diff --git a/BEdit/src/bedit/core/libraries.py b/BEdit/src/bedit/core/libraries.py
index 8da3b21..0d3d51d 100644
--- a/BEdit/src/bedit/core/libraries.py
+++ b/BEdit/src/bedit/core/libraries.py
@@ -13,7 +13,7 @@ class LibraryDocument:
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]:
diff --git a/BEdit/src/bedit/core/model.py b/BEdit/src/bedit/core/model.py
index b918133..52da2f4 100644
--- a/BEdit/src/bedit/core/model.py
+++ b/BEdit/src/bedit/core/model.py
@@ -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
class Annotation:
id: str
@@ -271,6 +295,19 @@ class Component:
kind = str(implementation.get("kind", "graph"))
if kind not in {"graph", "text"}:
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(
id=str(data["id"]),
name=str(data.get("name", "Unnamed")),
@@ -284,7 +321,7 @@ class Component:
show_subtree_in_library=bool(data.get("library", {}).get("showSubtree", True)),
implementation_kind=kind,
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:
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():
if component.id in seen:
raise ValueError(f"Duplicate component ID: {component.id}")
@@ -358,6 +398,11 @@ class GraphDocument:
@staticmethod
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}
output_ids = {port.id for port in owner.outputs}
if len(input_ids) != len(owner.inputs) or len(output_ids) != len(owner.outputs):
diff --git a/BEdit/src/bedit/data/libraries/example.json b/BEdit/src/bedit/data/libraries/default.json
similarity index 66%
rename from BEdit/src/bedit/data/libraries/example.json
rename to BEdit/src/bedit/data/libraries/default.json
index 2b788b4..bcf84c9 100644
--- a/BEdit/src/bedit/data/libraries/example.json
+++ b/BEdit/src/bedit/data/libraries/default.json
@@ -2,12 +2,12 @@
"format": "bedit-document",
"version": 1,
"metadata": {
- "name": "Example"
+ "name": "Untitled"
},
"roots": [
{
- "id": "bf714517-e7b2-4f4b-8b53-55642e3beb28",
- "name": "A",
+ "id": "7ad651ef-6f76-4064-81d3-3fd6dd4d2918",
+ "name": "gain",
"position": {
"x": 0.0,
"y": 0.0
@@ -16,15 +16,15 @@
"interface": {
"inputs": [
{
- "id": "port-5c5d4695",
- "name": "Port 1",
+ "id": "port-df34ce84",
+ "name": "in",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
- "x": 32.0,
+ "x": 64.0,
"y": 64.0
}
},
@@ -33,16 +33,16 @@
],
"outputs": [
{
- "id": "port-de109124",
- "name": "Port 2",
+ "id": "port-fe9e6486",
+ "name": "out",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
- "x": 96.0,
- "y": 64.0
+ "x": 88.0,
+ "y": 40.0
}
},
"type": "signal"
@@ -77,31 +77,40 @@
"y": 40.0,
"width": 48.0,
"height": 48.0,
- "text": "A",
- "color": "#303030",
- "fontSize": 12.0,
+ "text": "K",
+ "color": "#00007f",
+ "fontSize": 24.0,
"lineStyle": "solid",
"lineWidth": 1.5,
- "stroke": "#303030",
+ "stroke": "#00007f",
"fill": "#ffffff"
}
]
},
- "properties": {},
+ "properties": {
+ "showName": false
+ },
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
- "equations": [],
- "parameters": {}
+ "equations": "out = k*in;",
+ "parameters": [
+ {
+ "id": "parameter-61a43a86",
+ "name": "k",
+ "type": "real",
+ "value": "1"
+ }
+ ]
}
}
},
{
- "id": "73c9d0c0-293d-4a55-8b89-a1f095dfa75f",
- "name": "B",
+ "id": "12b21b11-8828-4a90-a561-19491dc9632e",
+ "name": "differentiate",
"position": {
"x": 0.0,
"y": 0.0
@@ -110,31 +119,16 @@
"interface": {
"inputs": [
{
- "id": "port-4f732b3e",
- "name": "Port 1",
+ "id": "port-1cbabc8f",
+ "name": "in",
"position": {
- "x": -176.0,
- "y": -144.0
+ "x": 0.0,
+ "y": 0.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
+ "x": 64.0,
+ "y": 64.0
}
},
"type": "signal"
@@ -142,16 +136,16 @@
],
"outputs": [
{
- "id": "port-b68679b2",
- "name": "Port 3",
+ "id": "port-4eeea4e7",
+ "name": "out",
"position": {
- "x": 128.0,
- "y": -80.0
+ "x": 0.0,
+ "y": 0.0
},
"properties": {
"iconPosition": {
- "x": 96.0,
- "y": 64.0
+ "x": 88.0,
+ "y": 40.0
}
},
"type": "signal"
@@ -162,7 +156,7 @@
"shape": "rectangle",
"fill": "#f4f4f4",
"border": "#303030",
- "text": "Graph",
+ "text": "Text",
"size": {
"width": 128.0,
"height": 128.0
@@ -186,25 +180,34 @@
"y": 40.0,
"width": 48.0,
"height": 48.0,
- "text": "B",
- "color": "#303030",
- "fontSize": 12.0,
+ "text": "d/dt",
+ "color": "#00007f",
+ "fontSize": 18.0,
"lineStyle": "solid",
"lineWidth": 1.5,
- "stroke": "#303030",
+ "stroke": "#00007f",
"fill": "#ffffff"
}
]
},
- "properties": {},
+ "properties": {
+ "showName": false
+ },
"library": {
"showSubtree": true
},
"implementation": {
- "kind": "graph",
- "graph": {
- "blocks": [],
- "connections": []
+ "kind": "text",
+ "source": {
+ "equations": "initial out = initial;\nout = der(in);",
+ "parameters": [
+ {
+ "id": "parameter-331e38be",
+ "name": "initial",
+ "type": "real",
+ "value": "0"
+ }
+ ]
}
}
}
diff --git a/BEdit/src/bedit/gui/controllers/commands.py b/BEdit/src/bedit/gui/controllers/commands.py
index 82b4a76..eda5285 100644
--- a/BEdit/src/bedit/gui/controllers/commands.py
+++ b/BEdit/src/bedit/gui/controllers/commands.py
@@ -322,3 +322,14 @@ class EditTextDefinitionCommand(QUndoCommand):
def undo(self) -> None:
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
diff --git a/BEdit/src/bedit/gui/controllers/document.py b/BEdit/src/bedit/gui/controllers/document.py
index 777149b..87a11c2 100644
--- a/BEdit/src/bedit/gui/controllers/document.py
+++ b/BEdit/src/bedit/gui/controllers/document.py
@@ -31,6 +31,7 @@ from bedit.core.model import (
Endpoint,
GraphDocument,
Icon,
+ Parameter,
Port,
clone_component,
)
@@ -47,6 +48,7 @@ class DocumentController(QObject):
componentMoved = Signal(str, QPointF)
componentRotated = Signal(str, float)
componentPropertiesChanged = Signal(str)
+ textDefinitionChanged = Signal(str)
connectionAdded = Signal(str)
connectionRemoved = Signal(str)
graphItemChanged = Signal(str, str)
@@ -153,12 +155,13 @@ class DocumentController(QObject):
if self.document is None:
raise ValueError("Open or create a document first")
number = len(self.document.roots) + 1
+ base_name = f"New {'Graph' if kind == 'graph' else 'Text'} Block "
component = Component(
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,
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.activate_component(component.id)
@@ -171,12 +174,13 @@ class DocumentController(QObject):
if owner is None or owner.implementation_kind != "graph":
raise ValueError("Children can only be added to graph components")
number = len(owner.graph.blocks) + 1
+ base_name = f"New {'Graph' if kind == 'graph' else 'Text'} Block "
component = Component(
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,
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))
return component.id
@@ -211,6 +215,9 @@ class DocumentController(QObject):
if self.active_component is None or self.active_component.implementation_kind != "graph":
raise ValueError("Open a graph component before placing components")
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()
self.undo_stack.push(AddComponentCommand(self, self.active_component_id, component))
return component.id
@@ -470,7 +477,8 @@ class DocumentController(QObject):
self,
inputs: list[Port],
outputs: list[Port],
- source: dict,
+ equations: str,
+ parameters: list[Parameter],
) -> None:
component = self.active_component
if component is None or component.implementation_kind != "text":
@@ -479,6 +487,13 @@ class DocumentController(QObject):
output_ids = [port.id for port in outputs]
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")
+ 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:
parent = self.document.find_parent(component.id)
if parent is not None:
@@ -505,7 +520,10 @@ class DocumentController(QObject):
new = {
"inputs": [port.to_dict() for port in inputs],
"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:
self.undo_stack.push(EditTextDefinitionCommand(self, component.id, old, new))
@@ -525,6 +543,9 @@ class DocumentController(QObject):
component = self.document.find_component(component_id)
if component is None:
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 = {
"name": component.name,
"icon": component.icon.to_dict(),
@@ -726,10 +747,13 @@ class DocumentController(QObject):
pairs = [(source, clone_component(source)) for source in source_components]
id_map = {source.id: clone.id for source, clone in pairs}
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.y += offset.y()
blocks[clone.id] = clone
+ used.append(clone)
connections = {}
for source in source_connections:
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")
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:
if self.document is None:
raise ValueError("There is no open document")
@@ -1039,5 +1083,4 @@ class DocumentController(QObject):
component.source = deepcopy(values["source"])
self.interfaceChanged.emit()
self.documentReset.emit()
- if component_id == self.active_component_id:
- self.activeGraphChanged.emit()
+ self.textDefinitionChanged.emit(component_id)
diff --git a/BEdit/src/bedit/gui/editors/__init__.py b/BEdit/src/bedit/gui/editors/__init__.py
new file mode 100644
index 0000000..c0cb643
--- /dev/null
+++ b/BEdit/src/bedit/gui/editors/__init__.py
@@ -0,0 +1 @@
+"""Reusable editing widgets."""
diff --git a/BEdit/src/bedit/gui/editors/text_definition.py b/BEdit/src/bedit/gui/editors/text_definition.py
new file mode 100644
index 0000000..df2d0cc
--- /dev/null
+++ b/BEdit/src/bedit/gui/editors/text_definition.py
@@ -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
+ )
diff --git a/BEdit/src/bedit/gui/generated/ui_main_window.py b/BEdit/src/bedit/gui/generated/ui_main_window.py
index 10c11df..24f45c0 100644
--- a/BEdit/src/bedit/gui/generated/ui_main_window.py
+++ b/BEdit/src/bedit/gui/generated/ui_main_window.py
@@ -18,10 +18,11 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QTransform)
from PySide6.QtWidgets import (QApplication, QDockWidget, QFrame, QHBoxLayout,
QHeaderView, QLabel, QMainWindow, QMenu,
- QMenuBar, QPlainTextEdit, QPushButton, QSizePolicy,
- QSpacerItem, QSplitter, QStackedWidget, QToolBar,
- QToolButton, QTreeView, QVBoxLayout, QWidget)
+ QMenuBar, QSizePolicy, QSpacerItem, QSplitter,
+ QStackedWidget, QToolBar, QToolButton, QTreeView,
+ QVBoxLayout, QWidget)
+from bedit.gui.editors.text_definition import TextDefinitionEditor
from bedit.gui.graphics.workspace import GraphWorkspaceView
from . import resources_rc
@@ -60,6 +61,8 @@ class Ui_MainWindow(object):
icon5 = QIcon()
icon5.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionOpen.setIcon(icon5)
+ self.actionReloadLibraries = QAction(MainWindow)
+ self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
self.actionSave = QAction(MainWindow)
self.actionSave.setObjectName(u"actionSave")
icon6 = QIcon()
@@ -217,12 +220,6 @@ class Ui_MainWindow(object):
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.setObjectName(u"pointerToolButton")
icon15 = QIcon()
@@ -291,18 +288,17 @@ class Ui_MainWindow(object):
self.graphPageLayout.addWidget(self.graphView)
self.workspaceStack.addWidget(self.graphPage)
- self.jsonPage = QWidget()
- self.jsonPage.setObjectName(u"jsonPage")
- self.jsonPageLayout = QVBoxLayout(self.jsonPage)
- self.jsonPageLayout.setObjectName(u"jsonPageLayout")
- self.jsonPageLayout.setContentsMargins(0, 0, 0, 0)
- self.jsonEditor = QPlainTextEdit(self.jsonPage)
- self.jsonEditor.setObjectName(u"jsonEditor")
- self.jsonEditor.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
+ self.textPage = QWidget()
+ self.textPage.setObjectName(u"textPage")
+ self.textPageLayout = QVBoxLayout(self.textPage)
+ self.textPageLayout.setObjectName(u"textPageLayout")
+ self.textPageLayout.setContentsMargins(0, 0, 0, 0)
+ self.textDefinitionEditor = TextDefinitionEditor(self.textPage)
+ self.textDefinitionEditor.setObjectName(u"textDefinitionEditor")
- 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.setObjectName(u"emptyPage")
self.emptyPage.setStyleSheet(u"background-color: #9a9a9a;")
@@ -364,6 +360,7 @@ class Ui_MainWindow(object):
self.menubar.addAction(self.menuHelp.menuAction())
self.menuFile.addAction(self.actionNew)
self.menuFile.addAction(self.actionOpen)
+ self.menuFile.addAction(self.actionReloadLibraries)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionSave)
self.menuFile.addAction(self.actionSaveAs)
@@ -439,6 +436,13 @@ class Ui_MainWindow(object):
#endif // QT_CONFIG(statustip)
#if QT_CONFIG(shortcut)
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)
self.actionSave.setText(QCoreApplication.translate("MainWindow", u"&Save", None))
#if QT_CONFIG(statustip)
@@ -505,7 +509,6 @@ class Ui_MainWindow(object):
self.navigateDownButton.setText(QCoreApplication.translate("MainWindow", u"Down", None))
self.graphBreadcrumbLabel.setText(QCoreApplication.translate("MainWindow", u"Untitled", 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.connectToolButton.setText(QCoreApplication.translate("MainWindow", u"Connect", None))
#if QT_CONFIG(tooltip)
@@ -524,7 +527,6 @@ class Ui_MainWindow(object):
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.jsonEditor.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Component JSON", None))
self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None))
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))
self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"&Edit", None))
diff --git a/BEdit/src/bedit/gui/generated/ui_text_definition_editor.py b/BEdit/src/bedit/gui/generated/ui_text_definition_editor.py
new file mode 100644
index 0000000..2feed68
--- /dev/null
+++ b/BEdit/src/bedit/gui/generated/ui_text_definition_editor.py
@@ -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
+
diff --git a/BEdit/src/bedit/gui/main_window.py b/BEdit/src/bedit/gui/main_window.py
index c3dd287..1e35e36 100644
--- a/BEdit/src/bedit/gui/main_window.py
+++ b/BEdit/src/bedit/gui/main_window.py
@@ -1,4 +1,3 @@
-import json
from copy import deepcopy
from pathlib import Path
@@ -13,7 +12,7 @@ from PySide6.QtWidgets import (
QTabWidget,
)
-from bedit.core.model import Component, Port
+from bedit.core.model import Component, Parameter
from bedit.core.serializer import JsonDocumentSerializer
from bedit.gui.controllers.document import DocumentController
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
@@ -40,6 +39,7 @@ class MainWindow(QMainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.settings = application_settings()
+ self._applying_text_definition = False
self.libraries = LibraryRepository(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.textToolButton.clicked.connect(lambda: self.set_graph_tool("text"))
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.textDefinitionChanged.connect(
+ self._text_definition_changed
+ )
def _connect_actions(self) -> None:
self.ui.actionNew.triggered.connect(self.new_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.actionSaveAs.triggered.connect(self.save_document_as)
self.ui.actionClose.triggered.connect(self.close_document)
@@ -189,7 +195,6 @@ class MainWindow(QMainWindow):
self.ui.navigateUpButton.setEnabled(False)
self.ui.workspaceModeLabel.setText("")
self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage)
- self.ui.applyJsonButton.setVisible(False)
self._set_graph_controls_visible(False)
self._update_edit_actions()
return
@@ -199,13 +204,14 @@ class MainWindow(QMainWindow):
)
is_graph = component.implementation_kind == "graph"
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.applyJsonButton.setVisible(not is_graph)
+ self.ui.workspaceStack.setCurrentWidget(
+ self.ui.graphPage if is_graph else self.ui.textPage
+ )
self._set_graph_controls_visible(is_graph)
if is_graph:
self.set_graph_tool("pointer")
else:
- self._load_source_json()
+ self._load_text_definition()
self._update_edit_actions()
def _update_edit_actions(self) -> None:
@@ -259,62 +265,70 @@ class MainWindow(QMainWindow):
"text": self.ui.textToolButton,
}[mode].setChecked(True)
- def _load_source_json(self) -> None:
+ def _load_text_definition(self) -> None:
component = self.document_controller.active_component
if component is None:
return
- text = json.dumps(
- {
- "inputs": [port.to_dict() for port in component.inputs],
- "outputs": [port.to_dict() for port in component.outputs],
- "source": component.source,
- },
- indent=2,
+ self.ui.textDefinitionEditor.set_definition(
+ component.source.get("equations", ""),
+ component.inputs,
+ component.outputs,
+ [
+ Parameter.from_dict(parameter)
+ for parameter in component.source.get("parameters", [])
+ ],
)
- self.ui.jsonEditor.setPlainText(text)
- self.ui.jsonEditor.document().setModified(False)
def _resolve_source_edits(self) -> bool:
component = self.document_controller.active_component
if (
component is None
or component.implementation_kind != "text"
- or not self.ui.jsonEditor.document().isModified()
+ or not self.ui.textDefinitionEditor.is_modified
):
return True
answer = QMessageBox.question(
self,
"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.Discard
| QMessageBox.StandardButton.Cancel,
)
if answer == QMessageBox.StandardButton.Apply:
- return self.apply_json()
+ return self.apply_text_definition()
return answer == QMessageBox.StandardButton.Discard
@Slot()
- def apply_json(self) -> bool:
+ def apply_text_definition(self) -> bool:
try:
- data = json.loads(self.ui.jsonEditor.toPlainText())
- if not isinstance(data, dict):
- raise ValueError("The text component JSON must be an object")
- if not isinstance(data.get("inputs"), list):
- raise ValueError("'inputs' must be a list")
- if not isinstance(data.get("outputs"), list):
- raise ValueError("'outputs' must be a list")
- if not isinstance(data.get("source"), dict):
- raise ValueError("'source' must be an object")
- inputs = [Port.from_dict(item) for item in data["inputs"]]
- outputs = [Port.from_dict(item) for item in data["outputs"]]
- self.document_controller.replace_active_text_definition(inputs, outputs, data["source"])
- except (TypeError, ValueError, json.JSONDecodeError) as error:
- QMessageBox.critical(self, "Invalid text component JSON", str(error))
+ inputs, outputs = self.ui.textDefinitionEditor.ports
+ self._applying_text_definition = True
+ try:
+ self.document_controller.replace_active_text_definition(
+ inputs,
+ outputs,
+ self.ui.textDefinitionEditor.equations,
+ self.ui.textDefinitionEditor.parameters,
+ )
+ finally:
+ self._applying_text_definition = False
+ except (TypeError, ValueError) as error:
+ QMessageBox.critical(self, "Invalid text component", str(error))
return False
- self._load_source_json()
+ self.ui.textDefinitionEditor.set_modified(False)
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:
if self.document_controller.document is None:
return True
@@ -436,9 +450,9 @@ class MainWindow(QMainWindow):
ports_action = menu.addAction("Port Options…")
delete_action = menu.addAction("Delete")
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")
- elif selected is text_action:
+ elif text_action is not None and selected is text_action:
self.document_controller.add_child(component_id, "text")
elif selected is options_action:
self.show_component_options(component_id)
@@ -521,15 +535,18 @@ class MainWindow(QMainWindow):
return
dialog = ComponentOptionsDialog(component, self)
if dialog.exec() == dialog.DialogCode.Accepted:
- self.document_controller.edit_component_appearance(
- component_id,
- dialog.ui.nameEdit.text().strip(),
- dialog.edited_icon,
- dialog.edited_inputs,
- dialog.edited_outputs,
- dialog.ui.showSubtreeCheckBox.isChecked(),
- dialog.ui.showNameCheckBox.isChecked(),
- )
+ try:
+ self.document_controller.edit_component_appearance(
+ component_id,
+ dialog.ui.nameEdit.text().strip(),
+ dialog.edited_icon,
+ dialog.edited_inputs,
+ dialog.edited_outputs,
+ dialog.ui.showSubtreeCheckBox.isChecked(),
+ dialog.ui.showNameCheckBox.isChecked(),
+ )
+ except ValueError as error:
+ QMessageBox.warning(self, "Cannot rename component", str(error))
@Slot(str, str)
def show_port_options(self, port_id: str, direction: str) -> None:
diff --git a/BEdit/ui/main_window.ui b/BEdit/ui/main_window.ui
index 28b604f..3abb5a1 100644
--- a/BEdit/ui/main_window.ui
+++ b/BEdit/ui/main_window.ui
@@ -264,16 +264,6 @@
- -
-
-
- false
-
-
- Apply JSON
-
-
-
-
@@ -397,8 +387,8 @@
-
-
+
+
0
@@ -412,14 +402,7 @@
0
-
-
-
- QPlainTextEdit::LineWrapMode::NoWrap
-
-
- Component JSON
-
-
+
@@ -466,6 +449,7 @@
+
@@ -667,6 +651,17 @@
Ctrl+O
+
+
+ Reload &Libraries
+
+
+ Reload configured library files from disk
+
+
+ F5
+
+
@@ -806,6 +801,12 @@
+
+ TextDefinitionEditor
+ QWidget
+ bedit.gui.editors.text_definition
+ 1
+
GraphWorkspaceView
QGraphicsView
diff --git a/BEdit/ui/text_definition_editor.ui b/BEdit/ui/text_definition_editor.ui
new file mode 100644
index 0000000..20f0cb5
--- /dev/null
+++ b/BEdit/ui/text_definition_editor.ui
@@ -0,0 +1,45 @@
+
+
+ TextDefinitionEditor
+
+ 00900600
+
+ 6
+ 6
+ 6
+ 6
+ -
+
+ Qt::Orientation::Horizontal
+ false
+
+ Equations
+
+
- QPlainTextEdit::LineWrapMode::NoWrapEnter equations here…
+
+
+
+ Qt::Orientation::Vertical
+ false
+
+ Ports
+
+ - QAbstractItemView::SelectionBehavior::SelectRowsQAbstractItemView::SelectionMode::SingleSelection3NameTypeOrientation
+ - Add Port
- Remove Port
- Qt::Orientation::Horizontal4020
+
+
+
+ Parameters
+
+ - QAbstractItemView::SelectionBehavior::SelectRowsQAbstractItemView::SelectionMode::SingleSelection3NameTypeValue
+ - Add Parameter
- Remove Parameter
- Qt::Orientation::Horizontal4020
+
+
+
+
+
+
+
+
+
+
diff --git a/BEdit/untitled.bedit.json b/BEdit/untitled.bedit.json
index de3fae5..1fe0870 100644
--- a/BEdit/untitled.bedit.json
+++ b/BEdit/untitled.bedit.json
@@ -6,7 +6,7 @@
},
"roots": [
{
- "id": "fd375563-d8cb-4c7b-b686-cd6f1edbb654",
+ "id": "b53c8186-0926-46b1-80ec-51a22ac5e0c6",
"name": "New Graph Block 1",
"position": {
"x": 0.0,
@@ -58,242 +58,8 @@
"implementation": {
"kind": "graph",
"graph": {
- "blocks": [
- {
- "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
- }
- }
- }
- ],
+ "blocks": [],
+ "connections": [],
"annotations": []
}
}