Skip to content

Commit

Permalink
Searchable/Filterable preferences (#4415)
Browse files Browse the repository at this point in the history
* add search box to preferences

* replace deprecated method call

* Fix codestyle/checkstyle

* Add Preferences Search Handler

still work in progress
mapping search to preference and start searching it
still needs a way to filter the found items

* Remove unused variable

* Add a list of filtered preftabs instead of filtered preftabnames

* 🎉 Add the actual filtering of the elements

* Favor SimpleListProperty over SimpleObjectProperty

* Fix Imports

also be explicit about method visibility

* Add filtering of labels

* Remove some unneeded member variables

* extract filterBy methods

* Fix Type of ListProperty

* Remove todo

Verified this is not the case: label names dont contain tab names

* Reformat code

* Favor regular List over ObservableList

* Use existing translated text for prompt

* Revert: Favor regular List over ObservableList

because build (guiTest) fails

* Favor regular List over ObservableList

is not the cause of the build failure

* Adding search highlight while filtering

* Replacing map by multimaps and changing the mapping relation

* Fixing checkstyle error

* Added Locale.ROOT when converting to lower case

* Removed redundant comments

* Removed LabeledWrapper inner class and refactored PreferencesSearchHandler

* Fixed checkstyle error

* add bidiretional binding to searchProperty

* Added change listener on searchBox and removed search text property

Co-authored-by: Yash Kothari <yashkothari199767@gmail.com>
  • Loading branch information
2 people authored and tobiasdiez committed Mar 27, 2019
1 parent 44163bf commit ef5710c
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
.preferencePaneContainer {
-fx-padding: 0em 1em 0em 3em;
}

*:search-highlight {
-fx-background-color: -jr-light-red;
}
21 changes: 17 additions & 4 deletions src/main/java/org/jabref/gui/preferences/PreferencesDialog.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jabref.gui.preferences;

import java.util.List;
import java.util.Locale;
import java.util.prefs.BackingStoreException;

import javafx.collections.FXCollections;
Expand All @@ -12,6 +13,7 @@
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
Expand Down Expand Up @@ -127,6 +129,15 @@ private void construct() {
.withText(PrefsTab::getTabName)
.install(tabsList);

TextField searchBox = new TextField();
searchBox.setPromptText(Localization.lang("Search"));

PreferencesSearchHandler searchHandler = new PreferencesSearchHandler(preferenceTabs);
tabsList.itemsProperty().bindBidirectional(searchHandler.filteredPreferenceTabsProperty());
searchBox.textProperty().addListener((observable, previousText, newText) -> {
searchHandler.filterTabs(newText.toLowerCase(Locale.ROOT));
});

VBox buttonContainer = new VBox();
buttonContainer.setAlignment(Pos.BOTTOM_LEFT);
buttonContainer.setSpacing(3.0);
Expand Down Expand Up @@ -155,9 +166,11 @@ private void construct() {
VBox.setVgrow(tabsList, Priority.ALWAYS);
VBox.setVgrow(spacer, Priority.SOMETIMES);
vBox.getChildren().addAll(
tabsList,
spacer,
buttonContainer);
searchBox,
tabsList,
spacer,
buttonContainer
);

container.setLeft(vBox);

Expand Down Expand Up @@ -230,7 +243,7 @@ private void storeAllSettings() {

GUIGlobals.updateEntryEditorColors();
frame.setupAllTables();
frame.output(Localization.lang("Preferences recorded."));
dialogService.notify(Localization.lang("Preferences recorded."));
}

public void setValues() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.jabref.gui.preferences;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Labeled;

import com.google.common.collect.ArrayListMultimap;

class PreferencesSearchHandler {

private static PseudoClass labelHighlight = PseudoClass.getPseudoClass("search-highlight");
private final List<PrefsTab> preferenceTabs;
private final ListProperty<PrefsTab> filteredPreferenceTabs;
private final ArrayListMultimap<PrefsTab, Labeled> preferenceTabsLabelNames;
private final ArrayList<Labeled> highlightedLabels = new ArrayList<>();

PreferencesSearchHandler(List<PrefsTab> preferenceTabs) {
this.preferenceTabs = preferenceTabs;
this.preferenceTabsLabelNames = getPrefsTabLabelMap();
this.filteredPreferenceTabs = new SimpleListProperty<>(FXCollections.observableArrayList(preferenceTabs));
}

public void filterTabs(String text) {
clearHighlights();
if (text.isEmpty()) {
clearSearch();
return;
}

filteredPreferenceTabs.clear();
for (PrefsTab tab : preferenceTabsLabelNames.keySet()) {
boolean tabContainsLabel = false;
for (Labeled labeled : preferenceTabsLabelNames.get(tab)) {
if (labelContainsText(labeled, text)) {
tabContainsLabel = true;
highlightLabel(labeled);
}
}
boolean tabNameIsMatchedByQuery = tab.getTabName().toLowerCase(Locale.ROOT).contains(text);
if (tabContainsLabel || tabNameIsMatchedByQuery) {
filteredPreferenceTabs.add(tab);
}
}
}

private boolean labelContainsText(Labeled labeled, String text) {
return labeled.getText().toLowerCase(Locale.ROOT).contains(text);
}

private void highlightLabel(Labeled labeled) {
labeled.pseudoClassStateChanged(labelHighlight, true);
highlightedLabels.add(labeled);
}

private void clearHighlights() {
highlightedLabels.forEach(labeled -> labeled.pseudoClassStateChanged(labelHighlight, false));
}

private void clearSearch() {
filteredPreferenceTabs.setAll(preferenceTabs);
}

/*
* Traverse all nodes of a PrefsTab and return a
* mapping from PrefsTab to all its Labeled type nodes.
*/
private ArrayListMultimap<PrefsTab, Labeled> getPrefsTabLabelMap() {
ArrayListMultimap<PrefsTab, Labeled> prefsTabLabelMap = ArrayListMultimap.create();
for (PrefsTab prefsTab : preferenceTabs) {
Node builder = prefsTab.getBuilder();
if (builder instanceof Parent) {
Parent parentBuilder = (Parent) builder;
for (Node child : parentBuilder.getChildrenUnmodifiable()) {
if (child instanceof Labeled) {
Labeled labeled = (Labeled) child;
if (!labeled.getText().isEmpty()) {
prefsTabLabelMap.put(prefsTab, labeled);
}
}
}
}
}
return prefsTabLabelMap;
}

protected ListProperty<PrefsTab> filteredPreferenceTabsProperty() {
return filteredPreferenceTabs;
}

}

0 comments on commit ef5710c

Please sign in to comment.