Skip to content

Commit 1fcb06c

Browse files
authored
Merge pull request jchanvfx#164 from ArnoChenFx/master
Node text item editable && nodegraph eidtable feature && garbage collect && Vertical Layout
2 parents 1ab4eec + bdfd658 commit 1fcb06c

23 files changed

+1095
-515
lines changed

NodeGraphQt/base/commands.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/python
22
from .. import QtWidgets
3-
43
from ..constants import IN_PORT, OUT_PORT
4+
from .utils import minimize_node_ref_count
55

66

77
class PropertyChangedCmd(QtWidgets.QUndoCommand):
@@ -158,10 +158,10 @@ def __init__(self, graph, node):
158158
self.node_parent = node.parent()
159159

160160
if hasattr(self.node, 'inputs'):
161-
input_ports = self.node.inputs().values()
161+
input_ports = self.node.input_ports()
162162
self.inputs = [(p, p.connected_ports()) for p in input_ports]
163163
if hasattr(self.node, 'outputs'):
164-
output_ports = self.node.outputs().values()
164+
output_ports = self.node.output_ports()
165165
self.outputs = [(p, p.connected_ports()) for p in output_ports]
166166

167167
def undo(self):
@@ -177,6 +177,9 @@ def redo(self):
177177
self.model.nodes.pop(self.node.id)
178178
self.node.delete()
179179

180+
def __del__(self):
181+
minimize_node_ref_count(self.node)
182+
180183

181184
class NodeInputConnectedCmd(QtWidgets.QUndoCommand):
182185
"""

NodeGraphQt/base/graph.py

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import re
66
import copy
7-
7+
import gc
88
from .. import QtCore, QtWidgets, QtGui
99
from .commands import (NodeAddedCmd,
1010
NodeRemovedCmd,
@@ -142,6 +142,7 @@ def __init__(self, parent=None):
142142
self._node_factory = NodeFactory()
143143
self._undo_stack = QtWidgets.QUndoStack(self)
144144
self._current_node_space = None
145+
self._editable = True
145146

146147
tab = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Tab), self._viewer)
147148
tab.activated.connect(self._toggle_tab_search)
@@ -176,6 +177,8 @@ def _insert_node(self, pipe, node_id, prev_node_pos):
176177
node_id (str): selected node id to insert.
177178
prev_node_pos (dict): previous node position. {NodeItem: [prev_x, prev_y]}
178179
"""
180+
if not self._editable:
181+
return
179182
node = self.get_node_by_id(node_id)
180183

181184
# exclude the BackdropNode
@@ -203,6 +206,8 @@ def _toggle_tab_search(self):
203206
"""
204207
toggle the tab search widget.
205208
"""
209+
if not self._editable:
210+
return
206211
if self._viewer.underMouse():
207212
self._viewer.tab_search_set_nodes(self._node_factory.names)
208213
self._viewer.tab_search_toggle()
@@ -217,6 +222,8 @@ def _on_property_bin_changed(self, node_id, prop_name, prop_value):
217222
prop_name (str): node property name.
218223
prop_value (object): python object.
219224
"""
225+
if not self._editable:
226+
return
220227
node = self.get_node_by_id(node_id)
221228

222229
# prevent signals from causing a infinite loop.
@@ -307,6 +314,8 @@ def _on_connection_changed(self, disconnected, connected):
307314
connected (list[list[widgets.port.PortItem]]):
308315
pair list of port view items.
309316
"""
317+
if not self._editable:
318+
return
310319
if not (disconnected or connected):
311320
return
312321

@@ -336,7 +345,7 @@ def _on_connection_sliced(self, ports):
336345
ports (list[list[widgets.port.PortItem]]):
337346
pair list of port connections (in port, out port)
338347
"""
339-
if not ports:
348+
if not ports or not self._editable:
340349
return
341350
ptypes = {IN_PORT: 'inputs', OUT_PORT: 'outputs'}
342351
self._undo_stack.beginMacro('slice connections')
@@ -385,6 +394,25 @@ def auto_update(self):
385394
"""
386395
return self._auto_update
387396

397+
@property
398+
def editable(self):
399+
"""
400+
Returns whether the graph is editable.
401+
"""
402+
return self._editable
403+
404+
@editable.setter
405+
def editable(self, state):
406+
"""
407+
Set whether the graph is editable.
408+
409+
Args:
410+
state(bool).
411+
"""
412+
self._editable = state
413+
self._viewer.editable = state
414+
self._viewer.scene().editable = state
415+
388416
def show(self):
389417
"""
390418
Show node graph widget this is just a convenience
@@ -513,6 +541,7 @@ def clear_undo_stack(self):
513541
:meth:`NodeGraph.undo_stack()`
514542
"""
515543
self._undo_stack.clear()
544+
gc.collect()
516545

