Fix #6517: language chooser modal hang and empty available-language list#7593
Conversation
PCGen#6517) The dialog is a Swing JDialog with an embedded JavaFX OKCloseButtonBar, so its OK/Cancel handlers fire on the JavaFX Application Thread. doOK and doRollback called chooser.commit()/rollback() and dispose() directly from that thread; on the affected platforms the modal pump never advanced and the dialog locked up, forcing users to kill the app. Wrap both bodies in SwingUtilities.invokeLater so the Swing-side commit and dispose() run on the EDT. doAdd/doRemove are unchanged: they're not reported as broken and the double-click path already runs on the EDT. Verified manually with the issue's repro (SRD 3.5 -> New character -> Int 12 -> Add Bonus Language -> OK / Cancel) on master + this fix; the full :test suite (17053 tests) is green pre- and post-fix. Closes PCGen#6517
…ooser (PCGen#6517) Race / Gender / AgeCat / Hand / Deity ComboBoxes on the Summary tab use DeferredCharacterComboBoxModel: setSelectedItem stages a value and the real commitSelectedItem (which calls e.g. CharacterFacade.setRace) only runs on the ComboBox's focusLost. If the user picks Race=Dwarf and clicks "Add Bonus Language" before any other focus change, the chooser opens against the not-yet-committed character state and the available language list comes up empty. When the cell editor's Add action fires, look up the focused component; if it's a JComboBox backed by a DeferredCharacterComboBoxModel, call commitSelectedItem(getSelectedItem()) synchronously so e.g. setRace runs before the chooser facade is asked for its available list. Resolve the chooser BEFORE the flush: setRace mutates the languages list (auto-granted languages get added), which fires a table model event that cancels the cell editor — table.getEditingRow() then returns -1 and choosers.getElementAt(-1 - languageCount) fails with IndexOutOfBoundsException. Verified live in the IntelliJ debugger: Race ComboBox keeps focus through the Add click; before the fix theCharacter.getDisplay().getRace() is "<none selected>" at the moment the chooser facade is queried, after the fix it is the selected race and the available list is populated. Closes PCGen#6517 (companion to PCGen#7593's modal-hang fix).
|
I'm a little suspicious of the use of transient, it's a valid keyword and JVM feature, but very rarely used and not that well understood. Let's see if Copilot can find another way. |
There was a problem hiding this comment.
Pull request overview
Fixes issue #6517 in the Swing/JavaFX hybrid language chooser workflow by ensuring UI/model mutations occur on the correct thread and by committing deferred Summary-tab ComboBox selections before computing available bonus languages.
Changes:
- Dispatch
LanguageChooserDialogOK/Cancel handlers from the JavaFX thread to the Swing EDT before callingcommit()/rollback()anddispose(). - In
LanguageTableModel, flush a pendingDeferredCharacterComboBoxModelselection (e.g., Race) before opening the language chooser so the available-language list is populated correctly. - Minor cleanup in the touched files (e.g., stream usage, removing stale commented code, Sonar-related
transientmarking).
Show a summary per file
| File | Description |
|---|---|
code/src/java/pcgen/gui2/tabs/summary/LanguageTableModel.java |
Flushes deferred ComboBox commits before constructing the chooser; minor refactors/cleanup in renderer/model fields. |
code/src/java/pcgen/gui2/dialog/LanguageChooserDialog.java |
Ensures OK/Cancel operations run on the Swing EDT (fixing modal hang) and includes small cleanup/refactors. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 0
|
Thanks for chasing this one down — the two bug-fix commits are great:
One suggestion on the Sonar cleanup commit ( PCGen has an explicit, documented "we don't use serialization" stanceThe project's own quality gate already turns off serialization-related rules with that exact rationale:
<!-- we don't use serialization -->
<exclude name="NonSerializableClass"/>
...
<!-- we don't use serialization -->
<exclude name="MissingSerialVersionUID"/>
<!-- not relevant to pcgen -->
<Bug pattern="...,SE_NO_SERIALVERSIONID"/>SonarQube isn't part of the PCGen CI quality gate either — no So S1948 here is being satisfied against a rule the project has, by its own published configs, consciously declined to enforce. Adding Alternatives I considered
What's not being askedThe non-
Happy to be overruled on the |
Drive-by cleanup of pre-existing Sonar issues on the two files this PR touches: - remove unused `Collectors` import (S1128) - replace `Collectors.toUnmodifiableList()` with `.toList()` - replace `instanceof X` + cast pairs with pattern matching (S6201) - replace `o -> o instanceof X` / `o -> (X) o` lambdas with method references `X.class::isInstance` / `X.class::cast` (S1612) - delete two commented-out lines (S125) - add a nested explanatory comment on an empty no-op `setData` (S1186) - fix stray `;` in `//$NON-NLS-1$;` marker that confused S125 - shorten the verbose Javadoc/inline comments added in the previous two commits — the technical narrative belongs in this PR description, not in the code S1948 (`transient` modifier on non-Serializable fields) intentionally not addressed — PCGen has an explicit "we don't use serialization" stance documented in `code/standards/ruleset.xml` (PMD `NonSerializableClass` / `MissingSerialVersionUID` excluded) and `code/standards/spotbugs_ignore.xml` (`SE_NO_SERIALVERSIONID`), and SonarQube isn't part of the CI gate. Adding `transient` here would perpetuate the vestigial Serializable fiction on classes that are never actually serialized.
e9622b6 to
b98f615
Compare
|
Thanks @karianna — fully agree, the inconsistency with the project's own Just force-pushed:
Compile-clean ( |
Summary
Fixes both observable symptoms of #6517. Two independent commits, each fixing one of them, plus a SonarQube cleanup pass on the touched files.
1.
LanguageChooserDialog: dispatch OK/Cancel to the EDT (commit 0940b62)The dialog is a Swing
JDialog(super(frame, true), modal) with an embedded JavaFXOKCloseButtonBarwrapped viaGuiUtility.wrapParentAsJFXPanel(...).OKCloseButtonBarwires its OK/Cancel buttons withsetOnAction(...), so the supplied handlers —this::doOKandthis::doRollback— fire on the JavaFX Application Thread. Their bodies were callingchooser.commit()/rollback()(which mutate Swing-side models) anddispose()directly from that thread; on the affected platforms the modal event pump never advanced past those calls and the dialog locked up.Wrap the bodies in
SwingUtilities.invokeLater(...)so the Swing-side work runs on the EDT, whereJDialog.dispose()and the Swing model mutations belong.doAdd/doRemoveare intentionally not changed: they aren't reported as broken, and the existingDoubleClickActionListenerpath already runs on the EDT.2.
LanguageTableModel: flush deferred ComboBox commits before opening chooser (commit 0addaff)The reporter and follow-up comments described a second symptom: opening Add Bonus Language right after picking a Race shows an empty available-list, and "doing something else" (e.g. changing Age) appears to fix it. Live debugging confirmed the cause:
The Race / Gender / AgeCat / Hand / Deity ComboBoxes on the Summary tab use
DeferredCharacterComboBoxModel, which holds the selection back untilfocusLost. If the user picks Race=Dwarf and clicks "Add Bonus Language" before any other focus change, the Race ComboBox still has focus at the moment the chooser is constructed —theCharacter.getDisplay().getRace()is still<none selected>, so the chooser'sLANGBONUScandidate set is empty.When the cell editor's Add action fires, look up the focused component; if it's a
JComboBoxbacked byDeferredCharacterComboBoxModel, callcommitSelectedItem(getSelectedItem())synchronously so e.g.setRaceruns before the chooser facade is asked for its available list.Subtlety: the chooser facade must be resolved before the flush.
setRacemutates the languages list (auto-granted languages get added), which fires a table model event that cancels the cell editor —table.getEditingRow()then returns -1 andchoosers.getElementAt(-1 - languageCount)would fail withIndexOutOfBoundsException.3. SonarQube cleanup on the touched files (commit 5353c0a)
Drive-by cleanup of pre-existing findings on
LanguageChooserDialogandLanguageTableModel:Serializablefieldstransient(S1948 ×6). Swing'sSerializableclaim has been vestigial since ~2000 — these dialogs/models are never actually serialized — buttransientis the cheapest way to silence the rule.Collectorsimport (S1128); replaceCollectors.toUnmodifiableList()with.toList().instanceof Language language && ...instead ofinstanceof Language && (Language) value(S6201 ×2).Language.class::isInstance/Language.class::castinstead ofo -> o instanceof Language/o -> (Language) olambdas (S1612 ×2).;in a//$NON-NLS-1$marker that S125 was misreading as commented-out code.// no-op: this view is read-onlyto an emptysetData(S1186).Test plan
./gradlew :test --rerun-tasks— full unit suite green (17053 tests, 0 failures, 0 errors) on the modal-hang fix; cherry-pick of the second commit is a clean apply touching onlyLanguageTableModel../gradlew compileJavaclean after the SonarQube pass.Not covered by automated tests. Both bugs are Swing-modal interactions that only reproduce against a real
JDialog.setVisible(true). The existing test config setsjava.awt.headless=true, so a regression test would require a separate non-headless test task. Verification is the manual repros above.Out of scope
data/pathfinder/alluria_publishing/remarkable_races/rr_abilitycategories.lst(theABILITYCATEGORY:Kval Langauge/Muse Langaugelines plus matchingBONUS:ABILITYPOOLreferences).