Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a separate search bar in the global search window #11032

Merged
merged 5 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading