Added new connection styles
This commit is contained in:
@@ -60,6 +60,12 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
|
|||||||
- Port removal or reorientation must be rejected when it would invalidate an
|
- Port removal or reorientation must be rejected when it would invalidate an
|
||||||
existing connection.
|
existing connection.
|
||||||
- Connections reference port IDs, never port names.
|
- Connections reference port IDs, never port names.
|
||||||
|
- Graph interaction has separate Pointer and Connect modes. Connections store a
|
||||||
|
`properties.routing` value (`direct`, `angled`, or `spline`); angled routes
|
||||||
|
store absolute scene points in `properties.waypoints`.
|
||||||
|
- Connection appearance is configured per port type in
|
||||||
|
`gui/graphics/connection_styles.py`, including color, width, pen style, and
|
||||||
|
source/target arrowheads. Do not scatter those constants through painters.
|
||||||
- Icon editing uses a fixed 128×128 coordinate space.
|
- Icon editing uses a fixed 128×128 coordinate space.
|
||||||
- The visible/selectable component hitbox is calculated from vector elements,
|
- The visible/selectable component hitbox is calculated from vector elements,
|
||||||
not from the complete 128×128 icon canvas.
|
not from the complete 128×128 icon canvas.
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
|
|||||||
- Components, interface terminals, and connections are selectable. Use a rubber
|
- Components, interface terminals, and connections are selectable. Use a rubber
|
||||||
band or Ctrl-click for multiple selection, Delete to remove items, and the
|
band or Ctrl-click for multiple selection, Delete to remove items, and the
|
||||||
standard Cut/Copy/Paste shortcuts to duplicate selected component groups.
|
standard Cut/Copy/Paste shortcuts to duplicate selected component groups.
|
||||||
- Click an output port and then an input port to create a connection.
|
- Switch the graph header to **Connect**, choose Direct, Angled, or Spline, then
|
||||||
|
click an output and input. Angled connections accept intermediate corner
|
||||||
|
clicks; right-click cancels an unfinished connection.
|
||||||
- Double-click a graph component to open its owned subgraph; use **Up** to return.
|
- Double-click a graph component to open its owned subgraph; use **Up** to return.
|
||||||
- Graph components show **Pointer**, **Input**, and **Output** tools. Select an
|
- Graph components show **Pointer**, **Input**, and **Output** tools. Select an
|
||||||
interface tool and click the canvas to add a visible internal terminal and a
|
interface tool and click the canvas to add a visible internal terminal and a
|
||||||
|
|||||||
@@ -225,7 +225,14 @@ class DocumentController(QObject):
|
|||||||
RotateComponentsCommand(self, self.active_component_id, rotations)
|
RotateComponentsCommand(self, self.active_component_id, rotations)
|
||||||
)
|
)
|
||||||
|
|
||||||
def connect(self, source: Endpoint, target: Endpoint) -> str:
|
def connect(
|
||||||
|
self,
|
||||||
|
source: Endpoint,
|
||||||
|
target: Endpoint,
|
||||||
|
*,
|
||||||
|
routing: str = "spline",
|
||||||
|
waypoints: list[QPointF] | None = None,
|
||||||
|
) -> str:
|
||||||
if self.active_component_id is None:
|
if self.active_component_id is None:
|
||||||
raise ValueError("There is no active graph")
|
raise ValueError("There is no active graph")
|
||||||
source_port = self._port_for_endpoint(source, "source")
|
source_port = self._port_for_endpoint(source, "source")
|
||||||
@@ -236,7 +243,19 @@ class DocumentController(QObject):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Cannot connect {source_port.type!r} to {target_port.type!r}"
|
f"Cannot connect {source_port.type!r} to {target_port.type!r}"
|
||||||
)
|
)
|
||||||
connection = Connection(str(uuid4()), source, target)
|
if routing not in {"direct", "angled", "spline"}:
|
||||||
|
raise ValueError(f"Unknown connection routing: {routing}")
|
||||||
|
connection = Connection(
|
||||||
|
str(uuid4()),
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
properties={
|
||||||
|
"routing": routing,
|
||||||
|
"waypoints": [
|
||||||
|
{"x": point.x(), "y": point.y()} for point in (waypoints or [])
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
self.undo_stack.push(
|
self.undo_stack.push(
|
||||||
AddConnectionCommand(self, self.active_component_id, connection)
|
AddConnectionCommand(self, self.active_component_id, connection)
|
||||||
)
|
)
|
||||||
@@ -255,6 +274,10 @@ class DocumentController(QObject):
|
|||||||
ports = component.outputs if role == "source" else component.inputs
|
ports = component.outputs if role == "source" else component.inputs
|
||||||
return next((port for port in ports if port.id == (endpoint.interface or endpoint.port)), None)
|
return next((port for port in ports if port.id == (endpoint.interface or endpoint.port)), None)
|
||||||
|
|
||||||
|
def connection_port_type(self, connection: Connection) -> str:
|
||||||
|
port = self._port_for_endpoint(connection.source, "source")
|
||||||
|
return port.type if port is not None else "signal"
|
||||||
|
|
||||||
def add_interface_port(self, direction: str, position: QPointF) -> str:
|
def add_interface_port(self, direction: str, position: QPointF) -> str:
|
||||||
component = self.active_component
|
component = self.active_component
|
||||||
if component is None or component.implementation_kind != "graph":
|
if component is None or component.implementation_kind != "graph":
|
||||||
@@ -489,10 +512,17 @@ class DocumentController(QObject):
|
|||||||
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:
|
||||||
continue
|
continue
|
||||||
|
properties = deepcopy(source.properties)
|
||||||
|
for point in properties.get("waypoints", []):
|
||||||
|
if isinstance(point, dict):
|
||||||
|
point["x"] = float(point.get("x", 0)) + offset.x()
|
||||||
|
point["y"] = float(point.get("y", 0)) + offset.y()
|
||||||
connection = Connection(
|
connection = Connection(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
source=Endpoint(block=id_map[source.source.block], port=source.source.port),
|
source=Endpoint(block=id_map[source.source.block], port=source.source.port),
|
||||||
target=Endpoint(block=id_map[source.target.block], port=source.target.port),
|
target=Endpoint(block=id_map[source.target.block], port=source.target.port),
|
||||||
|
name=source.name,
|
||||||
|
properties=properties,
|
||||||
)
|
)
|
||||||
connections[connection.id] = connection
|
connections[connection.id] = connection
|
||||||
if blocks:
|
if blocks:
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
|
||||||
|
|
||||||
|
self.navigateDownButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.navigateDownButton.setObjectName(u"navigateDownButton")
|
||||||
|
self.navigateDownButton.setEnabled(False)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
|
||||||
|
|
||||||
self.graphBreadcrumbLabel = QLabel(self.workspaceHeader)
|
self.graphBreadcrumbLabel = QLabel(self.workspaceHeader)
|
||||||
self.graphBreadcrumbLabel.setObjectName(u"graphBreadcrumbLabel")
|
self.graphBreadcrumbLabel.setObjectName(u"graphBreadcrumbLabel")
|
||||||
|
|
||||||
@@ -206,10 +212,39 @@ class Ui_MainWindow(object):
|
|||||||
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
self.pointerToolButton.setObjectName(u"pointerToolButton")
|
||||||
self.pointerToolButton.setCheckable(True)
|
self.pointerToolButton.setCheckable(True)
|
||||||
self.pointerToolButton.setChecked(True)
|
self.pointerToolButton.setChecked(True)
|
||||||
self.pointerToolButton.setAutoExclusive(True)
|
|
||||||
|
|
||||||
self.workspaceHeaderLayout.addWidget(self.pointerToolButton)
|
self.workspaceHeaderLayout.addWidget(self.pointerToolButton)
|
||||||
|
|
||||||
|
self.connectToolButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.connectToolButton.setObjectName(u"connectToolButton")
|
||||||
|
self.connectToolButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.connectToolButton)
|
||||||
|
|
||||||
|
self.routingLabel = QLabel(self.workspaceHeader)
|
||||||
|
self.routingLabel.setObjectName(u"routingLabel")
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.routingLabel)
|
||||||
|
|
||||||
|
self.directRoutingButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.directRoutingButton.setObjectName(u"directRoutingButton")
|
||||||
|
self.directRoutingButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.directRoutingButton)
|
||||||
|
|
||||||
|
self.angledRoutingButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.angledRoutingButton.setObjectName(u"angledRoutingButton")
|
||||||
|
self.angledRoutingButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.angledRoutingButton)
|
||||||
|
|
||||||
|
self.splineRoutingButton = QToolButton(self.workspaceHeader)
|
||||||
|
self.splineRoutingButton.setObjectName(u"splineRoutingButton")
|
||||||
|
self.splineRoutingButton.setCheckable(True)
|
||||||
|
self.splineRoutingButton.setChecked(True)
|
||||||
|
|
||||||
|
self.workspaceHeaderLayout.addWidget(self.splineRoutingButton)
|
||||||
|
|
||||||
|
|
||||||
self.workspaceEditorLayout.addWidget(self.workspaceHeader)
|
self.workspaceEditorLayout.addWidget(self.workspaceHeader)
|
||||||
|
|
||||||
@@ -437,11 +472,20 @@ class Ui_MainWindow(object):
|
|||||||
self.navigateUpButton.setText(QCoreApplication.translate("MainWindow", u"Up", None))
|
self.navigateUpButton.setText(QCoreApplication.translate("MainWindow", u"Up", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.navigateUpButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the containing graph", None))
|
self.navigateUpButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the containing graph", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.navigateDownButton.setText(QCoreApplication.translate("MainWindow", u"Down", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.navigateDownButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the selected block", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.graphBreadcrumbLabel.setText(QCoreApplication.translate("MainWindow", u"Untitled", None))
|
self.graphBreadcrumbLabel.setText(QCoreApplication.translate("MainWindow", u"Untitled", None))
|
||||||
self.workspaceModeLabel.setText(QCoreApplication.translate("MainWindow", u"Graph", None))
|
self.workspaceModeLabel.setText(QCoreApplication.translate("MainWindow", u"Graph", None))
|
||||||
self.applyJsonButton.setText(QCoreApplication.translate("MainWindow", u"Apply JSON", None))
|
self.applyJsonButton.setText(QCoreApplication.translate("MainWindow", u"Apply JSON", None))
|
||||||
self.pointerToolButton.setText(QCoreApplication.translate("MainWindow", u"Pointer", None))
|
self.pointerToolButton.setText(QCoreApplication.translate("MainWindow", u"Pointer", None))
|
||||||
|
self.connectToolButton.setText(QCoreApplication.translate("MainWindow", u"Connect", None))
|
||||||
|
self.routingLabel.setText(QCoreApplication.translate("MainWindow", u"Line:", None))
|
||||||
|
self.directRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Direct", None))
|
||||||
|
self.angledRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Angled", None))
|
||||||
|
self.splineRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Spline", None))
|
||||||
self.jsonEditor.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Component JSON", 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))
|
||||||
|
|||||||
25
BEdit/src/bedit/gui/graphics/connection_styles.py
Normal file
25
BEdit/src/bedit/gui/graphics/connection_styles.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ConnectionStyle:
|
||||||
|
color: str = "#285f9e"
|
||||||
|
selected_color: str = "#f59e0b"
|
||||||
|
width: float = 2.5
|
||||||
|
selected_width: float = 4.0
|
||||||
|
line_style: Qt.PenStyle = Qt.PenStyle.SolidLine
|
||||||
|
arrow_at_source: bool = False
|
||||||
|
arrow_at_target: bool = True
|
||||||
|
arrow_size: float = 10.0
|
||||||
|
|
||||||
|
|
||||||
|
# This is the intentional code-level styling point for every port/connection type.
|
||||||
|
CONNECTION_STYLES: dict[str, ConnectionStyle] = {
|
||||||
|
"signal": ConnectionStyle(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def connection_style(port_type: str) -> ConnectionStyle:
|
||||||
|
return CONNECTION_STYLES.get(port_type, ConnectionStyle())
|
||||||
@@ -10,6 +10,7 @@ from PySide6.QtGui import (
|
|||||||
QPainter,
|
QPainter,
|
||||||
QPainterPath,
|
QPainterPath,
|
||||||
QPen,
|
QPen,
|
||||||
|
QPolygonF,
|
||||||
QTransform,
|
QTransform,
|
||||||
)
|
)
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
@@ -32,6 +33,7 @@ from bedit.core.model import Component, Connection, Endpoint, Port
|
|||||||
from bedit.gui.controllers.document import DocumentController
|
from bedit.gui.controllers.document import DocumentController
|
||||||
from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE
|
from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE
|
||||||
from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon
|
from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon
|
||||||
|
from bedit.gui.graphics.connection_styles import ConnectionStyle, connection_style
|
||||||
from bedit.gui.preferences import application_settings
|
from bedit.gui.preferences import application_settings
|
||||||
|
|
||||||
|
|
||||||
@@ -235,10 +237,20 @@ class InterfaceTerminalItem(QGraphicsObject):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectionGraphicsItem(QGraphicsPathItem):
|
class ConnectionGraphicsItem(QGraphicsPathItem):
|
||||||
def __init__(self, connection_id: str, name: str = "") -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
connection_id: str,
|
||||||
|
name: str = "",
|
||||||
|
style: ConnectionStyle | None = None,
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.connection_id = connection_id
|
self.connection_id = connection_id
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.style = style or ConnectionStyle()
|
||||||
|
self.start = QPointF()
|
||||||
|
self.end = QPointF()
|
||||||
|
self.start_direction = QPointF(1, 0)
|
||||||
|
self.end_direction = QPointF(1, 0)
|
||||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
||||||
self._update_pen()
|
self._update_pen()
|
||||||
self.setZValue(-1)
|
self.setZValue(-1)
|
||||||
@@ -262,11 +274,40 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
|||||||
def _update_pen(self) -> None:
|
def _update_pen(self) -> None:
|
||||||
self.setPen(
|
self.setPen(
|
||||||
QPen(
|
QPen(
|
||||||
QColor("#f59e0b") if self.isSelected() else QColor("#285f9e"),
|
QColor(self.style.selected_color if self.isSelected() else self.style.color),
|
||||||
4.0 if self.isSelected() else 2.5,
|
self.style.selected_width if self.isSelected() else self.style.width,
|
||||||
|
self.style.line_style,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_connection_path(
|
||||||
|
self,
|
||||||
|
path: QPainterPath,
|
||||||
|
points: list[QPointF],
|
||||||
|
) -> None:
|
||||||
|
self.setPath(path)
|
||||||
|
self.start, self.end = points[0], points[-1]
|
||||||
|
if len(points) > 1:
|
||||||
|
self.start_direction = points[1] - points[0]
|
||||||
|
self.end_direction = points[-1] - points[-2]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _arrow(end: QPointF, direction: QPointF, size: float) -> QPolygonF:
|
||||||
|
length = max(0.001, (direction.x() ** 2 + direction.y() ** 2) ** 0.5)
|
||||||
|
unit = QPointF(direction.x() / length, direction.y() / length)
|
||||||
|
normal = QPointF(-unit.y(), unit.x())
|
||||||
|
base = end - unit * size
|
||||||
|
return QPolygonF([end, base + normal * size * 0.45, base - normal * size * 0.45])
|
||||||
|
|
||||||
|
def paint(self, painter: QPainter, option, widget=None) -> None:
|
||||||
|
super().paint(painter, option, widget)
|
||||||
|
painter.setPen(Qt.PenStyle.NoPen)
|
||||||
|
painter.setBrush(self.pen().color())
|
||||||
|
if self.style.arrow_at_target:
|
||||||
|
painter.drawPolygon(self._arrow(self.end, self.end_direction, self.style.arrow_size))
|
||||||
|
if self.style.arrow_at_source:
|
||||||
|
painter.drawPolygon(self._arrow(self.start, -self.start_direction, self.style.arrow_size))
|
||||||
|
|
||||||
|
|
||||||
class GraphScene(QGraphicsScene):
|
class GraphScene(QGraphicsScene):
|
||||||
componentOptionsRequested = Signal(str)
|
componentOptionsRequested = Signal(str)
|
||||||
@@ -282,6 +323,10 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.output_items: dict[str, InterfaceTerminalItem] = {}
|
self.output_items: dict[str, InterfaceTerminalItem] = {}
|
||||||
self.connection_items: dict[str, ConnectionGraphicsItem] = {}
|
self.connection_items: dict[str, ConnectionGraphicsItem] = {}
|
||||||
self.pending_source: ConnectionPortItem | None = None
|
self.pending_source: ConnectionPortItem | None = None
|
||||||
|
self.pending_waypoints: list[QPointF] = []
|
||||||
|
self.pending_preview: QGraphicsPathItem | None = None
|
||||||
|
self.interaction_mode = "pointer"
|
||||||
|
self.connection_routing = "spline"
|
||||||
self.setSceneRect(-2000, -2000, 4000, 4000)
|
self.setSceneRect(-2000, -2000, 4000, 4000)
|
||||||
|
|
||||||
controller.documentReset.connect(self.rebuild)
|
controller.documentReset.connect(self.rebuild)
|
||||||
@@ -297,6 +342,8 @@ class GraphScene(QGraphicsScene):
|
|||||||
self.output_items.clear()
|
self.output_items.clear()
|
||||||
self.connection_items.clear()
|
self.connection_items.clear()
|
||||||
self.pending_source = None
|
self.pending_source = None
|
||||||
|
self.pending_waypoints.clear()
|
||||||
|
self.pending_preview = None
|
||||||
owner = self.controller.active_component
|
owner = self.controller.active_component
|
||||||
if owner is None or owner.implementation_kind != "graph":
|
if owner is None or owner.implementation_kind != "graph":
|
||||||
return
|
return
|
||||||
@@ -316,7 +363,11 @@ class GraphScene(QGraphicsScene):
|
|||||||
item.setPos(component.x, component.y)
|
item.setPos(component.x, component.y)
|
||||||
self.component_items[component.id] = item
|
self.component_items[component.id] = item
|
||||||
for connection in owner.graph.connections.values():
|
for connection in owner.graph.connections.values():
|
||||||
item = ConnectionGraphicsItem(connection.id, connection.name)
|
item = ConnectionGraphicsItem(
|
||||||
|
connection.id,
|
||||||
|
connection.name,
|
||||||
|
connection_style(self.controller.connection_port_type(connection)),
|
||||||
|
)
|
||||||
self.addItem(item)
|
self.addItem(item)
|
||||||
self.connection_items[connection.id] = item
|
self.connection_items[connection.id] = item
|
||||||
self.update_connection(connection.id)
|
self.update_connection(connection.id)
|
||||||
@@ -367,34 +418,115 @@ class GraphScene(QGraphicsScene):
|
|||||||
if source is None or target is None:
|
if source is None or target is None:
|
||||||
return
|
return
|
||||||
start, end = source.scenePos(), target.scenePos()
|
start, end = source.scenePos(), target.scenePos()
|
||||||
distance = max(50.0, abs(end.x() - start.x()) * 0.5)
|
routing = str(connection.properties.get("routing", "spline"))
|
||||||
|
waypoints = [
|
||||||
|
QPointF(float(point["x"]), float(point["y"]))
|
||||||
|
for point in connection.properties.get("waypoints", [])
|
||||||
|
if isinstance(point, dict) and "x" in point and "y" in point
|
||||||
|
]
|
||||||
|
path, direction_points = self._route_path(start, end, routing, waypoints)
|
||||||
|
graphics.set_connection_path(path, direction_points)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _route_path(
|
||||||
|
start: QPointF,
|
||||||
|
end: QPointF,
|
||||||
|
routing: str,
|
||||||
|
waypoints: list[QPointF] | None = None,
|
||||||
|
) -> tuple[QPainterPath, list[QPointF]]:
|
||||||
|
waypoints = waypoints or []
|
||||||
path = QPainterPath(start)
|
path = QPainterPath(start)
|
||||||
path.cubicTo(start + QPointF(distance, 0), end - QPointF(distance, 0), end)
|
if routing == "angled":
|
||||||
graphics.setPath(path)
|
points = [start, *waypoints, end]
|
||||||
|
for point in points[1:]:
|
||||||
|
path.lineTo(point)
|
||||||
|
return path, points
|
||||||
|
if routing == "direct":
|
||||||
|
path.lineTo(end)
|
||||||
|
return path, [start, end]
|
||||||
|
distance = max(50.0, abs(end.x() - start.x()) * 0.5)
|
||||||
|
first_control = start + QPointF(distance, 0)
|
||||||
|
second_control = end - QPointF(distance, 0)
|
||||||
|
path.cubicTo(first_control, second_control, end)
|
||||||
|
return path, [start, first_control, second_control, end]
|
||||||
|
|
||||||
|
def set_interaction_mode(self, mode: str) -> None:
|
||||||
|
self.interaction_mode = mode
|
||||||
|
if mode != "connect":
|
||||||
|
self._clear_pending_source()
|
||||||
|
|
||||||
|
def set_connection_routing(self, routing: str) -> None:
|
||||||
|
self.connection_routing = routing
|
||||||
|
self._clear_pending_source()
|
||||||
|
|
||||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.interaction_mode != "connect":
|
||||||
|
self._clear_pending_source()
|
||||||
|
super().mousePressEvent(event)
|
||||||
|
return
|
||||||
|
if event.button() == Qt.MouseButton.RightButton:
|
||||||
|
self._clear_pending_source()
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
item = self.itemAt(event.scenePos(), QTransform())
|
item = self.itemAt(event.scenePos(), QTransform())
|
||||||
if isinstance(item, ConnectionPortItem):
|
if isinstance(item, ConnectionPortItem):
|
||||||
if item.role == "source":
|
if item.role == "source":
|
||||||
self._clear_pending_source()
|
self._clear_pending_source()
|
||||||
self.pending_source = item
|
self.pending_source = item
|
||||||
|
self.pending_waypoints = []
|
||||||
item.setBrush(QColor("#f5b642"))
|
item.setBrush(QColor("#f5b642"))
|
||||||
|
self._create_preview()
|
||||||
elif self.pending_source is not None:
|
elif self.pending_source is not None:
|
||||||
if self.pending_source.endpoint != item.endpoint:
|
if self.pending_source.endpoint != item.endpoint:
|
||||||
try:
|
try:
|
||||||
self.controller.connect(self.pending_source.endpoint, item.endpoint)
|
self.controller.connect(
|
||||||
|
self.pending_source.endpoint,
|
||||||
|
item.endpoint,
|
||||||
|
routing=self.connection_routing,
|
||||||
|
waypoints=self.pending_waypoints,
|
||||||
|
)
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
QToolTip.showText(event.screenPos(), str(error))
|
QToolTip.showText(event.screenPos(), str(error))
|
||||||
self._clear_pending_source()
|
self._clear_pending_source()
|
||||||
event.accept()
|
event.accept()
|
||||||
return
|
return
|
||||||
self._clear_pending_source()
|
if self.pending_source is not None and self.connection_routing == "angled":
|
||||||
super().mousePressEvent(event)
|
self.pending_waypoints.append(_snapped(event.scenePos()))
|
||||||
|
self._update_preview(event.scenePos())
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
if self.interaction_mode == "connect" and self.pending_source is not None:
|
||||||
|
self._update_preview(event.scenePos())
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
super().mouseMoveEvent(event)
|
||||||
|
|
||||||
|
def _create_preview(self) -> None:
|
||||||
|
self.pending_preview = QGraphicsPathItem()
|
||||||
|
self.pending_preview.setPen(QPen(QColor("#64748b"), 1.5, Qt.PenStyle.DashLine))
|
||||||
|
self.pending_preview.setZValue(-0.5)
|
||||||
|
self.addItem(self.pending_preview)
|
||||||
|
|
||||||
|
def _update_preview(self, cursor: QPointF) -> None:
|
||||||
|
if self.pending_source is None or self.pending_preview is None:
|
||||||
|
return
|
||||||
|
path, _points = self._route_path(
|
||||||
|
self.pending_source.scenePos(),
|
||||||
|
cursor,
|
||||||
|
self.connection_routing,
|
||||||
|
self.pending_waypoints,
|
||||||
|
)
|
||||||
|
self.pending_preview.setPath(path)
|
||||||
|
|
||||||
def _clear_pending_source(self) -> None:
|
def _clear_pending_source(self) -> None:
|
||||||
if self.pending_source is not None:
|
if self.pending_source is not None:
|
||||||
self.pending_source.setBrush(QColor("#ffffff"))
|
self.pending_source.setBrush(QColor("#ffffff"))
|
||||||
|
if self.pending_preview is not None:
|
||||||
|
self.removeItem(self.pending_preview)
|
||||||
self.pending_source = None
|
self.pending_source = None
|
||||||
|
self.pending_waypoints.clear()
|
||||||
|
self.pending_preview = None
|
||||||
|
|
||||||
|
|
||||||
class GraphWorkspaceView(QGraphicsView):
|
class GraphWorkspaceView(QGraphicsView):
|
||||||
@@ -411,6 +543,7 @@ class GraphWorkspaceView(QGraphicsView):
|
|||||||
self.tool_mode = "pointer"
|
self.tool_mode = "pointer"
|
||||||
self.paste_count = 0
|
self.paste_count = 0
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
|
self.setMouseTracking(True)
|
||||||
self.setRenderHint(QPainter.RenderHint.Antialiasing)
|
self.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||||
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
|
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
|
||||||
self.setBackgroundBrush(QColor("#f7f7f7"))
|
self.setBackgroundBrush(QColor("#f7f7f7"))
|
||||||
@@ -505,6 +638,29 @@ class GraphWorkspaceView(QGraphicsView):
|
|||||||
and any(isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems())
|
and any(isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def has_single_selected_component(self) -> bool:
|
||||||
|
scene = self.scene()
|
||||||
|
return bool(
|
||||||
|
scene
|
||||||
|
and sum(
|
||||||
|
isinstance(item, ComponentGraphicsItem) for item in scene.selectedItems()
|
||||||
|
)
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def open_selected_component(self) -> bool:
|
||||||
|
if self.controller is None or self.scene() is None:
|
||||||
|
return False
|
||||||
|
selected = [
|
||||||
|
item
|
||||||
|
for item in self.scene().selectedItems()
|
||||||
|
if isinstance(item, ComponentGraphicsItem)
|
||||||
|
]
|
||||||
|
if len(selected) != 1:
|
||||||
|
return False
|
||||||
|
self.controller.activate_component(selected[0].component_id)
|
||||||
|
return True
|
||||||
|
|
||||||
def rotate_selected(self) -> None:
|
def rotate_selected(self) -> None:
|
||||||
if self.controller is None or self.scene() is None:
|
if self.controller is None or self.scene() is None:
|
||||||
return
|
return
|
||||||
@@ -578,6 +734,14 @@ class GraphWorkspaceView(QGraphicsView):
|
|||||||
if mode == "pointer"
|
if mode == "pointer"
|
||||||
else QGraphicsView.DragMode.NoDrag
|
else QGraphicsView.DragMode.NoDrag
|
||||||
)
|
)
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.set_interaction_mode(mode)
|
||||||
|
|
||||||
|
def set_connection_routing(self, routing: str) -> None:
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
scene.set_connection_routing(routing)
|
||||||
|
|
||||||
def mousePressEvent(self, event: QMouseEvent) -> None: # noqa: N802
|
def mousePressEvent(self, event: QMouseEvent) -> None: # noqa: N802
|
||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from PySide6.QtCore import Qt, Slot
|
from PySide6.QtCore import Qt, Slot
|
||||||
from PySide6.QtGui import QCloseEvent
|
from PySide6.QtGui import QCloseEvent
|
||||||
from PySide6.QtWidgets import QFileDialog, QMainWindow, QMenu, QMessageBox
|
from PySide6.QtWidgets import QButtonGroup, QFileDialog, QMainWindow, QMenu, QMessageBox
|
||||||
|
|
||||||
from bedit.core.model import Component, Port
|
from bedit.core.model import Component, Port
|
||||||
from bedit.core.serializer import JsonDocumentSerializer
|
from bedit.core.serializer import JsonDocumentSerializer
|
||||||
@@ -93,8 +93,31 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.graphView.selectionAvailabilityChanged.connect(
|
self.ui.graphView.selectionAvailabilityChanged.connect(
|
||||||
lambda _available: self._update_edit_actions()
|
lambda _available: self._update_edit_actions()
|
||||||
)
|
)
|
||||||
|
self.mode_button_group = QButtonGroup(self)
|
||||||
|
self.mode_button_group.setExclusive(True)
|
||||||
|
self.mode_button_group.addButton(self.ui.pointerToolButton)
|
||||||
|
self.mode_button_group.addButton(self.ui.connectToolButton)
|
||||||
|
self.routing_button_group = QButtonGroup(self)
|
||||||
|
self.routing_button_group.setExclusive(True)
|
||||||
|
for button in (
|
||||||
|
self.ui.directRoutingButton,
|
||||||
|
self.ui.angledRoutingButton,
|
||||||
|
self.ui.splineRoutingButton,
|
||||||
|
):
|
||||||
|
self.routing_button_group.addButton(button)
|
||||||
self.ui.navigateUpButton.clicked.connect(self.navigate_up)
|
self.ui.navigateUpButton.clicked.connect(self.navigate_up)
|
||||||
|
self.ui.navigateDownButton.clicked.connect(self.navigate_down)
|
||||||
self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer"))
|
self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer"))
|
||||||
|
self.ui.connectToolButton.clicked.connect(lambda: self.set_graph_tool("connect"))
|
||||||
|
self.ui.directRoutingButton.clicked.connect(
|
||||||
|
lambda: self.set_connection_routing("direct")
|
||||||
|
)
|
||||||
|
self.ui.angledRoutingButton.clicked.connect(
|
||||||
|
lambda: self.set_connection_routing("angled")
|
||||||
|
)
|
||||||
|
self.ui.splineRoutingButton.clicked.connect(
|
||||||
|
lambda: self.set_connection_routing("spline")
|
||||||
|
)
|
||||||
self.ui.applyJsonButton.clicked.connect(self.apply_json)
|
self.ui.applyJsonButton.clicked.connect(self.apply_json)
|
||||||
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
||||||
|
|
||||||
@@ -173,7 +196,7 @@ class MainWindow(QMainWindow):
|
|||||||
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.ui.applyJsonButton.setVisible(False)
|
||||||
self.ui.pointerToolButton.setVisible(False)
|
self._set_graph_controls_visible(False)
|
||||||
self._update_edit_actions()
|
self._update_edit_actions()
|
||||||
return
|
return
|
||||||
self.ui.graphBreadcrumbLabel.setText(" › ".join(self.document_controller.breadcrumb()))
|
self.ui.graphBreadcrumbLabel.setText(" › ".join(self.document_controller.breadcrumb()))
|
||||||
@@ -184,7 +207,7 @@ class MainWindow(QMainWindow):
|
|||||||
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.graphPage if is_graph else self.ui.jsonPage)
|
||||||
self.ui.applyJsonButton.setVisible(not is_graph)
|
self.ui.applyJsonButton.setVisible(not is_graph)
|
||||||
self.ui.pointerToolButton.setVisible(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:
|
||||||
@@ -202,15 +225,43 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
self.ui.actionSelectAll.setEnabled(is_graph)
|
self.ui.actionSelectAll.setEnabled(is_graph)
|
||||||
self.ui.actionPaste.setEnabled(is_graph)
|
self.ui.actionPaste.setEnabled(is_graph)
|
||||||
|
self.ui.navigateDownButton.setEnabled(
|
||||||
|
is_graph and self.ui.graphView.has_single_selected_component()
|
||||||
|
)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def navigate_up(self) -> None:
|
def navigate_up(self) -> None:
|
||||||
if self._resolve_source_edits():
|
if self._resolve_source_edits():
|
||||||
self.document_controller.navigate_up()
|
self.document_controller.navigate_up()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def navigate_down(self) -> None:
|
||||||
|
if self._resolve_source_edits():
|
||||||
|
self.ui.graphView.open_selected_component()
|
||||||
|
|
||||||
|
def _set_graph_controls_visible(self, visible: bool) -> None:
|
||||||
|
for widget in (
|
||||||
|
self.ui.pointerToolButton,
|
||||||
|
self.ui.connectToolButton,
|
||||||
|
self.ui.routingLabel,
|
||||||
|
self.ui.directRoutingButton,
|
||||||
|
self.ui.angledRoutingButton,
|
||||||
|
self.ui.splineRoutingButton,
|
||||||
|
):
|
||||||
|
widget.setVisible(visible)
|
||||||
|
|
||||||
def set_graph_tool(self, mode: str) -> None:
|
def set_graph_tool(self, mode: str) -> None:
|
||||||
self.ui.graphView.set_tool_mode("pointer")
|
self.ui.graphView.set_tool_mode(mode)
|
||||||
self.ui.pointerToolButton.setChecked(True)
|
(self.ui.pointerToolButton if mode == "pointer" else self.ui.connectToolButton).setChecked(True)
|
||||||
|
|
||||||
|
def set_connection_routing(self, routing: str) -> None:
|
||||||
|
self.ui.graphView.set_connection_routing(routing)
|
||||||
|
buttons = {
|
||||||
|
"direct": self.ui.directRoutingButton,
|
||||||
|
"angled": self.ui.angledRoutingButton,
|
||||||
|
"spline": self.ui.splineRoutingButton,
|
||||||
|
}
|
||||||
|
buttons[routing].setChecked(True)
|
||||||
|
|
||||||
def _load_source_json(self) -> None:
|
def _load_source_json(self) -> None:
|
||||||
component = self.document_controller.active_component
|
component = self.document_controller.active_component
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
{
|
|
||||||
"format": "bedit-document",
|
|
||||||
"version": 1,
|
|
||||||
"metadata": {
|
|
||||||
"name": "Untitled"
|
|
||||||
},
|
|
||||||
"roots": [
|
|
||||||
{
|
|
||||||
"id": "5ee742db-b25c-4d86-b4ca-cc0e61c4002a",
|
|
||||||
"name": "New Graph Block 1",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"rotation": 0.0,
|
|
||||||
"interface": {
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": []
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"shape": "rectangle",
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"border": "#303030",
|
|
||||||
"text": "Graph",
|
|
||||||
"size": {
|
|
||||||
"width": 128.0,
|
|
||||||
"height": 128.0
|
|
||||||
},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"x": 1.0,
|
|
||||||
"y": 1.0,
|
|
||||||
"width": 126.0,
|
|
||||||
"height": 126.0,
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"stroke": "#303030",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"cornerRadius": 5.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 8.0,
|
|
||||||
"y": 8.0,
|
|
||||||
"width": 112.0,
|
|
||||||
"height": 112.0,
|
|
||||||
"text": "Graph",
|
|
||||||
"color": "#202020",
|
|
||||||
"fontSize": 12.0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "graph",
|
|
||||||
"graph": {
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"id": "89944479-e73e-446a-8d58-ebf35ac4144b",
|
|
||||||
"name": "New Graph Block 1",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": -64.0
|
|
||||||
},
|
|
||||||
"rotation": 0.0,
|
|
||||||
"interface": {
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "port-e5cb81a5",
|
|
||||||
"name": "Port 1",
|
|
||||||
"position": {
|
|
||||||
"x": 0.0,
|
|
||||||
"y": 0.0
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"iconPosition": {
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 64.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "signal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "port-eb0034af",
|
|
||||||
"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": "Graph",
|
|
||||||
"size": {
|
|
||||||
"width": 128.0,
|
|
||||||
"height": 128.0
|
|
||||||
},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 32.0,
|
|
||||||
"width": 64.0,
|
|
||||||
"height": 64.0,
|
|
||||||
"fill": "#f4f4f4",
|
|
||||||
"stroke": "#303030",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"cornerRadius": 5.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 40.0,
|
|
||||||
"width": 48.0,
|
|
||||||
"height": 48.0,
|
|
||||||
"text": "Graph",
|
|
||||||
"color": "#303030",
|
|
||||||
"fontSize": 12.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#303030",
|
|
||||||
"fill": "#ffffff"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {},
|
|
||||||
"library": {
|
|
||||||
"showSubtree": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"kind": "graph",
|
|
||||||
"graph": {
|
|
||||||
"blocks": [],
|
|
||||||
"connections": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"connections": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -144,11 +144,17 @@
|
|||||||
<property name="rightMargin"><number>6</number></property>
|
<property name="rightMargin"><number>6</number></property>
|
||||||
<property name="bottomMargin"><number>2</number></property>
|
<property name="bottomMargin"><number>2</number></property>
|
||||||
<item><widget class="QToolButton" name="navigateUpButton"><property name="text"><string>Up</string></property><property name="toolTip"><string>Open the containing graph</string></property></widget></item>
|
<item><widget class="QToolButton" name="navigateUpButton"><property name="text"><string>Up</string></property><property name="toolTip"><string>Open the containing graph</string></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="navigateDownButton"><property name="text"><string>Down</string></property><property name="toolTip"><string>Open the selected block</string></property><property name="enabled"><bool>false</bool></property></widget></item>
|
||||||
<item><widget class="QLabel" name="graphBreadcrumbLabel"><property name="text"><string>Untitled</string></property></widget></item>
|
<item><widget class="QLabel" name="graphBreadcrumbLabel"><property name="text"><string>Untitled</string></property></widget></item>
|
||||||
<item><widget class="QLabel" name="workspaceModeLabel"><property name="text"><string>Graph</string></property></widget></item>
|
<item><widget class="QLabel" name="workspaceModeLabel"><property name="text"><string>Graph</string></property></widget></item>
|
||||||
<item><spacer name="workspaceHeaderSpacer"><property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
|
<item><spacer name="workspaceHeaderSpacer"><property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
|
||||||
<item><widget class="QPushButton" name="applyJsonButton"><property name="text"><string>Apply JSON</string></property><property name="visible"><bool>false</bool></property></widget></item>
|
<item><widget class="QPushButton" name="applyJsonButton"><property name="text"><string>Apply JSON</string></property><property name="visible"><bool>false</bool></property></widget></item>
|
||||||
<item><widget class="QToolButton" name="pointerToolButton"><property name="text"><string>Pointer</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property><property name="autoExclusive"><bool>true</bool></property></widget></item>
|
<item><widget class="QToolButton" name="pointerToolButton"><property name="text"><string>Pointer</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="connectToolButton"><property name="text"><string>Connect</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
|
<item><widget class="QLabel" name="routingLabel"><property name="text"><string>Line:</string></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="directRoutingButton"><property name="text"><string>Direct</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="angledRoutingButton"><property name="text"><string>Angled</string></property><property name="checkable"><bool>true</bool></property></widget></item>
|
||||||
|
<item><widget class="QToolButton" name="splineRoutingButton"><property name="text"><string>Spline</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property></widget></item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user