pygpt-net 2.6.60__py3-none-any.whl → 2.6.61__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 (60) hide show
  1. pygpt_net/CHANGELOG.txt +7 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/chat/common.py +115 -6
  4. pygpt_net/controller/chat/input.py +4 -1
  5. pygpt_net/controller/presets/presets.py +121 -6
  6. pygpt_net/controller/settings/editor.py +0 -15
  7. pygpt_net/controller/theme/markdown.py +2 -5
  8. pygpt_net/controller/ui/ui.py +4 -7
  9. pygpt_net/core/agents/custom/__init__.py +7 -1
  10. pygpt_net/core/agents/custom/llama_index/factory.py +17 -6
  11. pygpt_net/core/agents/custom/llama_index/runner.py +35 -2
  12. pygpt_net/core/agents/custom/llama_index/utils.py +12 -1
  13. pygpt_net/core/agents/custom/router.py +45 -6
  14. pygpt_net/core/agents/custom/runner.py +2 -1
  15. pygpt_net/core/agents/custom/schema.py +3 -1
  16. pygpt_net/core/agents/custom/utils.py +13 -1
  17. pygpt_net/core/db/viewer.py +11 -5
  18. pygpt_net/core/node_editor/graph.py +18 -9
  19. pygpt_net/core/node_editor/models.py +9 -2
  20. pygpt_net/core/node_editor/types.py +3 -1
  21. pygpt_net/core/presets/presets.py +216 -29
  22. pygpt_net/core/render/markdown/parser.py +0 -2
  23. pygpt_net/data/config/config.json +5 -6
  24. pygpt_net/data/config/models.json +3 -3
  25. pygpt_net/data/config/settings.json +2 -38
  26. pygpt_net/data/locale/locale.de.ini +64 -1
  27. pygpt_net/data/locale/locale.en.ini +62 -3
  28. pygpt_net/data/locale/locale.es.ini +64 -1
  29. pygpt_net/data/locale/locale.fr.ini +64 -1
  30. pygpt_net/data/locale/locale.it.ini +64 -1
  31. pygpt_net/data/locale/locale.pl.ini +65 -2
  32. pygpt_net/data/locale/locale.uk.ini +64 -1
  33. pygpt_net/data/locale/locale.zh.ini +64 -1
  34. pygpt_net/data/locale/plugin.cmd_system.en.ini +62 -66
  35. pygpt_net/provider/agents/llama_index/flow_from_schema.py +2 -2
  36. pygpt_net/provider/core/config/patch.py +10 -1
  37. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -6
  38. pygpt_net/tools/agent_builder/tool.py +42 -26
  39. pygpt_net/tools/agent_builder/ui/dialogs.py +60 -11
  40. pygpt_net/ui/__init__.py +2 -4
  41. pygpt_net/ui/dialog/about.py +58 -38
  42. pygpt_net/ui/dialog/db.py +142 -3
  43. pygpt_net/ui/dialog/preset.py +47 -8
  44. pygpt_net/ui/layout/toolbox/presets.py +52 -16
  45. pygpt_net/ui/widget/dialog/db.py +0 -0
  46. pygpt_net/ui/widget/lists/preset.py +644 -60
  47. pygpt_net/ui/widget/node_editor/command.py +10 -10
  48. pygpt_net/ui/widget/node_editor/config.py +157 -0
  49. pygpt_net/ui/widget/node_editor/editor.py +183 -151
  50. pygpt_net/ui/widget/node_editor/item.py +12 -11
  51. pygpt_net/ui/widget/node_editor/node.py +267 -12
  52. pygpt_net/ui/widget/node_editor/view.py +180 -63
  53. pygpt_net/ui/widget/tabs/output.py +1 -1
  54. pygpt_net/ui/widget/textarea/input.py +2 -2
  55. pygpt_net/utils.py +114 -2
  56. {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/METADATA +11 -94
  57. {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/RECORD +59 -58
  58. {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/LICENSE +0 -0
  59. {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/WHEEL +0 -0
  60. {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.24 00:00:00 #
9
+ # Updated Date: 2025.09.25 12:05:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from __future__ import annotations
@@ -140,20 +140,16 @@ class PortItem(QGraphicsObject):
140
140
  except Exception:
141
141
  pass
142
142
  cap_val = self._allowed_capacity()
143
+ cfg = self.node_item.editor.config
143
144
  if isinstance(cap_val, int):
144
145
  if cap_val < 0:
145
- cap_str = "unlimited ()"
146
+ cap_str = cfg.cap_unlimited()
146
147
  else:
147
148
  cap_str = str(cap_val)
148
149
  else:
149
- cap_str = "n/a"
150
- tip = (
151
- f"Node: {node_name}\n"
152
- f"Port: {self.side.upper()} • {self.prop_id}\n"
153
- f"Allowed connections: {cap_str}\n\n"
154
- f"Click: start a new connection\n"
155
- f"Ctrl+Click: rewire/detach existing"
156
- )
150
+ cap_str = cfg.cap_na()
151
+ side_label = cfg.side_label(self.side).upper()
152
+ tip = cfg.port_tooltip(node_name, side_label, self.prop_id, cap_str)
157
153
  self.setToolTip(tip)
158
154
  try:
159
155
  self._label_cap.setToolTip(tip)
@@ -225,6 +221,11 @@ class PortItem(QGraphicsObject):
225
221
  def mousePressEvent(self, e):
226
222
  """Emit portClicked on left click to begin a connection or rewire."""
227
223
  if e.button() == Qt.LeftButton:
224
+ try:
225
+ # Bring parent node to front when clicking the port
226
+ self.node_item.editor.raise_node_to_top(self.node_item)
227
+ except Exception:
228
+ pass
228
229
  self.node_item.editor._dbg(f"Port clicked: side={self.side}, node={self.node_item.node.name}({self.node_item.node.uuid}), prop={self.prop_id}, connected_count={self._connected_count}")
229
230
  self.portClicked.emit(self)
230
231
  e.accept()
@@ -355,7 +356,7 @@ class EdgeItem(QGraphicsPathItem):
355
356
  ss = self._editor.window().styleSheet()
356
357
  if ss:
357
358
  menu.setStyleSheet(ss)
358
- act_del = QAction("Delete connection", menu)
359
+ act_del = QAction(self._editor.config.edge_context_delete(), menu)
359
360
  menu.addAction(act_del)
360
361
  chosen = menu.exec(event.screenPos())
361
362
  if chosen == act_del:
@@ -6,14 +6,14 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.24 00:00:00 #
9
+ # Updated Date: 2025.09.25 15:32:39 #
10
10
  # ================================================== #
11
11
 
12
12
  from __future__ import annotations
13
13
  import re
14
- from typing import Dict, Optional, List, Any
14
+ from typing import Dict, Optional, List, Any, Tuple
15
15
 
16
- from PySide6.QtCore import Qt, QPointF, QRectF, QSizeF, Signal,QEvent
16
+ from PySide6.QtCore import Qt, QPointF, QRectF, QSizeF, Signal, QEvent
17
17
  from PySide6.QtGui import QAction, QBrush, QColor, QPainter, QPainterPath, QPen
18
18
  from PySide6.QtWidgets import (
19
19
  QWidget, QApplication, QGraphicsItem, QGraphicsWidget, QGraphicsProxyWidget, QStyleOptionGraphicsItem,
@@ -86,12 +86,65 @@ class SingleLineTextEdit(QTextEdit):
86
86
  c.setPosition(min(pos, len(t2)))
87
87
  self.setTextCursor(c)
88
88
 
89
+ def _apply_menu_theme(self, menu: QMenu):
90
+ """Apply app/window stylesheet + palette + font to context menu."""
91
+ try:
92
+ wnd = self.window()
93
+ except Exception:
94
+ wnd = None
95
+ stylesheet = ""
96
+ pal = None
97
+ font = None
98
+ try:
99
+ if wnd:
100
+ stylesheet = wnd.styleSheet() or ""
101
+ pal = wnd.palette()
102
+ font = wnd.font()
103
+ except Exception:
104
+ pass
105
+ try:
106
+ app = QApplication.instance()
107
+ if app:
108
+ if not stylesheet and app.styleSheet():
109
+ stylesheet = app.styleSheet()
110
+ if pal is None:
111
+ pal = app.palette()
112
+ if font is None:
113
+ font = app.font()
114
+ except Exception:
115
+ pass
116
+ try:
117
+ if pal:
118
+ menu.setPalette(pal)
119
+ if font:
120
+ menu.setFont(font)
121
+ if stylesheet:
122
+ menu.setStyleSheet(stylesheet)
123
+ menu.ensurePolished()
124
+ except Exception:
125
+ pass
126
+
127
+ def contextMenuEvent(self, e):
128
+ """Ensure standard context menu follows app-wide (e.g., Qt Material) styling."""
129
+ try:
130
+ menu = self.createStandardContextMenu()
131
+ except Exception:
132
+ return super().contextMenuEvent(e)
133
+ self._apply_menu_theme(menu)
134
+ try:
135
+ menu.exec(e.globalPos())
136
+ finally:
137
+ try:
138
+ menu.deleteLater()
139
+ except Exception:
140
+ pass
141
+
89
142
 
90
143
  class NodeContentWidget(QWidget):
91
144
  """Form-like widget that renders property editors for a node.
92
145
 
93
146
  The widget builds appropriate Qt editors based on PropertyModel.type:
94
- - "str": QLineEdit
147
+ - "str": QLineEdit-like (SingleLineTextEdit with placeholder support)
95
148
  - "text": QTextEdit
96
149
  - "int": QSpinBox
97
150
  - "float": QDoubleSpinBox
@@ -103,6 +156,8 @@ class NodeContentWidget(QWidget):
103
156
 
104
157
  Notes:
105
158
  - For Base ID-like properties, the field is read-only; a composed display value is shown.
159
+ - Placeholder and description are applied when provided in the type spec or model
160
+ (description is shown as tooltip on both the editor and its label).
106
161
  - ShortcutOverride is handled so typing in editors does not trigger scene shortcuts.
107
162
 
108
163
  Signal:
@@ -125,7 +180,24 @@ class NodeContentWidget(QWidget):
125
180
  self._editors: Dict[str, QWidget] = {}
126
181
  for pid, pm in node.properties.items():
127
182
  editable = self._editable_from_spec(pid, pm)
183
+
184
+ # Resolve UI hints: placeholder and description from spec (fallback to model)
185
+ placeholder = self._spec_text_attr(pid, "placeholder")
186
+ try:
187
+ if not placeholder and hasattr(pm, "placeholder") and getattr(pm, "placeholder") not in (None, ""):
188
+ placeholder = str(getattr(pm, "placeholder"))
189
+ except Exception:
190
+ pass
191
+ description = self._spec_text_attr(pid, "description")
192
+ try:
193
+ if not description and hasattr(pm, "description") and getattr(pm, "description") not in (None, ""):
194
+ description = str(getattr(pm, "description"))
195
+ except Exception:
196
+ pass
197
+
128
198
  w: QWidget
199
+ extra_tooltip: Optional[str] = None # used e.g. for capacity hints
200
+
129
201
  if pm.type == "str":
130
202
  te = SingleLineTextEdit()
131
203
  te.setFocusPolicy(Qt.StrongFocus)
@@ -145,17 +217,31 @@ class NodeContentWidget(QWidget):
145
217
  te.setReadOnly(not editable)
146
218
 
147
219
  te.setPlainText(txt)
220
+ if placeholder:
221
+ try:
222
+ te.setPlaceholderText(placeholder)
223
+ except Exception:
224
+ pass
148
225
  te.textChanged.connect(lambda pid=pid, te=te: self.valueChanged.emit(pid, te.toPlainText()))
149
226
  te.editingFinished.connect(lambda pid=pid, te=te: self.valueChanged.emit(pid, te.toPlainText()))
150
227
  w = te
228
+
151
229
  elif pm.type == "text":
152
230
  te = QTextEdit()
153
231
  te.setFocusPolicy(Qt.StrongFocus)
154
232
  if pm.value is not None:
155
233
  te.setPlainText(str(pm.value))
156
234
  te.setReadOnly(not editable)
235
+ if placeholder:
236
+ try:
237
+ te.setPlaceholderText(placeholder)
238
+ except Exception:
239
+ pass
157
240
  te.textChanged.connect(lambda pid=pid, te=te: self.valueChanged.emit(pid, te.toPlainText()))
241
+ # Ensure context menu follows global (Material) style
242
+ self._install_styled_context_menu(te)
158
243
  w = te
244
+
159
245
  elif pm.type == "int":
160
246
  w = QSpinBox()
161
247
  w.setFocusPolicy(Qt.StrongFocus)
@@ -163,7 +249,10 @@ class NodeContentWidget(QWidget):
163
249
  if pm.value is not None:
164
250
  w.setValue(int(pm.value))
165
251
  w.setEnabled(editable)
252
+
253
+ # QSpinBox has no placeholder; use tooltip only
166
254
  w.valueChanged.connect(lambda v, pid=pid: self.valueChanged.emit(pid, int(v)))
255
+
167
256
  elif pm.type == "float":
168
257
  w = QDoubleSpinBox()
169
258
  w.setFocusPolicy(Qt.StrongFocus)
@@ -173,6 +262,7 @@ class NodeContentWidget(QWidget):
173
262
  w.setValue(float(pm.value))
174
263
  w.setEnabled(editable)
175
264
  w.valueChanged.connect(lambda v, pid=pid: self.valueChanged.emit(pid, float(v)))
265
+
176
266
  elif pm.type == "bool":
177
267
  w = QCheckBox()
178
268
  w.setFocusPolicy(Qt.StrongFocus)
@@ -180,6 +270,7 @@ class NodeContentWidget(QWidget):
180
270
  w.setChecked(bool(pm.value))
181
271
  w.setEnabled(editable)
182
272
  w.toggled.connect(lambda v, pid=pid: self.valueChanged.emit(pid, bool(v)))
273
+
183
274
  elif pm.type == "combo":
184
275
  w = QComboBox()
185
276
  w.setFocusPolicy(Qt.StrongFocus)
@@ -188,20 +279,92 @@ class NodeContentWidget(QWidget):
188
279
  if pm.value is not None and pm.value in (pm.options or []):
189
280
  w.setCurrentText(pm.value)
190
281
  w.setEnabled(editable)
282
+ # QComboBox placeholder works when editable; we keep non-editable by default
191
283
  w.currentTextChanged.connect(lambda v, pid=pid: self.valueChanged.emit(pid, v))
284
+
192
285
  else:
193
286
  # Render a read-only capacity placeholder for port-like properties ("input/output").
194
287
  cap_text = self._capacity_text_for_property(pid, pm)
195
288
  w = QLabel(cap_text)
196
289
  w.setEnabled(False)
197
- w.setToolTip(f"Allowed connections (IN/OUT): {cap_text}")
290
+ extra_tooltip = self.editor.config.port_capacity_tooltip(cap_text)
198
291
 
199
292
  name = self._display_name_for_property(pid, pm)
200
293
  if name == "Base ID":
201
- name = "ID"
294
+ name = self.editor.config.label_id()
202
295
  self.form.addRow(name, w)
203
296
  self._editors[pid] = w
204
297
 
298
+ # Apply tooltip(s) after the row is created so we can also set the label tooltip
299
+ if description or extra_tooltip:
300
+ final_tt_parts: List[str] = []
301
+ if description:
302
+ final_tt_parts.append(description)
303
+ if extra_tooltip:
304
+ final_tt_parts.append(extra_tooltip)
305
+ final_tt = "\n".join(final_tt_parts)
306
+ try:
307
+ w.setToolTip(final_tt)
308
+ except Exception:
309
+ pass
310
+ try:
311
+ lbl = self.form.labelForField(w)
312
+ if lbl is not None:
313
+ lbl.setToolTip(description or "")
314
+ except Exception:
315
+ pass
316
+
317
+ def _install_styled_context_menu(self, te: QTextEdit):
318
+ """Install a custom context menu handler that applies global styles."""
319
+ try:
320
+ te.setContextMenuPolicy(Qt.CustomContextMenu)
321
+ te.customContextMenuRequested.connect(
322
+ lambda pos, _te=te: self._show_styled_standard_menu(_te, pos)
323
+ )
324
+ except Exception:
325
+ pass
326
+
327
+ def _show_styled_standard_menu(self, te: QTextEdit, pos):
328
+ """Create standard menu and apply app/window stylesheet + palette + font."""
329
+ try:
330
+ menu = te.createStandardContextMenu()
331
+ except Exception:
332
+ return
333
+ stylesheet = ""
334
+ pal = None
335
+ font = None
336
+ try:
337
+ # Prefer editor helpers (consistent with the rest of NodeEditor)
338
+ stylesheet = self.editor._current_stylesheet()
339
+ pal = self.editor._current_palette()
340
+ font = self.editor._current_font()
341
+ except Exception:
342
+ try:
343
+ wnd = te.window()
344
+ if wnd:
345
+ stylesheet = wnd.styleSheet() or ""
346
+ pal = wnd.palette()
347
+ font = wnd.font()
348
+ except Exception:
349
+ pass
350
+ try:
351
+ if pal:
352
+ menu.setPalette(pal)
353
+ if font:
354
+ menu.setFont(font)
355
+ if stylesheet:
356
+ menu.setStyleSheet(stylesheet)
357
+ menu.ensurePolished()
358
+ except Exception:
359
+ pass
360
+ try:
361
+ menu.exec(te.mapToGlobal(pos))
362
+ finally:
363
+ try:
364
+ menu.deleteLater()
365
+ except Exception:
366
+ pass
367
+
205
368
  def event(self, e):
206
369
  """
207
370
  Filter ShortcutOverride so editor keystrokes are not eaten by the scene.
@@ -481,6 +644,55 @@ class NodeContentWidget(QWidget):
481
644
 
482
645
  return None
483
646
 
647
+ # --- Spec helpers for UI hints ---
648
+
649
+ def _get_prop_spec(self, pid: str) -> Optional[Any]:
650
+ """Return a property specification object from the registry for this node type."""
651
+ try:
652
+ reg = self.graph.registry
653
+ if not reg:
654
+ return None
655
+ type_spec = reg.get(self.node.type)
656
+ if not type_spec:
657
+ return None
658
+
659
+ for attr in ("properties", "props", "fields", "ports", "inputs", "outputs"):
660
+ try:
661
+ cont = getattr(type_spec, attr, None)
662
+ if isinstance(cont, dict) and pid in cont:
663
+ return cont[pid]
664
+ except Exception:
665
+ pass
666
+ for meth in ("get_property", "property_spec", "get_prop", "prop", "property"):
667
+ if hasattr(type_spec, meth):
668
+ try:
669
+ return getattr(type_spec, meth)(pid)
670
+ except Exception:
671
+ pass
672
+ except Exception:
673
+ pass
674
+ return None
675
+
676
+ def _spec_text_attr(self, pid: str, key: str) -> Optional[str]:
677
+ """Get a string attribute (e.g., 'placeholder', 'description') from spec if available."""
678
+ obj = self._get_prop_spec(pid)
679
+ if obj is None:
680
+ return None
681
+ try:
682
+ v = getattr(obj, key, None)
683
+ if isinstance(v, str) and v != "":
684
+ return v
685
+ except Exception:
686
+ pass
687
+ try:
688
+ if isinstance(obj, dict):
689
+ v = obj.get(key)
690
+ if isinstance(v, str) and v != "":
691
+ return v
692
+ except Exception:
693
+ pass
694
+ return None
695
+
484
696
 
485
697
  class NodeItem(QGraphicsWidget):
486
698
  """Rounded node with title and ports aligned to property rows.
@@ -854,6 +1066,14 @@ class NodeItem(QGraphicsWidget):
854
1066
  if self._resizing:
855
1067
  return
856
1068
 
1069
+ # When global grab mode is active, suppress resize/move cursors
1070
+ view = self.editor.view
1071
+ if getattr(view, "_global_grab_mode", False):
1072
+ self._hover_resize_mode = "none"
1073
+ self.unsetCursor()
1074
+ self.update()
1075
+ return
1076
+
857
1077
  mode = self._hit_resize_zone(pos)
858
1078
  self._hover_resize_mode = mode
859
1079
 
@@ -891,9 +1111,9 @@ class NodeItem(QGraphicsWidget):
891
1111
  self.update()
892
1112
 
893
1113
  def hoverMoveEvent(self, event):
894
- """While panning is active, suppress resize hints; otherwise update hover state."""
1114
+ """While panning is active or global grab is enabled, suppress resize hints; otherwise update hover state."""
895
1115
  view = self.editor.view
896
- if getattr(view, "_panning", False):
1116
+ if getattr(view, "_panning", False) or getattr(view, "_global_grab_mode", False):
897
1117
  self._hover_resize_mode = "none"
898
1118
  self.unsetCursor()
899
1119
  self.update()
@@ -912,6 +1132,17 @@ class NodeItem(QGraphicsWidget):
912
1132
 
913
1133
  def mousePressEvent(self, event):
914
1134
  """Start resize when pressing the proper zone; otherwise begin move drag."""
1135
+ # Always bring clicked node to front (dynamic z-order)
1136
+ try:
1137
+ self.editor.raise_node_to_top(self)
1138
+ except Exception:
1139
+ pass
1140
+
1141
+ # If global grab is active, do not initiate node drag/resize (view handles panning)
1142
+ if getattr(self.editor.view, "_global_grab_mode", False) and event.button() == Qt.LeftButton:
1143
+ event.ignore()
1144
+ return
1145
+
915
1146
  if event.button() == Qt.LeftButton:
916
1147
  mode = self._hit_resize_zone(event.pos())
917
1148
  if mode != "none":
@@ -971,7 +1202,7 @@ class NodeItem(QGraphicsWidget):
971
1202
  return
972
1203
 
973
1204
  if self._dragging and event.button() == Qt.LeftButton:
974
- if self._overlaps:
1205
+ if bool(getattr(self.editor, "enable_collisions", True)) and self._overlaps:
975
1206
  self.setPos(self._last_valid_pos)
976
1207
  else:
977
1208
  if self.pos() != self._start_pos:
@@ -1047,6 +1278,14 @@ class NodeItem(QGraphicsWidget):
1047
1278
  if _qt_is_valid(self._overlay):
1048
1279
  self._overlay.update()
1049
1280
  return False
1281
+
1282
+ if obj is self._content and et == QEvent.MouseButtonPress:
1283
+ # Bring to front when clicking inside embedded editors (proxy widget area)
1284
+ try:
1285
+ self.editor.raise_node_to_top(self)
1286
+ except Exception:
1287
+ pass
1288
+ return False
1050
1289
  except Exception:
1051
1290
  pass
1052
1291
  return False
@@ -1065,6 +1304,11 @@ class NodeItem(QGraphicsWidget):
1065
1304
  if not getattr(self, "_dragging", False):
1066
1305
  return value
1067
1306
 
1307
+ # When collisions are disabled, skip overlap checks entirely
1308
+ if not bool(getattr(self.editor, "enable_collisions", True)):
1309
+ self._overlaps = False
1310
+ return value
1311
+
1068
1312
  sc = self.scene()
1069
1313
  if sc is None or not _qt_is_valid(sc):
1070
1314
  return value
@@ -1115,6 +1359,17 @@ class NodeItem(QGraphicsWidget):
1115
1359
  pass
1116
1360
  return super().itemChange(change, value)
1117
1361
 
1362
+ if change == QGraphicsItem.ItemSelectedHasChanged:
1363
+ # Bring newly selected node to front as well (e.g., rubber-band selection)
1364
+ try:
1365
+ ed = getattr(self, "editor", None)
1366
+ if ed and getattr(ed, "_alive", True) and not getattr(ed, "_closing", False):
1367
+ if self.isSelected():
1368
+ ed.raise_node_to_top(self)
1369
+ except Exception:
1370
+ pass
1371
+ return super().itemChange(change, value)
1372
+
1118
1373
  return super().itemChange(change, value)
1119
1374
 
1120
1375
  def contextMenuEvent(self, event):
@@ -1123,15 +1378,15 @@ class NodeItem(QGraphicsWidget):
1123
1378
  ss = self.editor.window().styleSheet()
1124
1379
  if ss:
1125
1380
  menu.setStyleSheet(ss)
1126
- act_rename = QAction("Rename", menu)
1127
- act_delete = QAction("Delete", menu)
1381
+ act_rename = QAction(self.editor.config.node_context_rename(), menu)
1382
+ act_delete = QAction(self.editor.config.node_context_delete(), menu)
1128
1383
  menu.addAction(act_rename)
1129
1384
  menu.addSeparator()
1130
1385
  menu.addAction(act_delete)
1131
1386
  chosen = menu.exec(event.screenPos())
1132
1387
  if chosen == act_rename:
1133
1388
  from PySide6.QtWidgets import QInputDialog
1134
- new_name, ok = QInputDialog.getText(self.editor.window(), "Rename Node", "Name:", text=self.node.name)
1389
+ new_name, ok = QInputDialog.getText(self.editor.window(), self.editor.config.rename_dialog_title(), self.editor.config.rename_dialog_label(), text=self.node.name)
1135
1390
  if ok and new_name:
1136
1391
  self.node.name = new_name
1137
1392
  self.update()