Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -39,6 +39,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added a field for the latest ICORE conference ranking lookup on the General Tab. [#13476](https://github.com/JabRef/jabref/issues/13476)
- We added BibLaTeX datamodel validation support in order to improve error message quality in entries' fields validation. [#13318](https://github.com/JabRef/jabref/issues/13318)
- We added more supported formats of CAYW endpoint of HTTP server. [#13578](https://github.com/JabRef/jabref/issues/13578)
- We added chronological navigation for entries in each library. [#6352](https://github.com/JabRef/jabref/issues/6352)

### Changed

Expand Down
12 changes: 12 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/JabRefGuiStateManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public class JabRefGuiStateManager implements StateManager {
private final List<AiChatWindow> aiChatWindows = new ArrayList<>();
private final BooleanProperty editorShowing = new SimpleBooleanProperty(false);
private final OptionalObjectProperty<Walkthrough> activeWalkthrough = OptionalObjectProperty.empty();
private final BooleanProperty canGoBack = new SimpleBooleanProperty(false);
private final BooleanProperty canGoForward = new SimpleBooleanProperty(false);

@Override
public ObservableList<SidePaneType> getVisibleSidePaneComponents() {
Expand Down Expand Up @@ -307,4 +309,14 @@ public void setActiveWalkthrough(Walkthrough walkthrough) {
public Optional<Walkthrough> getActiveWalkthrough() {
return activeWalkthrough.get();
}

@Override
public BooleanProperty canGoBackProperty() {
return canGoBack;
}

@Override
public BooleanProperty canGoForwardProperty() {
return canGoForward;
}
}
65 changes: 65 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public class LibraryTab extends Tab implements CommandSelectionTab {
private final BibEntryTypesManager entryTypesManager;
private final BooleanProperty changedProperty = new SimpleBooleanProperty(false);
private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false);
private final NavigationHistory navigationHistory = new NavigationHistory();
private final BooleanProperty canGoBackProperty = new SimpleBooleanProperty(false);
private final BooleanProperty canGoForwardProperty = new SimpleBooleanProperty(false);
private boolean backOrForwardNavigationActionTriggered = false;


private BibDatabaseContext bibDatabaseContext;

Expand Down Expand Up @@ -490,6 +495,16 @@ private void createMainTable() {
mainTable.addSelectionListener(event -> {
List<BibEntry> entries = event.getList().stream().map(BibEntryTableViewModel::getEntry).toList();
stateManager.setSelectedEntries(entries);

// track navigation history for single selections
if (entries.size() == 1) {
newEntryShowing(entries.getFirst());
} else if (entries.isEmpty()) {
// an empty selection isn't a navigational step, so we don't alter the history list
// this avoids adding a "null" entry to the back/forward stack
// we just refresh the UI button states to ensure they are consistent with the latest history.
updateNavigationState();
}
});
}

Expand Down Expand Up @@ -964,6 +979,48 @@ public void resetChangedProperties() {
this.changedProperty.setValue(false);
}

public void back() {
navigationHistory.back().ifPresent(this::navigateToEntry);
}

public void forward() {
navigationHistory.forward().ifPresent(this::navigateToEntry);
}

private void navigateToEntry(BibEntry entry) {
backOrForwardNavigationActionTriggered = true;
clearAndSelect(entry);
updateNavigationState();
}

public boolean canGoBack() {
return navigationHistory.canGoBack();
}

public boolean canGoForward() {
return navigationHistory.canGoForward();
}

private void newEntryShowing(BibEntry entry) {
// skip history updates if this is from a back/forward operation
if (backOrForwardNavigationActionTriggered) {
backOrForwardNavigationActionTriggered = false;
return;
}

navigationHistory.add(entry);
updateNavigationState();
}

/**
* Updates the StateManager with current navigation state
* Only update if this is the active tab
*/
public void updateNavigationState() {
canGoBackProperty.set(canGoBack());
canGoForwardProperty.set(canGoForward());
}

/**
* Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most of the other parameters are required by {@code resetChangeMonitor()}.
*
Expand Down Expand Up @@ -1034,6 +1091,14 @@ public static LibraryTab createLibraryTab(@NonNull BibDatabaseContext databaseCo
false);
}

public BooleanProperty canGoBackProperty() {
return canGoBackProperty;
}

public BooleanProperty canGoForwardProperty() {
return canGoForwardProperty;
}

private class GroupTreeListener {

@Subscribe
Expand Down
77 changes: 77 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/NavigationHistory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.jabref.gui;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import org.jabref.model.entry.BibEntry;

/**
* Manages the navigation history of viewed entries using two stacks.
* This class encapsulates the logic of moving back and forward by maintaining a "back" stack for past entries
* and a "forward" stack for future entries.
*/
public class NavigationHistory {
private final List<BibEntry> previousEntries = new ArrayList<>();
private final List<BibEntry> nextEntries = new ArrayList<>();
Comment on lines +16 to +17
Copy link

Choose a reason for hiding this comment

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

Using plain ArrayList instead of JavaFX ObservableList prevents UI from automatically updating when the collection changes. This impacts reactive UI updates.

private BibEntry currentEntry;

/**
* Sets a new entry as the current one, clearing the forward history.
* The previously current entry is moved to the back stack.
*
* @param entry The BibEntry to add to the history.
*/
public void add(BibEntry entry) {
if (Objects.equals(currentEntry, entry)) {
return;
}

// a new selection invalidates the forward history
Copy link

Choose a reason for hiding this comment

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

Comment merely restates what the code does (clearing nextEntries). It doesn't provide additional information about why this is necessary.

nextEntries.clear();

if (currentEntry != null) {
previousEntries.add(currentEntry);
}
Comment on lines +34 to +36
Copy link

Choose a reason for hiding this comment

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

Null check for currentEntry indicates possible null state handling. Methods should use Optional instead of null for better type safety and clarity.

currentEntry = entry;
}

/**
* Moves to the previous entry in the history.
* The current entry is pushed to the forward stack, and the last entry from the back stack becomes current.
*
* @return An Optional containing the previous BibEntry, or an empty Optional if there's no history to go back to.
*/
public Optional<BibEntry> back() {
if (canGoBack()) {
nextEntries.add(currentEntry);
currentEntry = previousEntries.removeLast();
return Optional.of(currentEntry);
}
return Optional.empty();
}

/**
* Moves to the next entry in the history.
* The current entry is pushed to the back stack, and the last entry from the forward stack becomes current.
*
* @return An Optional containing the next BibEntry, or an empty Optional if there is no "forward" history.
*/
public Optional<BibEntry> forward() {
if (canGoForward()) {
previousEntries.add(currentEntry);
currentEntry = nextEntries.removeLast();
return Optional.of(currentEntry);
}
return Optional.empty();
}

public boolean canGoBack() {
return !previousEntries.isEmpty();
}

public boolean canGoForward() {
return !nextEntries.isEmpty();
}
}
4 changes: 4 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/StateManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,8 @@ public interface StateManager extends SrvStateManager {
void setActiveWalkthrough(Walkthrough walkthrough);

Optional<Walkthrough> getActiveWalkthrough();

BooleanProperty canGoBackProperty();

BooleanProperty canGoForwardProperty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ public enum StandardActions implements Action {
MANAGE_KEYWORDS(Localization.lang("Manage keywords")),
MASS_SET_FIELDS(Localization.lang("Manage field names & content")),

BACK(Localization.lang("Back"), IconTheme.JabRefIcons.LEFT, KeyBinding.BACK),
FORWARD(Localization.lang("Forward"), Localization.lang("Forward"), IconTheme.JabRefIcons.RIGHT, KeyBinding.FORWARD),

AUTOMATIC_FIELD_EDITOR(Localization.lang("Automatic field editor")),
TOGGLE_GROUPS(Localization.lang("Groups"), IconTheme.JabRefIcons.TOGGLE_GROUPS, KeyBinding.TOGGLE_GROUPS_INTERFACE),
TOGGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION),
Expand Down
25 changes: 25 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
Expand Down Expand Up @@ -371,6 +372,14 @@ private void initKeyBindings() {
case NEW_INPROCEEDINGS:
new NewEntryAction(StandardEntryType.InProceedings, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute();
break;
case BACK:
Optional.ofNullable(getCurrentLibraryTab()).ifPresent(LibraryTab::back);
event.consume();
break;
case FORWARD:
Optional.ofNullable(getCurrentLibraryTab()).ifPresent(LibraryTab::forward);
event.consume();
break;
default:
}
}
Expand Down Expand Up @@ -452,6 +461,22 @@ private void initBindings() {
// Hide tab bar
stateManager.getOpenDatabases().addListener((ListChangeListener<BibDatabaseContext>) _ -> updateTabBarVisible());
EasyBind.subscribe(preferences.getWorkspacePreferences().hideTabBarProperty(), _ -> updateTabBarVisible());

stateManager.canGoBackProperty().bind(
stateManager.activeTabProperty().flatMap(
optionalTab -> optionalTab
.map(LibraryTab::canGoBackProperty)
.orElse(new SimpleBooleanProperty(false))
)
);

stateManager.canGoForwardProperty().bind(
stateManager.activeTabProperty().flatMap(
optionalTab -> optionalTab
.map(LibraryTab::canGoForwardProperty)
.orElse(new SimpleBooleanProperty(false))
)
);
}

private void updateTabBarVisible() {
Expand Down
38 changes: 38 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/frame/MainToolBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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.citationkeypattern.GenerateCitationKeyAction;
import org.jabref.gui.cleanup.CleanupAction;
Expand Down Expand Up @@ -57,6 +58,8 @@ public class MainToolBar extends ToolBar {
private final TaskExecutor taskExecutor;
private final BibEntryTypesManager entryTypesManager;
private final ClipBoardManager clipBoardManager;
private SimpleCommand backCommand;
private SimpleCommand forwardCommand;
private final CountingUndoManager undoManager;

private PopOver entryFromIdPopOver;
Expand Down Expand Up @@ -99,6 +102,7 @@ private void createToolBar() {

final Button pushToApplicationButton = factory.createIconButton(pushToApplicationCommand.getAction(), pushToApplicationCommand);
pushToApplicationCommand.registerReconfigurable(pushToApplicationButton);
initNavigationCommands();

// Setup Toolbar

Expand All @@ -121,6 +125,12 @@ private void createToolBar() {

new Separator(Orientation.VERTICAL),

new HBox(
factory.createIconButton(StandardActions.BACK, backCommand),
factory.createIconButton(StandardActions.FORWARD, forwardCommand)),

new Separator(Orientation.VERTICAL),

new HBox(
factory.createIconButton(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)),
factory.createIconButton(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)),
Expand Down Expand Up @@ -208,4 +218,32 @@ Group createTaskIndicator() {

return new Group(indicator);
}

private void initNavigationCommands() {
backCommand = new SimpleCommand() {
{
executable.bind(stateManager.canGoBackProperty());
}

@Override
public void execute() {
if (frame.getCurrentLibraryTab() != null) {
frame.getCurrentLibraryTab().back();
}
}
};

forwardCommand = new SimpleCommand() {
{
executable.bind(stateManager.canGoForwardProperty());
}

@Override
public void execute() {
if (frame.getCurrentLibraryTab() != null) {
frame.getCurrentLibraryTab().forward();
}
}
};
}
}
3 changes: 3 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ public enum KeyBinding {
IMPORT_INTO_NEW_DATABASE("Import into new library", Localization.lang("Import into new library"), "ctrl+alt+I", KeyBindingCategory.FILE),
MERGE_ENTRIES("Merge entries", Localization.lang("Merge entries"), "ctrl+M", KeyBindingCategory.TOOLS),

BACK("Back", Localization.lang("Back"), "alt+LEFT", KeyBindingCategory.VIEW),
FORWARD("Forward", Localization.lang("Forward"), "alt+RIGHT", KeyBindingCategory.VIEW),

ADD_ENTRY("Add entry", Localization.lang("Add entry"), "ctrl+N", KeyBindingCategory.BIBTEX),
ADD_ENTRY_IDENTIFIER("Enter identifier", Localization.lang("Enter identifier"), "ctrl+alt+shift+N", KeyBindingCategory.BIBTEX),
ADD_ENTRY_PLAINTEXT("Interpret citations", Localization.lang("Interpret citations"), "ctrl+shift+N", KeyBindingCategory.BIBTEX),
Expand Down
3 changes: 3 additions & 0 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3401,3 +3401,6 @@ Commit\ aborted\:\ Local\ repository\ has\ unresolved\ merge\ conflicts.=Commit
Commit\ aborted\:\ Path\ is\ not\ inside\ a\ Git\ repository.=Commit aborted: Path is not inside a Git repository.
Commit\ aborted\:\ The\ file\ is\ not\ under\ Git\ version\ control.=Commit aborted: The file is not under Git version control.
Update\ references=Update references

Back=Back
Forward=Forward
Loading