517546
def begin_undo(self, name):
518547
"""
@@ -747,6 +776,8 @@ def create_node(self, node_type, name=None, selected=True, color=None,
747776
Returns:
748777
NodeGraphQt.BaseNode: the created instance of the node.
749778
"""
779+
if not self._editable:
780+
return
750781
NodeCls = self._node_factory.create_node_instance(node_type)
751782
if NodeCls:
752783
node = NodeCls()
@@ -813,6 +844,8 @@ def add_node(self, node, pos=None, unique_name=True):
813844
pos (list[float]): node x,y position. (optional)
814845
unique_name (bool): make node name unique
815846
"""
847+
if not self._editable:
848+
return
816849
assert isinstance(node, NodeObject), 'node must be a Node instance.'
817850

818851
wid_types = node.model.__dict__.pop('_TEMP_property_widget_types')
@@ -850,6 +883,10 @@ def set_node_space(self, node):
850883
if node is not None:
851884
node.enter()
852885
self._node_space_bar.set_node(node)
886+
self.editable = node.is_editable()
887+
[n.set_editable(self.editable) for n in node.children() if isinstance(n, BaseNode)]
888+
else:
889+
self.editable = True
853890

854891
def get_node_space(self):
855892
"""
@@ -867,6 +904,8 @@ def delete_node(self, node):
867904
Args:
868905
node (NodeGraphQt.BaseNode): node object.
869906
"""
907+
if not self._editable:
908+
return
870909
assert isinstance(node, NodeObject), \
871910
'node must be a instance of a NodeObject.'
872911
if node is self.root_node():
@@ -887,6 +926,8 @@ def delete_nodes(self, nodes):
887926
Args:
888927
nodes (list[NodeGraphQt.BaseNode]): list of node instances.
889928
"""
929+
if not self._editable:
930+
return
890931
root_node = self.root_node()
891932
self.nodes_deleted.emit([n.id for n in nodes])
892933
self._undo_stack.beginMacro('delete nodes')
@@ -1056,7 +1097,7 @@ def clear_session(self):
10561097
continue
10571098
self._undo_stack.push(NodeRemovedCmd(self, n))
10581099
self.set_node_space(root_node)
1059-
self._undo_stack.clear()
1100+
self.clear_undo_stack()
10601101
self._model.session = None
10611102
self.session_changed.emit("")
10621103

@@ -1082,9 +1123,7 @@ def _serialize(self, nodes):
10821123
node_dict = n.model.to_dict
10831124

10841125
if isinstance(n, SubGraph):
1085-
published = n.has_property('published')
1086-
if published:
1087-
published = n.get_property('published')
1126+
published = n.get_property('published')
10881127
if not published:
10891128
children = n.children()
10901129
if children:
@@ -1132,6 +1171,8 @@ def _deserialize(self, data, relative_pos=False, pos=None, set_parent=True):
11321171
Returns:
11331172
list[NodeGraphQt.Nodes]: list of node instances.
11341173
"""
1174+
if not self._editable:
1175+
return
11351176
nodes = {}
11361177
# build the nodes.
11371178
for n_id, n_data in data.get('nodes', {}).items():
@@ -1148,18 +1189,18 @@ def _deserialize(self, data, relative_pos=False, pos=None, set_parent=True):
11481189
for prop, val in n_data.get('custom', {}).items():
11491190
node.model.set_property(prop, val)
11501191
nodes[n_id] = node
1151-
self.add_node(node, n_data.get('pos'), unique_name=set_parent)
1152-
node.set_disabled(n_data.get('disabled', False))
1192+
11531193
if isinstance(node, SubGraph):
1154-
if n_data.get('custom', None):
1155-
published = n_data['custom'].get('published', False)
1156-
else:
1157-
published = False
1194+
node.create_by_deserialize = True
1195+
self.add_node(node, n_data.get('pos'), unique_name=set_parent)
1196+
published = n_data['custom'].get('published', False)
11581197
if not published:
11591198
sub_graph = n_data.get('sub_graph', None)
11601199
if sub_graph:
11611200
children = self._deserialize(sub_graph, relative_pos, pos, False)
11621201
[child.set_parent(node) for child in children]
1202+
else:
1203+
self.add_node(node, n_data.get('pos'), unique_name=set_parent)
11631204

11641205
if n_data.get('dynamic_port', None):
11651206
node.set_ports({'input_ports': n_data['input_ports'], 'output_ports': n_data['output_ports']})
@@ -1212,7 +1253,7 @@ def deserialize_session(self, layout_data):
12121253
"""
12131254
self.clear_session()
12141255
self._deserialize(layout_data)
1215-
self._undo_stack.clear()
1256+
self.clear_undo_stack()
12161257

