enthought / enthought/traitsui

CustomFontEditor (Qt): update_editor() causes spurious font-size round-trip (e.g. 24 → 8 → 24) via unguarded facename/point_size combo signals

Open Beginner friendly
#2,072 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
306
Forks
99
PR merge metrics
No merged PRs in 30d

Description

**Component:** `traitsui.qt.font_editor.CustomFontEditor`

## Description

When a `Font` trait using the `Custom` style is updated externally (i.e. `update_editor()` is called, not through user interaction with the widget), the trait fires two spurious `__changed` notifications with an intermediate wrong point size, before settling back on the correct value.

## Steps to reproduce

```python
from traits.api import HasTraits
from traitsui.api import View, Item

class Demo(HasTraits):
font = 'font' # replace with actual Font trait declaration used

def _font_changed(self, old, new):
print(f"font changed: {old.pointSize()} -> {new.pointSize()}")

view = View(Item('font', style='custom'))

d = Demo()
d.configure_traits()
# then set d.font to a 24pt font programmatically, or open a second
# view on the same trait and change it there
```

## Observed

```
font changed: -> 8
font changed: 8 -> 24
```

## Expected

A single notification, directly to the new value (24).

## Root cause

`update_editor()` sets the facename combo and the point-size combo sequentially:

```python
def update_editor(self):
font = self.factory.to_qt_font(self)
...
self._facename.setCurrentFont(font) # (A)
...
self._point_size.setCurrentIndex(idx) # (B)
```

Both `currentFontChanged` (facename) and `currentIndexChanged` (point size) are connected to `update_object_parts()`, which reassigns `self.value` by reading **both** combos' current state:

```python
def update_object_parts(self):
fnt = self._facename.currentFont()
...
psz = int(self._point_size.currentText())
fnt.setPointSize(psz)
self.value = self.factory.from_qt_font(fnt)
```

Step (A) fires `update_object_parts()` immediately, but `self._point_size` hasn't been updated yet — it's still on whatever index it was previously (index 0 = `"8"` on first init). This produces a spurious intermediate assignment (e.g. 8pt) before step (B) runs and corrects it.

## Suggested fix

Block signals on `self._facename` and `self._point_size` for the duration of `update_editor()`:

```python
def update_editor(self):
font = self.factory.to_qt_font(self)
self._bold = font.bold()
self._italic = font.italic()

self._facename.blockSignals(True)
self._point_size.blockSignals(True)
try:
self._facename.setCurrentFont(font)
try:
idx = PointSizes.index(str(font.pointSize()))
except ValueError:
idx = PointSizes.index("9")
self._point_size.setCurrentIndex(idx)
finally:
self._facename.blockSignals(False)
self._point_size.blockSignals(False)
```

## Version

traitsui 8.0.0, Qt backend (PySide6/PyQt6). Same pattern likely present in earlier releases as well.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at traitsui.qt.font_editor.CustomFontEditor, reading update_editor() and update_object_parts() together, then reproduce the external Font update with the Qt backend. Verify that updating the facename and point-size controls no longer produces an intermediate notification and that the final change reaches the requested point size in a single notification.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
desktop
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.