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.
- nodeeditor/__init__.py +93 -0
- nodeeditor/node_colors_config.py +343 -0
- nodeeditor/node_content_widget.py +129 -0
- nodeeditor/node_decorators.py +65 -0
- nodeeditor/node_edge.py +518 -0
- nodeeditor/node_edge_dragging.py +185 -0
- nodeeditor/node_edge_intersect.py +219 -0
- nodeeditor/node_edge_rerouting.py +216 -0
- nodeeditor/node_edge_snapping.py +95 -0
- nodeeditor/node_edge_validators.py +82 -0
- nodeeditor/node_editor_widget.py +257 -0
- nodeeditor/node_editor_window.py +448 -0
- nodeeditor/node_graph.py +583 -0
- nodeeditor/node_graphics_cutline.py +57 -0
- nodeeditor/node_graphics_edge.py +375 -0
- nodeeditor/node_graphics_edge_path.py +226 -0
- nodeeditor/node_graphics_node.py +693 -0
- nodeeditor/node_graphics_scene.py +369 -0
- nodeeditor/node_graphics_socket.py +262 -0
- nodeeditor/node_graphics_view.py +907 -0
- nodeeditor/node_group.py +1286 -0
- nodeeditor/node_group_node.py +5 -0
- nodeeditor/node_icon_content_widget.py +176 -0
- nodeeditor/node_icon_graphics_node.py +524 -0
- nodeeditor/node_multi_input_node.py +450 -0
- nodeeditor/node_node.py +848 -0
- nodeeditor/node_scene.py +1106 -0
- nodeeditor/node_scene_clipboard.py +314 -0
- nodeeditor/node_scene_history.py +556 -0
- nodeeditor/node_serializable.py +43 -0
- nodeeditor/node_socket.py +445 -0
- nodeeditor/node_socket_shapes.py +261 -0
- nodeeditor/py.typed +0 -0
- nodeeditor/qss/nodestyle.qss +3 -0
- nodeeditor/utils.py +108 -0
- nodeeditor/utils_no_qt.py +21 -0
- nodeeditor_qtpy-0.8.1.dist-info/METADATA +165 -0
- nodeeditor_qtpy-0.8.1.dist-info/RECORD +40 -0
- nodeeditor_qtpy-0.8.1.dist-info/WHEEL +4 -0
- nodeeditor_qtpy-0.8.1.dist-info/licenses/LICENCE +23 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
A module containing the Main Window class
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import orjson as json
|
|
7
|
+
from orjson import OPT_INDENT_2, JSONDecodeError
|
|
8
|
+
from qtpy.QtCore import QSize, QSettings, QPoint
|
|
9
|
+
from qtpy.QtWidgets import QMainWindow, QLabel, QAction, QMessageBox, QFileDialog, QApplication, QMenu, QMenuBar
|
|
10
|
+
from nodeeditor.node_editor_widget import NodeEditorWidget
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, List, Optional, Tuple, Any, cast
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from nodeeditor.node_graphics_view import QDMGraphicsView
|
|
17
|
+
from nodeeditor.node_edge import Edge
|
|
18
|
+
from nodeeditor.node_socket import Socket
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NodeEditorWindow(QMainWindow):
|
|
22
|
+
"""The Main Window of NodeEditor.
|
|
23
|
+
|
|
24
|
+
Holds the file and edit menus, the actions behind them and the
|
|
25
|
+
:class:`~nodeeditor.node_editor_widget.NodeEditorWidget` that draws the graph.
|
|
26
|
+
|
|
27
|
+
:Instance Attributes:
|
|
28
|
+
|
|
29
|
+
- **name_company** - name of the company, used for permanent profile settings
|
|
30
|
+
- **name_product** - name of this App, used for permanent profile settings
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
NodeEditorWidget_class = NodeEditorWidget
|
|
34
|
+
|
|
35
|
+
"""Class representing NodeEditor's Main Window"""
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
"""
|
|
39
|
+
:Instance Attributes:
|
|
40
|
+
|
|
41
|
+
- **name_company** - name of the company, used for permanent profile settings
|
|
42
|
+
- **name_product** - name of this App, used for permanent profile settings
|
|
43
|
+
"""
|
|
44
|
+
super().__init__()
|
|
45
|
+
|
|
46
|
+
self.name_company = 'Trigger'
|
|
47
|
+
self.name_product = 'NodeEditor'
|
|
48
|
+
|
|
49
|
+
self.initUI()
|
|
50
|
+
|
|
51
|
+
def initUI(self) -> None:
|
|
52
|
+
"""Set up this ``QMainWindow``. Create :class:`~nodeeditor.node_editor_widget.NodeEditorWidget`, Actions and Menus"""
|
|
53
|
+
self.createActions()
|
|
54
|
+
self.createMenus()
|
|
55
|
+
|
|
56
|
+
# create node editor widget
|
|
57
|
+
self.nodeeditor = self.__class__.NodeEditorWidget_class(self)
|
|
58
|
+
self.nodeeditor.scene.addHasBeenModifiedListener(self.setTitle)
|
|
59
|
+
self.setCentralWidget(self.nodeeditor)
|
|
60
|
+
|
|
61
|
+
self.createStatusBar()
|
|
62
|
+
|
|
63
|
+
# set window properties
|
|
64
|
+
# self.setGeometry(200, 200, 800, 600)
|
|
65
|
+
self.setTitle()
|
|
66
|
+
self.show()
|
|
67
|
+
|
|
68
|
+
def sizeHint(self):
|
|
69
|
+
"""The preferred size of the main window.
|
|
70
|
+
|
|
71
|
+
:return: the default window size
|
|
72
|
+
:rtype: ``QSize``
|
|
73
|
+
"""
|
|
74
|
+
return QSize(800, 600)
|
|
75
|
+
|
|
76
|
+
def createStatusBar(self) -> None:
|
|
77
|
+
"""Create Status bar and connect to `Graphics View` scenePosChanged event"""
|
|
78
|
+
self.statusBar().showMessage("")
|
|
79
|
+
self.status_mouse_pos = QLabel("")
|
|
80
|
+
self.statusBar().addPermanentWidget(self.status_mouse_pos)
|
|
81
|
+
self.nodeeditor.view.scenePosChanged.connect(self.onScenePosChanged)
|
|
82
|
+
|
|
83
|
+
# type: ignore[override]
|
|
84
|
+
def createActions(self) -> None:
|
|
85
|
+
"""Create basic `File` and `Edit` actions"""
|
|
86
|
+
self.actNew = QAction('&New', self, shortcut='Ctrl+N',
|
|
87
|
+
statusTip="Create new graph", triggered=self.onFileNew)
|
|
88
|
+
self.actOpen = QAction('&Open', self, shortcut='Ctrl+O',
|
|
89
|
+
statusTip="Open file", triggered=self.onFileOpen)
|
|
90
|
+
self.actSave = QAction('&Save', self, shortcut='Ctrl+S',
|
|
91
|
+
statusTip="Save file", triggered=self.onFileSave)
|
|
92
|
+
self.actSaveAs = QAction('Save &As...', self, shortcut='Ctrl+Shift+S',
|
|
93
|
+
statusTip="Save file as...", triggered=self.onFileSaveAs)
|
|
94
|
+
self.actExit = QAction('E&xit', self, shortcut='Ctrl+Q',
|
|
95
|
+
statusTip="Exit application", triggered=self.close)
|
|
96
|
+
|
|
97
|
+
self.actUndo = QAction('&Undo', self, shortcut='Ctrl+Z',
|
|
98
|
+
statusTip="Undo last operation", triggered=self.onEditUndo)
|
|
99
|
+
self.actRedo = QAction('&Redo', self, shortcut='Ctrl+Shift+Z',
|
|
100
|
+
statusTip="Redo last operation", triggered=self.onEditRedo)
|
|
101
|
+
self.actCut = QAction('Cu&t', self, shortcut='Ctrl+X',
|
|
102
|
+
statusTip="Cut to clipboard", triggered=self.onEditCut)
|
|
103
|
+
self.actCopy = QAction('&Copy', self, shortcut='Ctrl+C',
|
|
104
|
+
statusTip="Copy to clipboard", triggered=self.onEditCopy)
|
|
105
|
+
self.actPaste = QAction('&Paste', self, shortcut='Ctrl+V',
|
|
106
|
+
statusTip="Paste from clipboard", triggered=self.onEditPaste)
|
|
107
|
+
self.actDelete = QAction('&Delete', self, shortcut='Del',
|
|
108
|
+
statusTip="Delete selected items", triggered=self.onEditDelete)
|
|
109
|
+
|
|
110
|
+
self.actGroup = QAction('&Group selected', self, shortcut='G',
|
|
111
|
+
statusTip="Group selected nodes", triggered=self.onGroupSelected)
|
|
112
|
+
self.actUngroup = QAction('&Ungroup', self, shortcut='U',
|
|
113
|
+
statusTip="Ungroup selected groups (keep nodes)", triggered=self.onUngroupSelected)
|
|
114
|
+
try:
|
|
115
|
+
self.actUngroup.setShortcuts(['U', 'Shift+G'])
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
self.actCollapseGroup = QAction('&Collapse/Expand group', self, shortcut='C',
|
|
119
|
+
statusTip="Collapse/expand selected groups", triggered=self.onToggleCollapseSelected)
|
|
120
|
+
|
|
121
|
+
def createMenus(self) -> None:
|
|
122
|
+
"""Create Menus for `File` and `Edit`"""
|
|
123
|
+
self.createFileMenu()
|
|
124
|
+
self.createEditMenu()
|
|
125
|
+
|
|
126
|
+
def createFileMenu(self) -> None:
|
|
127
|
+
"""Create the `File` menu and fill it with the file actions"""
|
|
128
|
+
menubar: QMenuBar = self.menuBar()
|
|
129
|
+
self.fileMenu: QMenu = menubar.addMenu('&File')
|
|
130
|
+
self.fileMenu.addAction(self.actNew)
|
|
131
|
+
self.fileMenu.addSeparator()
|
|
132
|
+
self.fileMenu.addAction(self.actOpen)
|
|
133
|
+
self.fileMenu.addAction(self.actSave)
|
|
134
|
+
self.fileMenu.addAction(self.actSaveAs)
|
|
135
|
+
self.fileMenu.addSeparator()
|
|
136
|
+
self.fileMenu.addAction(self.actExit)
|
|
137
|
+
|
|
138
|
+
def createEditMenu(self) -> None:
|
|
139
|
+
"""Create the `Edit` menu and fill it with the edit and grouping actions"""
|
|
140
|
+
menubar: QMenuBar = self.menuBar()
|
|
141
|
+
self.editMenu: QMenu = menubar.addMenu('&Edit')
|
|
142
|
+
self.editMenu.addAction(self.actUndo)
|
|
143
|
+
self.editMenu.addAction(self.actRedo)
|
|
144
|
+
self.editMenu.addSeparator()
|
|
145
|
+
self.editMenu.addAction(self.actCut)
|
|
146
|
+
self.editMenu.addAction(self.actCopy)
|
|
147
|
+
self.editMenu.addAction(self.actPaste)
|
|
148
|
+
self.editMenu.addSeparator()
|
|
149
|
+
self.editMenu.addAction(self.actDelete)
|
|
150
|
+
self.editMenu.addSeparator()
|
|
151
|
+
self.editMenu.addAction(self.actGroup)
|
|
152
|
+
self.editMenu.addAction(self.actUngroup)
|
|
153
|
+
self.editMenu.addAction(self.actCollapseGroup)
|
|
154
|
+
|
|
155
|
+
def setTitle(self) -> None:
|
|
156
|
+
"""Function responsible for setting window title"""
|
|
157
|
+
title = "Node Editor - "
|
|
158
|
+
title += self.getCurrentNodeEditorWidget().getUserFriendlyFilename()
|
|
159
|
+
|
|
160
|
+
self.setWindowTitle(title)
|
|
161
|
+
|
|
162
|
+
def closeEvent(self, event) -> None:
|
|
163
|
+
"""Handle close event. Ask before we loose work"""
|
|
164
|
+
if self.maybeSave():
|
|
165
|
+
event.accept()
|
|
166
|
+
else:
|
|
167
|
+
event.ignore()
|
|
168
|
+
|
|
169
|
+
def isModified(self) -> bool:
|
|
170
|
+
"""Has current :class:`~nodeeditor.node_scene.Scene` been modified?
|
|
171
|
+
|
|
172
|
+
:return: ``True`` if current :class:`~nodeeditor.node_scene.Scene` has been modified
|
|
173
|
+
:rtype: ``bool``
|
|
174
|
+
"""
|
|
175
|
+
nodeeditor = self.getCurrentNodeEditorWidget()
|
|
176
|
+
return nodeeditor.scene.isModified() if nodeeditor else False
|
|
177
|
+
|
|
178
|
+
def getCurrentNodeEditorWidget(self) -> NodeEditorWidget:
|
|
179
|
+
"""get current :class:`~nodeeditor.node_editor_widget`
|
|
180
|
+
|
|
181
|
+
:return: get current :class:`~nodeeditor.node_editor_widget`
|
|
182
|
+
:rtype: :class:`~nodeeditor.node_editor_widget`
|
|
183
|
+
"""
|
|
184
|
+
return cast(NodeEditorWidget, self.centralWidget())
|
|
185
|
+
|
|
186
|
+
def maybeSave(self) -> bool:
|
|
187
|
+
"""If current `Scene` is modified, ask a dialog to save the changes. Used before
|
|
188
|
+
closing window / mdi child document
|
|
189
|
+
|
|
190
|
+
:return: ``True`` if we can continue in the `Close Event` and shutdown. ``False`` if we should cancel
|
|
191
|
+
:rtype: ``bool``
|
|
192
|
+
"""
|
|
193
|
+
if not self.isModified():
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
res = QMessageBox.warning(self, "About to loose your work?",
|
|
197
|
+
"The document has been modified.\n Do you want to save your changes?",
|
|
198
|
+
QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if res == QMessageBox.StandardButton.Save:
|
|
202
|
+
return self.onFileSave()
|
|
203
|
+
elif res == QMessageBox.StandardButton.Cancel:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
def onScenePosChanged(self, x: int, y: int) -> None:
|
|
209
|
+
"""Handle event when cursor position changed on the `Scene`
|
|
210
|
+
|
|
211
|
+
:param x: new cursor x position
|
|
212
|
+
:type x:
|
|
213
|
+
:param y: new cursor y position
|
|
214
|
+
:type y:
|
|
215
|
+
"""
|
|
216
|
+
self.status_mouse_pos.setText("Scene Pos: [%d, %d]" % (x, y))
|
|
217
|
+
|
|
218
|
+
def getFileDialogDirectory(self) -> str:
|
|
219
|
+
"""Returns starting directory for ``QFileDialog`` file open/save"""
|
|
220
|
+
return ''
|
|
221
|
+
|
|
222
|
+
def getFileDialogFilter(self) -> str:
|
|
223
|
+
"""Returns ``str`` standard file open/save filter for ``QFileDialog``"""
|
|
224
|
+
return 'Design (*.tds);;All Files (*)'
|
|
225
|
+
|
|
226
|
+
def onFileNew(self) -> None:
|
|
227
|
+
"""Hande File New operation"""
|
|
228
|
+
if self.maybeSave():
|
|
229
|
+
self.getCurrentNodeEditorWidget().fileNew()
|
|
230
|
+
self.setTitle()
|
|
231
|
+
|
|
232
|
+
def onFileOpen(self) -> None:
|
|
233
|
+
"""Handle File Open operation"""
|
|
234
|
+
if self.maybeSave():
|
|
235
|
+
fname, filter = QFileDialog.getOpenFileName(
|
|
236
|
+
self, 'Open graph from file', self.getFileDialogDirectory(), self.getFileDialogFilter())
|
|
237
|
+
if fname != '' and os.path.isfile(fname):
|
|
238
|
+
self.getCurrentNodeEditorWidget().fileLoad(fname)
|
|
239
|
+
self.setTitle()
|
|
240
|
+
|
|
241
|
+
def onFileSave(self):
|
|
242
|
+
"""Handle File Save operation"""
|
|
243
|
+
current_nodeeditor = self.getCurrentNodeEditorWidget()
|
|
244
|
+
if current_nodeeditor is not None:
|
|
245
|
+
if not current_nodeeditor.isFilenameSet():
|
|
246
|
+
return self.onFileSaveAs()
|
|
247
|
+
|
|
248
|
+
current_nodeeditor.fileSave()
|
|
249
|
+
self.statusBar().showMessage("Successfully saved %s" %
|
|
250
|
+
current_nodeeditor.filename, 5000)
|
|
251
|
+
|
|
252
|
+
# support for MDI app
|
|
253
|
+
if hasattr(current_nodeeditor, "setTitle"):
|
|
254
|
+
current_nodeeditor.setTitle()
|
|
255
|
+
else:
|
|
256
|
+
self.setTitle()
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
def onFileSaveAs(self) -> bool:
|
|
260
|
+
"""Handle File Save As operation"""
|
|
261
|
+
current_nodeeditor = self.getCurrentNodeEditorWidget()
|
|
262
|
+
if current_nodeeditor is not None:
|
|
263
|
+
fname, filter = QFileDialog.getSaveFileName(
|
|
264
|
+
self, 'Save design to file', self.getFileDialogDirectory(), self.getFileDialogFilter())
|
|
265
|
+
if fname == '':
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
self.onBeforeSaveAs(current_nodeeditor, fname)
|
|
269
|
+
current_nodeeditor.fileSave(fname)
|
|
270
|
+
self.statusBar().showMessage("Successfully saved as %s" %
|
|
271
|
+
current_nodeeditor.filename, 5000)
|
|
272
|
+
|
|
273
|
+
# support for MDI app
|
|
274
|
+
if hasattr(current_nodeeditor, "setTitle"):
|
|
275
|
+
current_nodeeditor.setTitle()
|
|
276
|
+
else:
|
|
277
|
+
self.setTitle()
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
def onBeforeSaveAs(self, current_nodeeditor: 'NodeEditorWidget', filename: str) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Event triggered after choosing filename and before actual fileSave(). We are passing current_nodeeditor because
|
|
283
|
+
we will loose focus after asking with QFileDialog and therefore getCurrentNodeEditorWidget will return None
|
|
284
|
+
"""
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
def onEditUndo(self) -> None:
|
|
288
|
+
"""Handle Edit Undo operation"""
|
|
289
|
+
if self.getCurrentNodeEditorWidget():
|
|
290
|
+
self.getCurrentNodeEditorWidget().scene.undo()
|
|
291
|
+
|
|
292
|
+
def onEditRedo(self) -> None:
|
|
293
|
+
"""Handle Edit Redo operation"""
|
|
294
|
+
if self.getCurrentNodeEditorWidget():
|
|
295
|
+
self.getCurrentNodeEditorWidget().scene.redo()
|
|
296
|
+
|
|
297
|
+
def onEditDelete(self) -> None:
|
|
298
|
+
"""Handle Delete Selected operation"""
|
|
299
|
+
if self.getCurrentNodeEditorWidget():
|
|
300
|
+
self.getCurrentNodeEditorWidget().scene.getView().deleteSelected()
|
|
301
|
+
|
|
302
|
+
def _groupingFocusInTextEdit(self) -> bool:
|
|
303
|
+
"""Guard plain-letter group shortcuts so typing in node widgets still works."""
|
|
304
|
+
try:
|
|
305
|
+
from qtpy.QtWidgets import QLineEdit, QTextEdit, QPlainTextEdit
|
|
306
|
+
w = QApplication.focusWidget()
|
|
307
|
+
return isinstance(w, (QLineEdit, QTextEdit, QPlainTextEdit))
|
|
308
|
+
except Exception:
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
def _selectedLogicNodes(self, scene):
|
|
312
|
+
nodes = []
|
|
313
|
+
try:
|
|
314
|
+
for item in list(scene.getSelectedItems()):
|
|
315
|
+
try:
|
|
316
|
+
n = getattr(item, 'node', None)
|
|
317
|
+
if n is not None and n not in nodes:
|
|
318
|
+
nodes.append(n)
|
|
319
|
+
except Exception:
|
|
320
|
+
continue
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
return nodes
|
|
324
|
+
|
|
325
|
+
def _selectedGroups(self, scene):
|
|
326
|
+
try:
|
|
327
|
+
from nodeeditor.node_group import Group
|
|
328
|
+
except Exception:
|
|
329
|
+
return []
|
|
330
|
+
try:
|
|
331
|
+
return [it for it in list(scene.getSelectedItems()) if isinstance(it, Group)]
|
|
332
|
+
except Exception:
|
|
333
|
+
return []
|
|
334
|
+
|
|
335
|
+
def onGroupSelected(self) -> None:
|
|
336
|
+
"""Group currently selected nodes (needs 2+). Shortcut G."""
|
|
337
|
+
if self._groupingFocusInTextEdit():
|
|
338
|
+
return
|
|
339
|
+
editor = self.getCurrentNodeEditorWidget()
|
|
340
|
+
if not editor:
|
|
341
|
+
return
|
|
342
|
+
scene = editor.scene
|
|
343
|
+
nodes = self._selectedLogicNodes(scene)
|
|
344
|
+
if len(nodes) < 2:
|
|
345
|
+
try:
|
|
346
|
+
self.statusBar().showMessage("Select 2+ nodes to group", 3000)
|
|
347
|
+
except Exception:
|
|
348
|
+
pass
|
|
349
|
+
return
|
|
350
|
+
try:
|
|
351
|
+
from nodeeditor.node_group import Group
|
|
352
|
+
group = Group(scene, title=f"Group ({len(nodes)} nodes)")
|
|
353
|
+
for node in nodes:
|
|
354
|
+
group.addNode(node)
|
|
355
|
+
group.updateBounds()
|
|
356
|
+
scene.history.storeHistory(f"Created group with {len(nodes)} nodes", setModified=True)
|
|
357
|
+
except Exception:
|
|
358
|
+
pass
|
|
359
|
+
|
|
360
|
+
def onUngroupSelected(self) -> None:
|
|
361
|
+
"""Ungroup selected groups, keep nodes. Shortcut U / Shift+G."""
|
|
362
|
+
if self._groupingFocusInTextEdit():
|
|
363
|
+
return
|
|
364
|
+
editor = self.getCurrentNodeEditorWidget()
|
|
365
|
+
if not editor:
|
|
366
|
+
return
|
|
367
|
+
scene = editor.scene
|
|
368
|
+
groups = self._selectedGroups(scene)
|
|
369
|
+
if not groups:
|
|
370
|
+
return
|
|
371
|
+
for grp in list(groups):
|
|
372
|
+
try:
|
|
373
|
+
grp.ungroup()
|
|
374
|
+
except Exception:
|
|
375
|
+
continue
|
|
376
|
+
try:
|
|
377
|
+
scene.history.storeHistory("Ungrouped", setModified=True)
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
def onToggleCollapseSelected(self) -> None:
|
|
382
|
+
"""Collapse/expand selected groups. Shortcut C."""
|
|
383
|
+
if self._groupingFocusInTextEdit():
|
|
384
|
+
return
|
|
385
|
+
editor = self.getCurrentNodeEditorWidget()
|
|
386
|
+
if not editor:
|
|
387
|
+
return
|
|
388
|
+
scene = editor.scene
|
|
389
|
+
groups = self._selectedGroups(scene)
|
|
390
|
+
if not groups:
|
|
391
|
+
return
|
|
392
|
+
for grp in list(groups):
|
|
393
|
+
try:
|
|
394
|
+
grp.toggleCollapse()
|
|
395
|
+
except Exception:
|
|
396
|
+
continue
|
|
397
|
+
try:
|
|
398
|
+
scene.history.storeHistory("Toggled group collapse", setModified=True)
|
|
399
|
+
except Exception:
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
def onEditCut(self) -> None:
|
|
403
|
+
"""Handle Edit Cut to clipboard operation"""
|
|
404
|
+
if self.getCurrentNodeEditorWidget():
|
|
405
|
+
data = self.getCurrentNodeEditorWidget(
|
|
406
|
+
).scene.clipboard.serializeSelected(delete=True)
|
|
407
|
+
str_data = json.dumps(data, option=OPT_INDENT_2).decode("utf-8")
|
|
408
|
+
QApplication.instance().clipboard().setText(str_data)
|
|
409
|
+
|
|
410
|
+
def onEditCopy(self) -> None:
|
|
411
|
+
"""Handle Edit Copy to clipboard operation"""
|
|
412
|
+
if self.getCurrentNodeEditorWidget():
|
|
413
|
+
data = self.getCurrentNodeEditorWidget(
|
|
414
|
+
).scene.clipboard.serializeSelected(delete=False)
|
|
415
|
+
str_data = json.dumps(data, option=OPT_INDENT_2).decode("utf-8")
|
|
416
|
+
QApplication.instance().clipboard().setText(str_data)
|
|
417
|
+
|
|
418
|
+
def onEditPaste(self):
|
|
419
|
+
"""Handle Edit Paste from clipboard operation"""
|
|
420
|
+
if self.getCurrentNodeEditorWidget():
|
|
421
|
+
raw_data = QApplication.instance().clipboard().text()
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
data = json.loads(raw_data)
|
|
425
|
+
except JSONDecodeError as e:
|
|
426
|
+
print("Pasting of not valid json data!", e)
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
# check if the json data are correct
|
|
430
|
+
if 'nodes' not in data:
|
|
431
|
+
print("JSON does not contain any nodes!")
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
return self.getCurrentNodeEditorWidget().scene.clipboard.deserializeFromClipboard(data)
|
|
435
|
+
|
|
436
|
+
def readSettings(self) -> None:
|
|
437
|
+
"""Read the permanent profile settings for this app"""
|
|
438
|
+
settings = QSettings(self.name_company, self.name_product)
|
|
439
|
+
pos = settings.value('pos', QPoint(200, 200))
|
|
440
|
+
size = settings.value('size', QSize(400, 400))
|
|
441
|
+
self.move(pos)
|
|
442
|
+
self.resize(size)
|
|
443
|
+
|
|
444
|
+
def writeSettings(self) -> None:
|
|
445
|
+
"""Write the permanent profile settings for this app"""
|
|
446
|
+
settings = QSettings(self.name_company, self.name_product)
|
|
447
|
+
settings.setValue('pos', self.pos())
|
|
448
|
+
settings.setValue('size', self.size())
|