nodeeditor-qtpy 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. nodeeditor/__init__.py +93 -0
  2. nodeeditor/node_colors_config.py +343 -0
  3. nodeeditor/node_content_widget.py +129 -0
  4. nodeeditor/node_decorators.py +65 -0
  5. nodeeditor/node_edge.py +518 -0
  6. nodeeditor/node_edge_dragging.py +185 -0
  7. nodeeditor/node_edge_intersect.py +219 -0
  8. nodeeditor/node_edge_rerouting.py +216 -0
  9. nodeeditor/node_edge_snapping.py +95 -0
  10. nodeeditor/node_edge_validators.py +82 -0
  11. nodeeditor/node_editor_widget.py +257 -0
  12. nodeeditor/node_editor_window.py +448 -0
  13. nodeeditor/node_graph.py +583 -0
  14. nodeeditor/node_graphics_cutline.py +57 -0
  15. nodeeditor/node_graphics_edge.py +375 -0
  16. nodeeditor/node_graphics_edge_path.py +226 -0
  17. nodeeditor/node_graphics_node.py +693 -0
  18. nodeeditor/node_graphics_scene.py +369 -0
  19. nodeeditor/node_graphics_socket.py +262 -0
  20. nodeeditor/node_graphics_view.py +907 -0
  21. nodeeditor/node_group.py +1286 -0
  22. nodeeditor/node_group_node.py +5 -0
  23. nodeeditor/node_icon_content_widget.py +176 -0
  24. nodeeditor/node_icon_graphics_node.py +524 -0
  25. nodeeditor/node_multi_input_node.py +450 -0
  26. nodeeditor/node_node.py +848 -0
  27. nodeeditor/node_scene.py +1106 -0
  28. nodeeditor/node_scene_clipboard.py +314 -0
  29. nodeeditor/node_scene_history.py +556 -0
  30. nodeeditor/node_serializable.py +43 -0
  31. nodeeditor/node_socket.py +445 -0
  32. nodeeditor/node_socket_shapes.py +261 -0
  33. nodeeditor/py.typed +0 -0
  34. nodeeditor/qss/nodestyle.qss +3 -0
  35. nodeeditor/utils.py +108 -0
  36. nodeeditor/utils_no_qt.py +21 -0
  37. nodeeditor_qtpy-0.8.1.dist-info/METADATA +165 -0
  38. nodeeditor_qtpy-0.8.1.dist-info/RECORD +40 -0
  39. nodeeditor_qtpy-0.8.1.dist-info/WHEEL +4 -0
  40. nodeeditor_qtpy-0.8.1.dist-info/licenses/LICENCE +23 -0
