diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index c0fd90990ce..fc477ba1b31 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -1072,29 +1072,25 @@ TextFlow > .tooltip-text-monospaced { } /* search modifier buttons */ -.mainToolbar .search-field .toggle-button:selected { +.global-search-bar .toggle-button:selected { -fx-background-color: transparent; } -.mainToolbar .search-field .toggle-button:hover, -.mainToolbar .search-field .toggle-button:selected:hover { +.global-search-bar .toggle-button:hover, +.global-search-bar .toggle-button:selected:hover { -fx-background-color: -jr-icon-background-active; } -.mainToolbar .search-field .toggle-button .glyph-icon { - -fx-fill: derive(-jr-search-text, 80%); - -fx-text-fill: derive(-jr-search-text, 80%); +.global-search-bar .toggle-button .glyph-icon { -fx-icon-color: derive(-jr-search-text, 80%); } -.mainToolbar .search-field .toggle-button:selected .glyph-icon { - -fx-fill: -jr-search-text; - -fx-text-fill: -jr-search-text; +.global-search-bar .toggle-button:selected .glyph-icon { -fx-icon-color: -jr-search-text; } /* search text */ -.mainToolbar .search-field .label { +.global-search-bar .label { -fx-padding: 0em 1.8em 0em 0em; } diff --git a/src/main/java/org/jabref/gui/Dark.css b/src/main/java/org/jabref/gui/Dark.css index f621ba9f536..0e426159450 100644 --- a/src/main/java/org/jabref/gui/Dark.css +++ b/src/main/java/org/jabref/gui/Dark.css @@ -143,22 +143,8 @@ -fx-background-color: -jr-background; } -.mainToolbar .search-field .button .glyph-icon { - -fx-fill: derive(-fx-light-text-color, 80%); - -fx-text-fill: derive(-fx-light-text-color, 80%); - -fx-icon-color: derive(-fx-light-text-color, 80%); -} - -.mainToolbar .search-field .toggle-button .glyph-icon { - -fx-fill: -jr-search-text; - -fx-text-fill: -jr-search-text; - -fx-icon-color:-jr-search-text; -} - -.mainToolbar .search-field .toggle-button:selected .glyph-icon { - -fx-fill: derive(-fx-light-text-color, 80%); - -fx-text-fill: derive(-fx-light-text-color, 80%); - -fx-icon-color: derive(-fx-light-text-color, 80%); +.global-search-bar .toggle-button .glyph-icon { + -fx-icon-color: derive(-jr-search-background, 50%); } .notification-bar > .pane { diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index dd9a3722ebb..d8b3a7856f1 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -131,12 +131,14 @@ public SourceTab(BibDatabaseContext bibDatabaseContext, } private void highlightSearchPattern() { - if (searchHighlightPattern.isPresent() && (codeArea != null)) { + if (codeArea != null) { codeArea.setStyleClass(0, codeArea.getLength(), "text"); - Matcher matcher = searchHighlightPattern.get().matcher(codeArea.getText()); - while (matcher.find()) { - for (int i = 0; i <= matcher.groupCount(); i++) { - codeArea.setStyleClass(matcher.start(), matcher.end(), "search"); + if (searchHighlightPattern.isPresent()) { + Matcher matcher = searchHighlightPattern.get().matcher(codeArea.getText()); + while (matcher.find()) { + for (int i = 0; i <= matcher.groupCount(); i++) { + codeArea.setStyleClass(matcher.start(), matcher.end(), "search"); + } } } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java index 82048b914f3..8efd06eb6cf 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java @@ -35,6 +35,8 @@ public EditorContextAction(StandardActions command, TextInputControl textInputCo BooleanBinding hasSelectionBinding = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() > 0, textInputControl.selectionProperty()); BooleanBinding allSelectedBinding = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() == textInputControl.getLength()); BooleanBinding maskTextBinding = (BooleanBinding) BindingsHelper.constantOf(textInputControl instanceof PasswordField); // (maskText("A") != "A"); + BooleanBinding undoableBinding = Bindings.createBooleanBinding(textInputControl::isUndoable, textInputControl.undoableProperty()); + BooleanBinding redoableBinding = Bindings.createBooleanBinding(textInputControl::isRedoable, textInputControl.redoableProperty()); this.executable.bind( switch (command) { @@ -42,6 +44,8 @@ public EditorContextAction(StandardActions command, TextInputControl textInputCo case CUT -> maskTextBinding.not().and(hasSelectionBinding); case PASTE -> editableBinding.and(hasStringInClipboardBinding); case DELETE -> editableBinding.and(hasSelectionBinding); + case UNDO -> undoableBinding; + case REDO -> redoableBinding; case SELECT_ALL -> { if (SHOW_HANDLES) { yield hasTextBinding.and(allSelectedBinding.not()); @@ -61,6 +65,8 @@ public void execute() { case PASTE -> textInputControl.paste(); case DELETE -> textInputControl.deleteText(textInputControl.getSelection()); case SELECT_ALL -> textInputControl.selectAll(); + case UNDO -> textInputControl.undo(); + case REDO -> textInputControl.redo(); } textInputControl.requestFocus(); } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 61aaad6526e..8088c0501e2 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -266,7 +266,10 @@ private void initKeyBindings() { event.consume(); break; case SEARCH: - globalSearchBar.focus(); + globalSearchBar.requestFocus(); + break; + case OPEN_GLOBAL_SEARCH_DIALOG: + globalSearchBar.openGlobalSearchDialog(); break; case NEW_ARTICLE: new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.Article, dialogService, prefs, stateManager).execute(); diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index 857aee790d5..79a812f0b18 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -352,7 +352,7 @@ public enum JabRefIcons implements JabRefIcon { SELECT_ICONS(MaterialDesignA.APPS), KEEP_SEARCH_STRING(MaterialDesignE.EARTH), KEEP_ON_TOP(MaterialDesignP.PIN), - KEEP_ON_TOP_OFF(MaterialDesignP.PIN_OFF_OUTLINE), + KEEP_ON_TOP_OFF(MaterialDesignP.PIN_OFF), OPEN_GLOBAL_SEARCH(MaterialDesignO.OPEN_IN_NEW), REMOVE_TAGS(MaterialDesignC.CLOSE), ACCEPT_LEFT(MaterialDesignS.SUBDIRECTORY_ARROW_LEFT), diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index 31889504685..07ac871d22a 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -103,6 +103,7 @@ public enum KeyBinding { SAVE_DATABASE("Save library", Localization.lang("Save library"), "ctrl+S", KeyBindingCategory.FILE), SAVE_DATABASE_AS("Save library as ...", Localization.lang("Save library as..."), "ctrl+shift+S", KeyBindingCategory.FILE), SEARCH("Search", Localization.lang("Search"), "ctrl+F", KeyBindingCategory.SEARCH), + OPEN_GLOBAL_SEARCH_DIALOG("Open global search window", Localization.lang("Open global search window"), "ctrl+shift+F", KeyBindingCategory.SEARCH), SELECT_ALL("Select all", Localization.lang("Select all"), "ctrl+A", KeyBindingCategory.EDIT), SELECT_FIRST_ENTRY("Select first entry", Localization.lang("Select first entry"), "HOME", KeyBindingCategory.EDIT), SELECT_LAST_ENTRY("Select last entry", Localization.lang("Select last entry"), "END", KeyBindingCategory.EDIT), diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index 5e26e14699a..8838b0b7f7a 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -9,13 +9,12 @@ import javax.swing.undo.UndoManager; -import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; import javafx.css.PseudoClass; import javafx.event.Event; import javafx.geometry.Insets; @@ -55,7 +54,6 @@ import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.search.rules.describer.SearchDescribers; import org.jabref.gui.util.BindingsHelper; -import org.jabref.gui.util.IconValidationDecorator; import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.gui.util.TooltipTextUtil; import org.jabref.gui.util.UiTaskExecutor; @@ -66,10 +64,6 @@ import org.jabref.preferences.PreferencesService; import org.jabref.preferences.SearchPreferences; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.Validator; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import impl.org.controlsfx.skin.AutoCompletePopup; import org.controlsfx.control.textfield.AutoCompletionBinding; import org.controlsfx.control.textfield.CustomTextField; @@ -99,7 +93,6 @@ public class GlobalSearchBar extends HBox { private final StateManager stateManager; private final PreferencesService preferencesService; - private final Validator regexValidator; private final UndoManager undoManager; private final LibraryTabContainer tabContainer; @@ -145,32 +138,22 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, searchField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { - // Clear search and select first entry, if available searchField.clear(); if (searchType == SearchType.NORMAL_SEARCH) { - tabContainer.getCurrentLibraryTab().getMainTable().getSelectionModel().selectFirst(); + tabContainer.getCurrentLibraryTab().getMainTable().requestFocus(); } event.consume(); } }); - searchField.setContextMenu(SearchFieldRightClickMenu.create( - stateManager, - searchField, - tabContainer, - undoManager)); - - ObservableList search = stateManager.getWholeSearchHistory(); - search.addListener((ListChangeListener.Change change) -> - searchField.setContextMenu(SearchFieldRightClickMenu.create( - stateManager, - searchField, - tabContainer, - undoManager)) - ); - ClipBoardManager.addX11Support(searchField); + searchField.setContextMenu(SearchFieldRightClickMenu.create(stateManager, searchField)); + stateManager.getWholeSearchHistory().addListener((ListChangeListener.Change change) -> { + searchField.getContextMenu().getItems().removeLast(); + searchField.getContextMenu().getItems().add(SearchFieldRightClickMenu.createSearchFromHistorySubMenu(stateManager, searchField)); + }); + regularExpressionButton = IconTheme.JabRefIcons.REG_EX.asToggleButton(); caseSensitiveButton = IconTheme.JabRefIcons.CASE_SENSITIVE.asToggleButton(); fulltextButton = IconTheme.JabRefIcons.FULLTEXT.asToggleButton(); @@ -184,8 +167,7 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, .or(caseSensitiveButton.focusedProperty()) .or(fulltextButton.focusedProperty()) .or(keepSearchString.focusedProperty()) - .or(searchField.textProperty() - .isNotEmpty()); + .or(searchField.textProperty().isNotEmpty()); regularExpressionButton.visibleProperty().unbind(); regularExpressionButton.visibleProperty().bind(focusedOrActive); @@ -205,18 +187,10 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, modifierButtons.setAlignment(Pos.CENTER); searchField.setRight(new HBox(searchField.getRight(), modifierButtons)); - searchField.getStyleClass().add("search-field"); + searchField.getStyleClass().add("global-search-bar"); searchField.setMinWidth(100); HBox.setHgrow(searchField, Priority.ALWAYS); - regexValidator = new FunctionBasedValidator<>( - searchField.textProperty(), - query -> !(regularExpressionButton.isSelected() && !validRegex()), - ValidationMessage.error(Localization.lang("Invalid regular expression"))); - ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); - visualizer.setDecoration(new IconValidationDecorator(Pos.CENTER_LEFT)); - Platform.runLater(() -> visualizer.initVisualization(regexValidator.getValidationStatus(), searchField)); - if (searchType == SearchType.NORMAL_SEARCH) { this.getChildren().addAll(searchField, openGlobalSearchButton, currentResults); } else { @@ -261,52 +235,64 @@ private void initSearchModifierButtons() { regularExpressionButton.setSelected(searchPreferences.isRegularExpression()); regularExpressionButton.setTooltip(new Tooltip(Localization.lang("regular expression"))); initSearchModifierButton(regularExpressionButton); - regularExpressionButton.setOnAction(event -> { - searchPreferences.setSearchFlag(SearchRules.SearchFlags.REGULAR_EXPRESSION, regularExpressionButton.isSelected()); + regularExpressionButton.selectedProperty().addListener((obs, oldVal, newVal) -> { + searchPreferences.setSearchFlag(SearchRules.SearchFlags.REGULAR_EXPRESSION, newVal); updateSearchQuery(); }); caseSensitiveButton.setSelected(searchPreferences.isCaseSensitive()); caseSensitiveButton.setTooltip(new Tooltip(Localization.lang("Case sensitive"))); initSearchModifierButton(caseSensitiveButton); - caseSensitiveButton.setOnAction(event -> { - searchPreferences.setSearchFlag(SearchRules.SearchFlags.CASE_SENSITIVE, caseSensitiveButton.isSelected()); + caseSensitiveButton.selectedProperty().addListener((obs, oldVal, newVal) -> { + searchPreferences.setSearchFlag(SearchRules.SearchFlags.CASE_SENSITIVE, newVal); updateSearchQuery(); }); fulltextButton.setSelected(searchPreferences.isFulltext()); fulltextButton.setTooltip(new Tooltip(Localization.lang("Fulltext search"))); initSearchModifierButton(fulltextButton); - fulltextButton.setOnAction(event -> { - searchPreferences.setSearchFlag(SearchRules.SearchFlags.FULLTEXT, fulltextButton.isSelected()); + fulltextButton.selectedProperty().addListener((obs, oldVal, newVal) -> { + searchPreferences.setSearchFlag(SearchRules.SearchFlags.FULLTEXT, newVal); updateSearchQuery(); }); keepSearchString.setSelected(searchPreferences.shouldKeepSearchString()); keepSearchString.setTooltip(new Tooltip(Localization.lang("Keep search string across libraries"))); initSearchModifierButton(keepSearchString); - keepSearchString.setOnAction(evt -> { - searchPreferences.setSearchFlag(SearchRules.SearchFlags.KEEP_SEARCH_STRING, keepSearchString.isSelected()); + keepSearchString.selectedProperty().addListener((obs, oldVal, newVal) -> { + searchPreferences.setSearchFlag(SearchRules.SearchFlags.KEEP_SEARCH_STRING, newVal); updateSearchQuery(); }); openGlobalSearchButton.disableProperty().bindBidirectional(globalSearchActive); openGlobalSearchButton.setTooltip(new Tooltip(Localization.lang("Search across libraries in a new window"))); initSearchModifierButton(openGlobalSearchButton); - openGlobalSearchButton.setOnAction(evt -> { - globalSearchActive.setValue(true); - if (globalSearchResultDialog == null) { - globalSearchResultDialog = new GlobalSearchResultDialog(undoManager, tabContainer); - } - stateManager.activeGlobalSearchQueryProperty().setValue(searchQueryProperty.get()); - updateSearchQuery(); - dialogService.showCustomDialogAndWait(globalSearchResultDialog); - globalSearchActive.setValue(false); + openGlobalSearchButton.setOnAction(evt -> openGlobalSearchDialog()); + + searchPreferences.getObservableSearchFlags().addListener((SetChangeListener.Change change) -> { + regularExpressionButton.setSelected(searchPreferences.isRegularExpression()); + caseSensitiveButton.setSelected(searchPreferences.isCaseSensitive()); + fulltextButton.setSelected(searchPreferences.isFulltext()); + keepSearchString.setSelected(searchPreferences.shouldKeepSearchString()); }); } + public void openGlobalSearchDialog() { + if (globalSearchActive.get()) { + return; + } + globalSearchActive.setValue(true); + if (globalSearchResultDialog == null) { + globalSearchResultDialog = new GlobalSearchResultDialog(undoManager, tabContainer); + } + stateManager.activeGlobalSearchQueryProperty().setValue(searchQueryProperty.get()); + updateSearchQuery(); + dialogService.showCustomDialogAndWait(globalSearchResultDialog); + globalSearchActive.setValue(false); + } + private void initSearchModifierButton(ButtonBase searchButton) { - searchButton.setCursor(Cursor.DEFAULT); + searchButton.setCursor(Cursor.HAND); searchButton.setMinHeight(28); searchButton.setMaxHeight(28); searchButton.setMinWidth(28); @@ -319,7 +305,8 @@ private void initSearchModifierButton(ButtonBase searchButton) { /** * Focuses the search field if it is not focused. */ - public void focus() { + @Override + public void requestFocus() { if (!searchField.isFocused()) { searchField.requestFocus(); } @@ -339,7 +326,7 @@ public void updateSearchQuery() { } // Invalid regular expression - if (!regexValidator.getValidationStatus().isValid()) { + if (regularExpressionButton.isSelected() && !validRegex()) { currentResults.setText(Localization.lang("Invalid regular expression")); return; } diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.fxml b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.fxml index 0823a696e1c..dc58788cdd9 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.fxml +++ b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.fxml @@ -6,7 +6,6 @@ - - - - diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java index ab5addb830f..46d5eaf536e 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java @@ -14,6 +14,7 @@ import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.SpecialFieldColumn; import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.theme.ThemeManager; @@ -24,6 +25,7 @@ import com.airhacks.afterburner.views.ViewLoader; import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; import jakarta.inject.Inject; public class GlobalSearchResultDialog extends BaseDialog { @@ -35,14 +37,15 @@ public class GlobalSearchResultDialog extends BaseDialog { private final UndoManager undoManager; private final LibraryTabContainer libraryTabContainer; + // Reference needs to be kept, since java garbage collection would otherwise destroy the subscription + @SuppressWarnings("FieldCanBeLocal") private Subscription keepOnTopSubscription; + @Inject private PreferencesService preferencesService; @Inject private StateManager stateManager; @Inject private DialogService dialogService; @Inject private ThemeManager themeManager; @Inject private TaskExecutor taskExecutor; - private GlobalSearchResultDialogViewModel viewModel; - public GlobalSearchResultDialog(UndoManager undoManager, LibraryTabContainer libraryTabContainer) { this.undoManager = undoManager; this.libraryTabContainer = libraryTabContainer; @@ -56,7 +59,7 @@ public GlobalSearchResultDialog(UndoManager undoManager, LibraryTabContainer lib @FXML private void initialize() { - viewModel = new GlobalSearchResultDialogViewModel(preferencesService); + GlobalSearchResultDialogViewModel viewModel = new GlobalSearchResultDialogViewModel(preferencesService.getSearchPreferences()); GlobalSearchBar searchBar = new GlobalSearchBar(libraryTabContainer, stateManager, preferencesService, undoManager, dialogService, SearchType.GLOBAL_SEARCH); searchBarContainer.getChildren().addFirst(searchBar); @@ -82,7 +85,10 @@ private void initialize() { resultsTable.setOnMouseClicked(event -> { if (event.getClickCount() == 2) { - var selectedEntry = resultsTable.getSelectionModel().getSelectedItem(); + BibEntryTableViewModel selectedEntry = resultsTable.getSelectionModel().getSelectedItem(); + if (selectedEntry == null) { + return; + } libraryTabContainer.getLibraryTabs().stream() .filter(tab -> tab.getBibDatabaseContext().equals(selectedEntry.getBibDatabaseContext())) .findFirst() @@ -98,7 +104,7 @@ private void initialize() { keepOnTop.selectedProperty().bindBidirectional(viewModel.keepOnTop()); - EasyBind.subscribe(viewModel.keepOnTop(), value -> { + keepOnTopSubscription = EasyBind.subscribe(viewModel.keepOnTop(), value -> { stage.setAlwaysOnTop(value); keepOnTop.setGraphic(value ? IconTheme.JabRefIcons.KEEP_ON_TOP.getGraphicNode() @@ -108,11 +114,14 @@ private void initialize() { stage.setOnShown(event -> { stage.setHeight(preferencesService.getSearchPreferences().getSearchWindowHeight()); stage.setWidth(preferencesService.getSearchPreferences().getSearchWindowWidth()); + container.setDividerPositions(preferencesService.getSearchPreferences().getSearchWindowDividerPosition()); + searchBar.requestFocus(); }); stage.setOnHidden(event -> { preferencesService.getSearchPreferences().setSearchWindowHeight(getHeight()); preferencesService.getSearchPreferences().setSearchWindowWidth(getWidth()); + preferencesService.getSearchPreferences().setSearchWindowDividerPosition(container.getDividers().getFirst().getPosition()); }); } } diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java index aab636eabc6..a6bf51618bf 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java @@ -4,7 +4,6 @@ import javafx.beans.property.SimpleBooleanProperty; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.preferences.PreferencesService; import org.jabref.preferences.SearchPreferences; import com.tobiasdiez.easybind.EasyBind; @@ -13,9 +12,7 @@ public class GlobalSearchResultDialogViewModel { private final BibDatabaseContext searchDatabaseContext = new BibDatabaseContext(); private final BooleanProperty keepOnTop = new SimpleBooleanProperty(); - public GlobalSearchResultDialogViewModel(PreferencesService preferencesService) { - SearchPreferences searchPreferences = preferencesService.getSearchPreferences(); - + public GlobalSearchResultDialogViewModel(SearchPreferences searchPreferences) { keepOnTop.set(searchPreferences.shouldKeepWindowOnTop()); EasyBind.subscribe(this.keepOnTop, searchPreferences::setKeepWindowOnTop); diff --git a/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java b/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java index 0f4c23989c1..1d2a64ad0b5 100644 --- a/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java +++ b/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java @@ -1,66 +1,57 @@ package org.jabref.gui.search; -import javax.swing.undo.UndoManager; +import java.util.List; import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; -import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; -import org.jabref.gui.edit.EditAction; +import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; import org.jabref.logic.l10n.Localization; import org.controlsfx.control.textfield.CustomTextField; public class SearchFieldRightClickMenu { - public static ContextMenu create(StateManager stateManager, - CustomTextField searchField, - LibraryTabContainer tabContainer, - UndoManager undoManager) { + public static ContextMenu create(StateManager stateManager, CustomTextField searchField) { ActionFactory factory = new ActionFactory(); ContextMenu contextMenu = new ContextMenu(); contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.UNDO, new EditAction(StandardActions.UNDO, tabContainer::getCurrentLibraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.REDO, new EditAction(StandardActions.REDO, tabContainer::getCurrentLibraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, tabContainer::getCurrentLibraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, tabContainer::getCurrentLibraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, tabContainer::getCurrentLibraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.DELETE, new EditAction(StandardActions.DELETE, tabContainer::getCurrentLibraryTab, stateManager, undoManager)), - + factory.createMenuItem(StandardActions.UNDO, new EditorContextAction(StandardActions.UNDO, searchField)), + factory.createMenuItem(StandardActions.REDO, new EditorContextAction(StandardActions.REDO, searchField)), + factory.createMenuItem(StandardActions.CUT, new EditorContextAction(StandardActions.CUT, searchField)), + factory.createMenuItem(StandardActions.COPY, new EditorContextAction(StandardActions.COPY, searchField)), + factory.createMenuItem(StandardActions.PASTE, new EditorContextAction(StandardActions.PASTE, searchField)), + factory.createMenuItem(StandardActions.DELETE, new EditorContextAction(StandardActions.DELETE, searchField)), + factory.createMenuItem(StandardActions.SELECT_ALL, new EditorContextAction(StandardActions.SELECT_ALL, searchField)), new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.SELECT_ALL, new EditAction(StandardActions.SELECT_ALL, null, stateManager, undoManager)), - createSearchFromHistorySubMenu(factory, stateManager, searchField) - ); - + createSearchFromHistorySubMenu(stateManager, searchField)); return contextMenu; } - private static Menu createSearchFromHistorySubMenu(ActionFactory factory, - StateManager stateManager, - CustomTextField searchField) { + public static Menu createSearchFromHistorySubMenu(StateManager stateManager, CustomTextField searchField) { + ActionFactory factory = new ActionFactory(); Menu searchFromHistorySubMenu = factory.createMenu(() -> Localization.lang("Search from history...")); - int num = stateManager.getLastSearchHistory(10).size(); - if (num == 0) { + final int numberOfLastQueries = 10; + List searchHistory = stateManager.getLastSearchHistory(numberOfLastQueries); + if (searchHistory.isEmpty()) { MenuItem item = new MenuItem(Localization.lang("your search history is empty")); - searchFromHistorySubMenu.getItems().addAll(item); + searchFromHistorySubMenu.getItems().add(item); } else { - for (int i = 0; i < num; i++) { - int finalI = i; - MenuItem item = factory.createMenuItem(() -> stateManager.getLastSearchHistory(10).get(finalI), new SimpleCommand() { + for (String query : searchHistory) { + MenuItem item = factory.createMenuItem(() -> query, new SimpleCommand() { @Override public void execute() { - searchField.setText(stateManager.getLastSearchHistory(10).get(finalI)); + searchField.setText(query); } }); - searchFromHistorySubMenu.getItems().addAll(item); + searchFromHistorySubMenu.getItems().add(item); } MenuItem clear = factory.createMenuItem(() -> Localization.lang("Clear history"), new SimpleCommand() { @Override diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 3f88dd08814..f62ca44ff7b 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -293,6 +293,7 @@ public class JabRefPreferences implements PreferencesService { public static final String SEARCH_KEEP_GLOBAL_WINDOW_ON_TOP = "keepOnTop"; public static final String SEARCH_WINDOW_HEIGHT = "searchWindowHeight"; public static final String SEARCH_WINDOW_WIDTH = "searchWindowWidth"; + public static final String SEARCH_WINDOW_DIVIDER_POS = "searchWindowDividerPos"; public static final String SEARCH_CATALOGS = "searchCatalogs"; public static final String IMPORTERS_ENABLED = "importersEnabled"; public static final String GENERATE_KEY_ON_IMPORT = "generateKeyOnImport"; @@ -544,6 +545,7 @@ private JabRefPreferences() { defaults.put(SEARCH_KEEP_GLOBAL_WINDOW_ON_TOP, Boolean.TRUE); defaults.put(SEARCH_WINDOW_HEIGHT, 176.0); defaults.put(SEARCH_WINDOW_WIDTH, 600.0); + defaults.put(SEARCH_WINDOW_DIVIDER_POS, 0.5); defaults.put(SEARCH_CATALOGS, convertListToString(List.of( ACMPortalFetcher.FETCHER_NAME, SpringerFetcher.FETCHER_NAME, @@ -2709,7 +2711,8 @@ public SearchPreferences getSearchPreferences() { getBoolean(SEARCH_KEEP_SEARCH_STRING), getBoolean(SEARCH_KEEP_GLOBAL_WINDOW_ON_TOP), getDouble(SEARCH_WINDOW_HEIGHT), - getDouble(SEARCH_WINDOW_WIDTH)); + getDouble(SEARCH_WINDOW_WIDTH), + getDouble(SEARCH_WINDOW_DIVIDER_POS)); EasyBind.listen(searchPreferences.searchDisplayModeProperty(), (obs, oldValue, newValue) -> put(SEARCH_DISPLAY_MODE, Objects.requireNonNull(searchPreferences.getSearchDisplayMode()).toString())); searchPreferences.getObservableSearchFlags().addListener((SetChangeListener) c -> { @@ -2721,6 +2724,7 @@ public SearchPreferences getSearchPreferences() { 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())); + EasyBind.listen(searchPreferences.getSearchWindowDividerPositionProperty(), (obs, oldValue, newValue) -> putDouble(SEARCH_WINDOW_DIVIDER_POS, searchPreferences.getSearchWindowDividerPosition())); return searchPreferences; } diff --git a/src/main/java/org/jabref/preferences/SearchPreferences.java b/src/main/java/org/jabref/preferences/SearchPreferences.java index a4fdbf9cb53..737c267158b 100644 --- a/src/main/java/org/jabref/preferences/SearchPreferences.java +++ b/src/main/java/org/jabref/preferences/SearchPreferences.java @@ -19,13 +19,16 @@ public class SearchPreferences { private final ObjectProperty searchDisplayMode; private final ObservableSet searchFlags; private final BooleanProperty keepWindowOnTop; + private final DoubleProperty searchWindowHeight; + private final DoubleProperty searchWindowWidth; + private final DoubleProperty searchWindowDividerPosition; - private final DoubleProperty searchWindowHeight = new SimpleDoubleProperty(); - private final DoubleProperty searchWindowWidth = new SimpleDoubleProperty(); - - public SearchPreferences(SearchDisplayMode searchDisplayMode, boolean isCaseSensitive, boolean isRegularExpression, boolean isFulltext, boolean isKeepSearchString, boolean keepWindowOnTop, double searchWindowHeight, double searchWindowWidth) { + public SearchPreferences(SearchDisplayMode searchDisplayMode, boolean isCaseSensitive, boolean isRegularExpression, boolean isFulltext, boolean isKeepSearchString, boolean keepWindowOnTop, double searchWindowHeight, double searchWindowWidth, double searchWindowDividerPosition) { this.searchDisplayMode = new SimpleObjectProperty<>(searchDisplayMode); this.keepWindowOnTop = new SimpleBooleanProperty(keepWindowOnTop); + this.searchWindowHeight = new SimpleDoubleProperty(searchWindowHeight); + this.searchWindowWidth = new SimpleDoubleProperty(searchWindowWidth); + this.searchWindowDividerPosition = new SimpleDoubleProperty(searchWindowDividerPosition); searchFlags = FXCollections.observableSet(EnumSet.noneOf(SearchFlags.class)); if (isCaseSensitive) { @@ -40,16 +43,6 @@ public SearchPreferences(SearchDisplayMode searchDisplayMode, boolean isCaseSens if (isKeepSearchString) { searchFlags.add(SearchFlags.KEEP_SEARCH_STRING); } - - this.setSearchWindowHeight(searchWindowHeight); - this.setSearchWindowWidth(searchWindowWidth); - } - - public SearchPreferences(SearchDisplayMode searchDisplayMode, EnumSet searchFlags, boolean keepWindowOnTop) { - this.searchDisplayMode = new SimpleObjectProperty<>(searchDisplayMode); - this.keepWindowOnTop = new SimpleBooleanProperty(keepWindowOnTop); - - this.searchFlags = FXCollections.observableSet(searchFlags); } public EnumSet getSearchFlags() { @@ -60,7 +53,7 @@ public EnumSet getSearchFlags() { return EnumSet.copyOf(searchFlags); } - protected ObservableSet getObservableSearchFlags() { + public ObservableSet getObservableSearchFlags() { return searchFlags; } @@ -120,6 +113,10 @@ public double getSearchWindowWidth() { return this.searchWindowWidth.get(); } + public Double getSearchWindowDividerPosition() { + return this.searchWindowDividerPosition.get(); + } + public DoubleProperty getSearchWindowHeightProperty() { return this.searchWindowHeight; } @@ -128,6 +125,10 @@ public DoubleProperty getSearchWindowWidthProperty() { return this.searchWindowWidth; } + public DoubleProperty getSearchWindowDividerPositionProperty() { + return this.searchWindowDividerPosition; + } + public void setSearchWindowHeight(double height) { this.searchWindowHeight.set(height); } @@ -135,4 +136,8 @@ public void setSearchWindowHeight(double height) { public void setSearchWindowWidth(double width) { this.searchWindowWidth.set(width); } + + public void setSearchWindowDividerPosition(double position) { + this.searchWindowDividerPosition.set(position); + } } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 9679586dc62..221b352aa2d 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -829,6 +829,7 @@ Search\ term\ is\ empty.=Search term is empty. Invalid\ regular\ expression.=Invalid regular expression. Searching\ for\ a\ keyword=Searching for a keyword +Open\ global\ search\ window=Open global search window Search\ across\ libraries\ in\ a\ new\ window=Search across libraries in a new window Keep\ search\ string\ across\ libraries=Keep search string across libraries Keep\ dialog\ always\ on\ top=Keep dialog always on top diff --git a/src/test/java/org/jabref/cli/ArgumentProcessorTest.java b/src/test/java/org/jabref/cli/ArgumentProcessorTest.java index a74063b9a01..66502ed18b6 100644 --- a/src/test/java/org/jabref/cli/ArgumentProcessorTest.java +++ b/src/test/java/org/jabref/cli/ArgumentProcessorTest.java @@ -2,7 +2,6 @@ import java.nio.file.Files; import java.nio.file.Path; -import java.util.EnumSet; import java.util.List; import java.util.Objects; @@ -19,7 +18,6 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; -import org.jabref.model.search.rules.SearchRules; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.ExportPreferences; @@ -49,7 +47,7 @@ void setup() { when(preferencesService.getImporterPreferences()).thenReturn(importerPreferences); when(preferencesService.getImportFormatPreferences()).thenReturn(importFormatPreferences); when(preferencesService.getSearchPreferences()).thenReturn( - new SearchPreferences(null, EnumSet.noneOf(SearchRules.SearchFlags.class), false) + new SearchPreferences(null, false, false, false, false, false, 0, 0, 0) ); } diff --git a/src/test/java/org/jabref/gui/search/GlobalSearchBarTest.java b/src/test/java/org/jabref/gui/search/GlobalSearchBarTest.java index 565681198db..b28dbfa697c 100644 --- a/src/test/java/org/jabref/gui/search/GlobalSearchBarTest.java +++ b/src/test/java/org/jabref/gui/search/GlobalSearchBarTest.java @@ -3,6 +3,7 @@ import java.util.EnumSet; import java.util.List; +import javafx.collections.FXCollections; import javafx.scene.Scene; import javafx.scene.control.TextInputControl; import javafx.scene.layout.HBox; @@ -44,6 +45,7 @@ public class GlobalSearchBarTest { public void onStart(Stage stage) { SearchPreferences searchPreferences = mock(SearchPreferences.class); when(searchPreferences.getSearchFlags()).thenReturn(EnumSet.noneOf(SearchRules.SearchFlags.class)); + when(searchPreferences.getObservableSearchFlags()).thenReturn(FXCollections.observableSet()); PreferencesService prefs = mock(PreferencesService.class, Answers.RETURNS_DEEP_STUBS); when(prefs.getSearchPreferences()).thenReturn(searchPreferences);