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
- 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