@@ -0,0 +1,518 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing NodeEditor's class for representing Edge and Edge Type Constants.
4
+ """
5
+ from collections import OrderedDict
6
+ from nodeeditor.node_graphics_edge import QDMGraphicsEdge
7
+ from nodeeditor.node_serializable import Serializable
8
+ from nodeeditor.utils_no_qt import dumpException
9
+
10
+ from typing import TYPE_CHECKING, List, Optional, Tuple, Any, Callable
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from nodeeditor.node_graphics_view import QDMGraphicsView
15
+ from nodeeditor.node_socket import Socket
16
+ from nodeeditor.node_scene import Scene
17
+
18
+
19
+ EDGE_TYPE_DIRECT = 1 #:
20
+ EDGE_TYPE_BEZIER = 2 #:
21
+ EDGE_TYPE_SQUARE = 3 #:
22
+ EDGE_TYPE_IMPROVED_SHARP = 4 #:
23
+ EDGE_TYPE_IMPROVED_BEZIER = 5 #:
24
+ EDGE_TYPE_DEFAULT = EDGE_TYPE_IMPROVED_BEZIER
25
+
26
+ DEBUG = False
27
+
28
+
29
+ class Edge(Serializable):
30
+ """
31
+ Class for representing Edge in NodeEditor.
32
+ """
33
+
34
+ #: class variable containing list of registered edge validators
35
+ edge_validators: List['function'] = []
36
+
37
+ def __init__(self, scene: 'Scene', start_socket: 'Socket' = None, end_socket: 'Socket' = None,
38
+ edge_type=EDGE_TYPE_DIRECT, label: str = "", input_index: int = -1) -> None:
39
+ """
40
+
41
+ :param scene: Reference to the :py:class:`~nodeeditor.node_scene.Scene`
42
+ :type scene: :py:class:`~nodeeditor.node_scene.Scene`
43
+ :param start_socket: Reference to the starting socket
44
+ :type start_socket: :py:class:`~nodeeditor.node_socket.Socket`
45
+ :param end_socket: Reference to the End socket or ``None``
46
+ :type end_socket: :py:class:`~nodeeditor.node_socket.Socket` or ``None``
47
+ :param edge_type: Constant determining type of edge. See :ref:`edge-type-constants`
48
+ :param label: Code-set text drawn on top of the edge. Not editable by
49
+ the user in the view; change it from code via ``edge.label = ...``
50
+ or ``edge.setLabel(...)`` at creation time or runtime.
51
+ :param input_index: Read position of this `Edge` on its **end** (input)
52
+ :class:`~nodeeditor.node_socket.Socket`, ``0`` being read first.
53
+ Only meaningful when ``end_socket`` is an input socket accepting
54
+ multiple `Edges`; pass ``-1`` (the default) to append the `Edge`,
55
+ i.e. read it last. See :ref:`multi-input-nodes`.
56
+
57
+ :Instance Attributes:
58
+
59
+ - **scene** - reference to the :class:`~nodeeditor.node_scene.Scene`
60
+ - **grEdge** - Instance of :class:`~nodeeditor.node_graphics_edge.QDMGraphicsEdge` subclass handling graphical representation in the ``QGraphicsScene``.
61
+ """
62
+ super().__init__()
63
+ self.scene = scene
64
+
65
+ # default init
66
+ self._start_socket = None
67
+ self._end_socket = None
68
+
69
+ # Must be set *before* attaching the sockets: an ordered input
70
+ # `Socket` reads it in `addEdge()` to place this Edge at the right
71
+ # slot instead of appending it.
72
+ self.input_index = input_index
73
+
74
+ self.start_socket = start_socket
75
+ self.end_socket = end_socket
76
+ self._edge_type = edge_type
77
+
78
+ # create Graphics Edge instance
79
+ self.grEdge: QDMGraphicsEdge = self.createEdgeClassInstance()
80
+
81
+ if label:
82
+ self.setLabel(label)
83
+
84
+ self.scene.addEdge(self)
85
+
86
+ def __str__(self) -> str:
87
+ """A short description of this `Edge` and the two sockets it connects"""
88
+ return "<Edge %s..%s -- S:%s E:%s>" % (
89
+ hex(id(self))[2:5], hex(id(self))[-3:],
90
+ self.start_socket, self.end_socket
91
+ )
92
+
93
+ @property
94
+ def input_index(self) -> int:
95
+ """
96
+ Read position of this `Edge` on its end (input) :class:`~nodeeditor.node_socket.Socket`.
97
+
98
+ ``0`` is read first, ``1`` next, and so on. Only meaningful for `Edges`
99
+ whose ``end_socket`` is an input socket; the owning
100
+ :class:`~nodeeditor.node_socket.Socket` owns and compacts the value, so
101
+ assign it from code only together with
102
+ :meth:`~nodeeditor.node_socket.Socket.compactEdgeOrder`. Use
103
+ :class:`~nodeeditor.node_multi_input_node.MultiInputNode` for a safe
104
+ reordering API.
105
+
106
+ :getter: current read position, or ``-1`` when not attached to an input socket
107
+ :setter: coerces to ``int``; anything invalid becomes ``-1``
108
+ :type: ``int``
109
+ """
110
+ return self._input_index
111
+
112
+ @input_index.setter
113
+ def input_index(self, value) -> None:
114
+ try:
115
+ self._input_index = int(value)
116
+ except (TypeError, ValueError):
117
+ self._input_index = -1
118
+
119
+ @property
120
+ def start_socket(self) -> Optional["Socket"]:
121
+ """
122
+ Start socket
123
+
124
+ :getter: Returns start :class:`~nodeeditor.node_socket.Socket`
125
+ :setter: Sets start :class:`~nodeeditor.node_socket.Socket` safely
126
+ :type: :class:`~nodeeditor.node_socket.Socket`
127
+ """
128
+ return self._start_socket if self._start_socket else None
129
+
130
+ @start_socket.setter
131
+ def start_socket(self, value: "Socket") -> None:
132
+ # if we were assigned to some socket before, delete us from the socket
133
+ if self._start_socket is not None:
134
+ self._start_socket.removeEdge(self)
135
+
136
+ # assign new start socket
137
+ self._start_socket = value
138
+ # addEdge to the Socket class
139
+ if self.start_socket is not None:
140
+ self.start_socket.addEdge(self)
141
+
142
+ @property
143
+ def end_socket(self):
144
+ """
145
+ End socket
146
+
147
+ :getter: Returns end :class:`~nodeeditor.node_socket.Socket` or ``None`` if not set
148
+ :setter: Sets end :class:`~nodeeditor.node_socket.Socket` safely
149
+ :type: :class:`~nodeeditor.node_socket.Socket` or ``None``
150
+ """
151
+ return self._end_socket
152
+
153
+ @end_socket.setter
154
+ def end_socket(self, value) -> None:
155
+ # if we were assigned to some socket before, delete us from the socket
156
+ if self._end_socket is not None:
157
+ self._end_socket.removeEdge(self)
158
+
159
+ # assign new end socket
160
+ self._end_socket = value
161
+ # addEdge to the Socket class
162
+ if self.end_socket is not None:
163
+ self.end_socket.addEdge(self)
164
+ if not self.end_socket.is_input:
165
+ # a read position is only meaningful on an input socket
166
+ self.input_index = -1
167
+
168
+ @property
169
+ def edge_type(self):
170
+ """
171
+ Edge type
172
+
173
+ :getter: get edge type constant for current ``Edge``. See :ref:`edge-type-constants`
174
+ :setter: sets new edge type. On background, creates new :class:`~nodeeditor.node_graphics_edge.QDMGraphicsEdge`
175
+ child class if necessary, adds this ``QGraphicsPathItem`` to the ``QGraphicsScene`` and updates edge sockets
176
+ positions.
177
+ """
178
+ return self._edge_type
179
+
180
+ @edge_type.setter
181
+ def edge_type(self, value) -> None:
182
+ # assign new value
183
+ self._edge_type = value
184
+
185
+ # update the grEdge pathCalculator
186
+ self.grEdge.createEdgePathCalculator()
187
+
188
+ if self.start_socket is not None:
189
+ self.updatePositions()
190
+
191
+ @property
192
+ def label(self) -> str:
193
+ """
194
+ Code-set text drawn on top of the edge.
195
+
196
+ :getter: current label text (``""`` when unset)
197
+ :setter: sets new label text; ``""``/``None`` hides the label.
198
+ Not user-editable in the view -- change only from code, both at
199
+ creation time (``Edge(..., label="...")``) and at runtime
200
+ (``edge.label = "..."``).
201
+ """
202
+ return self.grEdge.label()
203
+
204
+ @label.setter
205
+ def label(self, value: str) -> None:
206
+ self.setLabel(value)
207
+
208
+ def getLabel(self) -> str:
209
+ """Return the current code-set edge label."""
210
+ return self.grEdge.label()
211
+
212
+ def setLabel(self, text: str) -> None:
213
+ """Set the text drawn on top of the edge (``""`` hides it)."""
214
+ self.grEdge.setLabel(text)
215
+
216
+ @classmethod
217
+ def getEdgeValidators(cls):
218
+ """Return the list of Edge Validator Callbacks"""
219
+ return cls.edge_validators
220
+
221
+ @classmethod
222
+ def registerEdgeValidator(cls, validator_callback: 'function') -> None:
223
+ """Register Edge Validator Callback
224
+
225
+ :param validator_callback: A function handle to validate Edge
226
+ :type validator_callback: `function`
227
+ """
228
+ cls.edge_validators.append(validator_callback)
229
+
230
+ @classmethod
231
+ def validateEdge(cls, start_socket: 'Socket', end_socket: 'Socket') -> bool:
232
+ """Validate Edge agains all registered `Edge Validator Callbacks`
233
+
234
+ :param start_socket: Starting :class:`~nodeeditor.node_socket.Socket` of Edge to check
235
+ :type start_socket: :class:`~nodeeditor.node_socket.Socket`
236
+ :param end_socket: Target/End :class:`~nodeeditor.node_socket.Socket` of Edge to check
237
+ :type end_socket: :class:`~nodeeditor.node_socket.Socket`
238
+ :return: ``True`` if the Edge is valid or ``False`` if not
239
+ :rtype: ``bool``
240
+ """
241
+ for validator in cls.getEdgeValidators():
242
+ if not validator(start_socket, end_socket):
243
+ return False
244
+ return True
245
+
246
+ def reconnect(self, from_socket: 'Socket', to_socket: 'Socket') -> None:
247
+ """Helper function which reconnects edge `from_socket` to `to_socket`"""
248
+ if self.start_socket == from_socket:
249
+ self.start_socket = to_socket
250
+ elif self.end_socket == from_socket:
251
+ self.end_socket = to_socket
252
+
253
+ def getGraphicsEdgeClass(self):
254
+ """Returns the class representing Graphics Edge"""
255
+ return QDMGraphicsEdge
256
+
257
+ def createEdgeClassInstance(self):
258
+ """
259
+ Create instance of grEdge class
260
+ :return: Instance of `grEdge` class representing the Graphics Edge in the grScene
261
+ """
262
+ self.grEdge = self.getGraphicsEdgeClass()(self)
263
+ self.scene.grScene.addItem(self.grEdge)
264
+ if self.start_socket is not None:
265
+ self.updatePositions()
266
+ return self.grEdge
267
+
268
+ def getOtherSocket(self, known_socket: 'Socket'):
269
+ """
270
+ Returns the opposite socket on this ``Edge``
271
+
272
+ :param known_socket: Provide known :class:`~nodeeditor.node_socket.Socket` to be able to determine the opposite one.
273
+ :type known_socket: :class:`~nodeeditor.node_socket.Socket`
274
+ :return: The oposite socket on this ``Edge`` or ``None``
275
+ :rtype: :class:`~nodeeditor.node_socket.Socket` or ``None``
276
+ """
277
+ return self.start_socket if known_socket == self.end_socket else self.end_socket
278
+
279
+ def doSelect(self, new_state: bool = True) -> None:
280
+ """
281
+ Provide the safe selecting/deselecting operation. In the background it takes care about the flags, notifications
282
+ and storing history for undo/redo.
283
+
284
+ :param new_state: ``True`` if you want to select the ``Edge``, ``False`` if you want to deselect the ``Edge``
285
+ :type new_state: ``bool``
286
+ """
287
+ self.grEdge.doSelect(new_state)
288
+
289
+ def updatePositions(self) -> None:
290
+ """
291
+ Updates the internal `Graphics Edge` positions.
292
+
293
+ Collapsed-group aware: if an endpoint lives in a collapsed Group,
294
+ the endpoint is overridden to that group's per-edge stub row.
295
+ Logical sockets are never mutated. Internal edges of a collapsed
296
+ group stay hidden.
297
+ """
298
+ try:
299
+ if self.start_socket is None:
300
+ return
301
+ if getattr(self, 'grEdge', None) is None:
302
+ return
303
+
304
+ start_node = getattr(self.start_socket, 'node', None)
305
+ end_node = getattr(self.end_socket, 'node', None) if self.end_socket is not None else None
306
+
307
+ start_group = None
308
+ end_group = None
309
+ try:
310
+ if start_node is not None:
311
+ start_group = self.scene.findCollapsedGroupForNode(start_node)
312
+ if end_node is not None:
313
+ end_group = self.scene.findCollapsedGroupForNode(end_node)
314
+ except Exception:
315
+ start_group = end_group = None
316
+
317
+ # internal edge of a collapsed group -> keep hidden
318
+ try:
319
+ if start_group is not None and start_group is end_group and end_group is not None:
320
+ try:
321
+ self.grEdge.hide()
322
+ except Exception:
323
+ pass
324
+ return
325
+ except Exception:
326
+ pass
327
+
328
+ # source
329
+ source_pos = None
330
+ try:
331
+ if start_group is not None:
332
+ p = start_group.stubPosFor(self)
333
+ if p is not None:
334
+ source_pos = [p.x(), p.y()]
335
+ if source_pos is None:
336
+ gs = getattr(self.start_socket, 'grSocket', None)
337
+ if gs is None:
338
+ return
339
+ sp = gs.scenePos()
340
+ source_pos = [sp.x(), sp.y()]
341
+ except Exception:
342
+ return
343
+ self.grEdge.setSource(*source_pos)
344
+
345
+ if self.end_socket is not None:
346
+ end_pos = None
347
+ try:
348
+ if end_group is not None:
349
+ p = end_group.stubPosFor(self)
350
+ if p is not None:
351
+ end_pos = [p.x(), p.y()]
352
+ if end_pos is None:
353
+ gs = getattr(self.end_socket, 'grSocket', None)
354
+ if gs is None:
355
+ self.grEdge.setDestination(*source_pos)
356
+ self.grEdge.update()
357
+ return
358
+ ep = gs.scenePos()
359
+ end_pos = [ep.x(), ep.y()]
360
+ except Exception:
361
+ end_pos = list(source_pos)
362
+ self.grEdge.setDestination(*end_pos)
363
+ else:
364
+ self.grEdge.setDestination(*source_pos)
365
+ self.grEdge.update()
366
+ except Exception:
367
+ try:
368
+ dumpException()
369
+ except Exception:
370
+ pass
371
+
372
+ def remove_from_sockets(self) -> None:
373
+ """
374
+ Helper function which sets start and end :class:`~nodeeditor.node_socket.Socket` to ``None``
375
+ """
376
+ self.end_socket = None
377
+ self.start_socket = None
378
+ # forget the read position; re-attaching appends at the end by default
379
+ self.input_index = -1
380
+
381
+ def remove(self, silent_for_socket: 'Socket' = None, silent: bool=False) -> None:
382
+ """
383
+ Safely remove this Edge.
384
+
385
+ Removes `Graphics Edge` from the ``QGraphicsScene`` and it's reference to all GC to clean it up.
386
+ Notifies nodes previously connected :class:`~nodeeditor.node_node.Node` (s) about this event.
387
+
388
+ Triggers Nodes':
389
+
390
+ - :py:meth:`~nodeeditor.node_node.Node.onEdgeConnectionChanged`
391
+ - :py:meth:`~nodeeditor.node_node.Node.onInputChanged`
392
+
393
+ :param silent_for_socket: :class:`~nodeeditor.node_socket.Socket` of a :class:`~nodeeditor.node_node.Node` which
394
+ won't be notified, when this ``Edge`` is going to be removed
395
+ :type silent_for_socket: :class:`~nodeeditor.node_socket.Socket`
396
+ :param silent: ``True`` if no events should be triggered during removing
397
+ :type silent: ``bool``
398
+ """
399
+ old_sockets = [self.start_socket, self.end_socket]
400
+
401
+ # ugly hack, since I noticed that even when you remove grEdge from scene,
402
+ # sometimes it stays there! How dare you Qt!
403
+ if DEBUG:
404
+ print(" - hide grEdge")
405
+ self.grEdge.hide()
406
+
407
+ if DEBUG:
408
+ print(" - remove grEdge", self.grEdge)
409
+ self.scene.grScene.removeItem(self.grEdge)
410
+ if DEBUG:
411
+ print(" grEdge:", self.grEdge)
412
+
413
+ self.scene.grScene.update()
414
+
415
+ if DEBUG:
416
+ print("# Removing Edge", self)
417
+ if DEBUG:
418
+ print(" - remove edge from all sockets")
419
+ self.remove_from_sockets()
420
+ if DEBUG:
421
+ print(" - remove edge from scene")
422
+ try:
423
+ self.scene.removeEdge(self)
424
+ except ValueError:
425
+ pass
426
+ if DEBUG:
427
+ print(" - everything is done.")
428
+
429
+ try:
430
+ # notify nodes from old sockets
431
+ for socket in old_sockets:
432
+ if socket and socket.node:
433
+ if silent:
434
+ continue
435
+ if silent_for_socket is not None and socket == silent_for_socket:
436
+ # if we requested silence for Socket and it's this one, skip notifications
437
+ continue
438
+
439
+ # notify Socket's Node
440
+ socket.node.onEdgeConnectionChanged(self)
441
+ if socket.is_input:
442
+ socket.node.onInputChanged(socket)
443
+
444
+ except Exception as e:
445
+ dumpException(e)
446
+
447
+ def serialize(self) -> OrderedDict:
448
+ """
449
+ Serialize this `Edge` into an ``OrderedDict`` for storing in memory or on disk.
450
+
451
+ :return: data serialized in ``OrderedDict``
452
+ :rtype: ``OrderedDict``
453
+ """
454
+ return OrderedDict([
455
+ ('id', self.id),
456
+ ('edge_type', self.edge_type),
457
+ ('start', self.start_socket.id if self.start_socket is not None else None),
458
+ ('end', self.end_socket.id if self.end_socket is not None else None),
459
+ ('label', self.getLabel()),
460
+ ('input_index', self.input_index),
461
+ ])
462
+
463
+ def deserialize(self, data: dict, hashmap: dict = {}, restore_id: bool = True, *args, **kwargs) -> bool:
464
+ """
465
+ Deserialize this `Edge` from a ``dict`` of serialized data.
466
+
467
+ Both sockets are looked up in the ``hashmap``, so they have to be
468
+ deserialized before the edge that connects them.
469
+
470
+ :param data: Dictionary containing serialized data
471
+ :type data: ``dict``
472
+ :param hashmap: Helper dictionary containing references (by id == key) to existing objects
473
+ :type hashmap: ``dict``
474
+ :param restore_id: True if we are creating new Edges. False is useful when loading existing
475
+ Edges of which we want to keep the existing object's `id`.
476
+ :type restore_id: bool
477
+ :return: ``True`` if deserialization was successful, otherwise ``False``
478
+ :rtype: ``bool``
479
+ """
480
+ try:
481
+ if restore_id:
482
+ self.id = data['id']
483
+ # Restore the read position *before* attaching the sockets: an
484
+ # ordered input `Socket` inserts the `Edge` at this slot. Old files
485
+ # without the key load as -1, i.e. order of connection in the file.
486
+ self.input_index = data.get('input_index', -1)
487
+ try:
488
+ start_id = data.get('start', None)
489
+ end_id = data.get('end', None)
490
+ except Exception:
491
+ return False
492
+ if start_id not in hashmap or end_id not in hashmap:
493
+ return False
494
+ self.start_socket = hashmap[start_id]
495
+ self.end_socket = hashmap[end_id]
496
+ if self.start_socket is None or self.end_socket is None:
497
+ return False
498
+ self.edge_type = data['edge_type']
499
+ # 'label' is optional so old files without it still load
500
+ self.setLabel(data.get('label', "") or "")
501
+ return True
502
+ except Exception:
503
+ try:
504
+ dumpException()
505
+ except Exception:
506
+ pass
507
+ return False
508
+
509
+
510
+ # Example: using validators for Edge
511
+ # You can register edge validators wherever you want, even here...
512
+ # However if you do use overridden Edge, you should call registerEdgeValidator on that overridden class
513
+ #
514
+ # from nodeeditor.node_edge_validators import *
515
+ # Edge.registerEdgeValidator(edge_validator_debug)
516
+ # Edge.registerEdgeValidator(edge_cannot_connect_two_outputs_or_two_inputs)
517
+ # Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_same_node)
518
+ # Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_different_color)
@@ -0,0 +1,185 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing the Edge Dragging functionality
4
+ """
5
+ from nodeeditor.node_graphics_socket import QDMGraphicsSocket
6
+ from nodeeditor.node_edge import EDGE_TYPE_DEFAULT
7
+ from nodeeditor.utils import dumpException
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from qtpy.QtWidgets import QGraphicsItem
13
+ from nodeeditor.node_socket import Socket
14
+ from nodeeditor.node_edge import Edge
15
+ from nodeeditor.node_graphics_view import QDMGraphicsView
16
+
17
+ DEBUG = False
18
+
19
+
20
+ class EdgeDragging:
21
+ """Class handling the dragging of a new :class:`~nodeeditor.node_edge.Edge` between sockets"""
22
+
23
+ def __init__(self, grView: 'QDMGraphicsView') -> None:
24
+ """
25
+ :param grView: reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
26
+ :type grView: :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
27
+
28
+ :Instance Attributes:
29
+
30
+ - **grView** - reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
31
+ - **drag_edge** - the :class:`~nodeeditor.node_edge.Edge` currently being dragged
32
+ - **drag_start_socket** - the :class:`~nodeeditor.node_socket.Socket` where the drag started
33
+ """
34
+ self.grView = grView
35
+ # initializing these variable to know we're using them in this class...
36
+ self.drag_edge: Edge | None = None
37
+ self.drag_start_socket: Socket | None = None
38
+
39
+ def getEdgeClass(self):
40
+ """Helper function to get the Edge class. Using what Scene class provides"""
41
+ return self.grView.grScene.scene.getEdgeClass()
42
+
43
+ def updateDestination(self, x: float, y: float) -> None:
44
+ """
45
+ Update the end point of our dragging edge
46
+
47
+ :param x: new X scene position
48
+ :param y: new Y scene position
49
+ """
50
+ # according to sentry: 'NoneType' object has no attribute 'grEdge'
51
+ if self.drag_edge is None:
52
+ print(">>> Warning: drag_edge is None!")
53
+ return
54
+
55
+ if self.drag_edge.grEdge is None:
56
+ print(">>> Warning: drag_edge.grEdge is None!")
57
+ return
58
+
59
+ self.drag_edge.grEdge.setDestination(x, y)
60
+ self.drag_edge.grEdge.update()
61
+
62
+ def edgeDragStart(self, item: QDMGraphicsSocket) -> None:
63
+ """Code handling the start of a dragging an `Edge` operation"""
64
+ try:
65
+ if DEBUG:
66
+ print('View::edgeDragStart ~ Start dragging edge')
67
+ if DEBUG:
68
+ print('View::edgeDragStart ~ assign Start Socket to:', item.socket)
69
+ self.drag_start_socket = item.socket
70
+ self.drag_edge = self.getEdgeClass()(
71
+ item.socket.node.scene, item.socket, None, EDGE_TYPE_DEFAULT)
72
+
73
+ if self.drag_edge is None:
74
+ print(">>> Warning: Failed to create drag_edge")
75
+ return
76
+
77
+ if self.drag_edge.grEdge is None:
78
+ print(">>> Warning: drag_edge.grEdge is None")
79
+ return
80
+
81
+ self.drag_edge.grEdge.makeUnselectable()
82
+
83
+ if DEBUG:
84
+ print('View::edgeDragStart ~ dragEdge:', self.drag_edge)
85
+
86
+ except Exception as e:
87
+ dumpException(e)
88
+
89
+ def edgeDragEnd(self, item: 'QGraphicsItem'):
90
+ """Code handling the end of the dragging an `Edge` operation. If this code returns True then skip the
91
+ rest of the mouse event processing. Can be called with ``None`` to cancel the edge dragging mode
92
+
93
+ :param item: Item in the `Graphics Scene` where we ended dragging an `Edge`
94
+ :type item: ``QGraphicsItem``
95
+ """
96
+
97
+ # Cancel edge if clicking on empty space or receiving None (ESC pressed)
98
+ if item is None or not isinstance(item, QDMGraphicsSocket):
99
+ self.cancelDragEdge()
100
+ return False
101
+
102
+ # early out - clicked on something else than Socket
103
+ if not isinstance(item, QDMGraphicsSocket):
104
+ self.grView.resetMode()
105
+ if DEBUG:
106
+ print('View::edgeDragEnd ~ End dragging edge early')
107
+
108
+ # don't notify sockets about removing drag_edge
109
+ if self.drag_edge is not None:
110
+ self.drag_edge.remove(silent=True)
111
+ self.drag_edge = None
112
+ return
113
+
114
+ # clicked on socket
115
+ if isinstance(item, QDMGraphicsSocket):
116
+ if self.drag_edge is None or self.drag_start_socket is None:
117
+ return False
118
+
119
+ # check if edge would be valid
120
+ if not self.drag_edge.validateEdge(self.drag_start_socket, item.socket):
121
+ print("NOT VALID EDGE")
122
+ return False
123
+
124
+ # regular processing of drag edge
125
+ self.grView.resetMode()
126
+
127
+ if DEBUG:
128
+ print('View::edgeDragEnd ~ End dragging edge')
129
+ # don't notify sockets about removing drag_edge
130
+ self.drag_edge.remove(silent=True)
131
+ self.drag_edge = None
132
+
133
+ try:
134
+ if item.socket != self.drag_start_socket:
135
+ # First verify both sockets exist
136
+ if self.drag_start_socket is None:
137
+ print(">>> Warning: drag_start_socket is None!")
138
+ return False
139
+
140
+ # First remove old edges / send notifications
141
+ for socket in (item.socket, self.drag_start_socket):
142
+ if socket is None:
143
+ print(">>> Warning: socket is None!")
144
+ continue
145
+
146
+ if not socket.is_multi_edges:
147
+ if socket.is_input:
148
+ socket.removeAllEdges(silent=True)
149
+ else:
150
+ socket.removeAllEdges(silent=False)
151
+
152
+ # Create new Edge
153
+ new_edge = self.getEdgeClass()(item.socket.node.scene, self.drag_start_socket,
154
+ item.socket, edge_type=EDGE_TYPE_DEFAULT)
155
+ if DEBUG:
156
+ print("View::edgeDragEnd ~ created new edge:", new_edge,
157
+ "connecting", new_edge.start_socket, "<-->", new_edge.end_socket)
158
+
159
+ # Send notifications for the new edge
160
+ for socket in [self.drag_start_socket, item.socket]:
161
+ # @TODO: Add possibility (ie when an input edge was replaced) to be silent and don't trigger change
162
+ socket.node.onEdgeConnectionChanged(new_edge)
163
+ if socket.is_input:
164
+ socket.node.onInputChanged(socket)
165
+
166
+ self.grView.grScene.scene.history.storeHistory(
167
+ "Created new edge by dragging", setModified=True)
168
+ return True
169
+ except Exception as e:
170
+ dumpException(e)
171
+
172
+ if DEBUG:
173
+ print('View::edgeDragEnd ~ everything done.')
174
+ return False
175
+
176
+ def cancelDragEdge(self) -> None:
177
+ """Cancel the edge dragging operation and clean up"""
178
+ self.grView.resetMode()
179
+ if DEBUG:
180
+ print('View::cancelDragEdge ~ Cancel dragging edge')
181
+
182
+ if self.drag_edge is not None:
183
+ self.drag_edge.remove(silent=True)
184
+ self.drag_edge = None
185
+ self.drag_start_socket = None