Environment
- NodeGraphQt version: 0.6.44
- Qt binding: PyQt5
- OS: Cross-platform (reproduced on macOS and Windows)
What's happening
If a node has embedded widgets (QLineEdit, QSpinBox, QComboBox, etc.), zooming in and out a few times causes the widgets to drift out of alignment — they either spill outside the node border or leave a big empty gap inside. The longer you zoom, the worse it gets.
How to reproduce
- Create a node with a few embedded widgets — e.g. a spinbox, a text input, and a custom
QPlainTextEdit.
- Zoom out with the scroll wheel or a trackpad pinch until the node is roughly 20–30% of its original size.
- Zoom back in to roughly the original level.
- Repeat steps 2–3 about three to five times.
- Have a look at the node — the widgets no longer sit properly inside the node body. They might be too wide, too narrow, or sticking out past the border.
Why it happens
A few things interact to cause this:
1. Node size is calculated once and never updated
calc_size() runs inside draw_node(), reads boundingRect() for each widget, and stores the result in _width / _height. After that, boundingRect() just hands back those fixed values forever:
def boundingRect(self):
return QtCore.QRectF(0.0, 0.0, self._width, self._height)
Nothing ever triggers a recalculation after the initial draw. So when a widget's sizeHint() changes — due to a content update, a style change, or a hide/show cycle forcing a layout pass — the node body stays at the old size and things stop lining up.
2. NodeBaseWidget has no idea the view is being scaled
NodeBaseWidget inherits straight from QGraphicsProxyWidget with no overrides for paint(), sizeHint(), boundingRect(), or itemChange():
class NodeBaseWidget(QtWidgets.QGraphicsProxyWidget):
def __init__(self, parent=None, name=None, label=''):
super(NodeBaseWidget, self).__init__(parent)
self.setZValue(Z_VAL_NODE_WIDGET)
No ItemIgnoresTransformations, no resetTransform(), no compensation for the view scale whatsoever. It just relies on Qt's default QGraphicsProxyWidget behaviour — which has known geometry drift issues when used with fitInView() (see QTBUG-55112, QTBUG-49075).
3. Widgets get hidden and shown mid-zoom, and the timing is off
When the node's on-screen width drops below 70 px, auto_switch_mode() calls set_proxy_mode(True), which hides the internal QWidget:
for w in self._widgets.values():
w.widget().setVisible(visible)
When you zoom back in, setVisible(True) fires and Qt recalculates the widget's geometry. If that recalculation happens while the view is still mid-zoom, sizeHint() gets computed at the wrong scale. Since _width / _height are never updated, that mismatch sticks around permanently.
4. calc_size measures widgets via boundingRect(), which isn't stable for all widget types
The size calculation works like this:
widget_width = max(w.boundingRect().width() for w in self._widgets.values())
widget_height = sum(w.boundingRect().height() for w in self._widgets.values())
For QGraphicsProxyWidget, boundingRect() is derived from the embedded widget's sizeHint(). Widgets like QPlainTextEdit with QSizePolicy::Expanding have a sizeHint() that depends on content and available layout space — but inside a proxy there's no parent layout to constrain them, so the value can jump around unpredictably.
Environment
What's happening
If a node has embedded widgets (
QLineEdit,QSpinBox,QComboBox, etc.), zooming in and out a few times causes the widgets to drift out of alignment — they either spill outside the node border or leave a big empty gap inside. The longer you zoom, the worse it gets.How to reproduce
QPlainTextEdit.Why it happens
A few things interact to cause this:
1. Node size is calculated once and never updated
calc_size()runs insidedraw_node(), readsboundingRect()for each widget, and stores the result in_width/_height. After that,boundingRect()just hands back those fixed values forever:Nothing ever triggers a recalculation after the initial draw. So when a widget's
sizeHint()changes — due to a content update, a style change, or a hide/show cycle forcing a layout pass — the node body stays at the old size and things stop lining up.2.
NodeBaseWidgethas no idea the view is being scaledNodeBaseWidgetinherits straight fromQGraphicsProxyWidgetwith no overrides forpaint(),sizeHint(),boundingRect(), oritemChange():No
ItemIgnoresTransformations, noresetTransform(), no compensation for the view scale whatsoever. It just relies on Qt's defaultQGraphicsProxyWidgetbehaviour — which has known geometry drift issues when used withfitInView()(see QTBUG-55112, QTBUG-49075).3. Widgets get hidden and shown mid-zoom, and the timing is off
When the node's on-screen width drops below 70 px,
auto_switch_mode()callsset_proxy_mode(True), which hides the internalQWidget:When you zoom back in,
setVisible(True)fires and Qt recalculates the widget's geometry. If that recalculation happens while the view is still mid-zoom,sizeHint()gets computed at the wrong scale. Since_width/_heightare never updated, that mismatch sticks around permanently.4.
calc_sizemeasures widgets viaboundingRect(), which isn't stable for all widget typesThe size calculation works like this:
For
QGraphicsProxyWidget,boundingRect()is derived from the embedded widget'ssizeHint(). Widgets likeQPlainTextEditwithQSizePolicy::Expandinghave asizeHint()that depends on content and available layout space — but inside a proxy there's no parent layout to constrain them, so the value can jump around unpredictably.