diff --git a/CHANGELOG.md b/CHANGELOG.md index ff612b3f49d..805acdbb0c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added a "Jump to Field" dialog (`Ctrl+J`) to quickly search for and navigate to any field across all tabs. [#12276](https://github.com/JabRef/jabref/issues/12276). - We made the "Configure API key" option in the Web Search preferences tab searchable via preferences search. [#13929](https://github.com/JabRef/jabref/issues/13929) - We added the integrity check to the jabkit cli application. [#13848](https://github.com/JabRef/jabref/issues/13848) - We added support for Cygwin-file paths on a Windows Operating System. [#13274](https://github.com/JabRef/jabref/issues/13274) diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index fd3334039bc..09df0f96328 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -28,6 +28,7 @@ import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; +import javafx.stage.Modality; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -171,15 +172,14 @@ public EntryEditor(Supplier tabSupplier, UndoAction undoAction, Redo }); stateManager.getSelectedEntries().addListener((InvalidationListener) _ -> { - if (stateManager.getSelectedEntries().isEmpty()) { - // [impl->req~entry-editor.keep-showing~1] - // No change in the entry editor - // We allow users to edit the "old" entry - } else { - setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst()); - } - } - ); + if (stateManager.getSelectedEntries().isEmpty()) { + // [impl->req~entry-editor.keep-showing~1] + // No change in the entry editor + // We allow users to edit the "old" entry + } else { + setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst()); + } + }); EasyBind.listen(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), (_, _, newValue) -> { @@ -248,6 +248,10 @@ private void setupKeyBindings() { tabSupplier.get().selectPreviousEntry(); event.consume(); break; + case JUMP_TO_FIELD: + selectFieldDialog(); + event.consume(); + break; case HELP: new HelpAction(HelpFile.ENTRY_EDITOR, dialogService, preferences.getExternalApplicationsPreferences()).execute(); event.consume(); @@ -268,6 +272,15 @@ private void setupKeyBindings() { }); } + public void selectFieldDialog() { + if (getCurrentlyEditedEntry() == null) { + return; + } + JumpToFieldDialog dialog = new JumpToFieldDialog(this); + dialog.initModality(Modality.NONE); + dialog.show(); + } + @FXML private void close() { stateManager.getEditorShowing().set(false); @@ -420,6 +433,10 @@ public BibEntry getCurrentlyEditedEntry() { return currentlyEditedEntry; } + public List getAllPossibleTabs() { + return allPossibleTabs; + } + public void setCurrentlyEditedEntry(@NonNull BibEntry currentlyEditedEntry) { if (Objects.equals(this.currentlyEditedEntry, currentlyEditedEntry)) { return; @@ -434,15 +451,26 @@ public void setCurrentlyEditedEntry(@NonNull BibEntry currentlyEditedEntry) { } typeSubscription = EasyBind.subscribe(this.currentlyEditedEntry.typeProperty(), _ -> { - typeLabel.setText(new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()).getTypeForDisplay()); + typeLabel.setText(new TypedBibEntry(this.currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()).getTypeForDisplay()); adaptVisibleTabs(); setupToolBar(); - getSelectedTab().notifyAboutFocus(currentlyEditedEntry); + getSelectedTab().notifyAboutFocus(this.currentlyEditedEntry); }); + typeLabel.setText(new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()).getTypeForDisplay()); + + adaptVisibleTabs(); + + setupToolBar(); + if (preferences.getEntryEditorPreferences().showSourceTabByDefault()) { tabbed.getSelectionModel().select(sourceTab); } + + EntryEditorTab selectedTab = getSelectedTab(); + if (selectedTab != null) { + Platform.runLater(() -> selectedTab.notifyAboutFocus(currentlyEditedEntry)); + } } private EntryEditorTab getSelectedTab() { @@ -492,6 +520,10 @@ private void fetchAndMerge(EntryBasedFetcher fetcher) { new FetchAndMergeEntry(tabSupplier.get().getBibDatabaseContext(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(currentlyEditedEntry, fetcher); } + public void selectField(String fieldName) { + setFocusToField(org.jabref.model.entry.field.FieldFactory.parseField(fieldName)); + } + public void setFocusToField(Field field) { UiTaskExecutor.runInJavaFXThread(() -> { Field actualField = field; diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldDialog.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldDialog.java new file mode 100644 index 00000000000..1f0e3a7ee9b --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldDialog.java @@ -0,0 +1,63 @@ +package org.jabref.gui.entryeditor; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TextField; + +import org.jabref.gui.util.BaseDialog; +import org.jabref.logic.l10n.Localization; + +import com.airhacks.afterburner.views.ViewLoader; +import org.controlsfx.control.textfield.TextFields; + +public class JumpToFieldDialog extends BaseDialog { + @FXML private TextField searchField; + private final EntryEditor entryEditor; + private JumpToFieldViewModel viewModel; + + public JumpToFieldDialog(EntryEditor entryEditor) { + this.entryEditor = entryEditor; + this.setTitle(Localization.lang("Jump to field")); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + this.getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); + + this.setResultConverter(button -> { + if (button == ButtonType.OK) { + jumpToSelectedField(); + } + return null; + }); + + Platform.runLater(() -> searchField.requestFocus()); + } + + @FXML + private void initialize() { + viewModel = new JumpToFieldViewModel(this.entryEditor); + searchField.textProperty().bindBidirectional(viewModel.searchTextProperty()); + TextFields.bindAutoCompletion(searchField, viewModel.getFieldNames()); + + searchField.setOnAction(event -> { + Button okButton = (Button) getDialogPane().lookupButton(ButtonType.OK); + if (okButton != null) { + okButton.fire(); + } + event.consume(); + }); + } + + private void jumpToSelectedField() { + String selectedField = searchField.getText(); + + if (selectedField != null && !selectedField.isEmpty()) { + String fieldToJumpTo = selectedField.toLowerCase(); + entryEditor.selectField(fieldToJumpTo); + } + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldViewModel.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldViewModel.java new file mode 100644 index 00000000000..97ab16b0ac1 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldViewModel.java @@ -0,0 +1,41 @@ +package org.jabref.gui.entryeditor; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.gui.AbstractViewModel; +import org.jabref.model.entry.field.Field; + +public class JumpToFieldViewModel extends AbstractViewModel { + + private final StringProperty searchText = new SimpleStringProperty(""); + private final EntryEditor entryEditor; + + public JumpToFieldViewModel(EntryEditor entryEditor) { + this.entryEditor = entryEditor; + } + + public StringProperty searchTextProperty() { + return searchText; + } + + public List getFieldNames() { + if (entryEditor.getCurrentlyEditedEntry() == null) { + return Collections.emptyList(); + } + + List fieldNames = entryEditor.getAllPossibleTabs().stream() + .filter(FieldsEditorTab.class::isInstance) + .map(FieldsEditorTab.class::cast) + .flatMap(tab -> tab.getShownFields().stream()) + .map(Field::getName) + .distinct() + .sorted() + .collect(Collectors.toList()); + return fieldNames; + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index 08d68d5f7e2..76a53ca33ff 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -36,6 +36,7 @@ public enum KeyBinding { CLOSE("Close dialog", Localization.lang("Close dialog"), "Esc", KeyBindingCategory.VIEW), COPY("Copy", Localization.lang("Copy"), "ctrl+C", KeyBindingCategory.EDIT), COPY_TITLE("Copy title", Localization.lang("Copy title"), "ctrl+shift+alt+T", KeyBindingCategory.EDIT), + JUMP_TO_FIELD("Jump to field", Localization.lang("Jump to field"), "ctrl+J", KeyBindingCategory.EDIT), // We migrated from "Copy \\cite{citation key}" to "Copy citation key with configured cite command", therefore we keep the "old string" for backwards comppatibility COPY_CITE_CITATION_KEY("Copy \\cite{citation key}", Localization.lang("Copy citation key with configured cite command"), "ctrl+K", KeyBindingCategory.EDIT), diff --git a/jabgui/src/main/resources/org/jabref/gui/entryeditor/JumpToFieldDialog.fxml b/jabgui/src/main/resources/org/jabref/gui/entryeditor/JumpToFieldDialog.fxml new file mode 100644 index 00000000000..8f755716569 --- /dev/null +++ b/jabgui/src/main/resources/org/jabref/gui/entryeditor/JumpToFieldDialog.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 9b1b266fd5d..2f97fdda4b2 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -430,6 +430,7 @@ Duplicate\ citation\ key=Duplicate citation key Citation\ key\ '%0'\ to\ select\ not\ found\ in\ open\ libraries.=Citation key '%0' to select not found in open libraries. Jump\ to\ entry\ in\ library=Jump to entry in library +Jump\ to\ field=Jump to field Autolink\ files\ with\ names\ starting\ with\ the\ citation\ key=Autolink files with names starting with the citation key Autolink\ only\ files\ that\ match\ the\ citation\ key=Autolink only files that match the citation key @@ -2353,6 +2354,7 @@ Title\ of\ the\ work.=Title of the work. Total\ number\ of\ pages\ of\ the\ work.=Total number of pages of the work. Total\ number\ of\ volumes\ of\ a\ multi-volume\ work.=Total number of volumes of a multi-volume work. Type\ of\ the\ eprint\ identifier,\ e.g.,\ the\ name\ of\ the\ archive,\ repository,\ service,\ or\ system\ the\ eprint\ field\ refers\ to.=Type of the eprint identifier, e.g., the name of the archive, repository, service, or system the eprint field refers to. +Type\ a\ field\ name=Type a field name URL\ of\ an\ online\ publication.=URL of an online publication. Volume\ of\ a\ multi-volume\ book\ or\ a\ periodical.=Volume of a multi-volume book or a periodical. Year\ of\ publication.=Year of publication.