diff --git a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java index 8e0b5ff9592..96ced8e239b 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java @@ -14,6 +14,7 @@ import org.jabref.gui.groups.GroupViewMode; import org.jabref.gui.groups.GroupsPreferences; +import org.jabref.gui.search.SearchDisplayMode; import org.jabref.gui.search.SearchRank; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.BindingsHelper; @@ -26,8 +27,8 @@ import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.search.matchers.MatcherSet; import org.jabref.model.search.matchers.MatcherSets; -import org.jabref.model.search.rules.SearchRules; import org.jabref.preferences.PreferencesService; +import org.jabref.preferences.SearchPreferences; import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.Subscription; @@ -38,10 +39,12 @@ public class MainTableDataModel { private final SortedList<BibEntryTableViewModel> entriesFilteredAndSorted; private final ObjectProperty<MainTableFieldValueFormatter> fieldValueFormatter = new SimpleObjectProperty<>(); private final GroupsPreferences groupsPreferences; + private final SearchPreferences searchPreferences; private final NameDisplayPreferences nameDisplayPreferences; private final BibDatabaseContext bibDatabaseContext; private final TaskExecutor taskExecutor; private final Subscription searchQuerySubscription; + private final Subscription searchDisplayModeSubscription; private final Subscription selectedGroupsSubscription; private final Subscription groupViewModeSubscription; private Optional<MatcherSet> groupsMatcher; @@ -53,6 +56,7 @@ public MainTableDataModel(BibDatabaseContext context, OptionalObjectProperty<SearchQuery> searchQueryProperty, IntegerProperty resultSizeProperty) { this.groupsPreferences = preferencesService.getGroupsPreferences(); + this.searchPreferences = preferencesService.getSearchPreferences(); this.nameDisplayPreferences = preferencesService.getNameDisplayPreferences(); this.taskExecutor = taskExecutor; this.bibDatabaseContext = context; @@ -69,9 +73,8 @@ public MainTableDataModel(BibDatabaseContext context, if (change.wasAdded() || change.wasUpdated()) { BackgroundTask.wrap(() -> { for (BibEntryTableViewModel entry : change.getList().subList(change.getFrom(), change.getTo())) { - updateSearchVisibility(searchQueryProperty.get(), entry); - updateGroupVisibility(groupsMatcher, entry); - entry.updateSearchRank(); + updateEntrySearchMatch(searchQueryProperty.get(), entry, searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT); + updateEntryGroupMatch(entry, groupsMatcher, groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT), !groupsPreferences.getGroupViewMode().contains(GroupViewMode.FILTER)); } }).onSuccess(result -> entriesFiltered.refilter(change.getFrom(), change.getTo())).executeWith(taskExecutor); } @@ -79,6 +82,7 @@ public MainTableDataModel(BibDatabaseContext context, }); searchQuerySubscription = EasyBind.listen(searchQueryProperty, (observable, oldValue, newValue) -> updateSearchMatches(newValue)); + searchDisplayModeSubscription = EasyBind.listen(searchPreferences.searchDisplayModeProperty(), (observable, oldValue, newValue) -> updateSearchDisplayMode(newValue)); selectedGroupsSubscription = EasyBind.listen(selectedGroupsProperty, (observable, oldValue, newValue) -> updateGroupMatches(newValue)); groupViewModeSubscription = EasyBind.listen(preferencesService.getGroupsPreferences().groupViewModeProperty(), observable -> updateGroupMatches(selectedGroupsProperty.get())); @@ -87,49 +91,53 @@ public MainTableDataModel(BibDatabaseContext context, entriesFilteredAndSorted = new SortedList<>(entriesFiltered); } - public void unbind() { - searchQuerySubscription.unsubscribe(); - selectedGroupsSubscription.unsubscribe(); - groupViewModeSubscription.unsubscribe(); - } - private void updateSearchMatches(Optional<SearchQuery> query) { BackgroundTask.wrap(() -> { - entriesViewModel.forEach(entry -> { - updateSearchVisibility(query, entry); - entry.updateSearchRank(); - }); + boolean isFloatingMode = searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT; + entriesViewModel.forEach(entry -> updateEntrySearchMatch(query, entry, isFloatingMode)); }).onSuccess(result -> entriesFiltered.refilter()).executeWith(taskExecutor); } - private void updateGroupMatches(ObservableList<GroupTreeNode> groups) { - BackgroundTask.wrap(() -> { - groupsMatcher = createGroupMatcher(groups, groupsPreferences); - entriesViewModel.forEach(entry -> { - updateGroupVisibility(groupsMatcher, entry); - entry.updateSearchRank(); - }); - }).onSuccess(result -> entriesFiltered.refilter()).executeWith(taskExecutor); + private static void updateEntrySearchMatch(Optional<SearchQuery> query, BibEntryTableViewModel entry, boolean isFloatingMode) { + boolean isMatched = query.map(matcher -> matcher.isMatch(entry.getEntry())).orElse(true); + entry.isMatchedBySearch().set(isMatched); + entry.updateSearchRank(); + setEntrySearchVisibility(entry, isMatched, isFloatingMode); } - private void updateSearchVisibility(Optional<SearchQuery> query, BibEntryTableViewModel entry) { - if (query.map(matcher -> matcher.isMatch(entry.getEntry())).orElse(true)) { - entry.isMatchedBySearch().set(true); + private static void setEntrySearchVisibility(BibEntryTableViewModel entry, boolean isMatched, boolean isFloatingMode) { + if (isMatched) { entry.isVisibleBySearch().set(true); } else { - entry.isMatchedBySearch().set(false); - entry.isVisibleBySearch().set(!query.get().getSearchFlags().contains(SearchRules.SearchFlags.FILTERING_SEARCH)); + entry.isVisibleBySearch().set(isFloatingMode); } } - private void updateGroupVisibility(Optional<MatcherSet> groupsMatcher, BibEntryTableViewModel entry) { - if (groupsMatcher.map(matcher -> matcher.isMatch(entry.getEntry()) ^ groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT)) - .orElse(true)) { - entry.isMatchedByGroup().set(true); + private void updateSearchDisplayMode(SearchDisplayMode mode) { + BackgroundTask.wrap(() -> { + boolean isFloatingMode = mode == SearchDisplayMode.FLOAT; + entriesViewModel.forEach(entry -> setEntrySearchVisibility(entry, entry.isMatchedBySearch().get(), isFloatingMode)); + }).onSuccess(result -> entriesFiltered.refilter()).executeWith(taskExecutor); + } + + private void updateGroupMatches(ObservableList<GroupTreeNode> groups) { + BackgroundTask.wrap(() -> { + groupsMatcher = createGroupMatcher(groups, groupsPreferences); + boolean isInvertMode = groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT); + boolean isFloatingMode = !groupsPreferences.getGroupViewMode().contains(GroupViewMode.FILTER); + entriesViewModel.forEach(entry -> updateEntryGroupMatch(entry, groupsMatcher, isInvertMode, isFloatingMode)); + }).onSuccess(result -> entriesFiltered.refilter()).executeWith(taskExecutor); + } + + private void updateEntryGroupMatch(BibEntryTableViewModel entry, Optional<MatcherSet> groupsMatcher, boolean isInvertMode, boolean isFloatingMode) { + boolean isMatched = groupsMatcher.map(matcher -> matcher.isMatch(entry.getEntry()) ^ isInvertMode) + .orElse(true); + entry.isMatchedByGroup().set(isMatched); + entry.updateSearchRank(); + if (isMatched) { entry.isVisibleByGroup().set(true); } else { - entry.isMatchedByGroup().set(false); - entry.isVisibleByGroup().set(!groupsPreferences.groupViewModeProperty().contains(GroupViewMode.FILTER)); + entry.isVisibleByGroup().set(isFloatingMode); } } @@ -150,6 +158,13 @@ private static Optional<MatcherSet> createGroupMatcher(List<GroupTreeNode> selec return Optional.of(searchRules); } + public void unbind() { + searchQuerySubscription.unsubscribe(); + searchDisplayModeSubscription.unsubscribe(); + selectedGroupsSubscription.unsubscribe(); + groupViewModeSubscription.unsubscribe(); + } + public SortedList<BibEntryTableViewModel> getEntriesFilteredAndSorted() { return entriesFilteredAndSorted; } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java b/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java index fa5aa79e00f..f4886a526ff 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java @@ -67,7 +67,7 @@ private void constructItems() { // Populate the menu with currently used fields for (TableColumn<BibEntryTableViewModel, ?> column : mainTable.getColumns()) { - if (((MainTableColumn<?>) column).getModel().getType().equals(MainTableColumnModel.Type.SEARCH_RANK)) { + if (((MainTableColumn<?>) column).getModel().getType() == MainTableColumnModel.Type.SEARCH_RANK) { continue; } // Append only if the column has not already been added (a common column) diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index 5da2fc65ccf..d1313aad0db 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -267,13 +267,10 @@ private void initSearchModifierButtons() { updateSearchQuery(); }); - filterModeButton.setSelected(searchPreferences.isFilteringMode()); + filterModeButton.setSelected(searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FILTER); filterModeButton.setTooltip(new Tooltip(Localization.lang("Filter search results"))); initSearchModifierButton(filterModeButton); - filterModeButton.setOnAction(event -> { - searchPreferences.setSearchFlag(SearchRules.SearchFlags.FILTERING_SEARCH, filterModeButton.isSelected()); - updateSearchQuery(); - }); + filterModeButton.setOnAction(event -> searchPreferences.setSearchDisplayMode(filterModeButton.isSelected() ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT)); openGlobalSearchButton.disableProperty().bindBidirectional(globalSearchActive); openGlobalSearchButton.setTooltip(new Tooltip(Localization.lang("Search across libraries in a new window"))); @@ -284,8 +281,6 @@ private void initSearchModifierButtons() { regularExpressionButton.setSelected(searchPreferences.isRegularExpression()); caseSensitiveButton.setSelected(searchPreferences.isCaseSensitive()); fulltextButton.setSelected(searchPreferences.isFulltext()); - keepSearchString.setSelected(searchPreferences.shouldKeepSearchString()); - filterModeButton.setSelected(searchPreferences.isFilteringMode()); }); } diff --git a/src/main/java/org/jabref/gui/search/SearchDisplayMode.java b/src/main/java/org/jabref/gui/search/SearchDisplayMode.java new file mode 100644 index 00000000000..f0029758c99 --- /dev/null +++ b/src/main/java/org/jabref/gui/search/SearchDisplayMode.java @@ -0,0 +1,6 @@ +package org.jabref.gui.search; + +public enum SearchDisplayMode { + FLOAT, + FILTER +} diff --git a/src/main/java/org/jabref/gui/search/SearchRank.java b/src/main/java/org/jabref/gui/search/SearchRank.java index 3ad56e61856..b173d57b0ec 100644 --- a/src/main/java/org/jabref/gui/search/SearchRank.java +++ b/src/main/java/org/jabref/gui/search/SearchRank.java @@ -4,5 +4,5 @@ public enum SearchRank { MATCHING_SEARCH_AND_GROUPS, MATCHING_SEARCH_NOT_GROUPS, MATCHING_GROUPS_NOT_SEARCH, - NOT_MATCHING_SEARCH_AND_GROUPS; + NOT_MATCHING_SEARCH_AND_GROUPS } diff --git a/src/main/java/org/jabref/model/search/rules/SearchRules.java b/src/main/java/org/jabref/model/search/rules/SearchRules.java index 69ce076cc58..ba28e66b133 100644 --- a/src/main/java/org/jabref/model/search/rules/SearchRules.java +++ b/src/main/java/org/jabref/model/search/rules/SearchRules.java @@ -44,6 +44,6 @@ static SearchRule getSearchRule(EnumSet<SearchFlags> searchFlags) { } public enum SearchFlags { - CASE_SENSITIVE, REGULAR_EXPRESSION, FULLTEXT, KEEP_SEARCH_STRING, FILTERING_SEARCH + CASE_SENSITIVE, REGULAR_EXPRESSION, FULLTEXT, KEEP_SEARCH_STRING } } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 6660738817f..97c541eecef 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -54,6 +54,7 @@ import org.jabref.gui.maintable.NameDisplayPreferences.DisplayStyle; import org.jabref.gui.mergeentries.DiffMode; import org.jabref.gui.push.PushToApplications; +import org.jabref.gui.search.SearchDisplayMode; import org.jabref.gui.sidepane.SidePaneType; import org.jabref.gui.specialfields.SpecialFieldsPreferences; import org.jabref.gui.theme.Theme; @@ -285,7 +286,7 @@ public class JabRefPreferences implements PreferencesService { public static final String FILE_BROWSER_COMMAND = "fileBrowserCommand"; public static final String MAIN_FILE_DIRECTORY = "fileDirectory"; - public static final String SEARCH_FILTERING_MODE = "filteringSearch"; + public static final String SEARCH_DISPLAY_MODE = "searchDisplayMode"; public static final String SEARCH_CASE_SENSITIVE = "caseSensitiveSearch"; public static final String SEARCH_REG_EXP = "regExpSearch"; public static final String SEARCH_FULLTEXT = "fulltextSearch"; @@ -541,7 +542,7 @@ private JabRefPreferences() { Localization.setLanguage(getLanguage()); defaults.put(SEARCH_CASE_SENSITIVE, Boolean.FALSE); - defaults.put(SEARCH_FILTERING_MODE, Boolean.TRUE); + defaults.put(SEARCH_DISPLAY_MODE, Boolean.TRUE); defaults.put(SEARCH_REG_EXP, Boolean.FALSE); defaults.put(SEARCH_FULLTEXT, Boolean.FALSE); defaults.put(SEARCH_KEEP_SEARCH_STRING, Boolean.FALSE); @@ -2731,11 +2732,11 @@ public SearchPreferences getSearchPreferences() { } searchPreferences = new SearchPreferences( + getBoolean(SEARCH_DISPLAY_MODE) ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT, getBoolean(SEARCH_CASE_SENSITIVE), getBoolean(SEARCH_REG_EXP), getBoolean(SEARCH_FULLTEXT), getBoolean(SEARCH_KEEP_SEARCH_STRING), - getBoolean(SEARCH_FILTERING_MODE), getBoolean(SEARCH_KEEP_GLOBAL_WINDOW_ON_TOP), getDouble(SEARCH_WINDOW_HEIGHT), getDouble(SEARCH_WINDOW_WIDTH), @@ -2746,8 +2747,8 @@ public SearchPreferences getSearchPreferences() { putBoolean(SEARCH_REG_EXP, searchPreferences.getObservableSearchFlags().contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)); putBoolean(SEARCH_FULLTEXT, searchPreferences.getObservableSearchFlags().contains(SearchRules.SearchFlags.FULLTEXT)); putBoolean(SEARCH_KEEP_SEARCH_STRING, searchPreferences.getObservableSearchFlags().contains(SearchRules.SearchFlags.KEEP_SEARCH_STRING)); - putBoolean(SEARCH_FILTERING_MODE, searchPreferences.getObservableSearchFlags().contains(SearchRules.SearchFlags.FILTERING_SEARCH)); }); + EasyBind.listen(searchPreferences.searchDisplayModeProperty(), (obs, oldValue, newValue) -> putBoolean(SEARCH_DISPLAY_MODE, newValue == SearchDisplayMode.FILTER)); EasyBind.listen(searchPreferences.keepWindowOnTopProperty(), (obs, oldValue, newValue) -> putBoolean(SEARCH_KEEP_GLOBAL_WINDOW_ON_TOP, searchPreferences.shouldKeepWindowOnTop())); EasyBind.listen(searchPreferences.getSearchWindowHeightProperty(), (obs, oldValue, newValue) -> putDouble(SEARCH_WINDOW_HEIGHT, searchPreferences.getSearchWindowHeight())); EasyBind.listen(searchPreferences.getSearchWindowWidthProperty(), (obs, oldValue, newValue) -> putDouble(SEARCH_WINDOW_WIDTH, searchPreferences.getSearchWindowWidth())); diff --git a/src/main/java/org/jabref/preferences/SearchPreferences.java b/src/main/java/org/jabref/preferences/SearchPreferences.java index 886f14767e9..c372bba96e3 100644 --- a/src/main/java/org/jabref/preferences/SearchPreferences.java +++ b/src/main/java/org/jabref/preferences/SearchPreferences.java @@ -4,11 +4,14 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableSet; +import org.jabref.gui.search.SearchDisplayMode; import org.jabref.model.search.rules.SearchRules.SearchFlags; import com.google.common.annotations.VisibleForTesting; @@ -20,8 +23,9 @@ public class SearchPreferences { private final DoubleProperty searchWindowHeight; private final DoubleProperty searchWindowWidth; private final DoubleProperty searchWindowDividerPosition; + private final ObjectProperty<SearchDisplayMode> searchDisplayMode; - public SearchPreferences(boolean isCaseSensitive, boolean isRegularExpression, boolean isFulltext, boolean isKeepSearchString, boolean isFilteringMode, boolean keepWindowOnTop, double searchWindowHeight, double searchWindowWidth, double searchWindowDividerPosition) { + public SearchPreferences(SearchDisplayMode searchDisplayMode, boolean isCaseSensitive, boolean isRegularExpression, boolean isFulltext, boolean isKeepSearchString, boolean keepWindowOnTop, double searchWindowHeight, double searchWindowWidth, double searchWindowDividerPosition) { this.keepWindowOnTop = new SimpleBooleanProperty(keepWindowOnTop); this.searchWindowHeight = new SimpleDoubleProperty(searchWindowHeight); this.searchWindowWidth = new SimpleDoubleProperty(searchWindowWidth); @@ -40,21 +44,20 @@ public SearchPreferences(boolean isCaseSensitive, boolean isRegularExpression, b if (isKeepSearchString) { searchFlags.add(SearchFlags.KEEP_SEARCH_STRING); } - if (isFilteringMode) { - searchFlags.add(SearchFlags.FILTERING_SEARCH); - } + this.searchDisplayMode = new SimpleObjectProperty<>(searchDisplayMode); this.setSearchWindowHeight(searchWindowHeight); this.setSearchWindowWidth(searchWindowWidth); } @VisibleForTesting - public SearchPreferences(EnumSet<SearchFlags> searchFlags, boolean keepWindowOnTop) { + public SearchPreferences(SearchDisplayMode searchDisplayMode, EnumSet<SearchFlags> searchFlags, boolean keepWindowOnTop) { this.searchFlags = FXCollections.observableSet(searchFlags); this.keepWindowOnTop = new SimpleBooleanProperty(keepWindowOnTop); this.searchWindowHeight = new SimpleDoubleProperty(0); this.searchWindowWidth = new SimpleDoubleProperty(0); this.searchWindowDividerPosition = new SimpleDoubleProperty(0); + this.searchDisplayMode = new SimpleObjectProperty<>(searchDisplayMode); } public EnumSet<SearchFlags> getSearchFlags() { @@ -93,10 +96,6 @@ public boolean shouldKeepSearchString() { return searchFlags.contains(SearchFlags.KEEP_SEARCH_STRING); } - public boolean isFilteringMode() { - return searchFlags.contains(SearchFlags.FILTERING_SEARCH); - } - public boolean shouldKeepWindowOnTop() { return keepWindowOnTop.get(); } @@ -129,10 +128,18 @@ public DoubleProperty getSearchWindowWidthProperty() { return this.searchWindowWidth; } + public SearchDisplayMode getSearchDisplayMode() { + return searchDisplayMode.get(); + } + public DoubleProperty getSearchWindowDividerPositionProperty() { return this.searchWindowDividerPosition; } + public ObjectProperty<SearchDisplayMode> searchDisplayModeProperty() { + return this.searchDisplayMode; + } + public void setSearchWindowHeight(double height) { this.searchWindowHeight.set(height); } @@ -144,4 +151,8 @@ public void setSearchWindowWidth(double width) { public void setSearchWindowDividerPosition(double position) { this.searchWindowDividerPosition.set(position); } + + public void setSearchDisplayMode(SearchDisplayMode searchDisplayMode) { + this.searchDisplayMode.set(searchDisplayMode); + } } diff --git a/src/test/java/org/jabref/cli/ArgumentProcessorTest.java b/src/test/java/org/jabref/cli/ArgumentProcessorTest.java index a7e8028f6b6..83ff4144d5f 100644 --- a/src/test/java/org/jabref/cli/ArgumentProcessorTest.java +++ b/src/test/java/org/jabref/cli/ArgumentProcessorTest.java @@ -9,6 +9,7 @@ import javafx.collections.FXCollections; import org.jabref.cli.ArgumentProcessor.Mode; +import org.jabref.gui.search.SearchDisplayMode; import org.jabref.logic.bibtex.BibEntryAssert; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; @@ -49,7 +50,7 @@ void setup() { when(preferencesService.getImporterPreferences()).thenReturn(importerPreferences); when(preferencesService.getImportFormatPreferences()).thenReturn(importFormatPreferences); when(preferencesService.getSearchPreferences()).thenReturn( - new SearchPreferences(EnumSet.noneOf(SearchRules.SearchFlags.class), false) + new SearchPreferences(SearchDisplayMode.FILTER, EnumSet.noneOf(SearchRules.SearchFlags.class), false) ); }