12171258
def save_session(self, file_path):
12181259
"""
@@ -1287,7 +1328,7 @@ def import_session(self, file_path):
12871328
self.set_grid_mode(layout_data['graph'].get('grid_mode', VIEWER_GRID_LINES))
12881329

12891330
self.set_node_space(self.root_node())
1290-
self._undo_stack.clear()
1331+
self.clear_undo_stack()
12911332
self._model.session = file_path
12921333
self.session_changed.emit(file_path)
12931334
self._auto_update = _temp_auto_update
@@ -1325,6 +1366,8 @@ def paste_nodes(self):
13251366
"""
13261367
Pastes nodes copied from the clipboard.
13271368
"""
1369+
if not self._editable:
1370+
return
13281371
clipboard = QtWidgets.QApplication.clipboard()
13291372
cb_text = clipboard.text()
13301373
if not cb_text:
@@ -1346,7 +1389,7 @@ def duplicate_nodes(self, nodes):
13461389
Returns:
13471390
list[NodeGraphQt.BaseNode]: list of duplicated node instances.
13481391
"""
1349-
if not nodes:
1392+
if not nodes or not self._editable:
13501393
return
13511394

13521395
self._undo_stack.beginMacro('duplicate nodes')
@@ -1374,7 +1417,7 @@ def disable_nodes(self, nodes, mode=None):
13741417
nodes (list[NodeGraphQt.BaseNode]): list of node instances.
13751418
mode (bool): (optional) disable state of the nodes.
13761419
"""
1377-
if not nodes:
1420+
if not nodes or not self._editable:
13781421
return
13791422
if mode is None:
13801423
mode = not nodes[0].disabled()

NodeGraphQt/base/node.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
NODE_PROP_FILE,
1111
NODE_PROP_FLOAT,
1212
NODE_PROP_INT,
13-
IN_PORT, OUT_PORT)
13+
IN_PORT, OUT_PORT,
14+
NODE_LAYOUT_VERTICAL,
15+
NODE_LAYOUT_HORIZONTAL,
16+
NODE_LAYOUT_DIRECTION)
1417
from ..errors import PortRegistrationError
1518
from ..qgraphics.node_backdrop import BackdropNodeItem
16-
from ..qgraphics.node_base import NodeItem
19+
from ..qgraphics.node_base import NodeItem, NodeItemVertical
1720
from ..widgets.node_widgets import (NodeComboBox,
1821
NodeLineEdit,
1922
NodeFloatEdit,
@@ -325,6 +328,8 @@ def set_property(self, name, value):
325328
pass
326329

327330
if self.graph and name == 'name':
331+
if len(value) == 0:
332+
value = '_'
328333
value = self.graph.get_unique_name(value)
329334
self.NODE_NAME = value
330335

@@ -457,7 +462,7 @@ def delete(self):
457462
self._view.delete()
458463
if self._parent is not None:
459464
self._parent.remove_child(self)
460-
# self.set_parent(None)
465+
self._parent = None
461466

462467
def hide(self):
463468
"""
@@ -512,10 +517,16 @@ def __init__(self):
512517
NODE_NAME = 'Base Node'
513518

514519
def __init__(self):
515-
super(BaseNode, self).__init__(NodeItem())
520+
view = None
521+
if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL:
522+
view = NodeItemVertical()
523+
elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL:
524+
view = NodeItem()
525+
super(BaseNode, self).__init__(view)
516526
self._inputs = []
517527
self._outputs = []
518528
self._has_draw = False
529+
self._view.text_item.editingFinished.connect(self.set_name)
519530

520531
def draw(self, force=True):
521532
if force:
@@ -878,7 +889,7 @@ def delete_input(self, port):
878889
self._inputs.remove(port)
879890
self._model.inputs.pop(port.name())
880891
self._view.delete_input(port.view)
881-
del port
892+
port.model.node = None
882893
self.draw()
883894

884895
def delete_output(self, port):
@@ -895,7 +906,7 @@ def delete_output(self, port):
895906
self._outputs.remove(port)
896907
self._model.outputs.pop(port.name())
897908
self._view.delete_output(port.view)
898-
del port
909+
port.model.node = None
899910
self.draw()
900911

901912
def set_ports(self, port_data):
@@ -906,8 +917,12 @@ def set_ports(self, port_data):
906917
port_data(dict): {'input_ports':[{'name':...,'multi_connection':...,'display_name':...,'data_type':...}, ...],
907918
" 'output_ports':[{'name':...,'multi_connection':...,'display_name':...,'data_type':...}, ...]}
908919
"""
909-
[self._view.delete_input(port.view) for port in self._inputs]
910-
[self._view.delete_output(port.view) for port in self._outputs]
920+
for port in self._inputs:
921+
self._view.delete_input(port.view)
922+
port.model.node = None
923+
for port in self._outputs:
924+
self._view.delete_output(port.view)
925+
port.model.node = None
911926
self._inputs = []
912927
self._outputs = []
913928
self._model.outputs = {}
@@ -1079,6 +1094,16 @@ def when_disabled(self):
10791094
"""
10801095
return
10811096

1097+
def set_editable(self, state):
1098+
"""
1099+
Returns whether the node view widgets is editable.
1100+
1101+
Args:
1102+
state(bool).
1103+
"""
1104+
[wid.setEnabled(state) for wid in self.view._widgets.values()]
1105+
self.view.text_item.setEnabled(state)
1106+
10821107

10831108
class BackdropNode(NodeObject):
10841109
"""

0 commit comments

Comments
 (0)