Skip to content

Commit

Permalink
Search floating mode (JabRef#11510)
Browse files Browse the repository at this point in the history
* Search/Groups floating mode

* Hide search rank column

* Update query listener from global to library-specific

* Update selected groups listener from global to library-specific

* Move table-row CSS classes to Base.css

* Adapt tests

* Update JabRef_en.properties

* CHANGELOG

* Fix jumpToSearchKey

* Add shortcut to scroll to the next/prev rank

Left, right arrows

* Localization

* Fix scroll shortcut to handle rank gaps

* OpenRewrite

* Add temporary MappedBackedList implementation

* Use constants for rank values

* Update rank colors

* Add CustomFilteredList

* Improve group switching and search performance

- Update matches in the background
- Prevent unnecessary search query rechecks when switching groups

* OpenRewrite

* Fix NPE

* Fix NPE

* Create a list of observables

* refilter the list after updateVisibility

* Add onUpdateCallback to the CustomFilteredList

* iterate over updated range

* Rename onUpdateCallback to onUpdate

* Register events to the row

* Update matches in the background

* Delete MappedBackedList.java

* Update matches in the background for global search

* Pass properties to MainTableDataModel instead of LibraryTab

* Remove search rank column from the preferences

* Add SearchRank enum

* EnumSet constructor

* Remove int value from SearchRank enum

* Move SearchRank enum to search package

* Remove FILTERING_SEARCH from search flags

* Remove KEEP_SEARCH_STRING from search flags

* Fix SearchPreferences constructor

* Move comment up

* Rename SearchRank to MatchCategory

* Update src/main/java/org/jabref/gui/util/CustomFilteredList.java

Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>

* Update src/main/java/org/jabref/gui/util/CustomFilteredList.java

Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>

* Update CustomFilteredList.java

* Update PreferencesMigrations.java

* Replace CustomFilteredList.java with reflection

Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>

* Minor stylistic fixes

* Fix reflection

Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>

* Correct typo

* Fix typo

* Fix unit tests

* CHANGELOG

---------

Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com>
Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>
  • Loading branch information
3 people committed Aug 4, 2024
1 parent 79c1998 commit db9f83c
Show file tree
Hide file tree
Showing 49 changed files with 773 additions and 407 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/deployment-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ jobs:
--java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \
--java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \
--java-options --add-modules=jdk.incubator.vector
- name: Build pkg (macOS)
if: (steps.checksecrets.outputs.secretspresent == 'YES')
Expand Down Expand Up @@ -179,6 +181,8 @@ jobs:
--java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \
--java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \
--java-options --add-modules=jdk.incubator.vector
- name: Rename files with arm64 suffix as well
if: (steps.checksecrets.outputs.secretspresent == 'YES')
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ jobs:
--java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \
--java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \
--java-options --add-modules=jdk.incubator.vector
- name: Build pkg (macOS)
if: (matrix.os == 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES')
Expand Down Expand Up @@ -192,6 +194,8 @@ jobs:
--java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \
--java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections=org.jabref \
--java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \
--java-options --add-modules=jdk.incubator.vector
- name: Build runtime image and installer (linux, Windows)
if: (matrix.os != 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES')
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- The dialog for [adding an entry using reference text](https://docs.jabref.org/collect/newentryfromplaintext) is now filled with the clipboard contents as default. [#11565](https://github.com/JabRef/jabref/pull/11565)
- Added minimal support for [biblatex data annotation](https://mirrors.ctan.org/macros/latex/contrib/biblatex/doc/biblatex.pdf#subsection.3.7) fields in `.layout` files. [#11505](https://github.com/JabRef/jabref/issues/11505)
- Added saving of selected options in the [Lookup -> Search for unlinked local files dialog](https://docs.jabref.org/collect/findunlinkedfiles#link-the-pdfs-to-your-bib-library). [#11439](https://github.com/JabRef/jabref/issues/11439)
- We added a toggle button to invert the selected groups. [#9073](https://github.com/JabRef/jabref/issues/9073)
- We reintroduced the floating search in the main table. [#4237](https://github.com/JabRef/jabref/issues/4237)
- We fixed an issue where the selection of an entry in the table lost after searching for a group. [#3176](https://github.com/JabRef/jabref/issues/3176)

### Changed

Expand Down
10 changes: 8 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ application {
'--add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref',
'--add-opens=javafx.graphics/javafx.scene=org.jabref',
'--add-opens=javafx.controls/javafx.scene.control=org.jabref',
'--add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref'
'--add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref',

'--add-opens=javafx.base/javafx.collections=org.jabref',
'--add-opens=javafx.base/javafx.collections.transformation=org.jabref'
]
}

Expand Down Expand Up @@ -495,7 +498,10 @@ run {
'org.controlsfx.controls/org.controlsfx.control.textfield' : 'org.jabref',

'javafx.controls/javafx.scene.control.skin' : 'org.controlsfx.controls',
'javafx.graphics/javafx.scene' : 'org.controlsfx.controls'
'javafx.graphics/javafx.scene' : 'org.controlsfx.controls',

'javafx.base/javafx.collections' : 'org.jabref',
'javafx.base/javafx.collections.transformation' : 'org.jabref'
]

addModules = [
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/jabref/gui/Base.css
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,22 @@ TextFlow > .tooltip-text-monospaced {
-fx-padding: 0 .5 0 .5;
}

.table-row-cell:matching-search-and-groups {
-fx-background-color: white;
}

.table-row-cell:matching-search-not-groups {
-fx-background-color: rgba(180, 180, 180, 0.86);
}

.table-row-cell:matching-groups-not-search {
-fx-background-color: rgba(140, 140, 140, 0.86);
}

.table-row-cell:not-matching-search-and-groups {
-fx-opacity: 60%;
}

.table-row-cell:hover,
.tree-table-row-cell:hover {
-fx-background-color: -jr-hover;
Expand Down
55 changes: 33 additions & 22 deletions src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
import javafx.animation.PauseTransition;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.ListChangeListener;
import javafx.event.Event;
Expand Down Expand Up @@ -58,6 +62,7 @@
import org.jabref.gui.undo.UndoableInsertEntries;
import org.jabref.gui.undo.UndoableRemoveEntries;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.OptionalObjectProperty;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.gui.util.UiTaskExecutor;
import org.jabref.logic.citationstyle.CitationStyleCache;
Expand Down Expand Up @@ -88,6 +93,7 @@
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.util.DirectoryMonitor;
import org.jabref.model.util.DirectoryMonitorManager;
import org.jabref.model.util.FileUpdateMonitor;
Expand Down Expand Up @@ -149,8 +155,9 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR }
@SuppressWarnings({"FieldCanBeLocal"})
private Subscription dividerPositionSubscription;

// the query the user searches when this BasePanel is active
private Optional<SearchQuery> currentSearchQuery = Optional.empty();
private ListProperty<GroupTreeNode> selectedGroupsProperty;
private final OptionalObjectProperty<SearchQuery> searchQueryProperty = OptionalObjectProperty.empty();
private final IntegerProperty resultSize = new SimpleIntegerProperty(0);

private Optional<DatabaseChangeMonitor> changeMonitor = Optional.empty();

Expand All @@ -162,15 +169,15 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR }
private final DirectoryMonitorManager directoryMonitorManager;

private LibraryTab(BibDatabaseContext bibDatabaseContext,
LibraryTabContainer tabContainer,
DialogService dialogService,
PreferencesService preferencesService,
StateManager stateManager,
FileUpdateMonitor fileUpdateMonitor,
BibEntryTypesManager entryTypesManager,
CountingUndoManager undoManager,
ClipBoardManager clipBoardManager,
TaskExecutor taskExecutor) {
LibraryTabContainer tabContainer,
DialogService dialogService,
PreferencesService preferencesService,
StateManager stateManager,
FileUpdateMonitor fileUpdateMonitor,
BibEntryTypesManager entryTypesManager,
CountingUndoManager undoManager,
ClipBoardManager clipBoardManager,
TaskExecutor taskExecutor) {
this.tabContainer = Objects.requireNonNull(tabContainer);
this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext);
this.undoManager = undoManager;
Expand All @@ -187,7 +194,8 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext,
bibDatabaseContext.getDatabase().registerListener(this);
bibDatabaseContext.getMetaData().registerListener(this);

this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, stateManager);
this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext));
this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, taskExecutor, selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty());

citationStyleCache = new CitationStyleCache(bibDatabaseContext);
annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences());
Expand Down Expand Up @@ -318,7 +326,10 @@ private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) {
bibDatabaseContext.getDatabase().registerListener(this);
bibDatabaseContext.getMetaData().registerListener(this);

this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, stateManager);
this.tableModel.unbind();
this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext));
this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, taskExecutor, selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty());

