Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3f5ce74
feat(JabRef #12276): Adding in Keybind enum for JUMP_TO_FIELD
ZachCraw Oct 20, 2025
383e22f
feat(JabRef #12276): Initialising JumpToField command
ZachCraw Oct 20, 2025
cf9cdc7
docs(JabRef #12276): Adding in english localisation for JUMP to FIELD…
ZachCraw Oct 20, 2025
fbda3f4
feat(JabRef #12276): Creating base fxml for JUMP to field box
ZachCraw Oct 20, 2025
0755740
feat(JabRef #12276): FXML action class to search through loaded fields
ZachCraw Oct 20, 2025
84529b8
feat(JabRef #12276): Class to extract fieldNames for comparison and a…
ZachCraw Oct 20, 2025
848ff0b
feat(JabRef #12276): Implementing field search handling within entryE…
ZachCraw Oct 20, 2025
fb5f482
Merge branch 'JabRef:main' into feat/field_jumping_12276
ZachCraw Oct 20, 2025
d188809
docs(JabRef #12276): Explained addition in CHANGELOG.md
ZachCraw Oct 20, 2025
ae000fc
Merge branch 'feat/field_jumping_12276' of https://github.com/ZachCra…
ZachCraw Oct 20, 2025
ff0e43f
docs(JabRef #12276): Fixing missing blank line in CHANGELOG
ZachCraw Oct 20, 2025
fc798a2
fix(JabRef #12276): Reverting docs deletion to include implementation…
ZachCraw Oct 20, 2025
60c494b
fix(JabRef #12276): Fixing frame focusing and scene hierarchy.
ZachCraw Oct 25, 2025
d5c4405
fix(JabRef #12276): Removing necessary initialisation.
ZachCraw Oct 25, 2025
0dffbf0
Merge branch 'main' into feat/field_jumping_12276
ZachCraw Oct 25, 2025
57da947
docs(JabRef #12276): Changing function names to reflect "select" patt…
ZachCraw Oct 25, 2025
27e03f6
Merge branch 'feat/field_jumping_12276' of https://github.com/ZachCra…
ZachCraw Oct 25, 2025
fbcb65a
Merge branch 'main' into feat/field_jumping_12276
ZachCraw Oct 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 38 additions & 11 deletions jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,11 @@ public EntryEditor(Supplier<LibraryTab> tabSupplier, UndoAction undoAction, Redo
});

stateManager.getSelectedEntries().addListener((InvalidationListener) _ -> {
if (stateManager.getSelectedEntries().isEmpty()) {
// [impl->req~entry-editor.keep-showing~1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please keep this as it referes to implementation spec

// No change in the entry editor
// We allow users to edit the "old" entry
} else {
setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst());
}
}
);
if (!stateManager.getSelectedEntries().isEmpty()) {
setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst());
Platform.runLater(() -> tabbed.requestFocus());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need Platform.runLater here?

Copy link
Author

@ZachCraw ZachCraw Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that this was required to solve a focus-handling issue. Without it, a "race condition" occurs when switching entries: the main table gains focus from the click, and the EntryEditor's simultaneous requestFocus() call is lost. This prevents the EntryEditor from receiving the Ctrl+J key event. Using Platform.runLater defers the focus request until after the table's selection event is processed, ensuring the EntryEditor reliably regains keyboard focus.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now you are always stealing the focus from the maintable and force it to the entry editor every time the user selects a different entry in the main table? I dont think that this is a desired behaviour.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. If this is not the desired behaviour, what would be? The ability to interact back with the other entries and main window while having the search box open?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, leaving the focus in the maintable. Think of a user navigating with the arrowkeys in the maintable.

}
});

EasyBind.listen(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(),
(_, _, newValue) -> {
Expand Down Expand Up @@ -261,13 +257,25 @@ private void setupKeyBindings() {
close();
event.consume();
break;
case JUMP_TO_FIELD:
showJumpToFieldDialog();
event.consume();
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should go in the scene hierarchy more up, if you want it to be callable from the entry editor and from the maintable alike.

default:
// Pass other keys to parent
}
}
});
}

private void showJumpToFieldDialog() {
if (getCurrentlyEditedEntry() == null) {
return;
}
JumpToFieldDialog dialog = new JumpToFieldDialog(this);
dialog.showAndWait();
}

@FXML
private void close() {
stateManager.getEditorShowing().set(false);
Expand Down Expand Up @@ -420,6 +428,10 @@ public BibEntry getCurrentlyEditedEntry() {
return currentlyEditedEntry;
}

public List<EntryEditorTab> getAllPossibleTabs() {
return allPossibleTabs;
}

public void setCurrentlyEditedEntry(@NonNull BibEntry currentlyEditedEntry) {
if (Objects.equals(this.currentlyEditedEntry, currentlyEditedEntry)) {
return;
Expand All @@ -434,15 +446,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() {
Expand Down Expand Up @@ -492,6 +515,10 @@ private void fetchAndMerge(EntryBasedFetcher fetcher) {
new FetchAndMergeEntry(tabSupplier.get().getBibDatabaseContext(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(currentlyEditedEntry, fetcher);
}

public void jumpToField(String fieldName) {
setFocusToField(org.jabref.model.entry.field.FieldFactory.parseField(fieldName));
}

public void setFocusToField(Field field) {
UiTaskExecutor.runInJavaFXThread(() -> {
Field actualField = field;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Void> {
@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.jumpToField(fieldToJumpTo);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String> getFieldNames() {
if (entryEditor.getCurrentlyEditedEntry() == null) {
return Collections.emptyList();
}

List<String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.DialogPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>

<DialogPane prefWidth="300" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jabref.gui.entryeditor.JumpToFieldDialog">
<content>
<VBox>
<TextField fx:id="searchField" promptText="%Type a field name"/>
</VBox>
</content>
</DialogPane>
3 changes: 3 additions & 0 deletions jablib/src/main/java/org/jabref/logic/UiCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ record BlankWorkspace() implements UiCommand {
record JumpToEntryKey(String citationKey) implements UiCommand {
}

record JumpToField() implements UiCommand {
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies this was a left over initialisation from a previous implementation.

record OpenLibraries(List<Path> toImport) implements UiCommand {
}

Expand Down
2 changes: 2 additions & 0 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2354,6 +2355,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.
Expand Down
Loading