diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index b621e4cb97f..e17855652dc 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -265,7 +265,10 @@ private void openWindow() { } public void onShowing(WindowEvent event) { - Platform.runLater(() -> mainFrame.updateDividerPosition()); + Platform.runLater(() -> { + mainFrame.updateHorizontalDividerPosition(); + mainFrame.updateVerticalDividerPosition(); + }); // Open last edited databases if (uiCommands.stream().noneMatch(UiCommand.BlankWorkspace.class::isInstance) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index cf00d78197a..74317471573 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -7,7 +7,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Random; -import java.util.function.Supplier; import java.util.stream.Collectors; import javax.swing.undo.UndoManager; @@ -23,13 +22,11 @@ import javafx.beans.value.ObservableBooleanValue; import javafx.collections.ListChangeListener; import javafx.event.Event; -import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.ProgressIndicator; -import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.Tooltip; @@ -45,7 +42,6 @@ import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.dialogs.AutosaveUiManager; -import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.externalfiles.ImportHandler; import org.jabref.gui.fieldeditors.LinkedFileViewModel; @@ -57,8 +53,6 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.RedoAction; -import org.jabref.gui.undo.UndoAction; import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.undo.UndoableInsertEntries; import org.jabref.gui.undo.UndoableRemoveEntries; @@ -91,7 +85,6 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.FieldChangedEvent; -import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.search.query.SearchQuery; @@ -112,11 +105,6 @@ * Represents the ui area where the notifier pane, the library table and the entry editor are shown. */ public class LibraryTab extends Tab { - /** - * Defines the different modes that the tab can operate in - */ - private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } - private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); private final LibraryTabContainer tabContainer; private final CountingUndoManager undoManager; @@ -131,10 +119,7 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private BibDatabaseContext bibDatabaseContext; private MainTableDataModel tableModel; private FileAnnotationCache annotationCache; - private EntryEditor entryEditor; private MainTable mainTable; - private PanelMode mode = PanelMode.MAIN_TABLE; - private SplitPane splitPane; private DatabaseNotification databaseNotificationPane; // Indicates whether the tab is loading data using a dataloading task @@ -225,7 +210,7 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { bibDatabaseContext.getMetaData().registerListener(this); this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), selectedGroupsProperty(), searchQueryProperty, resultSizeProperty()); new CitationStyleCache(bibDatabaseContext); annotationCache = new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); @@ -242,7 +227,6 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { setupAutoCompletion(); this.getDatabase().registerListener(new IndexUpdateListener()); - this.getDatabase().registerListener(new EntriesRemovedListener()); // ensure that at each addition of a new entry, the entry is added to the groups interface this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); @@ -251,8 +235,6 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { this.getDatabase().registerListener(new UpdateTimestampListener(preferences)); - this.entryEditor = createEntryEditor(); - aiService.setupDatabase(bibDatabaseContext); Platform.runLater(() -> { @@ -262,14 +244,6 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { }); } - private EntryEditor createEntryEditor() { - Supplier tabSupplier = () -> this; - return new EntryEditor(this, - // Actions are recreated here since this avoids passing more parameters and the amount of additional memory consumption is neglegtable. - new UndoAction(tabSupplier, undoManager, dialogService, stateManager), - new RedoAction(tabSupplier, undoManager, dialogService, stateManager)); - } - private static void addChangedInformation(StringBuilder text, String fileName) { text.append("\n"); text.append(Localization.lang("Library '%0' has changed.", fileName)); @@ -478,14 +452,6 @@ public void registerUndoableChanges(List changes) { } } - public void editEntryAndFocusField(BibEntry entry, Field field) { - showAndEdit(entry); - Platform.runLater(() -> { - // Focus field and entry in main table (async to give entry editor time to load) - entryEditor.setFocusToField(field); - }); - } - private void createMainTable() { mainTable = new MainTable(tableModel, this, @@ -499,34 +465,21 @@ private void createMainTable() { entryTypesManager, taskExecutor, importHandler); + // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) // content binding between StateManager#getselectedEntries and mainTable#getSelectedEntries does not work here as it does not trigger the ActionHelper#needsEntriesSelected checker for the menubar mainTable.addSelectionListener(event -> { List entries = event.getList().stream().map(BibEntryTableViewModel::getEntry).toList(); stateManager.setSelectedEntries(entries); - if (!entries.isEmpty()) { - // Update entry editor and preview according to selected entries - entryEditor.setCurrentlyEditedEntry(entries.getFirst()); - } }); } public void setupMainPanel() { - splitPane = new SplitPane(); - splitPane.setOrientation(Orientation.VERTICAL); - createMainTable(); - splitPane.getItems().add(mainTable); - databaseNotificationPane = new DatabaseNotification(splitPane); + databaseNotificationPane = new DatabaseNotification(mainTable); setContent(databaseNotificationPane); - // Saves the divider position as soon as it changes - // We need to keep a reference to the subscription, otherwise the binding gets garbage collected - dividerPositionSubscription = EasyBind.valueAt(splitPane.getDividers(), 0) - .mapObservable(SplitPane.Divider::positionProperty) - .subscribeToValues(this::saveDividerLocation); - // Add changePane in case a file is present - otherwise just add the splitPane to the panel Optional file = bibDatabaseContext.getDatabasePath(); if (file.isPresent()) { @@ -562,37 +515,9 @@ public SuggestionProvider getAutoCompleter() { return searchAutoCompleter; } - public EntryEditor getEntryEditor() { - return entryEditor; - } - - /** - * Sets the entry editor as the bottom component in the split pane. If an entry editor already was shown, makes sure that the divider doesn't move. Updates the mode to {@link PanelMode#MAIN_TABLE_AND_ENTRY_EDITOR}. - * Then shows the given entry. - * - * Additionally, selects the entry in the main table - so that the selected entry in the main table always corresponds to the edited entry. - * - * @param entry The entry to edit. - */ public void showAndEdit(BibEntry entry) { this.clearAndSelect(entry); - if (!splitPane.getItems().contains(entryEditor)) { - splitPane.getItems().addLast(entryEditor); - mode = PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR; - splitPane.setDividerPositions(preferences.getEntryEditorPreferences().getDividerPosition()); - } - - // We use != instead of equals because of performance reasons - if (entry != showing) { - entryEditor.setCurrentlyEditedEntry(entry); - showing = entry; - } - entryEditor.requestFocus(); - } - - public void closeBottomPane() { - mode = PanelMode.MAIN_TABLE; - splitPane.getItems().remove(entryEditor); + stateManager.getEditorShowing().setValue(true); } /** @@ -610,25 +535,6 @@ public void selectNextEntry() { mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() + 1); } - /** - * This method is called from an EntryEditor when it should be closed. We relay to the selection listener, which takes care of the rest. - */ - public void entryEditorClosing() { - closeBottomPane(); - mainTable.requestFocus(); - } - - /** - * Closes the entry editor if it is showing any of the given entries. - */ - private void ensureNotShowingBottomPanel(List entriesToCheck) { - // This method is not able to close the bottom pane currently - - if ((mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) && (entriesToCheck.contains(entryEditor.getCurrentlyEditedEntry()))) { - closeBottomPane(); - } - } - /** * Put an asterisk behind the filename to indicate the database has changed. */ @@ -677,15 +583,6 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { } } - /** - * Depending on whether a preview or an entry editor is showing, save the current divider location in the correct preference setting. - */ - private void saveDividerLocation(Number position) { - if (mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) { - preferences.getEntryEditorPreferences().setDividerPosition(position.doubleValue()); - } - } - public boolean requestClose() { if (bibDatabaseContext.getLocation() == DatabaseLocation.LOCAL) { if (isModified()) { @@ -890,6 +787,7 @@ public void insertEntries(final List entries) { importHandler.importCleanedEntries(entries); getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); markBaseChanged(); + stateManager.setSelectedEntries(entries); if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { showAndEdit(entries.getFirst()); } else { @@ -1015,7 +913,6 @@ private int doDeleteEntry(StandardActions mode, List entries) { } } - ensureNotShowingBottomPanel(entries); markBaseChanged(); // prevent the main table from loosing focus @@ -1131,14 +1028,6 @@ public void listen(EntriesAddedEvent addedEntriesEvent) { } } - private class EntriesRemovedListener { - - @Subscribe - public void listen(EntriesRemovedEvent entriesRemovedEvent) { - ensureNotShowingBottomPanel(entriesRemovedEvent.getBibEntries()); - } - } - private class IndexUpdateListener { @Subscribe @@ -1186,8 +1075,4 @@ public String toString() { ", showing=" + showing + '}'; } - - public LibraryTabContainer getLibraryTabContainer() { - return tabContainer; - } } diff --git a/src/main/java/org/jabref/gui/LibraryTabContainer.java b/src/main/java/org/jabref/gui/LibraryTabContainer.java index 139653c87ca..b572d190e6d 100644 --- a/src/main/java/org/jabref/gui/LibraryTabContainer.java +++ b/src/main/java/org/jabref/gui/LibraryTabContainer.java @@ -2,6 +2,8 @@ import java.util.List; +import javafx.collections.ObservableList; + import org.jabref.model.database.BibDatabaseContext; import org.jspecify.annotations.NonNull; @@ -10,7 +12,7 @@ @NullMarked public interface LibraryTabContainer { - List getLibraryTabs(); + ObservableList getLibraryTabs(); @Nullable LibraryTab getCurrentLibraryTab(); diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 435c018619d..3cb34cd17f8 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -8,8 +8,10 @@ import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; @@ -78,6 +80,7 @@ public class StateManager { private final ObjectProperty lastAutomaticFieldEditorEdit = new SimpleObjectProperty<>(); private final ObservableList searchHistory = FXCollections.observableArrayList(); private final List aiChatWindows = new ArrayList<>(); + private final BooleanProperty editorShowing = new SimpleBooleanProperty(false); public ObservableList getVisibleSidePaneComponents() { return visibleSidePanes; @@ -231,4 +234,8 @@ public void clearSearchHistory() { public List getAiChatWindows() { return aiChatWindows; } + + public BooleanProperty getEditorShowing() { + return editorShowing; + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index ab1bb640dfe..7ec9042f9f3 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -2,6 +2,7 @@ import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -11,9 +12,11 @@ import java.util.Optional; import java.util.Set; import java.util.SortedSet; +import java.util.function.Supplier; import java.util.stream.Collectors; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.fxml.FXML; import javafx.geometry.Side; import javafx.scene.control.Button; @@ -43,6 +46,7 @@ import org.jabref.gui.menus.ChangeEntryTypeMenu; import org.jabref.gui.mergeentries.FetchAndMergeEntry; import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.preview.PreviewControls; import org.jabref.gui.preview.PreviewPanel; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.undo.CountingUndoManager; @@ -72,8 +76,6 @@ import com.tobiasdiez.easybind.Subscription; import jakarta.inject.Inject; import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * GUI component that allows editing of the fields of a BibEntry (i.e. the one that shows up, when you double click on @@ -86,16 +88,10 @@ *

* The editors for fields are created via {@link org.jabref.gui.fieldeditors.FieldEditors}. */ -public class EntryEditor extends BorderPane { - - private static final Logger LOGGER = LoggerFactory.getLogger(EntryEditor.class); - - private final LibraryTab libraryTab; - private final BibDatabaseContext databaseContext; - private final EntryEditorPreferences entryEditorPreferences; +public class EntryEditor extends BorderPane implements PreviewControls { + private final Supplier tabSupplier; private final ExternalFilesEntryLinker fileLinker; private final PreviewPanel previewPanel; - private final DirectoryMonitorManager directoryMonitorManager; private final UndoAction undoAction; private final RedoAction redoAction; @@ -124,12 +120,10 @@ public class EntryEditor extends BorderPane { @Inject private JournalAbbreviationRepository journalAbbreviationRepository; @Inject private AiService aiService; - private final List allPossibleTabs; + private final List allPossibleTabs = new ArrayList<>(); - public EntryEditor(LibraryTab libraryTab, UndoAction undoAction, RedoAction redoAction) { - this.libraryTab = libraryTab; - this.databaseContext = libraryTab.getBibDatabaseContext(); - this.directoryMonitorManager = libraryTab.getDirectoryMonitorManager(); + public EntryEditor(Supplier tabSupplier, UndoAction undoAction, RedoAction redoAction) { + this.tabSupplier = tabSupplier; this.undoAction = undoAction; this.redoAction = redoAction; @@ -137,21 +131,37 @@ public EntryEditor(LibraryTab libraryTab, UndoAction undoAction, RedoAction redo .root(this) .load(); - this.entryEditorPreferences = preferences.getEntryEditorPreferences(); - this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), dialogService, stateManager); + this.fileLinker = new ExternalFilesEntryLinker( + preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), + dialogService, + stateManager); + this.previewPanel = new PreviewPanel( dialogService, preferences.getKeyBindingRepository(), preferences, themeManager, taskExecutor, - stateManager, - libraryTab.searchQueryProperty()); - this.previewPanel.setDatabase(databaseContext); + stateManager); setupKeyBindings(); - this.allPossibleTabs = createTabs(); + EasyBind.subscribe(stateManager.activeTabProperty(), tab -> { + if (tab.isPresent()) { + this.previewPanel.setDatabase(tab.get().getBibDatabaseContext()); + + tabbed.getTabs().clear(); + + this.allPossibleTabs.clear(); + this.allPossibleTabs.addAll(createTabs(tab.get())); + + adaptVisibleTabs(); + } else { + this.previewPanel.setDatabase(null); + this.allPossibleTabs.clear(); + } + }); setupDragAndDrop(); @@ -162,6 +172,15 @@ public EntryEditor(LibraryTab libraryTab, UndoAction undoAction, RedoAction redo } }); + stateManager.getSelectedEntries().addListener((InvalidationListener) c -> { + if (stateManager.getSelectedEntries().isEmpty()) { + setCurrentlyEditedEntry(new BibEntry()); + } else { + setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst()); + } + } + ); + EasyBind.listen(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), (obs, oldValue, newValue) -> { if (currentlyEditedEntry != null) { @@ -222,11 +241,11 @@ private void setupKeyBindings() { event.consume(); break; case ENTRY_EDITOR_NEXT_ENTRY: - libraryTab.selectNextEntry(); + tabSupplier.get().selectNextEntry(); event.consume(); break; case ENTRY_EDITOR_PREVIOUS_ENTRY: - libraryTab.selectPreviousEntry(); + tabSupplier.get().selectPreviousEntry(); event.consume(); break; case HELP: @@ -250,40 +269,42 @@ private void setupKeyBindings() { } @FXML - public void close() { - libraryTab.entryEditorClosing(); + private void close() { + stateManager.getEditorShowing().set(false); } @FXML private void deleteEntry() { - libraryTab.deleteEntry(currentlyEditedEntry); + tabSupplier.get().deleteEntry(currentlyEditedEntry); } @FXML - void generateCiteKeyButton() { - GenerateCitationKeySingleAction action = new GenerateCitationKeySingleAction(getCurrentlyEditedEntry(), databaseContext, + private void generateCiteKeyButton() { + GenerateCitationKeySingleAction action = new GenerateCitationKeySingleAction(getCurrentlyEditedEntry(), tabSupplier.get().getBibDatabaseContext(), dialogService, preferences, undoManager); action.execute(); } @FXML - void generateCleanupButton() { + private void generateCleanupButton() { CleanupSingleAction action = new CleanupSingleAction(getCurrentlyEditedEntry(), preferences, dialogService, stateManager, undoManager); action.execute(); } @FXML private void navigateToPreviousEntry() { - libraryTab.selectPreviousEntry(); + tabSupplier.get().selectPreviousEntry(); } @FXML private void navigateToNextEntry() { - libraryTab.selectNextEntry(); + tabSupplier.get().selectNextEntry(); } - private List createTabs() { + private List createTabs(LibraryTab libraryTab) { List tabs = new LinkedList<>(); + BibDatabaseContext databaseContext = libraryTab.getBibDatabaseContext(); + DirectoryMonitorManager directoryMonitorManager = libraryTab.getDirectoryMonitorManager(); tabs.add(new PreviewTab(databaseContext, preferences, previewPanel)); @@ -335,7 +356,7 @@ private List createTabs() { * @return Map of tab names and the fields to show in them. */ private Map> getAdditionalUserConfiguredTabs() { - Map> entryEditorTabList = new HashMap<>(entryEditorPreferences.getEntryEditorTabs()); + Map> entryEditorTabList = new HashMap<>(preferences.getEntryEditorPreferences().getEntryEditorTabs()); // Same order as in org.jabref.gui.entryeditor.EntryEditor.createTabs before the call of getAdditionalUserConfiguredTabs entryEditorTabList.remove(PreviewTab.NAME); @@ -368,6 +389,11 @@ private void adaptVisibleTabs() { // the tabs give an ugly animation the looks like all tabs are shifting in from the right. In other words: // This hack is required since tabbed.getTabs().setAll(visibleTabs) changes the order of the tabs in the editor + if (currentlyEditedEntry == null) { + tabbed.getTabs().clear(); + return; + } + // First, remove tabs that we do not want to show List toBeRemoved = allPossibleTabs.stream().filter(tab -> !tab.shouldShow(currentlyEditedEntry)).toList(); tabbed.getTabs().removeAll(toBeRemoved); @@ -404,14 +430,17 @@ public void setCurrentlyEditedEntry(@NonNull BibEntry currentlyEditedEntry) { // Remove subscription for old entry if existing typeSubscription.unsubscribe(); } - typeSubscription = EasyBind.subscribe(this.currentlyEditedEntry.typeProperty(), type -> { - typeLabel.setText(new TypedBibEntry(currentlyEditedEntry, databaseContext.getMode()).getTypeForDisplay()); - adaptVisibleTabs(); - setupToolBar(); - getSelectedTab().notifyAboutFocus(currentlyEditedEntry); - }); - if (entryEditorPreferences.showSourceTabByDefault()) { + if (!currentlyEditedEntry.isEmpty()) { + typeSubscription = EasyBind.subscribe(this.currentlyEditedEntry.typeProperty(), type -> { + typeLabel.setText(new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()).getTypeForDisplay()); + adaptVisibleTabs(); + setupToolBar(); + getSelectedTab().notifyAboutFocus(currentlyEditedEntry); + }); + } + + if (preferences.getEntryEditorPreferences().showSourceTabByDefault()) { tabbed.getSelectionModel().select(sourceTab); } } @@ -422,11 +451,11 @@ private EntryEditorTab getSelectedTab() { private void setupToolBar() { // Update type label - TypedBibEntry typedEntry = new TypedBibEntry(currentlyEditedEntry, databaseContext.getMode()); + TypedBibEntry typedEntry = new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()); typeLabel.setText(typedEntry.getTypeForDisplay()); // Add type change menu - ContextMenu typeMenu = new ChangeEntryTypeMenu(Collections.singletonList(currentlyEditedEntry), databaseContext, undoManager, bibEntryTypesManager).asContextMenu(); + ContextMenu typeMenu = new ChangeEntryTypeMenu(Collections.singletonList(currentlyEditedEntry), tabSupplier.get().getBibDatabaseContext(), undoManager, bibEntryTypesManager).asContextMenu(); typeLabel.setOnMouseClicked(event -> typeMenu.show(typeLabel, Side.RIGHT, 0, 0)); typeChangeButton.setOnMouseClicked(event -> typeMenu.show(typeChangeButton, Side.RIGHT, 0, 0)); @@ -436,7 +465,7 @@ private void setupToolBar() { preferences.getImporterPreferences(), preferences.getImportFormatPreferences(), preferences.getFilePreferences(), - databaseContext); + tabSupplier.get().getBibDatabaseContext()); for (EntryBasedFetcher fetcher : entryBasedFetchers) { MenuItem fetcherMenuItem = new MenuItem(fetcher.getName()); if (fetcher instanceof PdfMergeMetadataImporter.EntryBasedFetcherWrapper) { @@ -447,7 +476,7 @@ private void setupToolBar() { new PdfMergeMetadataImporter.EntryBasedFetcherWrapper( preferences.getImportFormatPreferences(), preferences.getFilePreferences(), - databaseContext); + tabSupplier.get().getBibDatabaseContext()); fetchAndMerge(pdfMergeMetadataImporter); }); } else { @@ -460,7 +489,7 @@ private void setupToolBar() { } private void fetchAndMerge(EntryBasedFetcher fetcher) { - new FetchAndMergeEntry(libraryTab.getBibDatabaseContext(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(currentlyEditedEntry, fetcher); + new FetchAndMergeEntry(tabSupplier.get().getBibDatabaseContext(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(currentlyEditedEntry, fetcher); } public void setFocusToField(Field field) { @@ -490,10 +519,12 @@ public void setFocusToField(Field field) { }); } + @Override public void nextPreviewStyle() { this.previewPanel.nextPreviewStyle(); } + @Override public void previousPreviewStyle() { this.previewPanel.previousPreviewStyle(); } diff --git a/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java b/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java index 36758215401..5dbfd0690ee 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java +++ b/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java @@ -16,7 +16,7 @@ public OpenEntryEditorAction(Supplier tabSupplier, StateManager stat this.tabSupplier = tabSupplier; this.stateManager = stateManager; - this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); + this.executable.bind(ActionHelper.needsEntriesSelected(stateManager).and(stateManager.getEditorShowing().not())); } public void execute() { diff --git a/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java b/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java index aa98a5b7e24..1c75a8fbba8 100644 --- a/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java +++ b/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java @@ -1,10 +1,8 @@ package org.jabref.gui.entryeditor; -import java.util.function.Supplier; - -import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.preview.PreviewControls; import static org.jabref.gui.actions.ActionHelper.needsDatabase; @@ -12,11 +10,11 @@ public class PreviewSwitchAction extends SimpleCommand { public enum Direction { PREVIOUS, NEXT } - private final Supplier tabSupplier; + private final PreviewControls previewControls; private final Direction direction; - public PreviewSwitchAction(Direction direction, Supplier tabSupplier, StateManager stateManager) { - this.tabSupplier = tabSupplier; + public PreviewSwitchAction(Direction direction, PreviewControls previewControls, StateManager stateManager) { + this.previewControls = previewControls; this.direction = direction; this.executable.bind(needsDatabase(stateManager)); @@ -25,9 +23,9 @@ public PreviewSwitchAction(Direction direction, Supplier tabSupplier @Override public void execute() { if (direction == Direction.NEXT) { - tabSupplier.get().getEntryEditor().nextPreviewStyle(); + previewControls.nextPreviewStyle(); } else { - tabSupplier.get().getEntryEditor().previousPreviewStyle(); + previewControls.previousPreviewStyle(); } } } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java index bb679b77001..1056a6465ec 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -11,10 +11,14 @@ import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; -import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.event.Event; +import javafx.geometry.Orientation; import javafx.scene.control.ContextMenu; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SplitPane; @@ -35,6 +39,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.desktop.os.NativeDesktop; +import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.importer.NewEntryAction; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.keyboard.KeyBinding; @@ -46,6 +51,9 @@ import org.jabref.gui.sidepane.SidePane; import org.jabref.gui.sidepane.SidePaneType; import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.undo.RedoAction; +import org.jabref.gui.undo.UndoAction; +import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.UiCommand; import org.jabref.logic.ai.AiService; import org.jabref.logic.journals.JournalAbbreviationRepository; @@ -71,12 +79,15 @@ * Represents the inner frame of the JabRef window */ public class JabRefFrame extends BorderPane implements LibraryTabContainer, UiMessageHandler { + /** + * Defines the different modes that the tab can operate in + */ + private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } public static final String FRAME_TITLE = "JabRef"; private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); - private final SplitPane splitPane = new SplitPane(); private final GuiPreferences preferences; private final AiService aiService; private final GlobalSearchBar globalSearchBar; @@ -96,10 +107,16 @@ public class JabRefFrame extends BorderPane implements LibraryTabContainer, UiMe private final JabRefFrameViewModel viewModel; private final PushToApplicationCommand pushToApplicationCommand; + private final SplitPane horizontalSplit = new SplitPane(); private final SidePane sidePane; + private final SplitPane verticalSplit = new SplitPane(); private final TabPane tabbedPane = new TabPane(); + private final EntryEditor entryEditor; + private final ObjectProperty panelMode = new SimpleObjectProperty<>(PanelMode.MAIN_TABLE); - private Subscription dividerSubscription; + // We need to keep a reference to the subscription, otherwise the binding gets garbage collected + private Subscription horizontalDividerSubscription; + private Subscription verticalDividerSubscription; public JabRefFrame(Stage mainStage, DialogService dialogService, @@ -165,6 +182,12 @@ public JabRefFrame(Stage mainStage, clipBoardManager, undoManager); + this.entryEditor = new EntryEditor(this::getCurrentLibraryTab, + // Actions are recreated here since this avoids passing more parameters and the amount of additional memory consumption is neglegtable. + new UndoAction(this::getCurrentLibraryTab, undoManager, dialogService, stateManager), + new RedoAction(this::getCurrentLibraryTab, undoManager, dialogService, stateManager)); + Injector.setModelOrService(EntryEditor.class, entryEditor); + this.pushToApplicationCommand = new PushToApplicationCommand( stateManager, dialogService, @@ -219,38 +242,71 @@ private void initLayout() { undoManager, clipBoardManager, this::getOpenDatabaseAction, - aiService); + aiService, + entryEditor); VBox head = new VBox(mainMenu, mainToolBar); head.setSpacing(0d); setTop(head); - splitPane.getItems().addAll(tabbedPane); + verticalSplit.getItems().addAll(tabbedPane); + verticalSplit.setOrientation(Orientation.VERTICAL); + updateEditorPane(); + + horizontalSplit.getItems().addAll(verticalSplit); + horizontalSplit.setOrientation(Orientation.HORIZONTAL); + SplitPane.setResizableWithParent(sidePane, false); sidePane.widthProperty().addListener(_ -> updateSidePane()); sidePane.getChildren().addListener((InvalidationListener) _ -> updateSidePane()); updateSidePane(); - setCenter(splitPane); + setCenter(horizontalSplit); } private void updateSidePane() { if (sidePane.getChildren().isEmpty()) { - if (dividerSubscription != null) { - dividerSubscription.unsubscribe(); + if (horizontalDividerSubscription != null) { + horizontalDividerSubscription.unsubscribe(); + } + horizontalSplit.getItems().remove(sidePane); + } else { + if (!horizontalSplit.getItems().contains(sidePane)) { + horizontalSplit.getItems().addFirst(sidePane); + updateHorizontalDividerPosition(); } - splitPane.getItems().remove(sidePane); + } + } + + private void updateEditorPane() { + if (panelMode.get() == PanelMode.MAIN_TABLE) { + if (verticalDividerSubscription != null) { + verticalDividerSubscription.unsubscribe(); + } + verticalSplit.getItems().remove(entryEditor); } else { - if (!splitPane.getItems().contains(sidePane)) { - splitPane.getItems().addFirst(sidePane); - updateDividerPosition(); + if (!verticalSplit.getItems().contains(entryEditor)) { + verticalSplit.getItems().addLast(entryEditor); + updateVerticalDividerPosition(); } } } - public void updateDividerPosition() { + public void updateHorizontalDividerPosition() { if (mainStage.isShowing() && !sidePane.getChildren().isEmpty()) { - splitPane.setDividerPositions(preferences.getGuiPreferences().getSidePaneWidth() / splitPane.getWidth()); - dividerSubscription = EasyBind.listen(sidePane.widthProperty(), (_, _, newVal) -> preferences.getGuiPreferences().setSidePaneWidth(newVal.doubleValue())); + horizontalSplit.setDividerPositions(preferences.getGuiPreferences().getSidePaneWidth() / horizontalSplit.getWidth()); + horizontalDividerSubscription = EasyBind.valueAt(horizontalSplit.getDividers(), 0) + .mapObservable(SplitPane.Divider::positionProperty) + .listenToValues((_, newValue) -> preferences.getGuiPreferences().setSidePaneWidth(newValue.doubleValue())); + } + } + + public void updateVerticalDividerPosition() { + if (mainStage.isShowing() && panelMode.get() == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) { + verticalSplit.setDividerPositions(preferences.getEntryEditorPreferences().getDividerPosition()); + // ToDo: Move DividerPosition to GuiPreferences + verticalDividerSubscription = EasyBind.valueAt(verticalSplit.getDividers(), 0) + .mapObservable(SplitPane.Divider::positionProperty) + .listenToValues((oldValue, newValue) -> preferences.getEntryEditorPreferences().setDividerPosition(newValue.doubleValue())); } } @@ -350,47 +406,41 @@ private void initBindings() { if (selectedTab instanceof LibraryTab libraryTab) { stateManager.setActiveDatabase(libraryTab.getBibDatabaseContext()); stateManager.activeTabProperty().set(Optional.of(libraryTab)); - } else if (selectedTab == null) { - // All databases are closed + stateManager.setSelectedEntries(libraryTab.getSelectedEntries()); + + // Update active search query when switching between databases + if (preferences.getSearchPreferences().shouldKeepSearchString()) { + libraryTab.searchQueryProperty().set(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get()); + } else { + stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).set(libraryTab.searchQueryProperty().get()); + } + stateManager.searchResultSize(SearchType.NORMAL_SEARCH).bind(libraryTab.resultSizeProperty()); + globalSearchBar.setAutoCompleter(libraryTab.getAutoCompleter()); + + libraryTab.getMainTable().requestFocus(); + + // Set window title dynamically + mainStage.titleProperty().bind(Bindings.createStringBinding( + () -> libraryTab.textProperty().getValue() + " – " + FRAME_TITLE, // not a minus, but codepoint 2013 + libraryTab.textProperty())); + } else { + // All databases are closed or an unknown tab is selected stateManager.setActiveDatabase(null); stateManager.activeTabProperty().set(Optional.empty()); - } - }); - - /* - * The following state listener makes sure focus is registered with the - * correct database when the user switches tabs. Without this, - * cut/paste/copy operations would sometimes occur in the wrong tab. - */ - EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), tab -> { - if (!(tab instanceof LibraryTab libraryTab)) { stateManager.setSelectedEntries(Collections.emptyList()); mainStage.titleProperty().unbind(); mainStage.setTitle(FRAME_TITLE); - return; } + }); - // Poor-mans binding to global state - stateManager.setSelectedEntries(libraryTab.getSelectedEntries()); - - // Update active search query when switching between databases - if (preferences.getSearchPreferences().shouldKeepSearchString()) { - libraryTab.searchQueryProperty().set(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get()); - } else { - stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).set(libraryTab.searchQueryProperty().get()); + BindingsHelper.bindBidirectional((ObservableValue) stateManager.getEditorShowing(), panelMode, + mode -> stateManager.getEditorShowing().setValue(mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR), + showing -> panelMode.setValue(showing ? PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR : PanelMode.MAIN_TABLE)); + EasyBind.subscribe(panelMode, mode -> { + updateEditorPane(); + if (mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) { + entryEditor.requestFocus(); } - stateManager.searchResultSize(SearchType.NORMAL_SEARCH).bind(libraryTab.resultSizeProperty()); - - // Update search AutoCompleter with information for the correct database: - globalSearchBar.setAutoCompleter(libraryTab.getAutoCompleter()); - - libraryTab.getMainTable().requestFocus(); - - // Set window title - copy tab title - StringBinding windowTitle = Bindings.createStringBinding( - () -> libraryTab.textProperty().getValue() + " – " + FRAME_TITLE, // not a minus, but codepoint 2013 - libraryTab.textProperty()); - mainStage.titleProperty().bind(windowTitle); }); // Hide tab bar @@ -417,11 +467,8 @@ private void updateTabBarVisible() { /** * Returns a list of all LibraryTabs in this frame. */ - public @NonNull List getLibraryTabs() { - return tabbedPane.getTabs().stream() - .filter(LibraryTab.class::isInstance) - .map(LibraryTab.class::cast) - .toList(); + public @NonNull ObservableList getLibraryTabs() { + return EasyBind.map(tabbedPane.getTabs().filtered(LibraryTab.class::isInstance), LibraryTab.class::cast); } /** diff --git a/src/main/java/org/jabref/gui/frame/MainMenu.java b/src/main/java/org/jabref/gui/frame/MainMenu.java index a320bdd6348..6eb8daa9986 100644 --- a/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -57,6 +57,7 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.preferences.ShowPreferencesAction; import org.jabref.gui.preview.CopyCitationAction; +import org.jabref.gui.preview.PreviewControls; import org.jabref.gui.push.PushToApplicationCommand; import org.jabref.gui.search.RebuildFulltextSearchIndexAction; import org.jabref.gui.shared.ConnectToSharedDatabaseCommand; @@ -101,6 +102,7 @@ public class MainMenu extends MenuBar { private final ClipBoardManager clipBoardManager; private final Supplier openDatabaseActionSupplier; private final AiService aiService; + private final PreviewControls previewControls; public MainMenu(JabRefFrame frame, FileHistoryMenu fileHistoryMenu, @@ -116,7 +118,8 @@ public MainMenu(JabRefFrame frame, CountingUndoManager undoManager, ClipBoardManager clipBoardManager, Supplier openDatabaseActionSupplier, - AiService aiService) { + AiService aiService, + PreviewControls previewControls) { this.frame = frame; this.fileHistoryMenu = fileHistoryMenu; this.sidePane = sidePane; @@ -132,6 +135,7 @@ public MainMenu(JabRefFrame frame, this.clipBoardManager = clipBoardManager; this.openDatabaseActionSupplier = openDatabaseActionSupplier; this.aiService = aiService; + this.previewControls = previewControls; createMenu(); } @@ -330,8 +334,8 @@ private void createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.NEXT, frame::getCurrentLibraryTab, stateManager)), - factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.PREVIOUS, frame::getCurrentLibraryTab, stateManager)), + factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.NEXT, previewControls, stateManager)), + factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.PREVIOUS, previewControls, stateManager)), new SeparatorMenuItem(), diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java index 00b3cc32bc9..e252b802762 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.function.Function; +import javafx.application.Platform; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; @@ -14,6 +15,8 @@ import javafx.stage.Modality; import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; +import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ValueTableCellFactory; @@ -34,7 +37,9 @@ public class IntegrityCheckDialog extends BaseDialog { @FXML private MenuButton fieldFilterButton; @FXML private MenuButton messageFilterButton; + @Inject private EntryEditor entryEditor; @Inject private ThemeManager themeManager; + @Inject private StateManager stateManager; private final List messages; private final LibraryTab libraryTab; @@ -56,8 +61,14 @@ public IntegrityCheckDialog(List messages, LibraryTab libraryT private void onSelectionChanged(ListChangeListener.Change change) { if (change.next()) { - change.getAddedSubList().stream().findFirst().ifPresent(message -> - libraryTab.editEntryAndFocusField(message.entry(), message.field())); + change.getAddedSubList().stream().findFirst().ifPresent(message -> { + libraryTab.clearAndSelect(message.entry()); + + stateManager.getEditorShowing().setValue(true); + + // Focus field async to give entry editor time to load + Platform.runLater(() -> entryEditor.setFocusToField(message.field())); + }); } } diff --git a/src/main/java/org/jabref/gui/preview/PreviewControls.java b/src/main/java/org/jabref/gui/preview/PreviewControls.java new file mode 100644 index 00000000000..9b1eae12f3d --- /dev/null +++ b/src/main/java/org/jabref/gui/preview/PreviewControls.java @@ -0,0 +1,7 @@ +package org.jabref.gui.preview; + +public interface PreviewControls { + void nextPreviewStyle(); + + void previousPreviewStyle(); +} diff --git a/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/src/main/java/org/jabref/gui/preview/PreviewPanel.java index a1e27d45820..7c3d64c23f2 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -25,20 +25,19 @@ import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.search.SearchType; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.DragDrop; -import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.l10n.Localization; import org.jabref.logic.preview.PreviewLayout; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.model.search.query.SearchQuery; /// Displays the entry preview /// /// The instance is re-used at each tab. The code ensures that the panel is moved across tabs when the user switches the tab. -public class PreviewPanel extends VBox { +public class PreviewPanel extends VBox implements PreviewControls { private final ExternalFilesEntryLinker fileLinker; private final KeyBindingRepository keyBindingRepository; @@ -53,20 +52,19 @@ public PreviewPanel(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor, - StateManager stateManager, - OptionalObjectProperty searchQueryProperty) { + StateManager stateManager) { this.keyBindingRepository = keyBindingRepository; this.dialogService = dialogService; this.previewPreferences = preferences.getPreviewPreferences(); this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), dialogService, stateManager); PreviewPreferences previewPreferences = preferences.getPreviewPreferences(); - previewView = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor, searchQueryProperty); + previewView = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor, stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH)); previewView.setLayout(previewPreferences.getSelectedPreviewLayout()); previewView.setContextMenu(createPopupMenu()); previewView.setOnDragDetected(this::onDragDetected); previewView.setOnDragOver(PreviewPanel::onDragOver); - previewView.setOnDragDropped(event -> onDragDropped(event)); + previewView.setOnDragDropped(this::onDragDropped); this.getChildren().add(previewView); @@ -156,10 +154,12 @@ public void print() { previewView.print(); } + @Override public void nextPreviewStyle() { cyclePreview(previewPreferences.getLayoutCyclePosition() + 1); } + @Override public void previousPreviewStyle() { cyclePreview(previewPreferences.getLayoutCyclePosition() - 1); } diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index 6c70e7653f0..dc8f9d305f7 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -1,6 +1,7 @@ package org.jabref.gui.shared; import java.sql.SQLException; +import java.util.Collections; import java.util.Optional; import javax.swing.undo.UndoManager; @@ -15,7 +16,6 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; -import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.mergeentries.EntriesMergeResult; import org.jabref.gui.mergeentries.MergeEntriesDialog; @@ -142,16 +142,16 @@ public void listen(UpdateRefusedEvent updateRefusedEvent) { @Subscribe public void listen(SharedEntriesNotPresentEvent event) { LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); - EntryEditor entryEditor = libraryTab.getEntryEditor(); - libraryTab.getUndoManager().addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), event.bibEntries())); + if (libraryTab != null) { + undoManager.addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), event.bibEntries())); - if (entryEditor != null && (event.bibEntries().contains(entryEditor.getCurrentlyEditedEntry()))) { dialogService.showInformationDialogAndWait(Localization.lang("Shared entry is no longer present"), Localization.lang("The entry you currently work on has been deleted on the shared side.") + "\n" + Localization.lang("You can restore the entry using the \"Undo\" operation.")); - libraryTab.closeBottomPane(); + + stateManager.setSelectedEntries(Collections.emptyList()); } } diff --git a/src/test/java/org/jabref/gui/importer/NewEntryActionTest.java b/src/test/java/org/jabref/gui/importer/NewEntryActionTest.java index b6fe4ab5252..b87771b336c 100644 --- a/src/test/java/org/jabref/gui/importer/NewEntryActionTest.java +++ b/src/test/java/org/jabref/gui/importer/NewEntryActionTest.java @@ -1,6 +1,6 @@ package org.jabref.gui.importer; -import java.util.List; +import javafx.collections.FXCollections; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -40,7 +40,7 @@ void setUp() { void executeOnSuccessWithFixedType() { EntryType type = StandardEntryType.Article; newEntryAction = new NewEntryAction(() -> libraryTab, type, dialogService, preferences, stateManager); - when(tabContainer.getLibraryTabs()).thenReturn(List.of(libraryTab)); + when(tabContainer.getLibraryTabs()).thenReturn(FXCollections.observableArrayList(libraryTab)); newEntryAction.execute(); verify(libraryTab, times(1)).insertEntry(new BibEntry(type)); diff --git a/src/test/java/org/jabref/gui/importer/actions/OpenDatabaseActionTest.java b/src/test/java/org/jabref/gui/importer/actions/OpenDatabaseActionTest.java index 01e8f6f92a3..17011f45052 100644 --- a/src/test/java/org/jabref/gui/importer/actions/OpenDatabaseActionTest.java +++ b/src/test/java/org/jabref/gui/importer/actions/OpenDatabaseActionTest.java @@ -3,6 +3,8 @@ import java.nio.file.Path; import java.util.List; +import javafx.collections.FXCollections; + import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; @@ -32,13 +34,15 @@ public class OpenDatabaseActionTest { DialogService dialogService; GuiPreferences guiPreferences; OpenDatabaseAction openDatabaseAction; + LibraryTabContainer libraryTabContainer; @BeforeEach void initializeOpenDatabaseAction() { dialogService = mock(DialogService.class); guiPreferences = mock(GuiPreferences.class); + libraryTabContainer = mock(LibraryTabContainer.class); openDatabaseAction = spy(new OpenDatabaseAction( - mock(LibraryTabContainer.class), + libraryTabContainer, guiPreferences, mock(AiService.class), dialogService, @@ -60,6 +64,7 @@ void getFilesToOpenFailsToOpenPath(@TempDir Path tempDir) { FileDialogConfiguration goodConfig = mock(FileDialogConfiguration.class); when(guiPreferences.getFilePreferences()).thenReturn(filePreferences); + when(libraryTabContainer.getLibraryTabs()).thenReturn(FXCollections.emptyObservableList()); when(openDatabaseAction.getInitialDirectory()).thenReturn(path); // Make it so that showFileOpenDialogAndGetMultipleFiles will throw an error when called with the bad path, but