Fix: Unable to remove multiline property from field#15242
Conversation
Review Summary by QodoFix multiline property removal from fields in preferences
WalkthroughsDescription• Fix multiline property removal from fields in preferences • Implement resetMultilineFieldsToDefault() to restore standard field defaults • Add listener to sync multiline changes across all entry types • Ensure multiline property is properly removed when toggled off Diagramflowchart LR
A["User resets to default"] --> B["resetMultilineFieldsToDefault()"]
B --> C["Reset standard fields to defaults"]
B --> D["Reset non-wrappable fields"]
C --> E["ABSTRACT, COMMENT, REVIEW keep MULTILINE_TEXT"]
C --> F["Other fields remove MULTILINE_TEXT"]
D --> G["Update preferences"]
G --> H["Fields synchronized across entry types"]
File Changes1. jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java
|
Code Review by Qodo
1. Leaky multiline listeners
|
| private BooleanProperty createMultilinePropertyListener(TableColumn.CellDataFeatures<FieldViewModel, Boolean> item) { | ||
| BooleanProperty property = item.getValue().multilineProperty(); | ||
| property.addListener((obs, wasSelected, isSelected) -> { | ||
| viewModel.entryTypes().forEach(typeViewModel -> { | ||
| typeViewModel.fields().stream() | ||
| .filter(field -> field.displayNameProperty().get().equals(item.getValue().displayNameProperty().get())) | ||
| .forEach(field -> field.multilineProperty().set(isSelected)); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
1. Leaky multiline listeners 🐞 Bug ➹ Performance
A new ChangeListener is added inside the TableColumn cellValueFactory, which can be evaluated multiple times (e.g., on refresh/virtualization), causing duplicate listeners and cascading O(N²) propagation across entry types. This can degrade UI performance and retain objects longer than intended.
Agent Prompt
## Issue description
`CustomEntryTypesTab#createMultilinePropertyListener` adds a listener inside the TableColumn `cellValueFactory`. JavaFX may call the value factory multiple times (refresh, virtualization, re-skin), which can attach multiple listeners to the same `BooleanProperty`. Each listener then propagates changes to all entry types, which can also trigger other listeners, causing duplicated work and potential memory/perf issues.
## Issue Context
- The multiline column is configured with `setCellValueFactory(this::createMultilinePropertyListener)`.
- `createMultilinePropertyListener` unconditionally calls `property.addListener(...)`.
- The listener sets multiline on many other `FieldViewModel`s, which may also have listeners.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java[187-189]
- jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java[322-331]
- jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java[159-180]
## Suggested implementation directions (pick one)
1) **Install-once + reentrancy guard (smallest change):**
- Add an `IdentityHashMap`-backed `Set<FieldViewModel>` to track listener installation.
- In `createMultilinePropertyListener`, only `addListener` if not yet installed.
- Add a `boolean`/`AtomicBoolean` guard to prevent re-entrant propagation while applying updates.
2) **Central coordinator in ViewModel (cleaner):**
- Move propagation logic into `CustomEntryTypesTabViewModel` (or a helper) that registers listeners once when `EntryTypeViewModel`s/fields are created.
- Coordinate updates by field identity (prefer underlying `Field` name vs displayName), and suppress re-entrant updates.
3) **Shared property per field name (best UX/perf):**
- Maintain `Map<String, BooleanProperty>` for multiline state and bind each `FieldViewModel` to the shared property for that field name.
- This eliminates fan-out listeners entirely.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Related PR: #11230 |
| private final StringProperty newFieldToAdd = new SimpleStringProperty(""); | ||
| private final ObservableList<EntryTypeViewModel> entryTypesWithFields = FXCollections.observableArrayList(extractor -> new Observable[] {extractor.entryType(), extractor.fields()}); | ||
| private final List<BibEntryType> entryTypesToDelete = new ArrayList<>(); | ||
| private final Set<StandardField> DEFAULT_MULTILINE_FIELDS = Set.of( |
There was a problem hiding this comment.
I suggest move this into StandardFields as a static variable so it's more easily adaptable.
There was a problem hiding this comment.
Thank you for your review. I have set it as a static variable
| } | ||
|
|
||
| private List<Field> getDefaultNonWrappableFields() { | ||
| Object defaultNonWrappableFields = preferences.getDefaults().get(JabRefCliPreferences.NON_WRAPPABLE_FIELDS); |
There was a problem hiding this comment.
@calixtus Is there a better option to get a default value of a specifci preference?
There was a problem hiding this comment.
Conversion to new preferences must be continued in JabRefCliPreferences following the example for JabRefGuiPreferences (see #14400 ).
Direct access to JabRefCliPreferences is not acceptable anymore.
There was a problem hiding this comment.
Conversion to new preferences must be continued in JabRefCliPreferences following the example for JabRefGuiPreferences (see #14400 ).
Hi @calixtus , do you mean that getDefaultValue() should be done in a specific preference class(in this case FieldPreference), instead of directly accessing JabRefCliPreference.getDefaults()?
And according to #14400, I think we should also reset FieldPreference? For example, remove default values from JabRefCliPreference and add get/set default methods in FieldPreference.
There was a problem hiding this comment.
JabRefPreferences.getDefaults is to be removed. Follow the many example prs that were already made.
There was a problem hiding this comment.
Yeah I have removed getDefaults in FieldPreference, is there anything I should do?
This comment has been minimized.
This comment has been minimized.
| fieldPreferences = mock(FieldPreferences.class); | ||
| when(fieldPreferences.getNonWrappableFields()).thenReturn(FXCollections.observableArrayList()); | ||
| when(preferences.getFieldPreferences()).thenReturn(fieldPreferences); | ||
| when(preferences.getDefaults()).thenReturn(Map.of(JabRefCliPreferences.NON_WRAPPABLE_FIELDS, "pdf;ps;url;doi;file;isbn;issn")); |
There was a problem hiding this comment.
This should be part of defaults in a PreferencesObject
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Hi devs, I made some resettings to Moreover, I found that "reset to default" button works as expected, but "reset preference" would not reset multiline property. I fixed that issue by adding |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Resolved conflicts with #15395 |
✅ All tests passed ✅🏷️ Commit: c0c5811 Learn more about TestLens at testlens.app. |
|
LGTM, lets move forward |
Related issues and pull requests
Closes #11897
PR Description
Based on the work in #12040 from @PressJump, I added the resetMultilineFieldsToDefault() method. This method sets MULTILINE_TEXT property of each standard field and nonWrappableFields to default.
Steps to test
Save preferenc and restart jabref. The "Author" field became multiline for all entry types as it is global.
Untick "Author" field and save. "Author" field for all entry types is removed.
Checklist
CHANGELOG.mdin a way that can be understood by the average user (if change is visible to the user)