Skip to content

Commit

Permalink
Merge pull request #5289 from r0light/ensurejavafxthreadexecution
Browse files Browse the repository at this point in the history
Wrapper classes for UI Lists etc that ensure updates on JavaFX thread
  • Loading branch information
Siedlerchr committed Sep 9, 2019
2 parents 88b370a + 4592dca commit fc4201d
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.jabref.gui.keyboard.KeyBinding;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.gui.util.uithreadaware.UiThreadObservableList;
import org.jabref.logic.integrity.FieldCheckers;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
Expand All @@ -51,6 +52,7 @@ public class LinkedFilesEditor extends HBox implements FieldEditorFX {

private final DialogService dialogService;
private final BibDatabaseContext databaseContext;
private final UiThreadObservableList<LinkedFileViewModel> decoratedModelList;

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

listView.setCellFactory(cellFactory);

Bindings.bindContentBidirectional(listView.itemsProperty().get(), viewModel.filesProperty());
decoratedModelList = new UiThreadObservableList<>(viewModel.filesProperty());
Bindings.bindContentBidirectional(listView.itemsProperty().get(), decoratedModelList);
setUpKeyBindings();
}

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.jabref.gui.autocompleter.AutoCompleteSuggestionProvider;
import org.jabref.gui.autocompleter.AutoCompletionTextInputBinding;
import org.jabref.gui.fieldeditors.contextmenu.EditorMenus;
import org.jabref.gui.util.uithreadaware.UiThreadStringProperty;
import org.jabref.logic.integrity.FieldCheckers;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;
Expand All @@ -16,6 +17,7 @@ public class PersonsEditor extends HBox implements FieldEditorFX {

private final PersonsEditorViewModel viewModel;
private final TextInputControl textInput;
private final UiThreadStringProperty decoratedStringProperty;

public PersonsEditor(final Field field,
final AutoCompleteSuggestionProvider<?> suggestionProvider,
Expand All @@ -28,7 +30,8 @@ public PersonsEditor(final Field field,
? new EditorTextField()
: new EditorTextArea();

textInput.textProperty().bindBidirectional(viewModel.textProperty());
decoratedStringProperty = new UiThreadStringProperty(viewModel.textProperty());
textInput.textProperty().bindBidirectional(decoratedStringProperty);
((ContextMenuAddable) textInput).addToContextMenu(EditorMenus.getNameMenu(textInput));
this.getChildren().add(textInput);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jabref.gui.util.uithreadaware;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

class UiThreadChangeListener<T> implements ChangeListener<T> {

private ChangeListener<T> delegate;

public UiThreadChangeListener(ChangeListener<T> delegate) {
this.delegate = delegate;
}

@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
UiThreadHelper.ensureUiThreadExecution(() -> delegate.changed(observable, oldValue, newValue));
}

@Override
public boolean equals(Object o) {
return delegate.equals(o);
}

@Override
public int hashCode() {
return delegate.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.jabref.gui.util.uithreadaware;

import javafx.application.Platform;

class UiThreadHelper {

static void ensureUiThreadExecution(Runnable task) {
if (Platform.isFxApplicationThread()) {
task.run();
} else {
Platform.runLater(task);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jabref.gui.util.uithreadaware;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;

class UiThreadInvalidationListener implements InvalidationListener {

private final InvalidationListener delegate;

public UiThreadInvalidationListener(InvalidationListener delegate) {
this.delegate = delegate;
}

@Override
public void invalidated(Observable observable) {
UiThreadHelper.ensureUiThreadExecution(() -> delegate.invalidated(observable));
}

@Override
public boolean equals(Object o) {
return delegate.equals(o);
}

@Override
public int hashCode() {
return delegate.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.jabref.gui.util.uithreadaware;

import javafx.collections.ListChangeListener;

class UiThreadListChangeListener<E> implements ListChangeListener<E> {

private final ListChangeListener<E> delegate;

public UiThreadListChangeListener(ListChangeListener<E> delegate) {
this.delegate = delegate;
}

@Override
public void onChanged(Change<? extends E> c) {
UiThreadHelper.ensureUiThreadExecution(() -> delegate.onChanged(c));
}

@Override
public boolean equals(Object o) {
return delegate.equals(o);
}

@Override
public int hashCode() {
return delegate.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package org.jabref.gui.util.uithreadaware;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import javafx.beans.InvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

/**
* 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.
*
* @param <E> the type of the elements of the wrapped ObservableList.
*/
public class UiThreadObservableList<E> implements ObservableList<E> {

private final ObservableList<E> delegate;

public UiThreadObservableList(ObservableList<E> delegate) {
this.delegate = delegate;
}

@Override
public void addListener(ListChangeListener<? super E> listener) {
delegate.addListener(new UiThreadListChangeListener(listener));
}

@Override
public void removeListener(ListChangeListener<? super E> listener) {
delegate.removeListener(listener);
}

@Override
public boolean addAll(E... elements) {
return delegate.addAll(elements);
}

@Override
public boolean setAll(E... elements) {
return delegate.setAll(elements);
}

@Override
public boolean setAll(Collection<? extends E> col) {
return delegate.setAll(col);
}

@Override
public boolean removeAll(E... elements) {
return delegate.removeAll(elements);
}

@Override
public boolean retainAll(E... elements) {
return delegate.retainAll(elements);
}

@Override
public void remove(int from, int to) {
delegate.remove(from, to);
}

@Override
public int size() {
return delegate.size();
}

@Override
public boolean isEmpty() {
return delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
return delegate.contains(o);
}

@Override
public Iterator<E> iterator() {
return delegate.iterator();
}

@Override
public Object[] toArray() {
return delegate.toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return delegate.toArray(a);
}

@Override
public boolean add(E e) {
return delegate.add(e);
}

@Override
public boolean remove(Object o) {
return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
return delegate.addAll(c);
}

@Override
public boolean addAll(int index, Collection<? extends E> c) {
return delegate.addAll(index, c);
}

@Override
public boolean removeAll(Collection<?> c) {
return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
return delegate.retainAll(c);
}

@Override
public void clear() {
delegate.clear();
}

@Override
public E get(int index) {
return delegate.get(index);
}

@Override
public E set(int index, E element) {
return delegate.set(index, element);
}

@Override
public void add(int index, E element) {
delegate.add(index, element);
}

@Override
public E remove(int index) {
return delegate.remove(index);
}

@Override
public int indexOf(Object o) {
return delegate.indexOf(o);
}

@Override
public int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}

@Override
public ListIterator<E> listIterator() {
return delegate.listIterator();
}

@Override
public ListIterator<E> listIterator(int index) {
return delegate.listIterator(index);
}

@Override
public List<E> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}

@Override
public void addListener(InvalidationListener listener) {
delegate.addListener(new UiThreadInvalidationListener(listener));
}

@Override
public void removeListener(InvalidationListener listener) {
delegate.removeListener(listener);
}
}
Loading

0 comments on commit fc4201d

Please sign in to comment.