Skip to content

Commit fc4201d

Browse files
authored
Merge pull request #5289 from r0light/ensurejavafxthreadexecution
Wrapper classes for UI Lists etc that ensure updates on JavaFX thread
2 parents 88b370a + 4592dca commit fc4201d

8 files changed

+368
-2
lines changed

src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.jabref.gui.keyboard.KeyBinding;
3535
import org.jabref.gui.util.TaskExecutor;
3636
import org.jabref.gui.util.ViewModelListCellFactory;
37+
import org.jabref.gui.util.uithreadaware.UiThreadObservableList;
3738
import org.jabref.logic.integrity.FieldCheckers;
3839
import org.jabref.logic.l10n.Localization;
3940
import org.jabref.model.database.BibDatabaseContext;
@@ -51,6 +52,7 @@ public class LinkedFilesEditor extends HBox implements FieldEditorFX {
5152

5253
private final DialogService dialogService;
5354
private final BibDatabaseContext databaseContext;
55+
private final UiThreadObservableList<LinkedFileViewModel> decoratedModelList;
5456

5557
public LinkedFilesEditor(Field field, DialogService dialogService, BibDatabaseContext databaseContext, TaskExecutor taskExecutor, AutoCompleteSuggestionProvider<?> suggestionProvider,
5658
FieldCheckers fieldCheckers,
@@ -74,7 +76,8 @@ public LinkedFilesEditor(Field field, DialogService dialogService, BibDatabaseCo
7476

7577
listView.setCellFactory(cellFactory);
7678

77-
Bindings.bindContentBidirectional(listView.itemsProperty().get(), viewModel.filesProperty());
79+
decoratedModelList = new UiThreadObservableList<>(viewModel.filesProperty());
80+
Bindings.bindContentBidirectional(listView.itemsProperty().get(), decoratedModelList);
7881
setUpKeyBindings();
7982
}
8083

src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jabref.gui.autocompleter.AutoCompleteSuggestionProvider;
88
import org.jabref.gui.autocompleter.AutoCompletionTextInputBinding;
99
import org.jabref.gui.fieldeditors.contextmenu.EditorMenus;
10+
import org.jabref.gui.util.uithreadaware.UiThreadStringProperty;
1011
import org.jabref.logic.integrity.FieldCheckers;
1112
import org.jabref.model.entry.BibEntry;
1213
import org.jabref.model.entry.field.Field;
@@ -16,6 +17,7 @@ public class PersonsEditor extends HBox implements FieldEditorFX {
1617

1718
private final PersonsEditorViewModel viewModel;
1819
private final TextInputControl textInput;
20+
private final UiThreadStringProperty decoratedStringProperty;
1921

2022
public PersonsEditor(final Field field,
2123
final AutoCompleteSuggestionProvider<?> suggestionProvider,
@@ -28,7 +30,8 @@ public PersonsEditor(final Field field,
2830
? new EditorTextField()
2931
: new EditorTextArea();
3032

31-
textInput.textProperty().bindBidirectional(viewModel.textProperty());
33+
decoratedStringProperty = new UiThreadStringProperty(viewModel.textProperty());
34+
textInput.textProperty().bindBidirectional(decoratedStringProperty);
3235
((ContextMenuAddable) textInput).addToContextMenu(EditorMenus.getNameMenu(textInput));
3336
this.getChildren().add(textInput);
3437

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.jabref.gui.util.uithreadaware;
2+
3+
import javafx.beans.value.ChangeListener;
4+
import javafx.beans.value.ObservableValue;
5+
6+
class UiThreadChangeListener<T> implements ChangeListener<T> {
7+
8+
private ChangeListener<T> delegate;
9+
10+
public UiThreadChangeListener(ChangeListener<T> delegate) {
11+
this.delegate = delegate;
12+
}
13+
14+
@Override
15+
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
16+
UiThreadHelper.ensureUiThreadExecution(() -> delegate.changed(observable, oldValue, newValue));
17+
}
18+
19+
@Override
20+
public boolean equals(Object o) {
21+
return delegate.equals(o);
22+
}
23+
24+
@Override
25+
public int hashCode() {
26+
return delegate.hashCode();
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.jabref.gui.util.uithreadaware;
2+
3+
import javafx.application.Platform;
4+
5+
class UiThreadHelper {
6+
7+
static void ensureUiThreadExecution(Runnable task) {
8+
if (Platform.isFxApplicationThread()) {
9+
task.run();
10+
} else {
11+
Platform.runLater(task);
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.jabref.gui.util.uithreadaware;
2+
3+
import javafx.beans.InvalidationListener;
4+
import javafx.beans.Observable;
5+
6+
class UiThreadInvalidationListener implements InvalidationListener {
7+
8+
private final InvalidationListener delegate;
9+
10+
public UiThreadInvalidationListener(InvalidationListener delegate) {
11+
this.delegate = delegate;
12+
}
13+
14+
@Override
15+
public void invalidated(Observable observable) {
16+
UiThreadHelper.ensureUiThreadExecution(() -> delegate.invalidated(observable));
17+
}
18+
19+
@Override
20+
public boolean equals(Object o) {
21+
return delegate.equals(o);
22+
}
23+
24+
@Override
25+
public int hashCode() {
26+
return delegate.hashCode();
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.jabref.gui.util.uithreadaware;
2+
3+
import javafx.collections.ListChangeListener;
4+
5+
class UiThreadListChangeListener<E> implements ListChangeListener<E> {
6+
7+
private final ListChangeListener<E> delegate;
8+
9+
public UiThreadListChangeListener(ListChangeListener<E> delegate) {
10+
this.delegate = delegate;
11+
}
12+
13+
@Override
14+
public void onChanged(Change<? extends E> c) {
15+
UiThreadHelper.ensureUiThreadExecution(() -> delegate.onChanged(c));
16+
}
17+
18+
@Override
19+
public boolean equals(Object o) {
20+
return delegate.equals(o);
21+
}
22+
23+
@Override
24+
public int hashCode() {
25+
return delegate.hashCode();
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package org.jabref.gui.util.uithreadaware;
2+
3+
import java.util.Collection;
4+
import java.util.Iterator;
5+
import java.util.List;
6+
import java.util.ListIterator;
7+
8+
import javafx.beans.InvalidationListener;
9+
import javafx.collections.ListChangeListener;
10+
import javafx.collections.ObservableList;
11+
12+
/**
13+
* This class can be used to wrap an @see ObservableList inside it. When wrapped, any Listener listening for updates to the wrapped ObservableList (for example because of a binding to it) is ensured to be notified on the JavaFX Application Thread. It should be used to implement bindings where updates come in from a background thread but should be reflected in the UI where it is necessary that changes to the UI are performed on the JavaFX Application thread.
14+
*
15+
* @param <E> the type of the elements of the wrapped ObservableList.
16+
*/
17+
public class UiThreadObservableList<E> implements ObservableList<E> {
18+
19+
private final ObservableList<E> delegate;
20+
21+
public UiThreadObservableList(ObservableList<E> delegate) {
22+
this.delegate = delegate;
23+
}
24+
25+
@Override
26+
public void addListener(ListChangeListener<? super E> listener) {
27+
delegate.addListener(new UiThreadListChangeListener(listener));
28+
}
29+
30+
@Override
31+
public void removeListener(ListChangeListener<? super E> listener) {
32+
delegate.removeListener(listener);
33+
}
34+
35+
@Override
36+
public boolean addAll(E... elements) {
37+
return delegate.addAll(elements);
38+
}
39+
40+
@Override
41+
public boolean setAll(E... elements) {
42+
return delegate.setAll(elements);
43+
}
44+
45+
@Override
46+
public boolean setAll(Collection<? extends E> col) {
47+
return delegate.setAll(col);
48+
}
49+
50+
@Override
51+
public boolean removeAll(E... elements) {
52+
return delegate.removeAll(elements);
53+
}
54+
55+
@Override
56+
public boolean retainAll(E... elements) {
57+
return delegate.retainAll(elements);
58+
}
59+
60+
@Override
61+
public void remove(int from, int to) {
62+
delegate.remove(from, to);
63+
}
64+
65+
@Override
66+
public int size() {
67+
return delegate.size();
68+
}
69+
70+
@Override
71+
public boolean isEmpty() {
72+
return delegate.isEmpty();
73+
}
74+
75+
@Override
76+
public boolean contains(Object o) {
77+
return delegate.contains(o);
78+
}
79+
80+
@Override
81+
public Iterator<E> iterator() {
82+
return delegate.iterator();
83+
}
84+
85+
@Override
86+
public Object[] toArray() {
87+
return delegate.toArray();
88+
}
89+
90+
@Override
91+
public <T> T[] toArray(T[] a) {
92+
return delegate.toArray(a);
93+
}
94+
95+
@Override
96+
public boolean add(E e) {
97+
return delegate.add(e);
98+
}
99+
100+
@Override
101+
public boolean remove(Object o) {
102+
return delegate.remove(o);
103+
}
104+
105+
@Override
106+
public boolean containsAll(Collection<?> c) {
107+
return delegate.containsAll(c);
108+
}
109+
110+
@Override
111+
public boolean addAll(Collection<? extends E> c) {
112+
return delegate.addAll(c);
113+
}
114+
115+
@Override
116+
public boolean addAll(int index, Collection<? extends E> c) {
117+
return delegate.addAll(index, c);
118+
}
119+
120+
@Override
121+
public boolean removeAll(Collection<?> c) {
122+
return delegate.removeAll(c);
123+
}
124+
125+
@Override
126+
public boolean retainAll(Collection<?> c) {
127+
return delegate.retainAll(c);
128+
}
129+
130+
@Override
131+
public void clear() {
132+
delegate.clear();
133+
}
134+
135+
@Override
136+
public E get(int index) {
137+
return delegate.get(index);
138+
}
139+
140+
@Override
141+
public E set(int index, E element) {
142+
return delegate.set(index, element);
143+
}
144+
145+
@Override
146+
public void add(int index, E element) {
147+
delegate.add(index, element);
148+
}
149+
150+
@Override
151+
public E remove(int index) {
152+
return delegate.remove(index);
153+
}
154+
155+
@Override
156+
public int indexOf(Object o) {
157+
return delegate.indexOf(o);
158+
}
159+
160+
@Override
161+
public int lastIndexOf(Object o) {
162+
return delegate.lastIndexOf(o);
163+
}
164+
165+
@Override
166+
public ListIterator<E> listIterator() {
167+
return delegate.listIterator();
168+
}
169+
170+
@Override
171+
public ListIterator<E> listIterator(int index) {
172+
return delegate.listIterator(index);
173+
}
174+
175+
@Override
176+
public List<E> subList(int fromIndex, int toIndex) {
177+
return delegate.subList(fromIndex, toIndex);
178+
}
179+
180+
@Override
181+
public void addListener(InvalidationListener listener) {
182+
delegate.addListener(new UiThreadInvalidationListener(listener));
183+
}
184+
185+
@Override
186+
public void removeListener(InvalidationListener listener) {
187+
delegate.removeListener(listener);
188+
}
189+
}

0 commit comments

Comments
 (0)