Skip to content

Commit

Permalink
Use a separate search bar in the global search window (JabRef#11032)
Browse files Browse the repository at this point in the history
* Use a separate search bar in the global search window

* Adapt gui test

* remove SearchResultDialogSearchBar class

* Update GlobalSearchBar.java
  • Loading branch information
LoayGhreeb authored Mar 19, 2024
1 parent 9babea4 commit 9ced996
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/main/java/org/jabref/gui/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.jabref.gui.menus.FileHistoryMenu;
import org.jabref.gui.push.PushToApplicationCommand;
import org.jabref.gui.search.GlobalSearchBar;
import org.jabref.gui.search.SearchType;
import org.jabref.gui.sidepane.SidePane;
import org.jabref.gui.sidepane.SidePaneType;
import org.jabref.gui.undo.CountingUndoManager;
Expand Down Expand Up @@ -136,7 +137,7 @@ public JabRefFrame(Stage mainStage,
this.entryTypesManager = Globals.entryTypesManager;
this.taskExecutor = Globals.TASK_EXECUTOR;

this.globalSearchBar = new GlobalSearchBar(this, stateManager, prefs, undoManager, dialogService);
this.globalSearchBar = new GlobalSearchBar(this, stateManager, prefs, undoManager, dialogService, SearchType.NORMAL_SEARCH);
this.pushToApplicationCommand = new PushToApplicationCommand(stateManager, dialogService, prefs, taskExecutor);
this.fileHistory = new FileHistoryMenu(prefs.getGuiPreferences().getFileHistory(), dialogService, getOpenDatabaseAction());

Expand Down
26 changes: 24 additions & 2 deletions src/main/java/org/jabref/gui/StateManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class StateManager {
private final ObservableList<BibEntry> selectedEntries = FXCollections.observableArrayList();
private final ObservableMap<BibDatabaseContext, ObservableList<GroupTreeNode>> selectedGroups = FXCollections.observableHashMap();
private final OptionalObjectProperty<SearchQuery> activeSearchQuery = OptionalObjectProperty.empty();
private final OptionalObjectProperty<SearchQuery> activeGlobalSearchQuery = OptionalObjectProperty.empty();
private final IntegerProperty globalSearchResultSize = new SimpleIntegerProperty(0);
private final ObservableMap<BibDatabaseContext, IntegerProperty> searchResultMap = FXCollections.observableHashMap();
private final OptionalObjectProperty<Node> focusOwner = OptionalObjectProperty.empty();
private final ObservableList<Pair<BackgroundTask<?>, Task<?>>> backgroundTasks = FXCollections.observableArrayList(task -> new Observable[]{task.getValue().progressProperty(), task.getValue().runningProperty()});
Expand Down Expand Up @@ -108,6 +110,22 @@ public IntegerProperty getSearchResultSize() {
return searchResultMap.getOrDefault(activeDatabase.getValue().orElse(new BibDatabaseContext()), new SimpleIntegerProperty(0));
}

public OptionalObjectProperty<SearchQuery> activeGlobalSearchQueryProperty() {
return activeGlobalSearchQuery;
}

public IntegerProperty getGlobalSearchResultSize() {
return globalSearchResultSize;
}

public IntegerProperty getSearchResultSize(OptionalObjectProperty<SearchQuery> searchQueryProperty) {
if (searchQueryProperty.equals(activeSearchQuery)) {
return getSearchResultSize();
} else {
return getGlobalSearchResultSize();
}
}

public ReadOnlyListProperty<GroupTreeNode> activeGroupProperty() {
return activeGroups.getReadOnlyProperty();
}
Expand Down Expand Up @@ -151,8 +169,12 @@ public void clearSearchQuery() {
activeSearchQuery.setValue(Optional.empty());
}

public void setSearchQuery(SearchQuery searchQuery) {
activeSearchQuery.setValue(Optional.of(searchQuery));
public void setSearchQuery(OptionalObjectProperty<SearchQuery> searchQueryProperty, SearchQuery query) {
searchQueryProperty.setValue(Optional.of(query));
}

public void clearSearchQuery(OptionalObjectProperty<SearchQuery> searchQueryProperty) {
searchQueryProperty.setValue(Optional.empty());
}

public OptionalObjectProperty<Node> focusOwnerProperty() {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/jabref/gui/entryeditor/SourceTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ public SourceTab(BibDatabaseContext bibDatabaseContext,
searchHighlightPattern = newValue.flatMap(SearchQuery::getPatternForWords);
highlightSearchPattern();
});

stateManager.activeGlobalSearchQueryProperty().addListener((observable, oldValue, newValue) -> {
searchHighlightPattern = newValue.flatMap(SearchQuery::getPatternForWords);
highlightSearchPattern();
});
}

private void highlightSearchPattern() {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jabref/gui/preview/PreviewViewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public PreviewViewer(BibDatabaseContext database,
}
if (!registered) {
stateManager.activeSearchQueryProperty().addListener(listener);
stateManager.activeGlobalSearchQueryProperty().addListener(listener);
registered = true;
}
highlightSearchPattern();
Expand Down
52 changes: 37 additions & 15 deletions src/main/java/org/jabref/gui/search/GlobalSearchBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@
import org.jabref.gui.keyboard.KeyBinding;
import org.jabref.gui.keyboard.KeyBindingRepository;
import org.jabref.gui.search.rules.describer.SearchDescribers;
import org.jabref.gui.undo.CountingUndoManager;
import org.jabref.gui.util.BindingsHelper;
import org.jabref.gui.util.DefaultTaskExecutor;
import org.jabref.gui.util.IconValidationDecorator;
import org.jabref.gui.util.OptionalObjectProperty;
import org.jabref.gui.util.TooltipTextUtil;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.search.SearchQuery;
Expand Down Expand Up @@ -94,32 +94,41 @@ public class GlobalSearchBar extends HBox {
private final ToggleButton fulltextButton;
private final Button openGlobalSearchButton;
private final ToggleButton keepSearchString;
// private final Button searchModeButton;
private final Tooltip searchFieldTooltip = new Tooltip();
private final Label currentResults = new Label("");

private final StateManager stateManager;
private final PreferencesService preferencesService;
private final Validator regexValidator;
private final UndoManager undoManager;
private final LibraryTabContainer tabContainer;

private final SearchPreferences searchPreferences;
private final DialogService dialogService;

private final BooleanProperty globalSearchActive = new SimpleBooleanProperty(false);
private final OptionalObjectProperty<SearchQuery> searchQueryProperty;
private GlobalSearchResultDialog globalSearchResultDialog;

public GlobalSearchBar(LibraryTabContainer tabContainer,
StateManager stateManager,
PreferencesService preferencesService,
CountingUndoManager undoManager,
DialogService dialogService) {
UndoManager undoManager,
DialogService dialogService,
SearchType searchType) {
super();
this.stateManager = stateManager;
this.preferencesService = preferencesService;
this.searchPreferences = preferencesService.getSearchPreferences();
this.undoManager = undoManager;
this.dialogService = dialogService;
this.tabContainer = tabContainer;

if (searchType == SearchType.NORMAL_SEARCH) {
searchQueryProperty = stateManager.activeSearchQueryProperty();
} else {
searchQueryProperty = stateManager.activeGlobalSearchQueryProperty();
}

searchField.disableProperty().bind(needsDatabase(stateManager).not());

Expand All @@ -138,7 +147,9 @@ public GlobalSearchBar(LibraryTabContainer tabContainer,
if (keyBinding.get() == KeyBinding.CLOSE) {
// Clear search and select first entry, if available
searchField.setText("");
tabContainer.getCurrentLibraryTab().getMainTable().getSelectionModel().selectFirst();
if (searchType == SearchType.NORMAL_SEARCH) {
tabContainer.getCurrentLibraryTab().getMainTable().getSelectionModel().selectFirst();
}
event.consume();
}
}
Expand Down Expand Up @@ -188,7 +199,13 @@ public GlobalSearchBar(LibraryTabContainer tabContainer,
keepSearchString.visibleProperty().unbind();
keepSearchString.visibleProperty().bind(focusedOrActive);

StackPane modifierButtons = new StackPane(new HBox(regularExpressionButton, caseSensitiveButton, fulltextButton, keepSearchString));
StackPane modifierButtons;
if (searchType == SearchType.NORMAL_SEARCH) {
modifierButtons = new StackPane(new HBox(regularExpressionButton, caseSensitiveButton, fulltextButton, keepSearchString));
} else {
modifierButtons = new StackPane(new HBox(regularExpressionButton, caseSensitiveButton, fulltextButton));
}

modifierButtons.setAlignment(Pos.CENTER);
searchField.setRight(new HBox(searchField.getRight(), modifierButtons));
searchField.getStyleClass().add("search-field");
Expand All @@ -203,22 +220,27 @@ public GlobalSearchBar(LibraryTabContainer tabContainer,
visualizer.setDecoration(new IconValidationDecorator(Pos.CENTER_LEFT));
Platform.runLater(() -> visualizer.initVisualization(regexValidator.getValidationStatus(), searchField));

this.getChildren().addAll(searchField, openGlobalSearchButton, currentResults);
if (searchType == SearchType.NORMAL_SEARCH) {
this.getChildren().addAll(searchField, openGlobalSearchButton, currentResults);
} else {
this.getChildren().addAll(searchField, currentResults);
}

this.setSpacing(4.0);
this.setAlignment(Pos.CENTER_LEFT);

Timer searchTask = FxTimer.create(Duration.ofMillis(SEARCH_DELAY), this::updateSearchQuery);
BindingsHelper.bindBidirectional(
stateManager.activeSearchQueryProperty(),
searchQueryProperty,
searchField.textProperty(),
searchTerm -> {
// Async update
searchTask.restart();
},
query -> setSearchTerm(query.map(SearchQuery::getQuery).orElse("")));

this.stateManager.activeSearchQueryProperty().addListener((obs, oldvalue, newValue) -> newValue.ifPresent(this::updateSearchResultsForQuery));
this.stateManager.activeDatabaseProperty().addListener((obs, oldValue, newValue) -> stateManager.activeSearchQueryProperty().get().ifPresent(this::updateSearchResultsForQuery));
this.searchQueryProperty.addListener((obs, oldValue, newValue) -> newValue.ifPresent(this::updateSearchResultsForQuery));
this.searchQueryProperty.addListener((obs, oldValue, newValue) -> searchQueryProperty.get().ifPresent(this::updateSearchResultsForQuery));
/*
* The listener tracks a change on the focus property value.
* This happens, from active (user types a query) to inactive / focus
Expand All @@ -234,7 +256,7 @@ public GlobalSearchBar(LibraryTabContainer tabContainer,
}

private void updateSearchResultsForQuery(SearchQuery query) {
updateResults(this.stateManager.getSearchResultSize().intValue(), SearchDescribers.getSearchDescriberFor(query).getDescription(),
updateResults(this.stateManager.getSearchResultSize(searchQueryProperty).intValue(), SearchDescribers.getSearchDescriberFor(query).getDescription(),
query.isGrammarBasedSearch());
}

Expand Down Expand Up @@ -276,7 +298,7 @@ private void initSearchModifierButtons() {
initSearchModifierButton(openGlobalSearchButton);
openGlobalSearchButton.setOnAction(evt -> {
globalSearchActive.setValue(true);
globalSearchResultDialog = new GlobalSearchResultDialog(undoManager);
globalSearchResultDialog = new GlobalSearchResultDialog(undoManager, tabContainer);
updateSearchQuery();
dialogService.showCustomDialogAndWait(globalSearchResultDialog);
globalSearchActive.setValue(false);
Expand Down Expand Up @@ -312,7 +334,7 @@ public void updateSearchQuery() {
if (searchField.getText().isEmpty()) {
currentResults.setText("");
setSearchFieldHintTooltip(null);
stateManager.clearSearchQuery();
stateManager.clearSearchQuery(searchQueryProperty);
return;
}

Expand All @@ -327,7 +349,7 @@ public void updateSearchQuery() {
informUserAboutInvalidSearchQuery();
return;
}
stateManager.setSearchQuery(searchQuery);
stateManager.setSearchQuery(searchQueryProperty, searchQuery);
}

private boolean validRegex() {
Expand All @@ -343,7 +365,7 @@ private boolean validRegex() {
private void informUserAboutInvalidSearchQuery() {
searchField.pseudoClassStateChanged(CLASS_NO_RESULTS, true);

stateManager.clearSearchQuery();
stateManager.clearSearchQuery(searchQueryProperty);

String illegalSearch = Localization.lang("Search failed: illegal search expression");
currentResults.setText(illegalSearch);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
prefHeight="400.0" prefWidth="600.0">
<content>
<VBox>
<HBox spacing="10" alignment="CENTER_RIGHT">
<HBox fx:id="searchBarContainer" spacing="10" alignment="CENTER_RIGHT">
<ToggleButton fx:id="keepOnTop" styleClass="icon-button,narrow" prefHeight="20.0" prefWidth="20.0">
<graphic>
<JabRefIconView glyph="KEEP_ON_TOP"/>
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
import javafx.fxml.FXML;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

import org.jabref.gui.DialogService;
import org.jabref.gui.LibraryTabContainer;
import org.jabref.gui.StateManager;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.maintable.columns.SpecialFieldColumn;
Expand All @@ -28,9 +31,9 @@ public class GlobalSearchResultDialog extends BaseDialog<Void> {

@FXML private SplitPane container;
@FXML private ToggleButton keepOnTop;

@FXML private HBox searchBarContainer;
private final UndoManager undoManager;

private final LibraryTabContainer libraryTabContainer;
@Inject private PreferencesService preferencesService;
@Inject private StateManager stateManager;
@Inject private DialogService dialogService;
Expand All @@ -39,8 +42,9 @@ public class GlobalSearchResultDialog extends BaseDialog<Void> {

private GlobalSearchResultDialogViewModel viewModel;

public GlobalSearchResultDialog(UndoManager undoManager) {
public GlobalSearchResultDialog(UndoManager undoManager, LibraryTabContainer libraryTabContainer) {
this.undoManager = undoManager;
this.libraryTabContainer = libraryTabContainer;

setTitle(Localization.lang("Search results from open libraries"));
ViewLoader.view(this)
Expand All @@ -53,6 +57,10 @@ public GlobalSearchResultDialog(UndoManager undoManager) {
private void initialize() {
viewModel = new GlobalSearchResultDialogViewModel(preferencesService);

GlobalSearchBar searchBar = new GlobalSearchBar(libraryTabContainer, stateManager, preferencesService, undoManager, dialogService, SearchType.GLOBAL_SEARCH);
searchBarContainer.getChildren().addFirst(searchBar);
HBox.setHgrow(searchBar, Priority.ALWAYS);

PreviewViewer previewViewer = new PreviewViewer(viewModel.getSearchDatabaseContext(), dialogService, preferencesService, stateManager, themeManager, taskExecutor);
previewViewer.setLayout(preferencesService.getPreviewPreferences().getSelectedPreviewLayout());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import java.util.List;
import java.util.Optional;

import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
Expand All @@ -23,29 +25,37 @@

public class SearchResultsTableDataModel {

private final FilteredList<BibEntryTableViewModel> entriesFiltered;
private final SortedList<BibEntryTableViewModel> entriesSorted;
private final ObjectProperty<MainTableFieldValueFormatter> fieldValueFormatter;
private final NameDisplayPreferences nameDisplayPreferences;
private final BibDatabaseContext bibDatabaseContext;
private final StateManager stateManager;

public SearchResultsTableDataModel(BibDatabaseContext bibDatabaseContext, PreferencesService preferencesService, StateManager stateManager) {
this.nameDisplayPreferences = preferencesService.getNameDisplayPreferences();
this.bibDatabaseContext = bibDatabaseContext;
this.stateManager = stateManager;
this.fieldValueFormatter = new SimpleObjectProperty<>(new MainTableFieldValueFormatter(nameDisplayPreferences, bibDatabaseContext));

ObservableList<BibEntryTableViewModel> entriesViewModel = FXCollections.observableArrayList();
populateEntriesViewModel(entriesViewModel);
stateManager.getOpenDatabases().addListener((ListChangeListener<BibDatabaseContext>) change -> populateEntriesViewModel(entriesViewModel));

FilteredList<BibEntryTableViewModel> entriesFiltered = new FilteredList<>(entriesViewModel);
entriesFiltered.predicateProperty().bind(EasyBind.map(stateManager.activeGlobalSearchQueryProperty(), query -> entry -> isMatchedBySearch(query, entry)));
stateManager.getGlobalSearchResultSize().bind(Bindings.size(entriesFiltered));

// We need to wrap the list since otherwise sorting in the table does not work
entriesSorted = new SortedList<>(entriesFiltered);
}

private void populateEntriesViewModel(ObservableList<BibEntryTableViewModel> entriesViewModel) {
entriesViewModel.clear();
for (BibDatabaseContext context : stateManager.getOpenDatabases()) {
ObservableList<BibEntry> entriesForDb = context.getDatabase().getEntries();
List<BibEntryTableViewModel> viewModelForDb = EasyBind.mapBacked(entriesForDb, entry -> new BibEntryTableViewModel(entry, context, fieldValueFormatter));
entriesViewModel.addAll(viewModelForDb);
}

entriesFiltered = new FilteredList<>(entriesViewModel);
entriesFiltered.predicateProperty().bind(EasyBind.map(stateManager.activeSearchQueryProperty(), query -> entry -> isMatchedBySearch(query, entry)));

// We need to wrap the list since otherwise sorting in the table does not work
entriesSorted = new SortedList<>(entriesFiltered);
}

private boolean isMatchedBySearch(Optional<SearchQuery> query, BibEntryTableViewModel entry) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/jabref/gui/search/SearchType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.jabref.gui.search;

public enum SearchType {
NORMAL_SEARCH,
GLOBAL_SEARCH
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public void onStart(Stage stage) {
area.appendText("some example\n text to go here\n across a couple of \n lines....");
StateManager stateManager = mock(StateManager.class);
when(stateManager.activeSearchQueryProperty()).thenReturn(OptionalObjectProperty.empty());
when(stateManager.activeGlobalSearchQueryProperty()).thenReturn(OptionalObjectProperty.empty());
KeyBindingRepository keyBindingRepository = new KeyBindingRepository(Collections.emptyList(), Collections.emptyList());
ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS);
when(importFormatPreferences.bibEntryPreferences().getKeywordSeparator()).thenReturn(',');
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/org/jabref/gui/search/GlobalSearchBarTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public void onStart(Stage stage) {
stateManager,
prefs,
mock(CountingUndoManager.class),
mock(DialogService.class)
mock(DialogService.class),
SearchType.NORMAL_SEARCH
);

hBox = new HBox(searchBar);
Expand Down

0 comments on commit 9ced996

Please sign in to comment.