citationStyleCache = new CitationStyleCache(bibDatabaseContext);
annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences());

Expand Down Expand Up @@ -875,6 +886,9 @@ private void onClosed(Event event) {
LOGGER.error("Problem when shutting down backup manager", e);
}

if (tableModel != null) {
tableModel.unbind();
}
// clean up the groups map
stateManager.clearSelectedGroups(bibDatabaseContext);
}
Expand Down Expand Up @@ -916,19 +930,16 @@ public MainTable getMainTable() {
return mainTable;
}

public Optional<SearchQuery> getCurrentSearchQuery() {
return currentSearchQuery;
public ListProperty<GroupTreeNode> selectedGroupsProperty() {
return selectedGroupsProperty;
}

/**
* Set the query the user currently searches while this basepanel is active
*/
public void setCurrentSearchQuery(Optional<SearchQuery> currentSearchQuery) {
this.currentSearchQuery = currentSearchQuery;
public OptionalObjectProperty<SearchQuery> searchQueryProperty() {
return searchQueryProperty;
}

public CitationStyleCache getCitationStyleCache() {
return citationStyleCache;
public IntegerProperty resultSizeProperty() {
return resultSize;
}

public FileAnnotationCache getAnnotationCache() {
Expand Down
72 changes: 11 additions & 61 deletions src/main/java/org/jabref/gui/StateManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
import java.util.Optional;

import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
Expand All @@ -22,6 +19,7 @@
import javafx.util.Pair;

import org.jabref.gui.edit.automaticfiededitor.LastAutomaticFieldEditorEdit;
import org.jabref.gui.search.SearchType;
import org.jabref.gui.sidepane.SidePaneType;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.CustomLocalDragboard;
Expand Down Expand Up @@ -56,29 +54,22 @@ public class StateManager {
private final ObservableList<BibDatabaseContext> openDatabases = FXCollections.observableArrayList();
private final OptionalObjectProperty<BibDatabaseContext> activeDatabase = OptionalObjectProperty.empty();
private final OptionalObjectProperty<LibraryTab> activeTab = OptionalObjectProperty.empty();
private final ReadOnlyListWrapper<GroupTreeNode> activeGroups = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
private final ObservableList<BibEntry> selectedEntries = FXCollections.observableArrayList();
private final ObservableMap<String, ObservableList<GroupTreeNode>> selectedGroups = FXCollections.observableHashMap();
private final OptionalObjectProperty<SearchQuery> activeSearchQuery = OptionalObjectProperty.empty();
private final OptionalObjectProperty<SearchQuery> activeGlobalSearchQuery = OptionalObjectProperty.empty();
private final IntegerProperty searchResultSize = new SimpleIntegerProperty(0);
private final IntegerProperty globalSearchResultSize = new SimpleIntegerProperty(0);
private final ObservableMap<String, 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()});
private final EasyBinding<Boolean> anyTaskRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.map(Pair::getValue).anyMatch(Task::isRunning));
private final EasyBinding<Boolean> anyTasksThatWillNotBeRecoveredRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.anyMatch(task -> !task.getKey().willBeRecoveredAutomatically() && task.getValue().isRunning()));
private final EasyBinding<Double> tasksProgress = EasyBind.reduce(backgroundTasks, tasks -> tasks.map(Pair::getValue).filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1));
private final ObservableMap<String, DialogWindowState> dialogWindowStates = FXCollections.observableHashMap();
private final ObservableList<SidePaneType> visibleSidePanes = FXCollections.observableArrayList();

