|
|
|
@ -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__))
|
|
|
|
|