Skip to content

Typing in a FloatItem linked to computed items overwrites user input #104

@PierreRaybaut

Description

@PierreRaybaut

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

  1. 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).
  2. Open the dataset in a DataSetEditGroupBox.
  3. Click into the dx field, select all, type 52.
  4. 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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions