Skip to content

graphics: open a combo box's drop-down on a click - #2263

Merged
jcelerier merged 4 commits into
masterfrom
feature/combobox-popup
Sep 7, 2026
Merged

jcelerier merged 4 commits into
masterfrom
feature/combobox-popup

Conversation

@jcelerier

Copy link
Copy Markdown
Member

Clicking a combo box on the canvas did nothing — the only way to change one was
to drag it like a slider. This makes a click open the drop-down, keeping the
drag.

Revisits the QGraphicsCombo: open a popup menu on click commit that was left
out of #2262.

Why not the original approach

That commit called QMenu::exec() from mousePressEvent. That runs a nested
event loop while the item holds the implicit mouse grab
— precisely the
situation the QEvent::UngrabMouse handling in the sliders, knobs and choosers
exists to survive — and self dangles if the process is deleted while the menu
is up. There is no other QMenu::exec() in a graphics item anywhere in score.

It also replaced dragging outright, which silently neutered
TEST_CASE("degenerate combo boxes survive being dragged") — the regression
test for a divide-by-zero on single-entry combos.

What this does instead

A press still begins a drag; the value only follows the pointer once it passes
QApplication::startDragDistance(). Below that it is a click, and a click opens
the same drop-down the right button already opened. Dragging is unchanged, its
regression test keeps its meaning, and a pixel of wobble while clicking no
longer nudges the value.

openEditor() already builds its editor from the event loop behind a
QPointer, with the index re-checked at commit time — no nested loop. Making it
reachable from both buttons needed two things of it:

  • one drop-down at a time. The build is deferred, so a second click arriving
    before the first editor appeared would stack another on top; closing the pair
    frees the proxy twice.
  • the pick is resolved by name, not by index. A runtime-populated combo box
    (the folder/dynamic comboboxes from Runtime-populated comboboxes and avnd UI file paths (reviewed #2110 + #2114) #2262) can be repopulated while the
    drop-down is open. The same index then means a different entry — or none, in
    which case nothing is selected rather than the wrong thing.

An empty combo box opens nothing.

Tests

Six new cases in GraphicsComboTest.cpp, driven through real scene events:
click opens / drag does not; three clicks plus a right click leave exactly one
editor; items reordered under an open drop-down still select what was picked;
an entry that has since disappeared selects nothing, leaving an index past the
end of the shorter list which is then painted to show readers tolerate it;
the combo box is deleted with the editor open and the editor activated anyway;
a degenerate combo box is clicked; and the scene taking the grab away mid-drag
is not mistaken for a click.

Verification

test_unit_graphics_combo: 67 assertions in 17 cases, all passing. Full
suite 145/150 — the five failures (analysis_gist, beat_tracker,
media_trim, unused_files, explorer_editor_look) reproduce on an unmodified
origin/master build.

Also run under valgrind: 17/17 pass, and no error context touches the combobox
code. The Qt-startup noise it does report (Invalid read of size 16 inside
QApplicationPrivate::init()) appears identically when running an untouched
pre-existing test, so it is environmental.

🤖 Generated with Claude Code

https://claude.ai/code/session_018oUFSPD1bswcWtvrsq25t3

jcelerier and others added 4 commits September 7, 2026 13:04
… drag

Clicking a combo box did nothing: the only way to change one on the canvas was
to drag it like a slider, which is not what a combo box looks like it does.

A press still begins a drag, but the value now only follows the pointer once it
has travelled past the platform's drag threshold -- below that it is a click,
and a click opens the drop-down that until now only the right button reached.
So scrubbing is unchanged, a click gains the obvious behaviour, and a wobble of
a pixel while clicking no longer nudges the value.

Deliberately not a QMenu::exec(): that runs a nested event loop inside
mousePressEvent while the item holds the implicit mouse grab, which is the
situation the QEvent::UngrabMouse handling in the sliders, knobs and choosers
exists to survive, and it leaves `self` dangling if the process is deleted
while the menu is up. openEditor() already builds its editor from the event
loop behind a QPointer, so the click reuses it.

Making that path reachable from both buttons needed two things of it:

 - one drop-down at a time. The build is deferred, so a second click arriving
   before the first editor appeared would stack another on top, and closing the
   pair frees the proxy twice;
 - the pick is resolved by name, not by the index the editor was built with. A
   runtime-populated combo box can be repopulated while the drop-down is open,
   and the same index then means a different entry -- or none, in which case
   nothing is selected rather than the wrong thing.

An empty combo box opens nothing: there is nothing to pick.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oUFSPD1bswcWtvrsq25t3
The editor is built from the event loop and lives in the scene, so it can
outlast the click, the item list, and the combo box itself. Each of those is
now driven through real scene events:

 - a click opens it and a drag does not;
 - three clicks and a right click leave exactly one editor behind;
 - the items being reordered while it is open still selects what was picked,
   by name, and an entry that has since disappeared selects nothing -- leaving
   an index past the end of the shorter list, which is then painted to show
   that readers tolerate it;
 - the combo box is deleted with the editor open, and the editor is then
   activated anyway;
 - a degenerate (empty, single-entry) combo box is clicked;
 - the scene takes the grab away mid-drag, which sends no release, and that
   must not be mistaken for a click and open an editor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oUFSPD1bswcWtvrsq25t3
Clicking a combo box and then clicking away left a collapsed editor sitting on
the scene: a small box showing one entry, in the widget style rather than the
canvas one. Clicking again put another next to it.

ComboBoxWithEnter reports a focus change only while its list is down, and
QComboBox has no signal for the list closing, so nothing ever took a
non-editable editor off the scene unless an entry was chosen or a key pressed.
Watch the view for QEvent::Hide instead.

Deferred by one turn: the list hides before `activated` arrives, and that has
to be allowed to commit first. An editable box keeps the old behaviour -- its
list closing is how one gets to the text field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oUFSPD1bswcWtvrsq25t3
Dragging a control that causes a combo box to be refilled moved the value by
one step and then stopped, left a widget behind, and could leave a right-click
editor on the scene that nothing would close again.

setAlternatives restated the domain, and DefaultEffectItem answers
domainChanged by deleting every control of the process and rebuilding them from
the event loop. So the sequence was: drag the control -> the running object
refills the combo box -> setDomain -> domainChanged -> the control being
dragged is destroyed. The pointer then had nothing to drag, and anything the
destroyed control had put on the scene outlived the item that knew how to take
it down.

The item list is what changed, and alternativesChanged already says so -- the
widgets rebuild from it in place. The domain still follows the items for
whoever reads it later; it just no longer claims the control's shape changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oUFSPD1bswcWtvrsq25t3
@jcelerier
jcelerier merged commit 83426be into master Sep 7, 2026
30 of 43 checks passed
@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage is 28.001% — feature/combobox-popup into master. No base build found for master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants