diff --git a/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java b/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java index 32feffb25ba..bae94851ca4 100644 --- a/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java +++ b/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java @@ -1,8 +1,6 @@ package org.jabref.gui.autocompleter; -import java.util.Locale; - -public class AppendPersonNamesStrategy implements AutoCompletionStrategy { +public class AppendPersonNamesStrategy extends AppendWordsStrategy { /** * true if the input should be split at a single white space instead of the usual delimiter " and " for names. @@ -19,22 +17,11 @@ public AppendPersonNamesStrategy(boolean separationBySpace) { } @Override - public AutoCompletionInput analyze(String input) { + public String getDelimiter() { if (this.separationBySpace) { - return determinePrefixAndReturnRemainder(input, " "); - } else { - return determinePrefixAndReturnRemainder(input, " and "); - } - } - - private AutoCompletionInput determinePrefixAndReturnRemainder(String input, String delimiter) { - int index = input.toLowerCase(Locale.ROOT).lastIndexOf(delimiter); - if (index >= 0) { - String prefix = input.substring(0, index + delimiter.length()); - String rest = input.substring(index + delimiter.length()); - return new AutoCompletionInput(prefix, rest); + return " "; } else { - return new AutoCompletionInput("", input); + return " and "; } } } diff --git a/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java b/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java new file mode 100644 index 00000000000..1cc0563be0b --- /dev/null +++ b/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java @@ -0,0 +1,26 @@ +package org.jabref.gui.autocompleter; + +import java.util.Locale; + +public class AppendWordsStrategy implements AutoCompletionStrategy { + + protected String getDelimiter() { + return " "; + } + + @Override + public AutoCompletionInput analyze(String input) { + return determinePrefixAndReturnRemainder(input, getDelimiter()); + } + + private AutoCompletionInput determinePrefixAndReturnRemainder(String input, String delimiter) { + int index = input.toLowerCase(Locale.ROOT).lastIndexOf(delimiter); + if (index >= 0) { + String prefix = input.substring(0, index + delimiter.length()); + String rest = input.substring(index + delimiter.length()); + return new AutoCompletionInput(prefix, rest); + } else { + return new AutoCompletionInput("", input); + } + } +} diff --git a/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java b/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java index 3c3ce92bb55..451eae63bad 100644 --- a/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java +++ b/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java @@ -121,6 +121,10 @@ public static void autoComplete(TextInputControl textArea, Callback(textArea, suggestionProvider, converter, inputAnalyzer); } + public static void autoComplete(TextInputControl textArea, Callback> suggestionProvider, AutoCompletionStrategy inputAnalyzer) { + autoComplete(textArea, suggestionProvider, AutoCompletionTextInputBinding.defaultStringConverter(), inputAnalyzer); + } + @Override public TextInputControl getCompletionTarget() { return (TextInputControl) super.getCompletionTarget(); diff --git a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java index 99532aff197..568b697996a 100644 --- a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java @@ -5,7 +5,6 @@ import org.jabref.logic.bibtex.comparator.EntryComparator; import org.jabref.model.entry.BibEntry; -import impl.org.controlsfx.autocompletion.SuggestionProvider; import org.controlsfx.control.textfield.AutoCompletionBinding; /** diff --git a/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java index 379ddc4ac99..8182be6a33c 100644 --- a/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java @@ -9,7 +9,6 @@ import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; -import impl.org.controlsfx.autocompletion.SuggestionProvider; import org.controlsfx.control.textfield.AutoCompletionBinding; /** diff --git a/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java index c25d05e2080..1ec2cf0c97d 100644 --- a/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java @@ -2,7 +2,6 @@ import java.util.Comparator; -import impl.org.controlsfx.autocompletion.SuggestionProvider; import org.controlsfx.control.textfield.AutoCompletionBinding; class StringSuggestionProvider extends SuggestionProvider { diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java new file mode 100644 index 00000000000..553b5c681a7 --- /dev/null +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2014, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jabref.gui.autocompleter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; + +import javafx.util.Callback; + +import org.controlsfx.control.textfield.AutoCompletionBinding.ISuggestionRequest; + +/** + * This is a simple implementation of a generic suggestion provider callback. + * The complexity of suggestion generation is O(n) where n is the number of possible suggestions. + * + * @param Type of suggestions + * + * This class is a copy of {@link impl.org.controlsfx.autocompletion.SuggestionProvider} with the only difference that + * we use a set instead of list to store the suggestions in order to eliminate duplicates. + */ +public abstract class SuggestionProvider implements Callback> { + + private final Collection possibleSuggestions = new HashSet<>(); + private final Object possibleSuggestionsLock = new Object(); + + /** + * Create a default suggestion provider based on the toString() method of the generic objects + * @param possibleSuggestions All possible suggestions + * @return + */ + public static SuggestionProvider create(Collection possibleSuggestions) { + return create(null, possibleSuggestions); + } + + /** + * Create a default suggestion provider based on the toString() method of the generic objects + * using the provided stringConverter + * + * @param stringConverter A stringConverter which converts generic T into a string + * @param possibleSuggestions All possible suggestions + * @return + */ + public static SuggestionProvider create(Callback stringConverter, Collection possibleSuggestions) { + SuggestionProviderString suggestionProvider = new SuggestionProviderString<>(stringConverter); + suggestionProvider.addPossibleSuggestions(possibleSuggestions); + return suggestionProvider; + } + + /** + * Add the given new possible suggestions to this SuggestionProvider + * @param newPossible + */ + public void addPossibleSuggestions(@SuppressWarnings("unchecked") T... newPossible) { + addPossibleSuggestions(Arrays.asList(newPossible)); + } + + /** + * Add the given new possible suggestions to this SuggestionProvider + * @param newPossible + */ + public void addPossibleSuggestions(Collection newPossible) { + synchronized (possibleSuggestionsLock) { + possibleSuggestions.addAll(newPossible); + } + } + + /** + * Remove all current possible suggestions + */ + public void clearSuggestions() { + synchronized (possibleSuggestionsLock) { + possibleSuggestions.clear(); + } + } + + @Override + public final Collection call(final ISuggestionRequest request) { + List suggestions = new ArrayList<>(); + if (!request.getUserText().isEmpty()) { + synchronized (possibleSuggestionsLock) { + for (T possibleSuggestion : possibleSuggestions) { + if (isMatch(possibleSuggestion, request)) { + suggestions.add(possibleSuggestion); + } + } + } + Collections.sort(suggestions, getComparator()); + } + return suggestions; + } + + + /*************************************************************************** + * * + * Static methods * + * * + **************************************************************************/ + + /** + * Get the comparator to order the suggestions + * @return + */ + protected abstract Comparator getComparator(); + + /** + * Check the given possible suggestion is a match (is a valid suggestion) + * @param suggestion + * @param request + * @return + */ + protected abstract boolean isMatch(T suggestion, ISuggestionRequest request); + + + /*************************************************************************** + * * + * Default implementations * + * * + **************************************************************************/ + + /** + * This is a simple string based suggestion provider. + * All generic suggestions T are turned into strings for processing. + * + */ + private static class SuggestionProviderString extends SuggestionProvider { + + private Callback stringConverter; + + private final Comparator stringComparator = new Comparator() { + @Override + public int compare(T o1, T o2) { + String o1str = stringConverter.call(o1); + String o2str = stringConverter.call(o2); + return o1str.compareTo(o2str); + } + }; + + /** + * Create a new SuggestionProviderString + * @param stringConverter + */ + public SuggestionProviderString(Callback stringConverter) { + this.stringConverter = stringConverter; + + // In case no stringConverter was provided, use the default strategy + if (this.stringConverter == null) { + this.stringConverter = new Callback() { + @Override + public String call(T obj) { + return obj != null ? obj.toString() : ""; //$NON-NLS-1$ + } + }; + } + } + + /**{@inheritDoc}*/ + @Override + protected Comparator getComparator() { + return stringComparator; + } + + /**{@inheritDoc}*/ + @Override + protected boolean isMatch(T suggestion, ISuggestionRequest request) { + String userTextLower = request.getUserText().toLowerCase(); + String suggestionStr = suggestion.toString().toLowerCase(); + return suggestionStr.contains(userTextLower); + } + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java index 8d652c7cdaf..27a6c823e38 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java @@ -8,6 +8,7 @@ import javafx.scene.layout.HBox; import org.jabref.gui.autocompleter.AutoCompleteSuggestionProvider; +import org.jabref.gui.autocompleter.AutoCompletionTextInputBinding; import org.jabref.gui.fieldeditors.contextmenu.EditorMenus; import org.jabref.gui.util.ControlHelper; import org.jabref.logic.journals.JournalAbbreviationLoader; @@ -26,8 +27,9 @@ public JournalEditor(String fieldName, JournalAbbreviationLoader journalAbbrevia ControlHelper.loadFXMLForControl(this); textArea.textProperty().bindBidirectional(viewModel.textProperty()); - textArea.addToContextMenu(EditorMenus.getDefaultMenu(textArea)); + + AutoCompletionTextInputBinding.autoComplete(textArea, viewModel::complete); } public JournalEditorViewModel getViewModel() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java index 876e4e48157..347504def11 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java @@ -23,7 +23,7 @@ public SimpleEditor(String fieldName, AutoCompleteSuggestionProvider suggesti textArea.addToContextMenu(EditorMenus.getDefaultMenu(textArea)); this.getChildren().add(textArea); - AutoCompletionTextInputBinding.autoComplete(textArea, viewModel::complete); + AutoCompletionTextInputBinding.autoComplete(textArea, viewModel::complete, viewModel.getAutoCompletionStrategy()); } @Override diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java index 5bb5bdb77f6..1cc61e93ab7 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java @@ -1,10 +1,16 @@ package org.jabref.gui.fieldeditors; +import org.jabref.gui.autocompleter.AppendWordsStrategy; import org.jabref.gui.autocompleter.AutoCompleteSuggestionProvider; +import org.jabref.gui.autocompleter.AutoCompletionStrategy; public class SimpleEditorViewModel extends AbstractEditorViewModel { public SimpleEditorViewModel(String fieldName, AutoCompleteSuggestionProvider suggestionProvider) { super(fieldName, suggestionProvider); } + + public AutoCompletionStrategy getAutoCompletionStrategy() { + return new AppendWordsStrategy(); + } }