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,524 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
A module containing Graphics representation of :class:`~nodeeditor.node_node.Node`
|
|
4
|
+
"""
|
|
5
|
+
from qtpy.QtWidgets import QGraphicsItem, QWidget, QGraphicsTextItem, QGraphicsPixmapItem, QGraphicsSceneHoverEvent
|
|
6
|
+
from qtpy.QtGui import QFont, QColor, QPen, QBrush, QPainterPath, QPixmap
|
|
7
|
+
from qtpy.QtCore import Qt, QRectF
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, List, Mapping, Optional, Tuple, Any
|
|
10
|
+
|
|
11
|
+
from nodeeditor.node_colors_config import get_color_scheme
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from nodeeditor.node_graphics_node import QDMGraphicsNodeLabel
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from nodeeditor.node_graphics_view import QDMGraphicsView
|
|
18
|
+
from nodeeditor.node_edge import Edge
|
|
19
|
+
from nodeeditor.node_socket import Socket
|
|
20
|
+
from nodeeditor.node_node import Node
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class QDMIconGraphicsNode(QGraphicsItem):
|
|
24
|
+
"""Class describing Graphics representation of :class:`~nodeeditor.node_node.Node`"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, node: 'Node', parent: QGraphicsItem = None, icon_path: QPixmap = None) -> None:
|
|
27
|
+
"""
|
|
28
|
+
:param node: reference to :class:`~nodeeditor.node_node.Node`
|
|
29
|
+
:type node: :class:`~nodeeditor.node_node.Node`
|
|
30
|
+
:param parent: parent widget
|
|
31
|
+
:type parent: QWidget
|
|
32
|
+
|
|
33
|
+
:Instance Attributes:
|
|
34
|
+
|
|
35
|
+
- **node** - reference to :class:`~nodeeditor.node_node.Node`
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(parent)
|
|
38
|
+
self.node = node
|
|
39
|
+
self.icon_path = icon_path
|
|
40
|
+
|
|
41
|
+
# init our flags
|
|
42
|
+
self.hovered = False
|
|
43
|
+
self._was_moved = False
|
|
44
|
+
self._last_selected_state = False
|
|
45
|
+
|
|
46
|
+
self.initSizes()
|
|
47
|
+
self.initAssets()
|
|
48
|
+
self.initUI()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def content(self):
|
|
52
|
+
"""Reference to `Node Content`"""
|
|
53
|
+
return self.node.content if self.node else None
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def title(self):
|
|
57
|
+
"""title of this `Node`
|
|
58
|
+
|
|
59
|
+
:getter: current Graphics Node title
|
|
60
|
+
:setter: stores and make visible the new title
|
|
61
|
+
:type: str
|
|
62
|
+
"""
|
|
63
|
+
return self._title
|
|
64
|
+
|
|
65
|
+
@title.setter
|
|
66
|
+
def title(self, value) -> None:
|
|
67
|
+
self._title = value
|
|
68
|
+
# self.title_item.setPlainText(self._title)
|
|
69
|
+
|
|
70
|
+
def initUI(self) -> None:
|
|
71
|
+
"""Set up this ``QGraphicsItem``"""
|
|
72
|
+
self.setFlag(QGraphicsItem.ItemIsSelectable)
|
|
73
|
+
self.setFlag(QGraphicsItem.ItemIsMovable)
|
|
74
|
+
# Qt then tells us when it has moved the item, which is how the
|
|
75
|
+
# connected edges are refreshed without rescanning the whole scene.
|
|
76
|
+
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
|
|
77
|
+
self.setAcceptHoverEvents(True)
|
|
78
|
+
|
|
79
|
+
# init title
|
|
80
|
+
self.initTitle()
|
|
81
|
+
# self.title = self.node.title
|
|
82
|
+
|
|
83
|
+
self.initContent()
|
|
84
|
+
|
|
85
|
+
# init icon
|
|
86
|
+
# self.initIcon()
|
|
87
|
+
|
|
88
|
+
self.initLabel()
|
|
89
|
+
|
|
90
|
+
def initSizes(self) -> None:
|
|
91
|
+
"""Set up internal attributes like `width`, `height`, etc."""
|
|
92
|
+
self.width = 180
|
|
93
|
+
self.height = 240
|
|
94
|
+
self.edge_roundness = 10.0
|
|
95
|
+
self.edge_padding = 10
|
|
96
|
+
self.title_height = 0
|
|
97
|
+
self.title_horizontal_padding = 0.0
|
|
98
|
+
self.title_vertical_padding = 0.0
|
|
99
|
+
self.label_offset: float = 10.0
|
|
100
|
+
self._label_visible: bool = False
|
|
101
|
+
#: Level-of-detail state pushed by the scene (see
|
|
102
|
+
#: ``QDMGraphicsScene.setDetailLod``). False below the scene's
|
|
103
|
+
#: threshold, when labels, content widgets and sockets are skipped.
|
|
104
|
+
self._detail_visible: bool = True
|
|
105
|
+
#: Resolved per-element state pushed by the scene (see
|
|
106
|
+
#: ``QDMGraphicsScene.set_detail_policy``). Each flag says whether
|
|
107
|
+
#: that element may be drawn right now.
|
|
108
|
+
self._content_visible: bool = True
|
|
109
|
+
self._sockets_visible: bool = True
|
|
110
|
+
self._label_enabled: bool = True
|
|
111
|
+
|
|
112
|
+
def initAssets(self) -> None:
|
|
113
|
+
"""Initialize ``QObjects`` like ``QColor``, ``QPen`` and ``QBrush``"""
|
|
114
|
+
self._title_color = Qt.white
|
|
115
|
+
self._title_font = QFont("Ubuntu", 10)
|
|
116
|
+
|
|
117
|
+
# Get colors from global color scheme
|
|
118
|
+
scheme = get_color_scheme()
|
|
119
|
+
self._color = QColor("#7F000000")
|
|
120
|
+
self._color_selected = scheme.nodes.selected
|
|
121
|
+
self._color_hovered = scheme.edges.hovered
|
|
122
|
+
|
|
123
|
+
self._pen_default = QPen(self._color)
|
|
124
|
+
self._pen_default.setWidthF(2.0)
|
|
125
|
+
self._pen_selected = QPen(self._color_selected)
|
|
126
|
+
self._pen_selected.setWidthF(2.0)
|
|
127
|
+
self._pen_hovered = QPen(self._color_hovered)
|
|
128
|
+
self._pen_hovered.setWidthF(3.0)
|
|
129
|
+
|
|
130
|
+
self._brush_title = QBrush(QColor("#FF313131"))
|
|
131
|
+
self._brush_background = QBrush(QColor("#E3212121"))
|
|
132
|
+
|
|
133
|
+
self._label_font = QFont("Ubuntu", 9)
|
|
134
|
+
self._label_color = QColor("#EEEEEE")
|
|
135
|
+
|
|
136
|
+
def onSelected(self) -> None:
|
|
137
|
+
"""Our event handling when the node was selected"""
|
|
138
|
+
self.node.scene.grScene.itemSelected.emit()
|
|
139
|
+
|
|
140
|
+
def doSelect(self, new_state: bool = True) -> None:
|
|
141
|
+
"""Safe version of selecting the `Graphics Node`. Takes care about the selection state flag used internally
|
|
142
|
+
|
|
143
|
+
:param new_state: ``True`` to select, ``False`` to deselect
|
|
144
|
+
:type new_state: ``bool``
|
|
145
|
+
"""
|
|
146
|
+
self.setSelected(new_state)
|
|
147
|
+
self._last_selected_state = new_state
|
|
148
|
+
if new_state:
|
|
149
|
+
self.onSelected()
|
|
150
|
+
|
|
151
|
+
def itemChange(self, change, value):
|
|
152
|
+
"""Refresh this node's edges when Qt moves it.
|
|
153
|
+
|
|
154
|
+
Qt moves the dragged node - and every other node in the selection -
|
|
155
|
+
itself. Reacting to the position change Qt already reports updates the
|
|
156
|
+
edges of exactly the nodes that moved, replacing the old per-mouse-move
|
|
157
|
+
scan of every node in the scene.
|
|
158
|
+
"""
|
|
159
|
+
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
|
160
|
+
node = getattr(self, "node", None)
|
|
161
|
+
if node is not None:
|
|
162
|
+
try:
|
|
163
|
+
node.updateConnectedEdges()
|
|
164
|
+
except (AttributeError, RuntimeError):
|
|
165
|
+
pass # socket list not built yet, or C++ side gone
|
|
166
|
+
elif change == QGraphicsItem.GraphicsItemChange.ItemSceneHasChanged:
|
|
167
|
+
# A node added while the view is already zoomed out must start in
|
|
168
|
+
# the scene's current level-of-detail state, not fully detailed:
|
|
169
|
+
# loading a big workflow while zoomed out would otherwise draw every
|
|
170
|
+
# new node's content widget until the next zoom change.
|
|
171
|
+
scene = value if value is not None else self.scene()
|
|
172
|
+
state = getattr(scene, "detail_state", None)
|
|
173
|
+
if callable(state):
|
|
174
|
+
self.setDetailState(state())
|
|
175
|
+
else:
|
|
176
|
+
lod = getattr(scene, "_detail_visible", True)
|
|
177
|
+
if lod != getattr(self, "_detail_visible", True):
|
|
178
|
+
self.setDetailVisible(lod)
|
|
179
|
+
elif change == QGraphicsItem.GraphicsItemChange.ItemChildAddedChange:
|
|
180
|
+
# Sockets and the content proxy are created after this node joined
|
|
181
|
+
# the scene, so the scene's one-off broadcast never reached them;
|
|
182
|
+
# without this a node built while zoomed out draws fully detailed
|
|
183
|
+
# until the next zoom change.
|
|
184
|
+
self._applyDetailToChild(value)
|
|
185
|
+
return super().itemChange(change, value)
|
|
186
|
+
|
|
187
|
+
def mouseMoveEvent(self, event) -> None:
|
|
188
|
+
"""Overridden event to detect that we moved with this `Node`"""
|
|
189
|
+
super().mouseMoveEvent(event)
|
|
190
|
+
# Connected edges follow via itemChange(); no scene-wide scan here.
|
|
191
|
+
self._was_moved = True
|
|
192
|
+
|
|
193
|
+
def mouseReleaseEvent(self, event) -> None:
|
|
194
|
+
"""Overriden event to handle when we moved, selected or deselected this `Node`"""
|
|
195
|
+
super().mouseReleaseEvent(event)
|
|
196
|
+
|
|
197
|
+
# handle when grNode moved
|
|
198
|
+
if self._was_moved:
|
|
199
|
+
self.node.positionChanged.emit(self.node)
|
|
200
|
+
print("emitting positionChanged for node:", self.node.title)
|
|
201
|
+
self._was_moved = False
|
|
202
|
+
self.node.scene.history.storeHistory(
|
|
203
|
+
"Node moved", setModified=True)
|
|
204
|
+
|
|
205
|
+
self.node.scene.resetLastSelectedStates()
|
|
206
|
+
self.doSelect() # also trigger itemSelected when node was moved
|
|
207
|
+
|
|
208
|
+
# we need to store the last selected state, because moving does also select the nodes
|
|
209
|
+
self.node.scene._last_selected_items = self.node.scene.getSelectedItems()
|
|
210
|
+
|
|
211
|
+
# now we want to skip storing selection
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
# handle when grNode was clicked on
|
|
215
|
+
if self._last_selected_state != self.isSelected() or self.node.scene._last_selected_items != self.node.scene.getSelectedItems():
|
|
216
|
+
self.node.scene.resetLastSelectedStates()
|
|
217
|
+
self._last_selected_state = self.isSelected()
|
|
218
|
+
self.onSelected()
|
|
219
|
+
|
|
220
|
+
def mouseDoubleClickEvent(self, event) -> None:
|
|
221
|
+
"""Overriden event for doubleclick. Resend to `Node::onDoubleClicked`"""
|
|
222
|
+
self.node.onDoubleClicked(event)
|
|
223
|
+
|
|
224
|
+
def hoverEnterEvent(self, event: Optional['QGraphicsSceneHoverEvent']) -> None:
|
|
225
|
+
"""Handle hover effect"""
|
|
226
|
+
self.hovered = True
|
|
227
|
+
self.update()
|
|
228
|
+
|
|
229
|
+
def hoverLeaveEvent(self, event: Optional['QGraphicsSceneHoverEvent']) -> None:
|
|
230
|
+
"""Handle hover effect"""
|
|
231
|
+
self.hovered = False
|
|
232
|
+
self.update()
|
|
233
|
+
|
|
234
|
+
def boundingRect(self) -> QRectF:
|
|
235
|
+
"""Defining Qt' bounding rectangle"""
|
|
236
|
+
return QRectF(
|
|
237
|
+
0,
|
|
238
|
+
0,
|
|
239
|
+
self.width,
|
|
240
|
+
self.height
|
|
241
|
+
).normalized()
|
|
242
|
+
|
|
243
|
+
def initTitle(self) -> None:
|
|
244
|
+
"""Set up the title Graphics representation: font, color, position, etc."""
|
|
245
|
+
self.title_item = QGraphicsTextItem(self)
|
|
246
|
+
# self.title = self.node.title
|
|
247
|
+
# self.title_item.node = self.node
|
|
248
|
+
# self.title_item.setDefaultTextColor(self._title_color)
|
|
249
|
+
# self.title_item.setFont(self._title_font)
|
|
250
|
+
|
|
251
|
+
# # Calculate horizontal and vertical center positions
|
|
252
|
+
# horizontal_center = (self.boundingRect().width(
|
|
253
|
+
# ) - self.title_item.boundingRect().width()) / 2
|
|
254
|
+
# vertical_center = (self.title_height -
|
|
255
|
+
# self.title_item.boundingRect().height()) / 2
|
|
256
|
+
|
|
257
|
+
# self.title_item.setPos(horizontal_center, vertical_center)
|
|
258
|
+
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
def setDetailState(self, state: Mapping[str, bool]) -> None:
|
|
262
|
+
"""Apply a resolved per-element detail state.
|
|
263
|
+
|
|
264
|
+
The scene resolves its detail policy into a mapping of element key to
|
|
265
|
+
"draw it" and hands the whole mapping over, so an application can add
|
|
266
|
+
elements of its own to the same broadcast and read them back here.
|
|
267
|
+
The zoom state itself rides along under the ``detail`` key.
|
|
268
|
+
|
|
269
|
+
Every child is pushed, not only the ones whose flag changed: a socket
|
|
270
|
+
or a content proxy built after the last flip has never seen the state.
|
|
271
|
+
"""
|
|
272
|
+
self._detail_visible = bool(state.get("detail", self._detail_visible))
|
|
273
|
+
self._content_visible = bool(state.get("content", True))
|
|
274
|
+
self._sockets_visible = bool(state.get("sockets", True))
|
|
275
|
+
self._label_enabled = bool(state.get("label", True))
|
|
276
|
+
|
|
277
|
+
self._refreshLabelVisibility()
|
|
278
|
+
|
|
279
|
+
# The content proxy, the sockets and any other child item that wants to
|
|
280
|
+
# fade out too.
|
|
281
|
+
for child in self.childItems():
|
|
282
|
+
self._applyDetailToChild(child)
|
|
283
|
+
|
|
284
|
+
def setDetailVisible(self, visible: bool) -> None:
|
|
285
|
+
"""Show or hide this node's fine detail at low zoom.
|
|
286
|
+
|
|
287
|
+
The legacy entry point, kept for callers outside the scene: it drops
|
|
288
|
+
the content widget, the floating label and the socket circles together.
|
|
289
|
+
The scene uses :meth:`setDetailState` instead, which resolves each of
|
|
290
|
+
those separately.
|
|
291
|
+
"""
|
|
292
|
+
visible = bool(visible)
|
|
293
|
+
self.setDetailState(
|
|
294
|
+
{
|
|
295
|
+
"detail": visible,
|
|
296
|
+
"content": visible,
|
|
297
|
+
"sockets": visible,
|
|
298
|
+
"label": visible,
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def _applyDetailToChild(self, child) -> None:
|
|
303
|
+
"""Push this node's detail state onto one child item.
|
|
304
|
+
|
|
305
|
+
Sockets own a ``setDetailVisible``; the content proxy widget does not,
|
|
306
|
+
so it is matched by identity against ``grContent``.
|
|
307
|
+
"""
|
|
308
|
+
setter = getattr(child, "setDetailVisible", None)
|
|
309
|
+
if setter is not None:
|
|
310
|
+
setter(self._sockets_visible)
|
|
311
|
+
elif child is getattr(self, "grContent", None):
|
|
312
|
+
child.setVisible(self._content_visible)
|
|
313
|
+
|
|
314
|
+
def syncDetailVisibility(self) -> None:
|
|
315
|
+
"""Push the scene's current detail state onto every child item.
|
|
316
|
+
|
|
317
|
+
The sockets and the content proxy are built during ``Node.__init__``,
|
|
318
|
+
after Qt already reported this node joining the scene, so the scene's
|
|
319
|
+
one-off broadcast never reached them - and Qt's
|
|
320
|
+
``ItemChildAddedChange`` hands over a base-class wrapper for an item
|
|
321
|
+
still inside its own constructor, which has no ``setDetailVisible``.
|
|
322
|
+
Called from ``Node.__init__`` once the node is fully built.
|
|
323
|
+
"""
|
|
324
|
+
scene = self.scene()
|
|
325
|
+
state = getattr(scene, "detail_state", None)
|
|
326
|
+
if callable(state):
|
|
327
|
+
self.setDetailState(state())
|
|
328
|
+
return
|
|
329
|
+
self._detail_visible = getattr(scene, "_detail_visible", True)
|
|
330
|
+
self._refreshLabelVisibility()
|
|
331
|
+
for child in self.childItems():
|
|
332
|
+
self._applyDetailToChild(child)
|
|
333
|
+
|
|
334
|
+
def initContent(self) -> None:
|
|
335
|
+
"""Set up the `grContent` - ``QGraphicsProxyWidget`` to have a container for `Graphics Content`"""
|
|
336
|
+
if self.content is not None:
|
|
337
|
+
self.content.setGeometry(self.edge_padding, self.title_height + self.edge_padding,
|
|
338
|
+
self.width - 2 * self.edge_padding, self.height - 2 * self.edge_padding - self.title_height)
|
|
339
|
+
|
|
340
|
+
# get the QGraphicsProxyWidget when inserted into the grScene
|
|
341
|
+
self.grContent = self.node.scene.grScene.addWidget(self.content)
|
|
342
|
+
self.grContent.node = self.node
|
|
343
|
+
self.grContent.setParentItem(self)
|
|
344
|
+
|
|
345
|
+
def initLabel(self) -> None:
|
|
346
|
+
"""Create the floating single-line label above the node (hidden until text is set)."""
|
|
347
|
+
self.label_item = QDMGraphicsNodeLabel(self, self)
|
|
348
|
+
self.label_item.setFont(self._label_font)
|
|
349
|
+
self.label_item.setDefaultTextColor(self._label_color)
|
|
350
|
+
self._label_visible = False
|
|
351
|
+
self.label_item.setVisible(False)
|
|
352
|
+
self._updateLabelPos()
|
|
353
|
+
|
|
354
|
+
def setLabelText(self, text: str) -> None:
|
|
355
|
+
"""Set label text (single-line). Empty text hides the label. No history stored."""
|
|
356
|
+
text = QDMGraphicsNodeLabel.sanitize(text)
|
|
357
|
+
self.label_item.setPlainText(text)
|
|
358
|
+
if text:
|
|
359
|
+
self._label_visible = True
|
|
360
|
+
self._refreshLabelVisibility()
|
|
361
|
+
self._updateLabelPos()
|
|
362
|
+
|
|
363
|
+
def labelText(self) -> str:
|
|
364
|
+
"""Return current label text."""
|
|
365
|
+
try:
|
|
366
|
+
return self.label_item.toPlainText()
|
|
367
|
+
except Exception:
|
|
368
|
+
return ""
|
|
369
|
+
|
|
370
|
+
def setLabelVisible(self, visible: bool) -> None:
|
|
371
|
+
"""Show/hide the label. Hidden when text is empty regardless."""
|
|
372
|
+
self._label_visible = bool(visible)
|
|
373
|
+
self._refreshLabelVisibility()
|
|
374
|
+
|
|
375
|
+
def isLabelVisible(self) -> bool:
|
|
376
|
+
"""``True`` while the label is requested visible and carries text."""
|
|
377
|
+
return bool(self._label_visible and bool(self.labelText()))
|
|
378
|
+
|
|
379
|
+
def setLabelOffset(self, offset: float) -> None:
|
|
380
|
+
"""Set the gap in px between node top and label bottom.
|
|
381
|
+
|
|
382
|
+
:param offset: the new gap in px
|
|
383
|
+
:type offset: ``float``
|
|
384
|
+
"""
|
|
385
|
+
self.label_offset = float(offset)
|
|
386
|
+
self._updateLabelPos()
|
|
387
|
+
|
|
388
|
+
def labelOffset(self) -> float:
|
|
389
|
+
"""Return the gap in px between node top and label bottom."""
|
|
390
|
+
return float(self.label_offset)
|
|
391
|
+
|
|
392
|
+
def setLabelColor(self, color) -> None:
|
|
393
|
+
"""Set the text color of the label.
|
|
394
|
+
|
|
395
|
+
:param color: the new text color
|
|
396
|
+
:type color: QColor
|
|
397
|
+
"""
|
|
398
|
+
self._label_color = QColor(color) if not isinstance(color, QColor) else color
|
|
399
|
+
self.label_item.setDefaultTextColor(self._label_color)
|
|
400
|
+
|
|
401
|
+
def setLabelFont(self, font: QFont) -> None:
|
|
402
|
+
"""Set the font of the label and move the label to its new position.
|
|
403
|
+
|
|
404
|
+
:param font: the new font
|
|
405
|
+
:type font: QFont
|
|
406
|
+
"""
|
|
407
|
+
self._label_font = font
|
|
408
|
+
self.label_item.setFont(font)
|
|
409
|
+
self._updateLabelPos()
|
|
410
|
+
|
|
411
|
+
def setLabelBackground(self, bg, border=None) -> None:
|
|
412
|
+
"""Set the background and border colors of the label box.
|
|
413
|
+
|
|
414
|
+
:param bg: background color of the label box
|
|
415
|
+
:type bg: QColor
|
|
416
|
+
:param border: border color of the label box
|
|
417
|
+
:type border: QColor
|
|
418
|
+
"""
|
|
419
|
+
bg = QColor(bg) if not isinstance(bg, QColor) else bg
|
|
420
|
+
b = QColor(border) if border is not None and not isinstance(border, QColor) else border
|
|
421
|
+
self.label_item.setLabelColors(bg=bg, border=b)
|
|
422
|
+
|
|
423
|
+
def _refreshLabelVisibility(self) -> None:
|
|
424
|
+
try:
|
|
425
|
+
self.label_item.setVisible(bool(
|
|
426
|
+
self._label_visible
|
|
427
|
+
and bool(self.label_item.toPlainText())
|
|
428
|
+
and self._label_enabled))
|
|
429
|
+
except Exception:
|
|
430
|
+
pass
|
|
431
|
+
|
|
432
|
+
def _updateLabelPos(self) -> None:
|
|
433
|
+
try:
|
|
434
|
+
w = self.label_item.boundingRect().width()
|
|
435
|
+
h = self.label_item.boundingRect().height()
|
|
436
|
+
self.label_item.setPos((self.width - w) / 2, -h - self.label_offset)
|
|
437
|
+
except Exception:
|
|
438
|
+
pass
|
|
439
|
+
|
|
440
|
+
def _onLabelEdited(self, text: str) -> None:
|
|
441
|
+
text = QDMGraphicsNodeLabel.sanitize(text)
|
|
442
|
+
if text:
|
|
443
|
+
self._label_visible = True
|
|
444
|
+
self._refreshLabelVisibility()
|
|
445
|
+
self._updateLabelPos()
|
|
446
|
+
try:
|
|
447
|
+
self.node.labelChanged.emit(text)
|
|
448
|
+
except Exception:
|
|
449
|
+
pass
|
|
450
|
+
try:
|
|
451
|
+
self.node.scene.history.storeHistory("Node label changed", setModified=True)
|
|
452
|
+
except Exception:
|
|
453
|
+
pass
|
|
454
|
+
|
|
455
|
+
# def initIcon(self):
|
|
456
|
+
# """Set up the icon Graphics representation"""
|
|
457
|
+
# print("setting icons")
|
|
458
|
+
|
|
459
|
+
# if self.icon_path:
|
|
460
|
+
# print("ICON:", self.icon_path)
|
|
461
|
+
# self.icon_item = QGraphicsPixmapItem(self.icon_path, self)
|
|
462
|
+
# icon_rect = self.icon_item.boundingRect()
|
|
463
|
+
# icon_center_x = (self.width - icon_rect.width()) / 2
|
|
464
|
+
# icon_center_y = (self.height - icon_rect.height()) / 2
|
|
465
|
+
# self.icon_item.setPos(icon_center_x, icon_center_y)
|
|
466
|
+
|
|
467
|
+
def paint(self, painter, QStyleOptionGraphicsItem, widget=None) -> None:
|
|
468
|
+
"""Painting the rounded rectanglar `Node`"""
|
|
469
|
+
# title
|
|
470
|
+
# path_title = QPainterPath()
|
|
471
|
+
# path_title.setFillRule(Qt.WindingFill)
|
|
472
|
+
# path_title.addRoundedRect(
|
|
473
|
+
# 0, 0, self.width, self.title_height, self.edge_roundness, self.edge_roundness)
|
|
474
|
+
# path_title.addRect(0, self.title_height - self.edge_roundness,
|
|
475
|
+
# self.edge_roundness, self.edge_roundness)
|
|
476
|
+
# path_title.addRect(self.width - self.edge_roundness, self.title_height -
|
|
477
|
+
# self.edge_roundness, self.edge_roundness, self.edge_roundness)
|
|
478
|
+
# painter.setPen(Qt.NoPen)
|
|
479
|
+
# painter.setBrush(self._brush_title)
|
|
480
|
+
# painter.drawPath(path_title.simplified())
|
|
481
|
+
|
|
482
|
+
# # content
|
|
483
|
+
# path_content = QPainterPath()
|
|
484
|
+
# path_content.setFillRule(Qt.WindingFill)
|
|
485
|
+
# path_content.addRoundedRect(0, self.title_height, self.width, self.height -
|
|
486
|
+
# self.title_height, self.edge_roundness, self.edge_roundness)
|
|
487
|
+
# path_content.addRect(0, self.title_height,
|
|
488
|
+
# self.edge_roundness, self.edge_roundness)
|
|
489
|
+
# path_content.addRect(self.width - self.edge_roundness,
|
|
490
|
+
# self.title_height, self.edge_roundness, self.edge_roundness)
|
|
491
|
+
# painter.setPen(Qt.NoPen)
|
|
492
|
+
# painter.setBrush(self._brush_background)
|
|
493
|
+
# painter.drawPath(path_content.simplified())
|
|
494
|
+
|
|
495
|
+
# outline
|
|
496
|
+
# path_outline = QPainterPath()
|
|
497
|
+
# # path_outline.addRoundedRect(-1, -1, self.width+2,
|
|
498
|
+
# # self.height+2, self.edge_roundness, self.edge_roundness)
|
|
499
|
+
# painter.setBrush(Qt.NoBrush)
|
|
500
|
+
# if self.hovered:
|
|
501
|
+
# path_outline.addRoundedRect(-1, -1, self.width + 2,
|
|
502
|
+
# self.height + 2, self.edge_roundness, self.edge_roundness)
|
|
503
|
+
# painter.setPen(self._pen_hovered)
|
|
504
|
+
# painter.drawPath(path_outline.simplified())
|
|
505
|
+
# painter.setPen(self._pen_default)
|
|
506
|
+
# painter.drawPath(path_outline.simplified())
|
|
507
|
+
# else:
|
|
508
|
+
# path_outline.addRoundedRect(-1, -1, self.width + 2,
|
|
509
|
+
# self.height + 2, self.edge_roundness, self.edge_roundness)
|
|
510
|
+
# painter.setPen(
|
|
511
|
+
# self._pen_default if not self.isSelected() else self._pen_selected)
|
|
512
|
+
# painter.drawPath(path_outline.simplified())
|
|
513
|
+
|
|
514
|
+
if self.hovered or self.isSelected():
|
|
515
|
+
path_outline = QPainterPath()
|
|
516
|
+
path_outline.addRoundedRect(-1, -1, self.width + 2,
|
|
517
|
+
self.height + 2, self.edge_roundness, self.edge_roundness)
|
|
518
|
+
painter.setBrush(Qt.NoBrush)
|
|
519
|
+
if self.hovered:
|
|
520
|
+
painter.setPen(self._pen_hovered)
|
|
521
|
+
else:
|
|
522
|
+
painter.setPen(self._pen_selected if self.isSelected()
|
|
523
|
+
else self._pen_default)
|
|
524
|
+
painter.drawPath(path_outline.simplified())
|