diff --git a/CHANGELOG.md b/CHANGELOG.md index ed836945ff6..35b6a6604e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129) - We added a highlighted diff regarding changes to the Group Tree Structure of a bib file, made outside JabRef. [#11221](https://github.com/JabRef/jabref/issues/11221) - We added a new setting in the 'Entry Editor' preferences to hide the 'File Annotations' tab when no annotations are available. [#13143](https://github.com/JabRef/jabref/issues/13143) +- We added functionality to merge bib files in a given directory to the current bib and added a 'Merge other bib files into current bib' tab in the Preferences menu [#12290](https://github.com/JabRef/jabref/issues/12290) - We added support for multi-file import across different formats. [#13269](https://github.com/JabRef/jabref/issues/13269) - We improved the detection of DOIs on the first page of a PDF. [#13487](https://github.com/JabRef/jabref/pull/13487) - We added support for dark title bar on Windows. [#11457](https://github.com/JabRef/jabref/issues/11457) diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index f21144c1d0a..da671717eb5 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -87,6 +87,7 @@ public enum StandardActions implements Action { REPLACE_ALL(Localization.lang("Find and replace"), KeyBinding.REPLACE_STRING), MANAGE_KEYWORDS(Localization.lang("Manage keywords")), MASS_SET_FIELDS(Localization.lang("Manage field names & content")), + MERGE_BIBTEX_FILES_INTO_CURRENT_LIBRARY(Localization.lang("Merge BibTeX files into current library")), AUTOMATIC_FIELD_EDITOR(Localization.lang("Automatic field editor")), TOGGLE_GROUPS(Localization.lang("Groups"), IconTheme.JabRefIcons.TOGGLE_GROUPS, KeyBinding.TOGGLE_GROUPS_INTERFACE), diff --git a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java index 78654f6939d..0b1e9e86f48 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -54,6 +54,7 @@ import org.jabref.gui.linkedfile.RedownloadMissingFilesAction; import org.jabref.gui.maintable.NewLibraryFromPdfActionOffline; import org.jabref.gui.maintable.NewLibraryFromPdfActionOnline; +import org.jabref.gui.mergebibfilesintocurrentbib.MergeBibFilesIntoCurrentBibAction; import org.jabref.gui.mergeentries.BatchEntryMergeWithFetchedDataAction; import org.jabref.gui.mergeentries.MergeEntriesAction; import org.jabref.gui.mergeentries.MergeWithFetchedEntryAction; @@ -181,6 +182,10 @@ private void createMenu() { new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.MERGE_BIBTEX_FILES_INTO_CURRENT_LIBRARY, new MergeBibFilesIntoCurrentBibAction(dialogService, preferences, stateManager, undoManager, fileUpdateMonitor, entryTypesManager)), + + new SeparatorMenuItem(), + factory.createSubMenu(StandardActions.REMOTE_DB, factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, new ConnectToSharedDatabaseCommand(frame, dialogService)), factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, new PullChangesFromSharedAction(stateManager))), diff --git a/jabgui/src/main/java/org/jabref/gui/mergebibfilesintocurrentbib/MergeBibFilesIntoCurrentBibAction.java b/jabgui/src/main/java/org/jabref/gui/mergebibfilesintocurrentbib/MergeBibFilesIntoCurrentBibAction.java new file mode 100644 index 00000000000..9a497c8ba7f --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/mergebibfilesintocurrentbib/MergeBibFilesIntoCurrentBibAction.java @@ -0,0 +1,190 @@ +package org.jabref.gui.mergebibfilesintocurrentbib; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.swing.undo.UndoManager; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.mergeentries.MergeEntriesAction; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.gui.util.DirectoryDialogConfiguration; +import org.jabref.logic.database.DuplicateCheck; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + +public class MergeBibFilesIntoCurrentBibAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(MergeBibFilesIntoCurrentBibAction.class); + + private final DialogService dialogService; + private final GuiPreferences preferences; + private final StateManager stateManager; + private final UndoManager undoManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + + private boolean shouldMergeSameKeyEntries; + private boolean shouldMergeDuplicateEntries; + + private final List entriesToMerge = new ArrayList<>(); + private final List> duplicatePairsToMerge = new ArrayList<>(); + private final List> sameKeyPairsToMerge = new ArrayList<>(); + + public MergeBibFilesIntoCurrentBibAction(DialogService dialogService, + GuiPreferences preferences, + StateManager stateManager, + UndoManager undoManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) { + this.dialogService = dialogService; + this.preferences = preferences; + this.stateManager = stateManager; + this.undoManager = undoManager; + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; + + this.executable.bind(needsDatabase(this.stateManager)); + } + + @Override + public void execute() { + Optional selectedDirectory = getDirectoryToMerge(); + Optional context = stateManager.getActiveDatabase(); + + MergeBibFilesIntoCurrentBibPreferences mergeBibFilesIntoCurrentBibPreferences = preferences.getMergeBibFilesIntoCurrentBibPreferences(); + + shouldMergeSameKeyEntries = mergeBibFilesIntoCurrentBibPreferences.shouldMergeSameKeyEntries(); + shouldMergeDuplicateEntries = mergeBibFilesIntoCurrentBibPreferences.shouldMergeDuplicateEntries(); + + if (selectedDirectory.isPresent() && context.isPresent()) { + mergeBibFilesIntoCurrentBib(selectedDirectory.get(), context.get()); + } + } + + private Optional getDirectoryToMerge() { + DirectoryDialogConfiguration config = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); + + return dialogService.showDirectorySelectionDialog(config); + } + + public void mergeBibFilesIntoCurrentBib(Path directory, BibDatabaseContext context) { + BibDatabase database = context.getDatabase(); + Optional databasePath = context.getDatabasePath(); + DuplicateCheck duplicateCheck = new DuplicateCheck(entryTypesManager); + + entriesToMerge.clear(); + sameKeyPairsToMerge.clear(); + duplicatePairsToMerge.clear(); + + for (Path path : getAllBibFiles(directory, databasePath.orElseGet(() -> Path.of("")))) { + ParserResult result; + try { + result = OpenDatabase.loadDatabase(path, preferences.getImportFormatPreferences(), fileUpdateMonitor); + } catch (IOException e) { + LOGGER.error("Could not load file '{}': {}", path, e.getMessage(), e); + continue; + } + for (BibEntry toMergeEntry : result.getDatabase().getEntries()) { + processEntry(toMergeEntry, database, duplicateCheck); + } + } + + database.insertEntries(entriesToMerge); + performMerges(); + + NamedCompound compound = new NamedCompound(Localization.lang("Merge BibTeX files into current library")); + compound.addEdit(new UndoableInsertEntries(database, entriesToMerge)); + compound.end(); + undoManager.addEdit(compound); + } + + private void processEntry(BibEntry entry, BibDatabase database, DuplicateCheck duplicateCheck) { + for (BibEntry existingEntry : database.getEntries()) { + if (entry.equals(existingEntry)) { + return; + } else if (entry.getCitationKey().equals(existingEntry.getCitationKey())) { + if (shouldMergeSameKeyEntries) { + sameKeyPairsToMerge.add(List.of(entry, existingEntry)); + } + return; + } else if (duplicateCheck.isDuplicate(entry, existingEntry, BibDatabaseMode.BIBTEX)) { + if (shouldMergeDuplicateEntries) { + duplicatePairsToMerge.add(List.of(entry, existingEntry)); + } + return; + } + } + entriesToMerge.add(entry); + } + + private void performMerges() { + for (List pair : sameKeyPairsToMerge) { + mergeEntries(pair); + } + for (List pair : duplicatePairsToMerge) { + mergeEntries(pair); + } + } + + private void mergeEntries(List entries) { + stateManager.setSelectedEntries(entries); + new MergeEntriesAction(dialogService, stateManager, undoManager, preferences).execute(); + } + + private List getAllBibFiles(Path directory, Path databasePath) { + if (!isValidPath(directory)) { + return List.of(); + } + try (Stream stream = Files.find( + directory, + Integer.MAX_VALUE, + (path, _) -> path.getFileName().toString().endsWith(".bib") && + !path.equals(databasePath) + )) { + return stream.collect(Collectors.toList()); + } catch (IOException e) { + LOGGER.error("Error finding .bib files in '{}': {}", directory.getFileName(), e.getMessage(), e); + } + return List.of(); + } + + private boolean isValidPath(Path directory) { + if (!Files.exists(directory)) { + dialogService.showErrorDialogAndWait(Localization.lang("Chosen folder does not exist:") + " " + directory); + return false; + } + if (!Files.isDirectory(directory)) { + dialogService.showErrorDialogAndWait(Localization.lang("Chosen path is not a folder:") + " " + directory); + return false; + } + if (!Files.isReadable(directory)) { + dialogService.showErrorDialogAndWait(Localization.lang("Chosen folder is not readable:") + " " + directory); + return false; + } + return true; + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/mergebibfilesintocurrentbib/MergeBibFilesIntoCurrentBibPreferences.java b/jabgui/src/main/java/org/jabref/gui/mergebibfilesintocurrentbib/MergeBibFilesIntoCurrentBibPreferences.java new file mode 100644 index 00000000000..2c42ad8dee7 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/mergebibfilesintocurrentbib/MergeBibFilesIntoCurrentBibPreferences.java @@ -0,0 +1,38 @@ +package org.jabref.gui.mergebibfilesintocurrentbib; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; + +public class MergeBibFilesIntoCurrentBibPreferences { + private final BooleanProperty shouldMergeSameKeyEntries = new SimpleBooleanProperty(); + private final BooleanProperty shouldMergeDuplicateEntries = new SimpleBooleanProperty(); + + public MergeBibFilesIntoCurrentBibPreferences(boolean shouldMergeSameKeyEntries, boolean shouldMergeDuplicateEntries) { + this.shouldMergeSameKeyEntries.set(shouldMergeSameKeyEntries); + this.shouldMergeDuplicateEntries.set(shouldMergeDuplicateEntries); + } + + public boolean shouldMergeSameKeyEntries() { + return this.shouldMergeSameKeyEntries.get(); + } + + public void setShouldMergeSameKeyEntries(boolean decision) { + this.shouldMergeSameKeyEntries.set(decision); + } + + public BooleanProperty shouldMergeSameKeyEntriesProperty() { + return this.shouldMergeSameKeyEntries; + } + + public boolean shouldMergeDuplicateEntries() { + return this.shouldMergeDuplicateEntries.get(); + } + + public void setShouldMergeDuplicateEntries(boolean decision) { + this.shouldMergeDuplicateEntries.set(decision); + } + + public BooleanProperty shouldMergeDuplicateEntriesProperty() { + return this.shouldMergeDuplicateEntries; + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java b/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java index 3e439c75a49..bb795c3d8db 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java @@ -13,6 +13,7 @@ import org.jabref.gui.maintable.ColumnPreferences; import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.maintable.NameDisplayPreferences; +import org.jabref.gui.mergebibfilesintocurrentbib.MergeBibFilesIntoCurrentBibPreferences; import org.jabref.gui.mergeentries.MergeDialogPreferences; import org.jabref.gui.newentry.NewEntryPreferences; import org.jabref.gui.preview.PreviewPreferences; @@ -58,4 +59,6 @@ public interface GuiPreferences extends CliPreferences { KeyBindingRepository getKeyBindingRepository(); NewEntryPreferences getNewEntryPreferences(); + + MergeBibFilesIntoCurrentBibPreferences getMergeBibFilesIntoCurrentBibPreferences(); } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java b/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java index fee55c3d475..29cd609db13 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java @@ -38,6 +38,7 @@ import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.maintable.NameDisplayPreferences; +import org.jabref.gui.mergebibfilesintocurrentbib.MergeBibFilesIntoCurrentBibPreferences; import org.jabref.gui.mergeentries.DiffMode; import org.jabref.gui.mergeentries.MergeDialogPreferences; import org.jabref.gui.newentry.NewEntryDialogTab; @@ -234,6 +235,11 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre private static final String CREATE_ENTRY_INTERPRET_PARSER_NAME = "latestInterpretParserName"; // endregion + // region MergeBibFilesPreferences + private static final String MERGE_SAME_KEY_ENTRIES = "mergeSameKeyEntries"; + private static final String MERGE_DUPLICATE_ENTRIES = "mergeDuplicateEntries"; + // endregion + private static JabRefGuiPreferences singleton; private EntryEditorPreferences entryEditorPreferences; @@ -255,6 +261,7 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre private KeyBindingRepository keyBindingRepository; private CopyToPreferences copyToPreferences; private NewEntryPreferences newEntryPreferences; + private MergeBibFilesIntoCurrentBibPreferences mergeBibFilesIntoCurrentBibPreferences; private JabRefGuiPreferences() { super(); @@ -431,6 +438,11 @@ private JabRefGuiPreferences() { defaults.put(CREATE_ENTRY_ID_FETCHER_NAME, DoiFetcher.NAME); defaults.put(CREATE_ENTRY_INTERPRET_PARSER_NAME, PlainCitationParserChoice.RULE_BASED.getLocalizedName()); // endregion + + // region MergeBibEntriesPreferences + defaults.put(MERGE_SAME_KEY_ENTRIES, true); + defaults.put(MERGE_DUPLICATE_ENTRIES, true); + // endregion } /** @@ -1300,6 +1312,24 @@ public NewEntryPreferences getNewEntryPreferences() { return newEntryPreferences; } + // region MergeBibFilesPreferences + @Override + public MergeBibFilesIntoCurrentBibPreferences getMergeBibFilesIntoCurrentBibPreferences() { + if (mergeBibFilesIntoCurrentBibPreferences != null) { + return mergeBibFilesIntoCurrentBibPreferences; + } + mergeBibFilesIntoCurrentBibPreferences = new MergeBibFilesIntoCurrentBibPreferences( + getBoolean(MERGE_SAME_KEY_ENTRIES), + getBoolean(MERGE_DUPLICATE_ENTRIES) + ); + + EasyBind.listen(mergeBibFilesIntoCurrentBibPreferences.shouldMergeSameKeyEntriesProperty(), (_, _, newValue) -> putBoolean(MERGE_SAME_KEY_ENTRIES, newValue)); + EasyBind.listen(mergeBibFilesIntoCurrentBibPreferences.shouldMergeDuplicateEntriesProperty(), (_, _, newValue) -> putBoolean(MERGE_DUPLICATE_ENTRIES, newValue)); + + return mergeBibFilesIntoCurrentBibPreferences; + } + // endregion + /** * In GUI mode, we can lookup the directory better */ diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java index c041d866707..8673b830e5c 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java @@ -50,6 +50,8 @@ public class GeneralTab extends AbstractPreferenceTabView i @FXML private CheckBox shouldAskForIncludingCrossReferences; @FXML private CheckBox confirmHideTabBar; @FXML private ComboBox biblatexMode; + @FXML private CheckBox mergeSameKeyEntries; + @FXML private CheckBox mergeDuplicateEntries; @FXML private CheckBox alwaysReformatBib; @FXML private CheckBox autosaveLocalLibraries; @FXML private Button autosaveLocalLibrariesHelp; @@ -131,6 +133,9 @@ public void initialize() { biblatexMode.itemsProperty().bind(viewModel.biblatexModeListProperty()); biblatexMode.valueProperty().bindBidirectional(viewModel.selectedBiblatexModeProperty()); + mergeSameKeyEntries.selectedProperty().bindBidirectional(viewModel.mergeSameKeyEntriesProperty()); + mergeDuplicateEntries.selectedProperty().bindBidirectional(viewModel.mergeDuplicateEntriesProperty()); + alwaysReformatBib.selectedProperty().bindBidirectional(viewModel.alwaysReformatBibProperty()); autosaveLocalLibraries.selectedProperty().bindBidirectional(viewModel.autosaveLocalLibrariesProperty()); ActionFactory actionFactory = new ActionFactory(); diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java index 9a2a6483ac6..af04913ea80 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java @@ -26,6 +26,7 @@ import org.jabref.gui.WorkspacePreferences; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.frame.UiMessageHandler; +import org.jabref.gui.mergebibfilesintocurrentbib.MergeBibFilesIntoCurrentBibPreferences; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.preferences.PreferenceTabViewModel; import org.jabref.gui.remote.CLIMessageHandler; @@ -102,6 +103,7 @@ public class GeneralTabViewModel implements PreferenceTabViewModel { private final LibraryPreferences libraryPreferences; private final FilePreferences filePreferences; private final RemotePreferences remotePreferences; + private final MergeBibFilesIntoCurrentBibPreferences mergeBibFilesIntoCurrentBibPreferences; private final Validator fontSizeValidator; private final Validator customPathToThemeValidator; @@ -115,6 +117,9 @@ public class GeneralTabViewModel implements PreferenceTabViewModel { private final FileUpdateMonitor fileUpdateMonitor; + private final BooleanProperty mergeSameKeyEntriesProperty = new SimpleBooleanProperty(); + private final BooleanProperty mergeDuplicateEntriesProperty = new SimpleBooleanProperty(); + public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor) { this.dialogService = dialogService; this.preferences = preferences; @@ -122,6 +127,7 @@ public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferenc this.libraryPreferences = preferences.getLibraryPreferences(); this.filePreferences = preferences.getFilePreferences(); this.remotePreferences = preferences.getRemotePreferences(); + this.mergeBibFilesIntoCurrentBibPreferences = preferences.getMergeBibFilesIntoCurrentBibPreferences(); this.fileUpdateMonitor = fileUpdateMonitor; fontSizeValidator = new FunctionBasedValidator<>( @@ -200,6 +206,9 @@ public void setValues() { bibliographyModeListProperty.setValue(FXCollections.observableArrayList(BibDatabaseMode.values())); selectedBiblatexModeProperty.setValue(libraryPreferences.getDefaultBibDatabaseMode()); + mergeSameKeyEntriesProperty.setValue(mergeBibFilesIntoCurrentBibPreferences.shouldMergeSameKeyEntries()); + mergeDuplicateEntriesProperty.setValue(mergeBibFilesIntoCurrentBibPreferences.shouldMergeDuplicateEntries()); + alwaysReformatBibProperty.setValue(libraryPreferences.shouldAlwaysReformatOnSave()); autosaveLocalLibraries.setValue(libraryPreferences.shouldAutoSave()); @@ -244,6 +253,9 @@ public void storeSettings() { libraryPreferences.setDefaultBibDatabaseMode(selectedBiblatexModeProperty.getValue()); + mergeBibFilesIntoCurrentBibPreferences.setShouldMergeSameKeyEntries(mergeSameKeyEntriesProperty.getValue()); + mergeBibFilesIntoCurrentBibPreferences.setShouldMergeDuplicateEntries(mergeDuplicateEntriesProperty.getValue()); + libraryPreferences.setAlwaysReformatOnSave(alwaysReformatBibProperty.getValue()); libraryPreferences.setAutoSave(autosaveLocalLibraries.getValue()); @@ -407,6 +419,14 @@ public ObjectProperty selectedBiblatexModeProperty() { return this.selectedBiblatexModeProperty; } + public BooleanProperty mergeSameKeyEntriesProperty() { + return this.mergeSameKeyEntriesProperty; + } + + public BooleanProperty mergeDuplicateEntriesProperty() { + return this.mergeDuplicateEntriesProperty; + } + public BooleanProperty alwaysReformatBibProperty() { return alwaysReformatBibProperty; } diff --git a/jabgui/src/main/resources/org/jabref/gui/preferences/general/GeneralTab.fxml b/jabgui/src/main/resources/org/jabref/gui/preferences/general/GeneralTab.fxml index 9f88b842a99..8bab9ec8d80 100644 --- a/jabgui/src/main/resources/org/jabref/gui/preferences/general/GeneralTab.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/preferences/general/GeneralTab.fxml @@ -96,6 +96,12 @@ +