Skip to content

Commit

Permalink
Fix context menu of the search bar (#11446)
Browse files Browse the repository at this point in the history
* Fix selecting group triggers in all open libraries

* Change color of the search modifier buttons for dark theme

#8963 (comment)

* Add shortcut to open global search window "Ctrl+shift+F"

* fix keep on top subscription

* Fix context menu of the search bar

* Store preview divider position in global search window

Remove constructor

* prevent NPE

* Fix update search query triggers in all open libraries

* Adapt GUI test

* Clear search highlight in SourceTab after resetting search query

* Revert "Fix update search query triggers in all open libraries"

This reverts commit d6d455c.

* Revert "Fix selecting group triggers in all open libraries"

This reverts commit b0017fb.

* Remove regexValidator

* listen for changes for search flags

---------

Co-authored-by: Siedlerchr <[email protected]>
  • Loading branch information
LoayGhreeb and Siedlerchr authored Jul 24, 2024
1 parent fed6bde commit d8ebffc
Show file tree
Hide file tree
Showing 17 changed files with 135 additions and 151 deletions.
16 changes: 6 additions & 10 deletions src/main/java/org/jabref/gui/Base.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
18 changes: 2 additions & 16 deletions src/main/java/org/jabref/gui/Dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/org/jabref/gui/entryeditor/SourceTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ 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) {
case COPY -> editableBinding.and(maskTextBinding.not()).and(hasSelectionBinding);
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());
Expand All @@ -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();
}
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/jabref/gui/frame/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/icon/IconTheme.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jabref/gui/keyboard/KeyBinding.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
99 changes: 43 additions & 56 deletions src/main/java/org/jabref/gui/search/GlobalSearchBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<String> search = stateManager.getWholeSearchHistory();
search.addListener((ListChangeListener.Change<? extends String> change) ->
searchField.setContextMenu(SearchFieldRightClickMenu.create(
stateManager,
searchField,
tabContainer,
undoManager))
);

ClipBoardManager.addX11Support(searchField);

searchField.setContextMenu(SearchFieldRightClickMenu.create(stateManager, searchField));
stateManager.getWholeSearchHistory().addListener((ListChangeListener.Change<? extends String> 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();
Expand All @@ -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);
Expand All @@ -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 {
Expand Down Expand Up @@ -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<? extends SearchRules.SearchFlags> 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);
Expand All @@ -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();
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.VBox?>
<?import org.jabref.gui.icon.JabRefIconView?>
<?import javafx.scene.layout.HBox?>
<DialogPane xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jabref.gui.search.GlobalSearchResultDialog"
Expand All @@ -15,9 +14,6 @@
<VBox>
<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"/>
</graphic>
<tooltip>
<Tooltip text="%Keep dialog always on top"/>
</tooltip>
Expand Down
Loading

0 comments on commit d8ebffc

Please sign in to comment.