Skip to content

Commit 793fa88

Browse files
tobiasdiezSiedlerchr
authored andcommitted
Post change notifications on JavaFX (#4871)
* Post change notifications on JavaFX Fixes #4817. The problem is that changes in other threads produce a "Not on FX application thread" exception. This is fixed by adding a wrapper around the list of entries. The wrapper only posts changes in the correct thread, without making a copy of the underlying list - so performance shouldn't be affected. * fix compile error * fix checkstyle
1 parent 504c0d3 commit 793fa88

File tree

5 files changed

+66
-17
lines changed

5 files changed

+66
-17
lines changed

src/main/java/org/jabref/gui/BasePanel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ public void output(String s) {
255255

256256
private void setupActions() {
257257
SaveDatabaseAction saveAction = new SaveDatabaseAction(this, Globals.prefs);
258-
CleanupAction cleanUpAction = new CleanupAction(this, Globals.prefs);
258+
CleanupAction cleanUpAction = new CleanupAction(this, Globals.prefs, Globals.TASK_EXECUTOR);
259259

260260
actions.put(Actions.UNDO, undoAction);
261261
actions.put(Actions.REDO, redoAction);

src/main/java/org/jabref/gui/actions/CleanupAction.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import org.jabref.gui.cleanup.CleanupDialog;
1010
import org.jabref.gui.undo.NamedCompound;
1111
import org.jabref.gui.undo.UndoableFieldChange;
12+
import org.jabref.gui.util.BackgroundTask;
13+
import org.jabref.gui.util.TaskExecutor;
1214
import org.jabref.logic.cleanup.CleanupPreset;
1315
import org.jabref.logic.cleanup.CleanupWorker;
1416
import org.jabref.logic.l10n.Localization;
@@ -20,15 +22,17 @@ public class CleanupAction implements BaseAction {
2022

2123
private final BasePanel panel;
2224
private final DialogService dialogService;
25+
private final TaskExecutor taskExecutor;
2326

2427
private boolean isCanceled;
2528
private int modifiedEntriesCount;
2629
private final JabRefPreferences preferences;
2730

28-
public CleanupAction(BasePanel panel, JabRefPreferences preferences) {
31+
public CleanupAction(BasePanel panel, JabRefPreferences preferences, TaskExecutor taskExecutor) {
2932
this.panel = panel;
3033
this.preferences = preferences;
3134
this.dialogService = panel.frame().getDialogService();
35+
this.taskExecutor = taskExecutor;
3236
}
3337

3438
@Override
@@ -58,8 +62,9 @@ public void action() {
5862

5963
preferences.setCleanupPreset(chosenPreset.get());
6064

61-
this.cleanup(chosenPreset.get());
62-
this.showResults();
65+
BackgroundTask.wrap(() -> cleanup(chosenPreset.get()))
66+
.onSuccess(result -> showResults())
67+
.executeWith(taskExecutor);
6368
}
6469
}
6570

src/main/java/org/jabref/gui/maintable/MainTable.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.jabref.gui.keyboard.KeyBindingRepository;
3737
import org.jabref.gui.undo.NamedCompound;
3838
import org.jabref.gui.undo.UndoableInsertEntry;
39+
import org.jabref.gui.util.BindingsHelper;
3940
import org.jabref.gui.util.CustomLocalDragboard;
4041
import org.jabref.gui.util.ViewModelTableRowFactory;
4142
import org.jabref.logic.l10n.Localization;
@@ -108,7 +109,7 @@ public MainTable(MainTableDataModel model, JabRefFrame frame,
108109
}
109110
this.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
110111

111-
this.setItems(model.getEntriesFilteredAndSorted());
112+
this.setItems(BindingsHelper.forUI(model.getEntriesFilteredAndSorted()));
112113
// Enable sorting
113114
model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty());
114115

src/main/java/org/jabref/gui/util/BindingsHelper.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static <A, B> MappedList<B, A> mapBacked(ObservableList<A> source, Functi
6060

6161
public static <T, U> ObservableList<U> map(ObservableValue<T> source, Function<T, List<U>> mapper) {
6262
PreboundBinding<List<U>> binding = new PreboundBinding<List<U>>(source) {
63+
6364
@Override
6465
protected List<U> computeValue() {
6566
return mapper.apply(source.getValue());
@@ -103,10 +104,10 @@ public static <A, B> void bindBidirectional(ObservableValue<A> propertyA, Observ
103104

104105
public static <A, B> void bindContentBidirectional(ObservableList<A> propertyA, ListProperty<B> propertyB, Consumer<ObservableList<B>> updateA, Consumer<List<A>> updateB) {
105106
bindContentBidirectional(
106-
propertyA,
107-
(ObservableValue<ObservableList<B>>) propertyB,
108-
updateA,
109-
updateB);
107+
propertyA,
108+
(ObservableValue<ObservableList<B>>) propertyB,
109+
updateA,
110+
updateB);
110111
}
111112

112113
public static <A, B> void bindContentBidirectional(ObservableList<A> propertyA, ObservableValue<B> propertyB, Consumer<B> updateA, Consumer<List<A>> updateB) {
@@ -124,10 +125,10 @@ public static <A, B> void bindContentBidirectional(ListProperty<A> listProperty,
124125
Consumer<List<A>> updateB = newValueList -> property.setValue(mapToB.apply(newValueList));
125126

126127
bindContentBidirectional(
127-
listProperty,
128-
property,
129-
updateList,
130-
updateB);
128+
listProperty,
129+
property,
130+
updateList,
131+
updateB);
131132
}
132133

133134
public static <A, V, B> void bindContentBidirectional(ObservableMap<A, V> propertyA, ObservableValue<B> propertyB, Consumer<B> updateA, Consumer<Map<A, V>> updateB) {
@@ -143,14 +144,15 @@ public static <A, V, B> void bindContentBidirectional(ObservableMap<A, V> proper
143144
public static <A, V, B> void bindContentBidirectional(ObservableMap<A, V> propertyA, Property<B> propertyB, Consumer<B> updateA, Function<Map<A, V>, B> mapToB) {
144145
Consumer<Map<A, V>> updateB = newValueList -> propertyB.setValue(mapToB.apply(newValueList));
145146
bindContentBidirectional(
146-
propertyA,
147-
propertyB,
148-
updateA,
149-
updateB);
147+
propertyA,
148+
propertyB,
149+
updateA,
150+
updateB);
150151
}
151152

152153
public static <T> ObservableValue<T> constantOf(T value) {
153154
return new ObjectBinding<T>() {
155+
154156
@Override
155157
protected T computeValue() {
156158
return value;
@@ -160,6 +162,7 @@ protected T computeValue() {
160162

161163
public static ObservableValue<Boolean> constantOf(boolean value) {
162164
return new BooleanBinding() {
165+
163166
@Override
164167
protected boolean computeValue() {
165168
return value;
@@ -169,13 +172,21 @@ protected boolean computeValue() {
169172

170173
public static ObservableValue<? extends String> emptyString() {
171174
return new StringBinding() {
175+
172176
@Override
173177
protected String computeValue() {
174178
return "";
175179
}
176180
};
177181
}
178182

183+
/**
184+
* Returns a wrapper around the given list that posts changes on the JavaFX thread.
185+
*/
186+
public static <T> ObservableList<T> forUI(ObservableList<T> list) {
187+
return new UiThreadList<>(list);
188+
}
189+
179190
private static class BidirectionalBinding<A, B> {
180191

181192
private final ObservableValue<A> propertyA;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.jabref.gui.util;
2+
3+
import javafx.application.Platform;
4+
import javafx.collections.ListChangeListener;
5+
import javafx.collections.ObservableList;
6+
import javafx.collections.transformation.TransformationList;
7+
8+
class UiThreadList<T> extends TransformationList<T, T> {
9+
public UiThreadList(ObservableList<? extends T> source) {
10+
super(source);
11+
}
12+
13+
@Override
14+
protected void sourceChanged(ListChangeListener.Change<? extends T> change) {
15+
Platform.runLater(() -> fireChange(change));
16+
}
17+
18+
@Override
19+
public int getSourceIndex(int index) {
20+
return index;
21+
}
22+
23+
@Override
24+
public T get(int index) {
25+
return getSource().get(index);
26+
}
27+
28+
@Override
29+
public int size() {
30+
return getSource().size();
31+
}
32+
}

0 commit comments

Comments
 (0)