Skip to content

Commit

Permalink
Updates to colored group indicator for cited entries (#7173)
Browse files Browse the repository at this point in the history
  • Loading branch information
k3KAW8Pnf7mkmdSMPHz27 authored Jan 13, 2021
1 parent 7f26a3a commit 4d8e4f0
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 117 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We fixed an issue where the password for a shared SQL database was not remembered [#6869](https://github.com/JabRef/jabref/issues/6869)
- We fixed an issue where newly added entires were not synced to a shared SQL database [#7176](https://github.com/JabRef/jabref/issues/7176)
- We fixed an issue where the PDF-Content importer threw an exception when no DOI number is present at the first page of the PDF document [#7203](https://github.com/JabRef/jabref/issues/7203)
- We fixed an issue where groups created from aux files did not update on file changes [#6394](https://github.com/JabRef/jabref/issues/6394)
- We fixed an issue where authors that only have last names were incorrectly identified as institutes when generating citation keys [#7199](https://github.com/JabRef/jabref/issues/7199)
- We fixed an issue where institutes were incorrectly identified as universities when generating citation keys [#6942](https://github.com/JabRef/jabref/issues/6942)

Expand Down
15 changes: 8 additions & 7 deletions src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@ public GroupChangeViewModel(GroupDiff diff) {

@Override
public void makeChange(BibDatabaseContext database, NamedCompound undoEdit) {
GroupTreeNode root = database.getMetaData().getGroups().orElse(null);
if (root == null) {
root = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup());
database.getMetaData().setGroups(root);
}
GroupTreeNode root = database.getMetaData().getGroups().orElseGet(() -> {
GroupTreeNode groupTreeNode = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup());
database.getMetaData().setGroups(groupTreeNode);
return groupTreeNode;
});

final UndoableModifySubtree undo = new UndoableModifySubtree(
new GroupTreeNodeViewModel(database.getMetaData().getGroups().orElse(null)),
new GroupTreeNodeViewModel(root), Localization.lang("Modified groups"));
root.removeAllChildren();
if (changedGroups == null) {
// I think setting root to null is not possible
root.setGroup(DefaultGroupsFactory.getAllEntriesGroup());
root.setGroup(DefaultGroupsFactory.getAllEntriesGroup(), false, false, null);
} else {
// change root group, even though it'll be AllEntries anyway
root.setGroup(changedGroups.getGroup());
root.setGroup(changedGroups.getGroup(), false, false, null);
for (GroupTreeNode child : changedGroups.getChildren()) {
child.copySubtree().moveTo(root);
}
Expand Down
40 changes: 27 additions & 13 deletions src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.Optional;
import java.util.stream.Collectors;

import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
Expand All @@ -23,6 +25,7 @@
import org.jabref.gui.icon.JabRefIcon;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.CustomLocalDragboard;
import org.jabref.gui.util.DefaultTaskExecutor;
import org.jabref.gui.util.DroppingMouseLocation;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.groups.DefaultGroupsFactory;
Expand All @@ -34,6 +37,7 @@
import org.jabref.model.groups.AutomaticGroup;
import org.jabref.model.groups.GroupEntryChanger;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.groups.TexGroup;
import org.jabref.model.strings.StringUtil;
import org.jabref.preferences.PreferencesService;

Expand All @@ -59,6 +63,7 @@ public class GroupNodeViewModel {
private final CustomLocalDragboard localDragBoard;
private final ObservableList<BibEntry> entriesList;
private final PreferencesService preferencesService;
private final InvalidationListener onInvalidatedGroup = (listener) -> refreshGroup();

public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, GroupTreeNode groupNode, CustomLocalDragboard localDragBoard, PreferencesService preferencesService) {
this.databaseContext = Objects.requireNonNull(databaseContext);
Expand All @@ -81,6 +86,9 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state
} else {
children = EasyBind.mapBacked(groupNode.getChildren(), this::toViewModel);
}
if (groupNode.getGroup() instanceof TexGroup) {
databaseContext.getMetaData().groupsBinding().addListener(new WeakInvalidationListener(onInvalidatedGroup));
}
hasChildren = new SimpleBooleanProperty();
hasChildren.bind(Bindings.isNotEmpty(children));
updateMatchedEntries();
Expand Down Expand Up @@ -250,6 +258,17 @@ private void onDatabaseChanged(ListChangeListener.Change<? extends BibEntry> cha
}
}

private void refreshGroup() {
DefaultTaskExecutor.runInJavaFXThread(() -> {
updateMatchedEntries(); // Update the entries matched by the group
// "Re-add" to the selected groups if it were selected, this refreshes the entries the user views
ObservableList<GroupTreeNode> selectedGroups = this.stateManager.getSelectedGroup(this.databaseContext);
if (selectedGroups.remove(this.groupNode)) {
selectedGroups.add(this.groupNode);
}
});
}

private void updateMatchedEntries() {
// We calculate the new hit value
// We could be more intelligent and try to figure out the new number of hits based on the entry change
Expand Down Expand Up @@ -290,10 +309,11 @@ public Optional<GroupNodeViewModel> getChildByPath(String pathToSource) {
}

/**
* Decides if the content stored in the given {@link Dragboard} can be droped on the given target row.
* Currently, the following sources are allowed:
* - another group (will be added as subgroup on drop)
* - entries if the group implements {@link GroupEntryChanger} (will be assigned to group on drop)
* Decides if the content stored in the given {@link Dragboard} can be dropped on the given target row. Currently, the following sources are allowed:
* <ul>
* <li>another group (will be added as subgroup on drop)</li>
* <li>entries if the group implements {@link GroupEntryChanger} (will be assigned to group on drop)</li>
* </ul>
*/
public boolean acceptableDrop(Dragboard dragboard) {
// TODO: we should also check isNodeDescendant
Expand Down Expand Up @@ -343,15 +363,9 @@ public void draggedOn(GroupNodeViewModel target, DroppingMouseLocation mouseLoca
// Bottom + top -> insert source row before / after this row
// Center -> add as child
switch (mouseLocation) {
case BOTTOM:
this.moveTo(targetParent.get(), targetIndex + 1);
break;
case CENTER:
this.moveTo(target);
break;
case TOP:
this.moveTo(targetParent.get(), targetIndex);
break;
case BOTTOM -> this.moveTo(targetParent.get(), targetIndex + 1);
case CENTER -> this.moveTo(target);
case TOP -> this.moveTo(targetParent.get(), targetIndex);
}
} else {
// No parent = root -> just add
Expand Down
30 changes: 14 additions & 16 deletions src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javafx.beans.Observable;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;

import org.jabref.gui.specialfields.SpecialFieldValueViewModel;
import org.jabref.gui.util.uithreadaware.UiThreadBinding;
import org.jabref.logic.importer.util.FileFieldParser;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
Expand All @@ -36,7 +38,7 @@ public class BibEntryTableViewModel {
private final Map<SpecialField, OptionalBinding<SpecialFieldValueViewModel>> specialFieldValues = new HashMap<>();
private final EasyBinding<List<LinkedFile>> linkedFiles;
private final EasyBinding<Map<Field, String>> linkedIdentifiers;
private final ObservableValue<List<AbstractGroup>> matchedGroups;
private final Binding<List<AbstractGroup>> matchedGroups;

public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseContext, ObservableValue<MainTableFieldValueFormatter> fieldValueFormatter) {
this.entry = entry;
Expand Down Expand Up @@ -67,19 +69,15 @@ public BibEntry getEntry() {
return entry;
}

private static ObservableValue<List<AbstractGroup>> createMatchedGroupsBinding(BibDatabaseContext database, BibEntry entry) {
Optional<GroupTreeNode> root = database.getMetaData().getGroups();
if (root.isPresent()) {
return EasyBind.map(entry.getFieldBinding(StandardField.GROUPS), field -> {
List<AbstractGroup> groups = root.get().getMatchingGroups(entry)
.stream()
.map(GroupTreeNode::getGroup)
.collect(Collectors.toList());
groups.remove(root.get().getGroup());
return groups;
});
}
return new SimpleObjectProperty<>(Collections.emptyList());
private static Binding<List<AbstractGroup>> createMatchedGroupsBinding(BibDatabaseContext database, BibEntry entry) {
return new UiThreadBinding<>(EasyBind.combine(entry.getFieldBinding(StandardField.GROUPS), database.getMetaData().groupsBinding(),
(a, b) ->
database.getMetaData().getGroups().map(groupTreeNode ->
groupTreeNode.getMatchingGroups(entry).stream()
.map(GroupTreeNode::getGroup)
.filter(Predicate.not(Predicate.isEqual(groupTreeNode.getGroup())))
.collect(Collectors.toList()))
.orElse(Collections.emptyList())));
}

public OptionalBinding<String> getField(Field field) {
Expand Down Expand Up @@ -119,7 +117,7 @@ public ObservableValue<String> getFields(OrFields fields) {
observables.add(fieldValueFormatter);

value = Bindings.createStringBinding(() ->
fieldValueFormatter.getValue().formatFieldsValues(fields, entry),
fieldValueFormatter.getValue().formatFieldsValues(fields, entry),
observables.toArray(Observable[]::new));
fieldValues.put(fields, value);
return value;
Expand Down
27 changes: 8 additions & 19 deletions src/main/java/org/jabref/gui/preview/CopyCitationAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
import org.slf4j.LoggerFactory;

/**
* Copies the selected entries and formats them with the selected citation style (or preview), then it is copied to the clipboard.
* This worker cannot be reused.
* Copies the selected entries and formats them with the selected citation style (or preview), then it is copied to the clipboard. This worker cannot be reused.
*/
public class CopyCitationAction extends SimpleCommand {

Expand Down Expand Up @@ -93,8 +92,7 @@ private List<String> generateCitations() throws IOException {
}

/**
* Generates a plain text string out of the preview and copies it additionally to the html to the clipboard
* (WYSIWYG Editors use the HTML, plain text editors the text)
* Generates a plain text string out of the preview and copies it additionally to the html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text)
*/
protected static ClipboardContent processPreview(List<String> citations) {
ClipboardContent content = new ClipboardContent();
Expand Down Expand Up @@ -182,23 +180,14 @@ private void setClipBoardContent(List<String> citations) {
// if it's generated by a citation style take care of each output format
ClipboardContent content;
switch (outputFormat) {
case HTML:
content = processHtml(citations);
break;
case RTF:
content = processRtf(citations);
break;
case XSL_FO:
content = processXslFo(citations);
break;
case ASCII_DOC:
case TEXT:
content = processText(citations);
break;
default:
case HTML -> content = processHtml(citations);
case RTF -> content = processRtf(citations);
case XSL_FO -> content = processXslFo(citations);
case ASCII_DOC, TEXT -> content = processText(citations);
default -> {
LOGGER.warn("unknown output format: '" + outputFormat + "', processing it via the default.");
content = processText(citations);
break;
}
}
clipBoardManager.setContent(content);
}
Expand Down
14 changes: 6 additions & 8 deletions src/main/java/org/jabref/gui/util/BindingsHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
import com.tobiasdiez.easybind.Subscription;

/**
* Helper methods for javafx binding.
* Some methods are taken from https://bugs.openjdk.java.net/browse/JDK-8134679
* Helper methods for javafx binding. Some methods are taken from https://bugs.openjdk.java.net/browse/JDK-8134679
*/
public class BindingsHelper {

Expand All @@ -43,7 +42,7 @@ public static Subscription includePseudoClassWhen(Node node, PseudoClass pseudoC
}

public static <T, U> ObservableList<U> map(ObservableValue<T> source, Function<T, List<U>> mapper) {
PreboundBinding<List<U>> binding = new PreboundBinding<List<U>>(source) {
PreboundBinding<List<U>> binding = new PreboundBinding<>(source) {

@Override
protected List<U> computeValue() {
Expand All @@ -57,7 +56,7 @@ protected List<U> computeValue() {
}

/**
* Binds propertA bidirectional to propertyB using the provided map functions to convert between them.
* Binds propertyA bidirectional to propertyB using the provided map functions to convert between them.
*/
public static <A, B> void bindBidirectional(Property<A> propertyA, Property<B> propertyB, Function<A, B> mapAtoB, Function<B, A> mapBtoA) {
Consumer<B> updateA = newValueB -> propertyA.setValue(mapBtoA.apply(newValueB));
Expand All @@ -66,15 +65,14 @@ public static <A, B> void bindBidirectional(Property<A> propertyA, Property<B> p
}

/**
* Binds propertA bidirectional to propertyB while using updateB to update propertyB when propertyA changed.
* Binds propertyA bidirectional to propertyB while using updateB to update propertyB when propertyA changed.
*/
public static <A> void bindBidirectional(Property<A> propertyA, ObservableValue<A> propertyB, Consumer<A> updateB) {
bindBidirectional(propertyA, propertyB, propertyA::setValue, updateB);
}

/**
* Binds propertA bidirectional to propertyB using updateB to update propertyB when propertyA changed and similar
* for updateA.
* Binds propertyA bidirectional to propertyB using updateB to update propertyB when propertyA changed and similar for updateA.
*/
public static <A, B> void bindBidirectional(ObservableValue<A> propertyA, ObservableValue<B> propertyB, Consumer<B> updateA, Consumer<A> updateB) {
final BidirectionalBinding<A, B> binding = new BidirectionalBinding<>(propertyA, propertyB, updateA, updateB);
Expand Down Expand Up @@ -135,7 +133,7 @@ public static <A, V, B> void bindContentBidirectional(ObservableMap<A, V> proper
}

public static <T> ObservableValue<T> constantOf(T value) {
return new ObjectBinding<T>() {
return new ObjectBinding<>() {

@Override
protected T computeValue() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.jabref.gui.util.uithreadaware;

import javafx.beans.InvalidationListener;
import javafx.beans.binding.Binding;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;

/**
* This class can be used to wrap a {@link Binding} inside it. When wrapped, any Listener listening for updates to the wrapped {@link Binding} (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.
*/
public class UiThreadBinding<T> implements Binding<T> {

private final Binding<T> delegate;

public UiThreadBinding(Binding<T> delegate) {
this.delegate = delegate;
}

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

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

@Override
public void addListener(ChangeListener<? super T> listener) {
delegate.addListener(new UiThreadChangeListener<>(listener));
}

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

@Override
public T getValue() {
return delegate.getValue();
}

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

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

@Override
public ObservableList<?> getDependencies() {
return delegate.getDependencies();
}

@Override
public void dispose() {
delegate.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ public Optional<BibEntry> parseSingleEntry(String bibtexString) throws ParseExce
}

/**
* Will parse the BibTex-Data found when reading from reader. Ignores any encoding supplied in the file by
* "Encoding: myEncoding".
* Will parse the BibTex-Data found when reading from reader. Ignores any encoding supplied in the file by "Encoding: myEncoding".
* <p>
* The reader will be consumed.
* <p>
Expand Down Expand Up @@ -311,8 +310,7 @@ private void parseBibtexString() throws IOException {
}

/**
* Puts all text that has been read from the reader, including newlines, etc., since the last call of this method into a string.
* Removes the JabRef file header, if it is found
* Puts all text that has been read from the reader, including newlines, etc., since the last call of this method into a string. Removes the JabRef file header, if it is found
*
* @return the text read so far
*/
Expand Down Expand Up @@ -611,8 +609,7 @@ private String parseFieldContent(Field field) throws IOException {
}

/**
* This method is used to parse string labels, field names, entry type and
* numbers outside brackets.
* This method is used to parse string labels, field names, entry type and numbers outside brackets.
*/
private String parseTextToken() throws IOException {
StringBuilder token = new StringBuilder(20);
Expand Down
Loading

0 comments on commit 4d8e4f0

Please sign in to comment.