|
|
|
@ -21,11 +21,13 @@
|
|
|
|
|
import logging |
|
|
|
|
import sys |
|
|
|
|
import textwrap |
|
|
|
|
from typing import Any, Callable, Dict, List |
|
|
|
|
|
|
|
|
|
from PySide2.QtCore import Qt |
|
|
|
|
from PySide2 import QtCore |
|
|
|
|
from PySide2 import QtGui |
|
|
|
|
from PySide2 import QtWidgets |
|
|
|
|
from PySide2 import QtQml |
|
|
|
|
from PySide2 import QtQuick |
|
|
|
|
|
|
|
|
|
from noisicaa import model |
|
|
|
|
from noisicaa import engine |
|
|
|
@ -51,7 +53,7 @@ class GraphNodePort(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObj
|
|
|
|
|
glowing, glowingChanged = ui_base.Property('glowing', bool) |
|
|
|
|
numConnections, numConnectionsChanged = ui_base.Property('numConnection', int) |
|
|
|
|
|
|
|
|
|
def __init__(self, *, node, port, **kwargs): |
|
|
|
|
def __init__(self, *, node: 'GraphNode', port: model.Port, **kwargs: Any) -> None: |
|
|
|
|
super().__init__(parent=node, **kwargs) |
|
|
|
|
|
|
|
|
|
self.__node = node |
|
|
|
@ -62,11 +64,11 @@ class GraphNodePort(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObj
|
|
|
|
|
|
|
|
|
|
# Can't declare this as GraphNode, because this would cause a dependency cycle. |
|
|
|
|
@ui_base.ConstProperty(QtCore.QObject) |
|
|
|
|
def node(self): |
|
|
|
|
def node(self) -> 'GraphNode': |
|
|
|
|
return self.__node |
|
|
|
|
|
|
|
|
|
@ui_base.ConstProperty(model.Port) |
|
|
|
|
def port(self): |
|
|
|
|
def port(self) -> model.Port: |
|
|
|
|
return self.__port |
|
|
|
|
|
|
|
|
|
@QtCore.Slot(float) |
|
|
|
@ -81,17 +83,15 @@ class GraphNodePort(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObj
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject): |
|
|
|
|
def __init__(self, *, node, **kwargs): |
|
|
|
|
def __init__(self, *, node: model.GraphNode, **kwargs: Any) -> None: |
|
|
|
|
super().__init__(**kwargs) |
|
|
|
|
|
|
|
|
|
self.__node_message_listener = None |
|
|
|
|
|
|
|
|
|
self.__node = node |
|
|
|
|
self.__description = self.app.nodeDB.nodeDescription(self.__node.uri) |
|
|
|
|
|
|
|
|
|
self.__ports = {} |
|
|
|
|
self.__in_ports = [] |
|
|
|
|
self.__out_ports = [] |
|
|
|
|
self.__ports = {} # type: Dict[str, GraphNodePort] |
|
|
|
|
self.__in_ports = [] # type: List[GraphNodePort] |
|
|
|
|
self.__out_ports = [] # type: List[GraphNodePort] |
|
|
|
|
for port in self.__node.ports: |
|
|
|
|
wrapper = GraphNodePort(node=self, port=port, context=self.context) |
|
|
|
|
self.__ports[port.name] = wrapper |
|
|
|
@ -107,41 +107,43 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
self.__active = False |
|
|
|
|
self.__selected = False |
|
|
|
|
|
|
|
|
|
self.__nodeMessageListener = None |
|
|
|
|
self.__nodeMessageListener = None # type: int |
|
|
|
|
|
|
|
|
|
self.__body = None |
|
|
|
|
self.__bodyComponent = None |
|
|
|
|
self.__body = None # type: QtQuick.QQuickItem |
|
|
|
|
self.__bodyComponent = None # type: QtQml.QQmlComponent |
|
|
|
|
|
|
|
|
|
def cleanup(self): |
|
|
|
|
def cleanup(self) -> None: |
|
|
|
|
if self.__nodeMessageListener is not None: |
|
|
|
|
self.engine.remove_node_message_listener(self.__node.id, self.__nodeMessageListener) |
|
|
|
|
self.__nodeMessageListener = None |
|
|
|
|
|
|
|
|
|
def setNodeMessageHandler(self, handler): |
|
|
|
|
def setNodeMessageHandler(self, handler: Callable[[bytes], None]) -> None: |
|
|
|
|
assert self.__nodeMessageListener is None |
|
|
|
|
self.__nodeMessageListener = self.engine.add_node_message_listener(self.__node.id, handler) |
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
|
def __str__(self) -> str: |
|
|
|
|
return '<{} {}>'.format(type(self).__name__, self.__node) |
|
|
|
|
|
|
|
|
|
@ui_base.ConstProperty(model.GraphNode) |
|
|
|
|
def node(self): |
|
|
|
|
def node(self) -> model.GraphNode: |
|
|
|
|
return self.__node |
|
|
|
|
|
|
|
|
|
@ui_base.ConstProperty(engine.NodeDescription) |
|
|
|
|
def description(self): |
|
|
|
|
def description(self) -> engine.NodeDescription: |
|
|
|
|
return self.__description |
|
|
|
|
|
|
|
|
|
@ui_base.ConstProperty(QtCore.QObject) |
|
|
|
|
def body(self): |
|
|
|
|
def body(self) -> QtQuick.QQuickItem: |
|
|
|
|
if self.__body is None and self.__description.ui is not None and self.__description.ui.body_qml is not None: |
|
|
|
|
logger.info("%s#%016x: Loading body component '%s'...", self.__node.uri, self.__node.id, self.__description.ui.body_qml) |
|
|
|
|
logger.info( |
|
|
|
|
"%s#%016x: Loading body component '%s'...", |
|
|
|
|
self.__node.uri, self.__node.id, self.__description.ui.body_qml) |
|
|
|
|
self.__bodyComponent = self.app.createQmlComponent(self.__description.ui.body_qml) |
|
|
|
|
self.__body = self.__bodyComponent.createWithInitialProperties({'d': self}) |
|
|
|
|
return self.__body |
|
|
|
|
|
|
|
|
|
@QtCore.Slot() |
|
|
|
|
def moveDone(self): |
|
|
|
|
def moveDone(self) -> None: |
|
|
|
|
with self.project.captureChanges("{}: Move node".format(self.__node.title)): |
|
|
|
|
self.__node.rect = QtCore.QRectF( |
|
|
|
|
int(self.__rect.x() / self.__zoom + 10) // 20 * 20, |
|
|
|
@ -150,7 +152,7 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
int(self.__rect.height() / self.__zoom + 10) // 20 * 20) |
|
|
|
|
|
|
|
|
|
@QtCore.Slot() |
|
|
|
|
def resizeDone(self): |
|
|
|
|
def resizeDone(self) -> None: |
|
|
|
|
with self.project.captureChanges("{}: Resize node".format(self.__node.title)): |
|
|
|
|
self.__node.rect = QtCore.QRectF( |
|
|
|
|
self.__rect.x() / self.__zoom, |
|
|
|
@ -159,34 +161,13 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
self.__rect.height() / self.__zoom) |
|
|
|
|
|
|
|
|
|
@QtCore.Slot() |
|
|
|
|
def showContextMenu(self): |
|
|
|
|
menu = QtWidgets.QMenu(self.__view) |
|
|
|
|
|
|
|
|
|
rename_action = menu.addAction("Rename...") |
|
|
|
|
rename_action.triggered.connect(self.__rename) |
|
|
|
|
|
|
|
|
|
color_menu = menu.addMenu("Set color") |
|
|
|
|
color_action = SelectColorAction(color_menu) |
|
|
|
|
color_action.colorSelected.connect(self.__setColor) |
|
|
|
|
color_menu.addAction(color_action) |
|
|
|
|
|
|
|
|
|
menu.addAction("Cut") |
|
|
|
|
menu.addAction("Copy") |
|
|
|
|
menu.addAction("Duplicate") |
|
|
|
|
delete_action = menu.addAction("Delete") |
|
|
|
|
delete_action.triggered.connect(self.delete) |
|
|
|
|
|
|
|
|
|
if not menu.isEmpty(): |
|
|
|
|
menu.popup(QtGui.QCursor.pos()) |
|
|
|
|
|
|
|
|
|
@QtCore.Slot() |
|
|
|
|
def delete(self): |
|
|
|
|
def delete(self) -> None: |
|
|
|
|
with self.project.captureChanges("{}: Delete node".format(self.__node.title)): |
|
|
|
|
self.model.deleteNode(self.__node) |
|
|
|
|
|
|
|
|
|
def getRect(self): |
|
|
|
|
def getRect(self) -> QtCore.QRectF: |
|
|
|
|
return self.__rect |
|
|
|
|
def setRect(self, r): |
|
|
|
|
def setRect(self, r: QtCore.QRectF) -> None: |
|
|
|
|
r = QtCore.QRectF( |
|
|
|
|
int(r.x() / self.__zoom + 10) // 20 * 20 * self.__zoom, |
|
|
|
|
int(r.y() / self.__zoom + 10) // 20 * 20 * self.__zoom, |
|
|
|
@ -223,7 +204,7 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
rectChanged = QtCore.Signal(QtCore.QRectF) |
|
|
|
|
rect = QtCore.Property(QtCore.QRectF, fget=getRect, fset=setRect, notify=rectChanged) |
|
|
|
|
|
|
|
|
|
def __nodeRectChanged(self, rect): |
|
|
|
|
def __nodeRectChanged(self, rect: QtCore.QRectF) -> None: |
|
|
|
|
self.setRect(QtCore.QRectF( |
|
|
|
|
int(self.__zoom * rect.x()), |
|
|
|
|
int(self.__zoom * rect.y()), |
|
|
|
@ -231,22 +212,22 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
int(self.__zoom * rect.height()))) |
|
|
|
|
|
|
|
|
|
@QtCore.Slot(str, result=GraphNodePort) |
|
|
|
|
def port(self, portName): |
|
|
|
|
def port(self, portName: str) -> GraphNodePort: |
|
|
|
|
return self.__ports.get(portName) |
|
|
|
|
|
|
|
|
|
def ports(self): |
|
|
|
|
@ui_base.ConstProperty(List[GraphNodePort], list) |
|
|
|
|
def ports(self) -> List[GraphNodePort]: |
|
|
|
|
return [self.__ports[port.name] for port in self.__node.ports] |
|
|
|
|
ports = QtCore.Property(list, fget=ports, constant=True) |
|
|
|
|
|
|
|
|
|
def setZoom(self, v): |
|
|
|
|
def setZoom(self, v: float) -> None: |
|
|
|
|
if v == self.__zoom: |
|
|
|
|
return |
|
|
|
|
self.__zoom = v |
|
|
|
|
self.__nodeRectChanged(self.__node.rect) |
|
|
|
|
|
|
|
|
|
def isActive(self): |
|
|
|
|
def isActive(self) -> bool: |
|
|
|
|
return self.__active |
|
|
|
|
def setActive(self, v): |
|
|
|
|
def setActive(self, v: bool) -> None: |
|
|
|
|
if v == self.__active: |
|
|
|
|
return |
|
|
|
|
self.__active = v |
|
|
|
@ -254,9 +235,9 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
activeChanged = QtCore.Signal(bool) |
|
|
|
|
active = QtCore.Property(bool, fget=isActive, fset=setActive, notify=activeChanged) |
|
|
|
|
|
|
|
|
|
def isSelected(self): |
|
|
|
|
def isSelected(self) -> bool: |
|
|
|
|
return self.__selected |
|
|
|
|
def setSelected(self, v): |
|
|
|
|
def setSelected(self, v: bool) -> None: |
|
|
|
|
if v == self.__selected: |
|
|
|
|
return |
|
|
|
|
self.__selected = v |
|
|
|
@ -270,7 +251,8 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
with self.project.captureChanges("{}: Rename node".format(self.__node.title)): |
|
|
|
|
self.__node.title = title |
|
|
|
|
|
|
|
|
|
def nodeColors(self): |
|
|
|
|
@ui_base.ConstProperty(List[QtGui.QColor], list) |
|
|
|
|
def nodeColors(self) -> List[QtGui.QColor]: |
|
|
|
|
return [ |
|
|
|
|
QtGui.QColor(180, 180, 180), |
|
|
|
|
QtGui.QColor(205, 205, 205), |
|
|
|
@ -282,8 +264,8 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
QtGui.QColor(255, 205, 205), |
|
|
|
|
QtGui.QColor(255, 230, 230), |
|
|
|
|
|
|
|
|
|
QtGui.QColor(255, 155, 0.1), |
|
|
|
|
QtGui.QColor(255, 180, 0.3), |
|
|
|
|
QtGui.QColor(255, 155, 26), |
|
|
|
|
QtGui.QColor(255, 180, 77), |
|
|
|
|
QtGui.QColor(255, 205, 155), |
|
|
|
|
QtGui.QColor(255, 230, 205), |
|
|
|
|
|
|
|
|
@ -312,7 +294,6 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
QtGui.QColor(205, 255, 255), |
|
|
|
|
QtGui.QColor(230, 255, 255), |
|
|
|
|
] |
|
|
|
|
nodeColors = QtCore.Property(list, fget=nodeColors, constant=True) |
|
|
|
|
|
|
|
|
|
@QtCore.Slot(QtGui.QColor) |
|
|
|
|
def setColor(self, color: QtGui.QColor) -> None: |
|
|
|
@ -321,7 +302,7 @@ class GraphNode(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject)
|
|
|
|
|
self.__node.color = color |
|
|
|
|
|
|
|
|
|
@QtCore.Slot(result=str) |
|
|
|
|
def debugInfo(self): |
|
|
|
|
def debugInfo(self) -> str: |
|
|
|
|
info = [] |
|
|
|
|
info.append(str(self)) |
|
|
|
|
info.append("class: {}".format(type(self).__qualname__)) |
|
|
|
|