diff --git a/NodeGraphQt/base/commands.py b/NodeGraphQt/base/commands.py index a7cf013b..e7a594c9 100644 --- a/NodeGraphQt/base/commands.py +++ b/NodeGraphQt/base/commands.py @@ -154,27 +154,14 @@ def __init__(self, graph, node): self.scene = graph.scene() self.model = graph.model self.node = node - self.inputs = [] - self.outputs = [] self.node_parent = node.parent() - if hasattr(self.node, 'inputs'): - input_ports = self.node.input_ports() - self.inputs = [(p, p.connected_ports()) for p in input_ports] - if hasattr(self.node, 'outputs'): - output_ports = self.node.output_ports() - self.outputs = [(p, p.connected_ports()) for p in output_ports] - def undo(self): self.model.nodes[self.node.id] = self.node self.scene.addItem(self.node.view) - [port.connect_to(p) for port, connected_ports in self.inputs for p in connected_ports] - [port.connect_to(p) for port, connected_ports in self.outputs for p in connected_ports] self.node.set_parent(self.node_parent) def redo(self): - [port.disconnect_from(p) for port, connected_ports in self.inputs for p in connected_ports] - [port.disconnect_from(p) for port, connected_ports in self.outputs for p in connected_ports] self.model.nodes.pop(self.node.id) self.node.delete() @@ -328,6 +315,50 @@ def redo(self): self.source.view.disconnect_from(self.target.view) +class PortLockedCmd(QtWidgets.QUndoCommand): + """ + Port locked command. + + Args: + port (NodeGraphQt.Port): node port. + """ + + def __init__(self, port): + QtWidgets.QUndoCommand.__init__(self) + self.setText('lock port "{}"'.format(port.name())) + self.port = port + + def undo(self): + self.port.model.locked = False + self.port.view.locked = False + + def redo(self): + self.port.model.locked = True + self.port.view.locked = True + + +class PortUnlockedCmd(QtWidgets.QUndoCommand): + """ + Port unlocked command. + + Args: + port (NodeGraphQt.Port): node port. + """ + + def __init__(self, port): + QtWidgets.QUndoCommand.__init__(self) + self.setText('unlock port "{}"'.format(port.name())) + self.port = port + + def undo(self): + self.port.model.locked = True + self.port.view.locked = True + + def redo(self): + self.port.model.locked = False + self.port.view.locked = False + + class PortVisibleCmd(QtWidgets.QUndoCommand): """ Port visibility command. diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index 30be0c2c..ef72f713 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -15,7 +15,7 @@ from .factory import NodeFactory from .menu import NodeGraphMenu, NodesMenu from .model import NodeGraphModel -from .node import NodeObject, BaseNode, BackdropNode +from .node import NodeObject, BackdropNode, BaseNode from .port import Port from ..constants import ( URI_SCHEME, URN_SCHEME, @@ -1002,14 +1002,26 @@ def delete_node(self, node): 'node must be a instance of a NodeObject.' if node is self.root_node(): return - self.nodes_deleted.emit([node.id]) + + node_id = node.id + self._undo_stack.beginMacro('delete node: "{}"'.format(node.name())) + if isinstance(node, BaseNode): + for p in node.input_ports(): + if p.locked(): + p.set_locked(False, connected_ports=False) + p.clear_connections() + for p in node.output_ports(): + if p.locked(): + p.set_locked(False, connected_ports=False) + p.clear_connections() + if isinstance(node, SubGraph): - self._undo_stack.beginMacro('delete sub graph') self.delete_nodes(node.children()) self._undo_stack.push(NodeRemovedCmd(self, node)) - self._undo_stack.endMacro() - else: - self._undo_stack.push(NodeRemovedCmd(self, node)) + + self._undo_stack.push(NodeRemovedCmd(self, node)) + self._undo_stack.endMacro() + self.nodes_deleted.emit([node_id]) def delete_nodes(self, nodes): """ @@ -1018,14 +1030,29 @@ def delete_nodes(self, nodes): Args: nodes (list[NodeGraphQt.BaseNode]): list of node instances. """ + if not nodes: + return if not self._editable: return + node_ids = [n.id for n in nodes] root_node = self.root_node() - self.nodes_deleted.emit([n.id for n in nodes]) self._undo_stack.beginMacro('delete nodes') - [self.delete_nodes(n.children()) for n in nodes if isinstance(n, SubGraph)] - [self._undo_stack.push(NodeRemovedCmd(self, n)) for n in nodes if n is not root_node] + for node in nodes: + if isinstance(node, BaseNode): + for p in node.input_ports(): + if p.locked(): + p.set_locked(False, connected_ports=False) + p.clear_connections() + for p in node.output_ports(): + if p.locked(): + p.set_locked(False, connected_ports=False) + p.clear_connections() + if isinstance(node, SubGraph): + self.delete_nodes(node.children()) + if node is not root_node: + self._undo_stack.push(NodeRemovedCmd(self, node)) self._undo_stack.endMacro() + self.nodes_deleted.emit(node_ids) def delete_pipe(self, pipe): self._on_connection_changed([(pipe.input_port, pipe.output_port)], []) diff --git a/NodeGraphQt/base/port.py b/NodeGraphQt/base/port.py index db3ec631..64b3425c 100644 --- a/NodeGraphQt/base/port.py +++ b/NodeGraphQt/base/port.py @@ -1,6 +1,8 @@ #!/usr/bin/python from .commands import (PortConnectedCmd, PortDisconnectedCmd, + PortLockedCmd, + PortUnlockedCmd, PortVisibleCmd, NodeInputConnectedCmd, NodeInputDisconnectedCmd) @@ -162,8 +164,12 @@ def set_locked(self, state=False, connected_ports=True): state (Bool): port lock state. connected_ports (Bool): apply to lock state to connected ports. """ - self.model.locked = state - self.__view.locked = state + graph = self.node().graph + undo_stack = graph.undo_stack() + if state: + undo_stack.push(PortLockedCmd(self)) + else: + undo_stack.push(PortUnlockedCmd(self)) if connected_ports: for port in self.connected_ports(): port.set_locked(state, connected_ports=False) @@ -272,6 +278,25 @@ def disconnect_from(self, port=None): ports = {p.type_(): p for p in [self, port]} graph.port_disconnected.emit(ports[IN_PORT], ports[OUT_PORT]) + def clear_connections(self): + """ + Disconnect from all pipe connections and emit the + :attr:`NodeGraph.port_disconnected` signals from the node graph. + """ + if self.locked(): + err = 'Can\'t clear connections because port "{}" is locked.' + raise PortError(err.format(self.name())) + + if not self.connected_ports(): + return + + graph = self.node().graph + undo_stack = graph.undo_stack() + undo_stack.beginMacro('"{}" clear connections') + for cp in self.connected_ports(): + self.disconnect_from(cp) + undo_stack.endMacro() + @property def color(self): return self.__view.color diff --git a/NodeGraphQt/qgraphics/node_base.py b/NodeGraphQt/qgraphics/node_base.py index 8b9ce069..41e1bb44 100644 --- a/NodeGraphQt/qgraphics/node_base.py +++ b/NodeGraphQt/qgraphics/node_base.py @@ -740,11 +740,6 @@ def get_widget(self, name): def has_widget(self, name): return name in self._widgets.keys() - def delete(self): - [port.delete() for port, text in self._input_items.items()] - [port.delete() for port, text in self._output_items.items()] - super(NodeItem, self).delete() - def from_dict(self, node_dict): super(NodeItem, self).from_dict(node_dict) widgets = node_dict.pop('widgets', {}) diff --git a/NodeGraphQt/qgraphics/port.py b/NodeGraphQt/qgraphics/port.py index 97091383..6e78648e 100644 --- a/NodeGraphQt/qgraphics/port.py +++ b/NodeGraphQt/qgraphics/port.py @@ -131,11 +131,11 @@ def mouseReleaseEvent(self, event): super(PortItem, self).mouseReleaseEvent(event) def hoverEnterEvent(self, event): - self.hovered = True + self._hovered = True super(PortItem, self).hoverEnterEvent(event) def hoverLeaveEvent(self, event): - self.hovered = False + self._hovered = False super(PortItem, self).hoverLeaveEvent(event) def viewer_start_connection(self): @@ -253,9 +253,6 @@ def port_type(self): def port_type(self, port_type): self._port_type = port_type - def delete(self): - [pipe.delete() for pipe in self.connected_pipes] - def connect_to(self, port): if not port: for pipe in self.connected_pipes: