Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
79b5fc5
Fix TAB navigation in EntryEditor to move focus to next tab's field
tejjgv Sep 12, 2025
77cf911
Merge branch 'JabRef:main' into tab-navigation-test
tejjgv Sep 12, 2025
e0ddc5b
- Fixed TAB navigation in the EntryEditor so that pressing TAB now mo…
tejjgv Sep 13, 2025
4ac7bb9
Merge branch 'main' of https://github.com/tejjgv/jabref
tejjgv Sep 13, 2025
ee1f72d
- Fixed TAB navigation in the EntryEditor so that pressing TAB now mo…
tejjgv Sep 13, 2025
13425c9
-Fixed TAB navigation in the EntryEditor so that pressing TAB now mov…
tejjgv Sep 13, 2025
af87b0f
Replace null returns with Optional
tejjgv Sep 13, 2025
dce3209
Update jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
tejjgv Sep 13, 2025
c22c466
Replace null returns with Optional
tejjgv Sep 13, 2025
236277c
Merge remote-tracking branch 'upstream/main' into fix-issue-11937
tejjgv Sep 13, 2025
c40c287
chore: trigger CI rerun
tejjgv Sep 13, 2025
70c99ea
chore: remove dummy.txt
tejjgv Sep 13, 2025
90b9bbe
Use KeyCode.TAB instead of text comparison for tab key detection
tejjgv Sep 14, 2025
3a48756
Merge branch 'main' into fix-issue-11937
tejjgv Sep 14, 2025
e9774b4
Merge branch 'main' into fix-issue-11937
calixtus Sep 14, 2025
54d51e2
Update CHANGELOG.md
koppor Sep 14, 2025
e75bbea
Update jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
koppor Sep 14, 2025
7a16a93
Reformat
calixtus Sep 14, 2025
c777854
Refine tab to move focus even when the last item is a button
tejjgv Sep 19, 2025
e705c3f
Merge branch 'fix-issue-11937' of https://github.com/tejjgv/jabref in…
tejjgv Sep 19, 2025
60a53aa
Merge branch 'JabRef:main' into fix-issue-11937
tejjgv Sep 19, 2025
ab99d8e
Refine tab to move focus even when the last item is a button
tejjgv Sep 19, 2025
beae442
Refine tab to move focus even when the last item is a button
tejjgv Sep 19, 2025
5074fb7
added supress warning
tejjgv Sep 20, 2025
c02a9a2
Merge branch 'main' into fix-issue-11937
tejjgv Sep 26, 2025
6f1a1b0
removed static injection
tejjgv Sep 27, 2025
38c4984
Merge branch 'fix-issue-11937' of https://github.com/tejjgv/jabref in…
tejjgv Sep 27, 2025
a45dd3c
removed static injection
tejjgv Sep 27, 2025
98c36b1
Merge branch 'main' into fix-issue-11937
tejjgv Oct 6, 2025
c0b22c0
Merge remote-tracking branch 'upstream/main' into fork/tejjgv/fix-iss…
calixtus Oct 6, 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 @@ -98,6 +98,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Fixed

- We fixed an issue where pressing <kbd>Tab</kbd> in the last text field of a tab did not move the focus to the next tab in the entry editor. [#11937](https://github.com/JabRef/jabref/issues/11937)
- When filename pattern is missing for linked files, pattern handling has been introduced to avoid suggesting meaningless filenames like "-". [#13735](https://github.com/JabRef/jabref/issues/13735)
- We fixed an issue where "Specify Bib(La)TeX" tab was not focused when Bib(La)TeX was in the clipboard [#13597](https://github.com/JabRef/jabref/issues/13597)
- We fixed an issue whereby the 'About' dialog was not honouring the user's configured font preferences. [#13558](https://github.com/JabRef/jabref/issues/13558)
Expand Down
26 changes: 26 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

import javafx.collections.ObservableList;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;

Expand Down Expand Up @@ -136,6 +139,8 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry,
if (!entry.hasField(userSpecificCommentField)) {
if (shouldShowHideButton) {
Button hideDefaultOwnerCommentButton = new Button(Localization.lang("Hide user-specific comments field"));
hideDefaultOwnerCommentButton.setId("HIDE_COMMENTS_BUTTON");
setupButtonTabNavigation(hideDefaultOwnerCommentButton);
hideDefaultOwnerCommentButton.setOnAction(e -> {
gridPane.getChildren().removeIf(node ->
(node instanceof FieldNameLabel fieldNameLabel && fieldNameLabel.getText().equals(userSpecificCommentField.getName()))
Expand All @@ -152,6 +157,8 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry,
} else {
// Show "Show" button when user comments field is hidden
Button showDefaultOwnerCommentButton = new Button(Localization.lang("Show user-specific comments field"));
showDefaultOwnerCommentButton.setId("SHOW_COMMENTS_BUTTON");
setupButtonTabNavigation(showDefaultOwnerCommentButton);
showDefaultOwnerCommentButton.setOnAction(e -> {
shouldShowHideButton = true;
setupPanel(bibDatabaseContext, entry, false);
Expand All @@ -162,4 +169,23 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry,
}
}
}

private void setupButtonTabNavigation(Button button) {
button.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.TAB && !event.isShiftDown()) {
// Find the EntryEditor in the parent hierarchy
Node parent = button.getParent();
while (parent != null && !(parent instanceof EntryEditor)) {
parent = parent.getParent();
}

if (parent instanceof EntryEditor entryEditor) {
if (entryEditor.isLastFieldInCurrentTab(button)) {
entryEditor.moveToNextTabAndFocus();
event.consume();
}
}
}
});
}
}
108 changes: 108 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -18,12 +19,15 @@
import javafx.beans.InvalidationListener;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.DataFormat;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
Expand All @@ -38,6 +42,7 @@
import org.jabref.gui.entryeditor.fileannotationtab.FileAnnotationTab;
import org.jabref.gui.entryeditor.fileannotationtab.FulltextSearchResultsTab;
import org.jabref.gui.externalfiles.ExternalFilesEntryLinker;
import org.jabref.gui.fieldeditors.EditorTextField;
import org.jabref.gui.help.HelpAction;
import org.jabref.gui.importer.GrobidUseDialogHelper;
import org.jabref.gui.keyboard.KeyBinding;
Expand Down Expand Up @@ -163,6 +168,11 @@ public EntryEditor(Supplier<LibraryTab> tabSupplier, UndoAction undoAction, Redo

setupDragAndDrop();

EditorTextField.setupTabNavigation(
this::isLastFieldInCurrentTab,
this::moveToNextTabAndFocus
);

EasyBind.subscribe(tabbed.getSelectionModel().selectedItemProperty(), tab -> {
EntryEditorTab activeTab = (EntryEditorTab) tab;
if (activeTab != null) {
Expand Down Expand Up @@ -528,4 +538,102 @@ public void nextPreviewStyle() {
public void previousPreviewStyle() {
this.previewPanel.previousPreviewStyle();
}

/**
* Checks if the given TextField is the last field in the currently selected tab.
*
* @param node the Node to check
* @return true if this is the last field in the current tab, false otherwise
*/
boolean isLastFieldInCurrentTab(Node node) {
if (node == null || tabbed.getSelectionModel().getSelectedItem() == null) {
return false;
}

Tab selectedTab = tabbed.getSelectionModel().getSelectedItem();
if (!(selectedTab instanceof FieldsEditorTab currentTab)) {
return false;
}

Collection<Field> shownFields = currentTab.getShownFields();
if (shownFields.isEmpty() || node.getId() == null) {
return false;
}

Optional<Field> lastField = shownFields.stream()
.reduce((first, second) -> second);

if (node instanceof Button) {
return true;
}

return lastField.map(Field::getName)
.map(displayName -> displayName.equalsIgnoreCase(node.getId()))
.orElse(false);
}

/**
* Moves to the next tab and focuses on its first field.
*/
void moveToNextTabAndFocus() {
tabbed.getSelectionModel().selectNext();

Platform.runLater(() -> {
Tab selectedTab = tabbed.getSelectionModel().getSelectedItem();
if (selectedTab instanceof FieldsEditorTab currentTab) {
focusFirstFieldInTab(currentTab);
}
});
}

private void focusFirstFieldInTab(FieldsEditorTab tab) {
Node tabContent = tab.getContent();
if (tabContent instanceof Parent parent) {
// First try to find field by ID (preferred method)
Collection<Field> shownFields = tab.getShownFields();
if (!shownFields.isEmpty()) {
Field firstField = shownFields.iterator().next();
String firstFieldId = firstField.getName();
Optional<TextInputControl> firstTextInput = findTextInputById(parent, firstFieldId);
if (firstTextInput.isPresent()) {
firstTextInput.get().requestFocus();
return;
}
}

Optional<TextInputControl> anyTextInput = findAnyTextInput(parent);
if (anyTextInput.isPresent()) {
anyTextInput.get().requestFocus();
}
}
}

/// Recursively searches for a TextInputControl (TextField or TextArea) with the given ID.
private Optional<TextInputControl> findTextInputById(Parent parent, String id) {
for (Node child : parent.getChildrenUnmodifiable()) {
if (child instanceof TextInputControl textInput && id.equalsIgnoreCase(textInput.getId())) {
return Optional.of(textInput);
} else if (child instanceof Parent childParent) {
Optional<TextInputControl> found = findTextInputById(childParent, id);
if (found.isPresent()) {
return found;
}
}
}
return Optional.empty();
}

private Optional<TextInputControl> findAnyTextInput(Parent parent) {
for (Node child : parent.getChildrenUnmodifiable()) {
if (child instanceof TextInputControl textInput) {
return Optional.of(textInput);
} else if (child instanceof Parent childParent) {
Optional<TextInputControl> found = findAnyTextInput(childParent);
if (found.isPresent()) {
return found;
}
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public CitationKeyEditor(Field field,
undoManager,
dialogService);

textField.setId(field.getName());

establishBinding(textField, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction);
textField.initContextMenu(Collections::emptyList, keyBindingRepository);
new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import java.util.function.Predicate;
import java.util.function.Supplier;

import javafx.fxml.Initializable;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;

Expand All @@ -20,13 +23,26 @@

public class EditorTextField extends TextField implements Initializable, ContextMenuAddable {

private static Runnable nextTabSelector;
private static Predicate<TextField> isLastFieldChecker;
private final ContextMenu contextMenu = new ContextMenu();

private Runnable additionalPasteActionHandler = () -> {
// No additional paste behavior
};

public EditorTextField() {
this("");
this.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.TAB &&
isLastFieldChecker != null &&
isLastFieldChecker.test(this)) {
if (nextTabSelector != null) {
nextTabSelector.run();
}
event.consume();
}
});
}

public EditorTextField(final String text) {
Expand All @@ -39,6 +55,11 @@ public EditorTextField(final String text) {
ClipBoardManager.addX11Support(this);
}

public static void setupTabNavigation(Predicate<TextField> isLastFieldChecker, Runnable nextTabSelector) {
EditorTextField.isLastFieldChecker = isLastFieldChecker;
EditorTextField.nextTabSelector = nextTabSelector;
}

@Override
public void initContextMenu(final Supplier<List<MenuItem>> items, KeyBindingRepository keyBindingRepository) {
setOnContextMenuRequested(event -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public MarkdownEditor(Field field, SuggestionProvider<?> suggestionProvider, Fie
}

@Override
protected TextInputControl createTextInputControl() {
protected TextInputControl createTextInputControl(@SuppressWarnings("unused") Field field) {
return new EditorTextArea() {
@Override
public void paste() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public PersonsEditor(final Field field,

this.viewModel = new PersonsEditorViewModel(field, suggestionProvider, preferences.getAutoCompletePreferences(), fieldCheckers, undoManager);
textInput = isMultiLine ? new EditorTextArea() : new EditorTextField();
textInput.setId(field.getName());
decoratedStringProperty = new UiThreadStringProperty(viewModel.textProperty());
establishBinding(textInput, decoratedStringProperty, keyBindingRepository, undoAction, redoAction);
((ContextMenuAddable) textInput).initContextMenu(EditorMenus.getNameMenu(textInput), keyBindingRepository);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public SimpleEditor(final Field field,
this.viewModel = new SimpleEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager);
this.isMultiLine = isMultiLine;

textInput = createTextInputControl();
textInput = createTextInputControl(field);
HBox.setHgrow(textInput, Priority.ALWAYS);

establishBinding(textInput, viewModel.textProperty(), preferences.getKeyBindingRepository(), undoAction, redoAction);
Expand All @@ -54,8 +54,10 @@ public SimpleEditor(final Field field,
new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textInput);
}

protected TextInputControl createTextInputControl() {
return isMultiLine ? new EditorTextArea() : new EditorTextField();
protected TextInputControl createTextInputControl(Field field) {
TextInputControl inputControl = isMultiLine ? new EditorTextArea() : new EditorTextField();
inputControl.setId(field.getName());
return inputControl;
}

@Override
Expand Down
Loading