private final ObjectProperty<LastAutomaticFieldEditorEdit> lastAutomaticFieldEditorEdit = new SimpleObjectProperty<>();

private final ObservableList<String> searchHistory = FXCollections.observableArrayList();

public StateManager() {
activeGroups.bind(Bindings.valueAt(selectedGroups, activeDatabase.orElseOpt(null).map(BibDatabaseContext::getUid)));
}

public ObservableList<SidePaneType> getVisibleSidePaneComponents() {
return visibleSidePanes;
}
Expand All @@ -99,36 +90,12 @@ public OptionalObjectProperty<LibraryTab> activeTabProperty() {
return activeTab;
}

public OptionalObjectProperty<SearchQuery> activeSearchQueryProperty() {
return activeSearchQuery;
}

public void setActiveSearchResultSize(BibDatabaseContext database, IntegerProperty resultSize) {
searchResultMap.put(database.getUid(), resultSize);
}

public IntegerProperty getSearchResultSize() {
return searchResultMap.getOrDefault(activeDatabase.getValue().orElse(new BibDatabaseContext()).getUid(), new SimpleIntegerProperty(0));
}

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

public IntegerProperty getGlobalSearchResultSize() {
return globalSearchResultSize;
public OptionalObjectProperty<SearchQuery> activeSearchQuery(SearchType type) {
return type == SearchType.NORMAL_SEARCH ? activeSearchQuery : activeGlobalSearchQuery;
}

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

public ReadOnlyListProperty<GroupTreeNode> activeGroupProperty() {
return activeGroups.getReadOnlyProperty();
public IntegerProperty searchResultSize(SearchType type) {
return type == SearchType.NORMAL_SEARCH ? searchResultSize : globalSearchResultSize;
}

public ObservableList<BibEntry> getSelectedEntries() {
Expand All @@ -139,18 +106,17 @@ public void setSelectedEntries(List<BibEntry> newSelectedEntries) {
selectedEntries.setAll(newSelectedEntries);
}

public void setSelectedGroups(BibDatabaseContext database, List<GroupTreeNode> newSelectedGroups) {
public void setSelectedGroups(BibDatabaseContext context, List<GroupTreeNode> newSelectedGroups) {
Objects.requireNonNull(newSelectedGroups);
selectedGroups.put(database.getUid(), FXCollections.observableArrayList(newSelectedGroups));
selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).setAll(newSelectedGroups);
}

public ObservableList<GroupTreeNode> getSelectedGroups(BibDatabaseContext context) {
ObservableList<GroupTreeNode> selectedGroupsForDatabase = selectedGroups.get(context.getUid());
return selectedGroupsForDatabase != null ? selectedGroupsForDatabase : FXCollections.observableArrayList();
return selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList());
}

public void clearSelectedGroups(BibDatabaseContext database) {
selectedGroups.remove(database.getUid());
public void clearSelectedGroups(BibDatabaseContext context) {
selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).clear();
}

public Optional<BibDatabaseContext> getActiveDatabase() {
Expand All @@ -166,18 +132,6 @@ public void setActiveDatabase(BibDatabaseContext database) {
}
}

public void clearSearchQuery() {
activeSearchQuery.setValue(Optional.empty());
}

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() {
return focusOwner;
}
Expand Down Expand Up @@ -219,10 +173,6 @@ public ObjectProperty<LastAutomaticFieldEditorEdit> lastAutomaticFieldEditorEdit
return lastAutomaticFieldEditorEdit;
}

public LastAutomaticFieldEditorEdit getLastAutomaticFieldEditorEdit() {
return lastAutomaticFieldEditorEditProperty().get();
}

public void setLastAutomaticFieldEditorEdit(LastAutomaticFieldEditorEdit automaticFieldEditorEdit) {
lastAutomaticFieldEditorEditProperty().set(automaticFieldEditorEdit);
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,12 @@ private List<EntryEditorTab> createTabs() {
preferencesService.getImportFormatPreferences(),
fileMonitor,
dialogService,
stateManager,
bibEntryTypesManager,
keyBindingRepository);
keyBindingRepository,
libraryTab.searchQueryProperty());
tabs.add(sourceTab);
tabs.add(new LatexCitationsTab(databaseContext, preferencesService, dialogService, directoryMonitorManager));
tabs.add(new FulltextSearchResultsTab(stateManager, preferencesService, dialogService, taskExecutor));
tabs.add(new FulltextSearchResultsTab(stateManager, preferencesService, dialogService, taskExecutor, libraryTab.searchQueryProperty()));

return tabs;
}
Expand Down
Loading

0 comments on commit db9f83c

Please sign in to comment.