Summary
When the user types into a LineEditWidget whose DataSet contains other items declared via set_computed(...) (or any display.callback), the input is silently corrupted: characters typed after the first one are interpreted as if the field had been re-rendered from the model between each keystroke.
For example, selecting the entire content of an Δx field whose current value is 1.0 and typing 52 produces 5.02 instead of 52. Typing 0.25 produces 0.0.25. The bug only affects datasets that have at least one computed item (or a display.callback) — plain datasets are unaffected.
Reproduction
- Define a
DataSet with a FloatItem (e.g. dx) and another item using set_computed(...) that depends on dx (e.g. xmax = xmin + dx * N). Bonus: nest both items inside a BeginGroup (it makes the bug easier to trigger but is not required).
- Open the dataset in a
DataSetEditGroupBox.
- Click into the
dx field, select all, type 52.
- Observed: the field shows
5.02. Expected: the field shows 52.
Root cause
_display_callback is invoked from LineEditWidget.line_edit_changed whenever the edited value is valid. It then calls top_level_layout.update_widgets(except_this_one=widget) to refresh the dependent (computed) widgets.
The refresh calls setText(...) on each sibling widget. Each setText fires Qt's textChanged signal, which re-enters line_edit_changed → _display_callback. This second invocation calls update_widgets again, but with except_this_one now pointing at the sibling widget (e.g. xmin), not at the widget the user is actually typing in (dx).
As a result, the original widget (dx) is no longer protected and gets re-rendered from the dataset value, which still holds the previous, partially parsed input (5.0 after typing the first digit). The next keystroke is then appended to that re-rendered text, producing 5.02 instead of 52.
In short: the reactive update of computed siblings recursively re-enters the very callback that triggered it, with a different exclusion target, and ends up overwriting the field the user is editing.
Fix
Suppress the re-entrant cascade inside _display_callback:
- Before calling
update_widgets, set build_mode = True on every terminal widget of the top-level layout, then restore the previous values in a finally block.
- When
_display_callback is entered while build_mode is True, it only persists the current widget's value to the dataset (widget.set()) and returns immediately, instead of triggering another sibling refresh.
This relies on the existing build_mode flag, which already serves as a "we are currently rebuilding widgets, do not react" marker (it is set during DataSetEditGroupBox.get() for the same reason).
Validation
- Repro case (
dx with computed xmin/xmax): typing 52 now yields 52, typing 0.25 yields 0.25, and the computed siblings correctly reflect the new value (e.g. xmax = 693.0 for dx = 7).
- Full guidata test suite still passes (136 passed, 2 skipped).
Impact
- Affects any downstream application using
DataSets that mix editable numeric items with computed items or display.callback items.
- Reported on DataLab 1.2.0+ when changing the pixel size (
Δx / Δy) of an image, but the bug is not DataLab-specific.
Summary
When the user types into a
LineEditWidgetwhoseDataSetcontains other items declared viaset_computed(...)(or anydisplay.callback), the input is silently corrupted: characters typed after the first one are interpreted as if the field had been re-rendered from the model between each keystroke.For example, selecting the entire content of an
Δxfield whose current value is1.0and typing52produces5.02instead of52. Typing0.25produces0.0.25. The bug only affects datasets that have at least one computed item (or adisplay.callback) — plain datasets are unaffected.Reproduction
DataSetwith aFloatItem(e.g.dx) and another item usingset_computed(...)that depends ondx(e.g.xmax = xmin + dx * N). Bonus: nest both items inside aBeginGroup(it makes the bug easier to trigger but is not required).DataSetEditGroupBox.dxfield, select all, type52.5.02. Expected: the field shows52.Root cause
_display_callbackis invoked fromLineEditWidget.line_edit_changedwhenever the edited value is valid. It then callstop_level_layout.update_widgets(except_this_one=widget)to refresh the dependent (computed) widgets.The refresh calls
setText(...)on each sibling widget. EachsetTextfires Qt'stextChangedsignal, which re-entersline_edit_changed→_display_callback. This second invocation callsupdate_widgetsagain, but withexcept_this_onenow pointing at the sibling widget (e.g.xmin), not at the widget the user is actually typing in (dx).As a result, the original widget (
dx) is no longer protected and gets re-rendered from the dataset value, which still holds the previous, partially parsed input (5.0after typing the first digit). The next keystroke is then appended to that re-rendered text, producing5.02instead of52.In short: the reactive update of computed siblings recursively re-enters the very callback that triggered it, with a different exclusion target, and ends up overwriting the field the user is editing.
Fix
Suppress the re-entrant cascade inside
_display_callback:update_widgets, setbuild_mode = Trueon every terminal widget of the top-level layout, then restore the previous values in afinallyblock._display_callbackis entered whilebuild_modeisTrue, it only persists the current widget's value to the dataset (widget.set()) and returns immediately, instead of triggering another sibling refresh.This relies on the existing
build_modeflag, which already serves as a "we are currently rebuilding widgets, do not react" marker (it is set duringDataSetEditGroupBox.get()for the same reason).Validation
dxwith computedxmin/xmax): typing52now yields52, typing0.25yields0.25, and the computed siblings correctly reflect the new value (e.g.xmax = 693.0fordx = 7).Impact
DataSets that mix editable numeric items with computed items ordisplay.callbackitems.Δx/Δy) of an image, but the bug is not DataLab-specific.