diff --git a/.github/workflows/rerun-merge-conflict-check.yml b/.github/workflows/rerun-merge-conflict-check.yml index 4dfbf96b211..b2dcbcd81dc 100644 --- a/.github/workflows/rerun-merge-conflict-check.yml +++ b/.github/workflows/rerun-merge-conflict-check.yml @@ -7,6 +7,7 @@ on: jobs: rerun: + if: github.repository == 'JabRef/jabref' runs-on: ubuntu-latest permissions: actions: write diff --git a/.github/workflows/run-openrewrite.yml b/.github/workflows/run-openrewrite.yml index 6ee606c65ab..6536a448ba2 100644 --- a/.github/workflows/run-openrewrite.yml +++ b/.github/workflows/run-openrewrite.yml @@ -33,7 +33,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v5 with: - java-version: 24 + java-version: 25 distribution: 'corretto' check-latest: true - name: Setup Gradle diff --git a/.github/workflows/tests-code.yml b/.github/workflows/tests-code.yml index 18a9fb50129..fe5d89d7828 100644 --- a/.github/workflows/tests-code.yml +++ b/.github/workflows/tests-code.yml @@ -89,6 +89,12 @@ jobs: with: submodules: 'true' show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: 24 + distribution: 'corretto' + check-latest: true - name: Set up JDK uses: actions/setup-java@v5 with: @@ -532,7 +538,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v5 with: - java-version: 24 + java-version: 25 distribution: 'corretto' check-latest: true - name: Generate JBang cache key diff --git a/.jbang/JabKitLauncher.java b/.jbang/JabKitLauncher.java index bcfb1d9a1c8..ce9aca211e9 100755 --- a/.jbang/JabKitLauncher.java +++ b/.jbang/JabKitLauncher.java @@ -2,7 +2,7 @@ //DESCRIPTION jabkit - mange BibTeX files using JabRef -//JAVA 24 +//JAVA 24+ //RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED // raw is for https://github.com/unicode-org/icu/pull/2127 diff --git a/.jbang/JabLsLauncher.java b/.jbang/JabLsLauncher.java index af68b46b170..f76bbae2dcf 100755 --- a/.jbang/JabLsLauncher.java +++ b/.jbang/JabLsLauncher.java @@ -2,7 +2,7 @@ //DESCRIPTION jabls - start a bibtex languageserver -//JAVA 24 +//JAVA 24+ //RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED // raw is for https://github.com/unicode-org/icu/pull/2127 diff --git a/.jbang/JabSrvLauncher.java b/.jbang/JabSrvLauncher.java index 9035a13c9d4..726254e0daf 100755 --- a/.jbang/JabSrvLauncher.java +++ b/.jbang/JabSrvLauncher.java @@ -2,7 +2,7 @@ //DESCRIPTION jabsrv - serve BibTeX files using JabRef -//JAVA 24 +//JAVA 24+ //RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED // raw is for https://github.com/unicode-org/icu/pull/2127 diff --git a/CHANGELOG.md b/CHANGELOG.md index d2899e99066..d6caa11ada3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added - Improved merge dialog decisions for fields containing person names (e.g., `author`, `editor`) by using a new plausibility comparator. JabRef now prefers the side with more detailed/complete author information. [#14454](https://github.com/JabRef/jabref/issues/14454) +- We added 15 non-standard BibLaTeX entry types (Audio, Image, Legal, ...) to the group "Non-standard types" group in the New Entry dialog and in the context menu. [#12963](https://github.com/JabRef/jabref/issues/12963) - We added a drop-down menu to those custom fields in the main table for which content selector values exists. [#14087](https://github.com/JabRef/jabref/issues/14087) - We added a "Jump to Field" dialog (`Ctrl+J`) to quickly search for and navigate to any field across all tabs. [#12276](https://github.com/JabRef/jabref/issues/12276). - We added "IEEE" as another option for parsing plain text citations. [#14233](github.com/JabRef/jabref/pull/14233) @@ -42,10 +43,13 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - After importing, now all imported entries are marked. [#13535](https://github.com/JabRef/jabref/pull/13535) - The URL integrity check now checks the complete URL syntax. [#14370](https://github.com/JabRef/jabref/pull/14370) - Tab in the last text field of a tab moves the focus to the next tab in the entry editor. [#11937](https://github.com/JabRef/jabref/issues/11937) +- When pasting invalid BibTeX data, the content is now pasted as `@Misc` with the raw data in the `comment` field. [#14520](https://github.com/JabRef/jabref/pull/14520) - We changed fixed-value ComboBoxes to SearchableComboBox for better usability. [#14083](https://github.com/JabRef/jabref/issues/14083) +- We renamed "Search pre-configured" to "Search pre-selected" and "Web search fetchers" to "Pre-selected fetchers". [#14557](https://github.com/JabRef/jabref/issues/14557) ### Fixed +- We fixed an issue where bulk import operations polluted the navigation history, making the Back/Forward buttons navigate through imported entries instead of only user-selected entries. [#13878](https://github.com/JabRef/jabref/issues/13878) - We fixed an issue where pressing ESC in the preferences dialog would not always close the dialog. [#8888](https://github.com/JabRef/jabref/issues/8888) - We fixed the checkbox in merge dialog "Treat duplicates the same way" to make it functional. [#14224](https://github.com/JabRef/jabref/pull/14224) - We fixed the fallback window height (786 → 768) in JabRefGUI. [#14295](https://github.com/JabRef/jabref/pull/14295) diff --git a/build-support/src/main/java/CitationStyleCatalogGenerator.java b/build-support/src/main/java/CitationStyleCatalogGenerator.java index cdccc34357a..e107a739660 100644 --- a/build-support/src/main/java/CitationStyleCatalogGenerator.java +++ b/build-support/src/main/java/CitationStyleCatalogGenerator.java @@ -1,4 +1,4 @@ -//JAVA 24 +//JAVA 24+ //RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED //DEPS org.jspecify:jspecify:1.0.0 diff --git a/build-support/src/main/java/JournalListMvGenerator.java b/build-support/src/main/java/JournalListMvGenerator.java index 3d95f3cb7cc..424236afb0d 100644 --- a/build-support/src/main/java/JournalListMvGenerator.java +++ b/build-support/src/main/java/JournalListMvGenerator.java @@ -1,15 +1,15 @@ -//JAVA 24 +//JAVA 24+ //RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED //DEPS com.h2database:h2:2.4.240 +//DEPS info.debatty:java-string-similarity:2.0.0 //DEPS org.antlr:antlr4-runtime:4.13.2 //DEPS org.apache.commons:commons-csv:1.14.1 -//DEPS info.debatty:java-string-similarity:2.0.0 //DEPS org.jooq:jool:0.9.15 +//DEPS org.jspecify:jspecify:1.0.0 //DEPS org.openjfx:javafx-base:24.0.2 //DEPS org.slf4j:slf4j-api:2.0.17 //DEPS org.slf4j:slf4j-simple:2.0.17 -//DEPS org.jspecify:jspecify:1.0.0 //SOURCES ../../../../jablib/src/main/java/org/jabref/logic/journals/Abbreviation.java //SOURCES ../../../../jablib/src/main/java/org/jabref/logic/journals/AbbreviationFormat.java diff --git a/build-support/src/main/java/LtwaListMvGenerator.java b/build-support/src/main/java/LtwaListMvGenerator.java index ab7b6897146..8c56ff6db81 100644 --- a/build-support/src/main/java/LtwaListMvGenerator.java +++ b/build-support/src/main/java/LtwaListMvGenerator.java @@ -1,4 +1,4 @@ -//JAVA 24 +//JAVA 24+ //RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED //DEPS com.h2database:h2:2.4.240 @@ -6,8 +6,8 @@ //DEPS org.apache.commons:commons-csv:1.14.1 //DEPS info.debatty:java-string-similarity:2.0.0 //DEPS org.jooq:jool:0.9.15 -//DEPS org.openjfx:javafx-base:24.0.2 //DEPS org.jspecify:jspecify:1.0.0 +//DEPS org.openjfx:javafx-base:24.0.2 //DEPS org.slf4j:slf4j-api:2.0.17 //DEPS org.slf4j:slf4j-simple:2.0.17 diff --git a/docs/code-howtos/fetchers.md b/docs/code-howtos/fetchers.md index efdf8729387..6b16835884a 100644 --- a/docs/code-howtos/fetchers.md +++ b/docs/code-howtos/fetchers.md @@ -12,8 +12,8 @@ Fetchers are the implementation of the [search using online services](https://do | [Medline/Pubmed](https://pubmed.ncbi.nlm.nih.gov/) | [NCBI User account](https://account.ncbi.nlm.nih.gov/settings/) | `medlineApiKey` | 10 requests/seconds | | [MathSciNet](http://www.ams.org/mathscinet) | (none) | (none) | Depending on the current network | | [SAO/NASA Astrophysics Data System](https://docs.jabref.org/collect/import-using-online-bibliographic-database#sao-nasa-astrophysics-data-system) | [ADS UI](https://ui.adsabs.harvard.edu/user/settings/token) | `AstrophysicsDataSystemAPIKey` | 5000 calls/day | -| [ScienceDirect](https://www.sciencedirect.com) | [Elsevier Dev Portal](https://dev.elsevier.com/) | `ScienceDirectApiKey` | | -| [SemanticScholar](https://www.semanticscholar.org/) | | `SemanticScholarApiKey` | [20.000 calls/week](https://dev.elsevier.com/api_key_settings.html) | +| [ScienceDirect](https://www.sciencedirect.com) | [Elsevier Dev Portal](https://dev.elsevier.com/) | `ScienceDirectApiKey` | [20.000 calls/week](https://dev.elsevier.com/api_key_settings.html) | +| [SemanticScholar](https://www.semanticscholar.org/) | | `SemanticScholarApiKey` | | | [Springer Nature](https://docs.jabref.org/collect/import-using-online-bibliographic-database#springer) | [Springer Nature API portal](https://dev.springernature.com). Use the "Meta API" API key. | `SpringerNatureAPIKey` | 5000 calls/day | | [Zentralblatt Math](https://www.zbmath.org) | (none) | (none) | Depending on the current network | diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index 0363eac5f90..7dcaf06c32f 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -92,6 +92,8 @@ import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.FieldChangedEvent; import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.search.query.SearchQuery; import org.jabref.model.util.FileUpdateMonitor; @@ -103,6 +105,7 @@ import org.controlsfx.control.NotificationPane; import org.controlsfx.control.action.Action; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -126,6 +129,7 @@ public class LibraryTab extends Tab implements CommandSelectionTab { private final BooleanProperty canGoBackProperty = new SimpleBooleanProperty(false); private final BooleanProperty canGoForwardProperty = new SimpleBooleanProperty(false); private boolean backOrForwardNavigationActionTriggered = false; + private NavigationHistory.@Nullable Suppression currentBulkImportSuppression; private BibDatabaseContext bibDatabaseContext; @@ -239,7 +243,9 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { undoManager, stateManager, dialogService, - taskExecutor); + taskExecutor, + this::startBulkImport, + this::endBulkImport); setupMainPanel(); setupAutoCompletion(); @@ -821,18 +827,30 @@ public void insertEntry(final BibEntry bibEntry) { } public void insertEntries(final List entries) { + insertEntries(entries, false); + } + + /** + * Inserts entries into the library. + * + * @param entries the entries to insert + * @param suppressNavigation if true, navigation history will not be updated for these entries + */ + public void insertEntries(final List entries, boolean suppressNavigation) { if (entries.isEmpty()) { return; } - importHandler.importCleanedEntries(null, entries); - getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); - markBaseChanged(); - stateManager.setSelectedEntries(entries); - if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { - showAndEdit(entries.getFirst()); - } else { - clearAndSelect(entries.getFirst()); + try (NavigationHistory.Suppression ignored = suppressNavigation ? navigationHistory.suppressUpdates() : NavigationHistory.Suppression.noOp()) { + importHandler.importCleanedEntries(null, entries); + getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); + markBaseChanged(); + stateManager.setSelectedEntries(entries); + if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { + showAndEdit(entries.getFirst()); + } else { + clearAndSelect(entries.getFirst()); + } } } @@ -894,7 +912,10 @@ private List handleNonBibTeXStringData(String data) { } else { dialogService.showErrorDialogAndWait(exception); } - return List.of(); + BibEntry fallBack = new BibEntry(StandardEntryType.Misc) + .withField(StandardField.COMMENT, data) + .withChanged(true); + return List.of(fallBack); } } @@ -1038,6 +1059,27 @@ public void updateNavigationState() { canGoForwardProperty.set(canGoForward()); } + /** + * Called by ImportHandler when starting a bulk import operation. + * Suppresses navigation history updates during the import. + */ + private void startBulkImport() { + if (currentBulkImportSuppression == null) { + currentBulkImportSuppression = navigationHistory.suppressUpdates(); + } + } + + /** + * Called by ImportHandler when ending a bulk import operation. + * Resumes normal navigation history tracking. + */ + private void endBulkImport() { + if (currentBulkImportSuppression != null) { + currentBulkImportSuppression.close(); + currentBulkImportSuppression = null; + } + } + /** * Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most of the other parameters are required by {@code resetChangeMonitor()}. * diff --git a/jabgui/src/main/java/org/jabref/gui/NavigationHistory.java b/jabgui/src/main/java/org/jabref/gui/NavigationHistory.java index bf0a802f2b2..d60ede8dff0 100644 --- a/jabgui/src/main/java/org/jabref/gui/NavigationHistory.java +++ b/jabgui/src/main/java/org/jabref/gui/NavigationHistory.java @@ -16,6 +16,7 @@ public class NavigationHistory { private final List previousEntries = new ArrayList<>(); private final List nextEntries = new ArrayList<>(); private BibEntry currentEntry; + private int suppressionDepth; /** * Sets a new entry as the current one, clearing the forward history. @@ -24,6 +25,10 @@ public class NavigationHistory { * @param entry The BibEntry to add to the history. */ public void add(BibEntry entry) { + if (isSuppressed()) { + return; + } + if (Objects.equals(currentEntry, entry)) { return; } @@ -74,4 +79,65 @@ public boolean canGoBack() { public boolean canGoForward() { return !nextEntries.isEmpty(); } + + /** + * Suppresses navigation history updates while the returned guard is open. + * Intended for bulk operations that perform multiple selection changes. + */ + public Suppression suppressUpdates() { + return new Suppression(this); + } + + /** + * Convenience helper to suppress updates conditionally. + */ + public Suppression suppressUpdatesIf(boolean active) { + return active ? suppressUpdates() : Suppression.noOp(); + } + + private boolean isSuppressed() { + return suppressionDepth > 0; + } + + private void beginSuppression() { + suppressionDepth++; + } + + private void endSuppression() { + if (suppressionDepth > 0) { + suppressionDepth--; + } + } + + public static final class Suppression implements AutoCloseable { + private static final Suppression NO_OP = new Suppression(); + + private final NavigationHistory owner; + private boolean closed; + private final boolean active; + + private Suppression() { + this.owner = null; + this.active = false; + } + + private Suppression(NavigationHistory owner) { + this.owner = owner; + this.active = true; + owner.beginSuppression(); + } + + @Override + public void close() { + if (closed || !active) { + return; + } + closed = true; + owner.endSuppression(); + } + + public static Suppression noOp() { + return NO_OP; + } + } } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index eff6b7bb250..7d9d77036d6 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -64,6 +64,7 @@ import com.airhacks.afterburner.injection.Injector; import com.google.common.annotations.VisibleForTesting; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,6 +85,10 @@ public class ImportHandler { private final DialogService dialogService; private final TaskExecutor taskExecutor; private final FilePreferences filePreferences; + @Nullable + private final Runnable onBulkImportStart; + @Nullable + private final Runnable onBulkImportEnd; public ImportHandler(BibDatabaseContext targetBibDatabaseContext, GuiPreferences preferences, @@ -92,12 +97,26 @@ public ImportHandler(BibDatabaseContext targetBibDatabaseContext, StateManager stateManager, DialogService dialogService, TaskExecutor taskExecutor) { + this(targetBibDatabaseContext, preferences, fileupdateMonitor, undoManager, stateManager, dialogService, taskExecutor, null, null); + } + + public ImportHandler(BibDatabaseContext targetBibDatabaseContext, + GuiPreferences preferences, + FileUpdateMonitor fileupdateMonitor, + UndoManager undoManager, + StateManager stateManager, + DialogService dialogService, + TaskExecutor taskExecutor, + @Nullable Runnable onBulkImportStart, + @Nullable Runnable onBulkImportEnd) { this.targetBibDatabaseContext = targetBibDatabaseContext; this.preferences = preferences; this.fileUpdateMonitor = fileupdateMonitor; this.stateManager = stateManager; this.dialogService = dialogService; this.taskExecutor = taskExecutor; + this.onBulkImportStart = onBulkImportStart; + this.onBulkImportEnd = onBulkImportEnd; this.filePreferences = preferences.getFilePreferences(); @@ -236,21 +255,31 @@ public void importEntries(List entries) { } public void importCleanedEntries(@Nullable TransferInformation transferInformation, List entries) { - targetBibDatabaseContext.getDatabase().insertEntries(entries); - generateKeys(entries); - setAutomaticFields(entries); - addToGroups(entries, stateManager.getSelectedGroups(targetBibDatabaseContext)); - addToImportEntriesGroup(entries); - - if (transferInformation != null) { - entries.stream().forEach(entry -> { - LinkedFileTransferHelper - .adjustLinkedFilesForTarget(filePreferences, transferInformation, targetBibDatabaseContext, entry); - }); + boolean isBulkImport = entries.size() > 1; + if (isBulkImport && onBulkImportStart != null) { + onBulkImportStart.run(); } + try { + targetBibDatabaseContext.getDatabase().insertEntries(entries); + generateKeys(entries); + setAutomaticFields(entries); + addToGroups(entries, stateManager.getSelectedGroups(targetBibDatabaseContext)); + addToImportEntriesGroup(entries); + + if (transferInformation != null) { + entries.stream().forEach(entry -> { + LinkedFileTransferHelper + .adjustLinkedFilesForTarget(filePreferences, transferInformation, targetBibDatabaseContext, entry); + }); + } - // TODO: Should only be done if NOT copied from other library - entries.stream().forEach(entry -> downloadLinkedFiles(entry)); + // TODO: Should only be done if NOT copied from other library + entries.stream().forEach(entry -> downloadLinkedFiles(entry)); + } finally { + if (isBulkImport && onBulkImportEnd != null) { + onBulkImportEnd.run(); + } + } } public void importEntryWithDuplicateCheck(@Nullable TransferInformation transferInformation, BibEntry entry) { @@ -391,7 +420,11 @@ private void generateKeys(List entries) { entries.forEach(keyGenerator::generateAndSetKey); } - public List handleBibTeXData(String entries) { + public @NonNull List<@NonNull BibEntry> handleBibTeXData(@NonNull String entries) { + if (!entries.contains("@")) { + LOGGER.debug("Seems not to be BibTeX data: {}", entries); + return List.of(); + } BibtexParser parser = new BibtexParser(preferences.getImportFormatPreferences(), fileUpdateMonitor); try { List result = parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8))); @@ -399,7 +432,8 @@ public List handleBibTeXData(String entries) { importStringConstantsWithDuplicateCheck(stringConstants); return result; } catch (ParseException ex) { - LOGGER.error("Could not paste", ex); + LOGGER.info("Data could not be interpreted as Bib(La)TeX", ex); + dialogService.notify(Localization.lang("Failed to parse Bib(La)TeX: %0", ex.getLocalizedMessage())); return List.of(); } } @@ -472,21 +506,31 @@ public void importEntriesWithDuplicateCheck(@Nullable TransferInformation transf } public void importEntriesWithDuplicateCheck(@Nullable TransferInformation transferInformation, List entriesToAdd, EntryImportHandlerTracker tracker) { - boolean firstEntry = true; - for (BibEntry entry : entriesToAdd) { - if (firstEntry) { - LOGGER.debug("First entry to import, we use BREAK (\"Ask every time\") as decision"); - importEntryWithDuplicateCheck(transferInformation, entry, BREAK, tracker); - firstEntry = false; - continue; + boolean isBulkImport = entriesToAdd.size() > 1; + if (isBulkImport && onBulkImportStart != null) { + onBulkImportStart.run(); + } + try { + boolean firstEntry = true; + for (BibEntry entry : entriesToAdd) { + if (firstEntry) { + LOGGER.debug("First entry to import, we use BREAK (\"Ask every time\") as decision"); + importEntryWithDuplicateCheck(transferInformation, entry, BREAK, tracker); + firstEntry = false; + continue; + } + if (preferences.getMergeDialogPreferences().shouldMergeApplyToAllEntries()) { + DuplicateResolverDialog.DuplicateResolverResult decision = preferences.getMergeDialogPreferences().getAllEntriesDuplicateResolverDecision(); + LOGGER.debug("Not first entry, pref flag is true, we use {}", decision); + importEntryWithDuplicateCheck(transferInformation, entry, decision, tracker); + } else { + LOGGER.debug("not first entry, not pref flag, break will be used"); + importEntryWithDuplicateCheck(transferInformation, entry, BREAK, tracker); + } } - if (preferences.getMergeDialogPreferences().shouldMergeApplyToAllEntries()) { - DuplicateResolverDialog.DuplicateResolverResult decision = preferences.getMergeDialogPreferences().getAllEntriesDuplicateResolverDecision(); - LOGGER.debug("Not first entry, pref flag is true, we use {}", decision); - importEntryWithDuplicateCheck(transferInformation, entry, decision, tracker); - } else { - LOGGER.debug("not first entry, not pref flag, break will be used"); - importEntryWithDuplicateCheck(transferInformation, entry, BREAK, tracker); + } finally { + if (isBulkImport && onBulkImportEnd != null) { + onBulkImportEnd.run(); } } } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java index f2f42a62503..9031ef474a1 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java @@ -1,5 +1,7 @@ package org.jabref.gui.maintable; +import java.util.List; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -16,6 +18,26 @@ public MainTablePreferences(ColumnPreferences columnPreferences, this.extraFileColumnsEnabled.set(extraFileColumnsEnabled); } + /// Creates Object with default values + private MainTablePreferences() { + this( + new ColumnPreferences(List.of(), List.of()), // Default column preferences + false, // Default resize columns to fit + false // Default extra file columns disabled + ); + } + + public static MainTablePreferences getDefault() { + return new MainTablePreferences(); + } + + public void setAll(MainTablePreferences preferences) { + this.columnPreferences.setColumns(preferences.getColumnPreferences().getColumns()); + this.columnPreferences.setColumnSortOrder(preferences.getColumnPreferences().getColumnSortOrder()); + this.setResizeColumnsToFit(preferences.getResizeColumnsToFit()); + this.setExtraFileColumnsEnabled(preferences.getExtraFileColumnsEnabled()); + } + public ColumnPreferences getColumnPreferences() { return columnPreferences; } diff --git a/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java b/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java index 638fd4a2d4a..a8c38499721 100644 --- a/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java @@ -3,6 +3,8 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import javax.swing.undo.UndoManager; @@ -20,7 +22,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.types.BiblatexNonStandardEntryTypeDefinitions; import org.jabref.model.entry.types.BibtexEntryTypeDefinitions; +import org.jabref.model.entry.types.EntryType; import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions; public class ChangeEntryTypeMenu { @@ -58,8 +62,22 @@ private ObservableList getMenuItems(List entries, BibDatabas ObservableList items = FXCollections.observableArrayList(); if (bibDatabaseContext.isBiblatexMode()) { - // Default BibLaTeX - items.addAll(fromEntryTypes(entryTypesManager.getAllTypes(BibDatabaseMode.BIBLATEX), entries, undoManager)); + // Default BibLaTeX - exclude non-standard types to avoid duplicates + Set nonStandardEntryTypes = BiblatexNonStandardEntryTypeDefinitions.ALL.stream() + .map(BibEntryType::getType) + .collect(Collectors.toSet()); + Collection allTypes = entryTypesManager.getAllTypes(BibDatabaseMode.BIBLATEX); + Collection standardTypes = allTypes.stream() + .filter(type -> !nonStandardEntryTypes.contains(type.getType())) + .toList(); + items.addAll(fromEntryTypes(standardTypes, entries, undoManager)); + + // Non-standard types + createSubMenu(Localization.lang("Non-standard types"), BiblatexNonStandardEntryTypeDefinitions.ALL, entries, undoManager) + .ifPresent(subMenu -> items.addAll( + new SeparatorMenuItem(), + subMenu + )); // Custom types createSubMenu(Localization.lang("Custom"), entryTypesManager.getAllCustomTypes(BibDatabaseMode.BIBLATEX), entries, undoManager) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 59d8e75c266..b10369e620d 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -60,6 +60,8 @@ import org.jabref.model.entry.identifier.SSRN; import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions; +import org.jabref.model.entry.types.BiblatexNonStandardEntryType; +import org.jabref.model.entry.types.BiblatexNonStandardEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexSoftwareEntryTypeDefinitions; import org.jabref.model.entry.types.BibtexEntryTypeDefinitions; import org.jabref.model.entry.types.EntryType; @@ -109,6 +111,8 @@ public class NewEntryView extends BaseDialog { @FXML private TilePane entryOther; @FXML private TitledPane entryCustomTitle; @FXML private TilePane entryCustom; + @FXML private TitledPane entryNonStandardTitle; + @FXML private TilePane entryNonStandard; @FXML private TextField idText; @FXML private Tooltip idTextTooltip; @@ -236,6 +240,9 @@ private void initializeAddEntry() { entryCustomTitle.expandedProperty().bindBidirectional(preferences.typesCustomExpandedProperty()); entryCustom.managedProperty().bind(entryCustom.visibleProperty()); + entryNonStandardTitle.managedProperty().bind(entryNonStandardTitle.visibleProperty()); + entryNonStandard.managedProperty().bind(entryNonStandard.visibleProperty()); + final boolean isBiblatexMode = libraryTab.getBibDatabaseContext().isBiblatexMode(); List recommendedEntries; @@ -263,6 +270,12 @@ private void initializeAddEntry() { } else { addEntriesToPane(entryCustom, customEntries); } + + if (isBiblatexMode) { + addEntriesToPane(entryNonStandard, BiblatexNonStandardEntryTypeDefinitions.ALL); + } else { + entryNonStandardTitle.setVisible(false); + } } private void initializeLookupIdentifier() { @@ -507,6 +520,9 @@ private static String descriptionOfEntryType(EntryType type) { if (type instanceof StandardEntryType entryType) { return descriptionOfStandardEntryType(entryType); } + if (type instanceof BiblatexNonStandardEntryType entryType) { + return descriptionOfNonStandardEntryType(entryType); + } return null; } @@ -582,6 +598,44 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { }; } + private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntryType type) { + // These descriptions are taken from subsection 2.1.3 of the biblatex package documentation. + // Non-standard Types (BibLaTeX only) - these use the @misc driver in standard bibliography styles. + // See [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf]. + return switch (type) { + case Artwork -> + Localization.lang("Works of the visual arts such as paintings, sculpture, and installations."); + case Audio -> + Localization.lang("Audio recordings, typically on audio cd, dvd, audio cassette, or similar media."); + case Bibnote -> + Localization.lang("This special entry type is not meant to be used in the bib file like other types. It is provided for third-party packages which merge notes into the bibliography."); + case Commentary -> + Localization.lang("Commentaries which have a status different from regular books, such as legal commentaries."); + case Image -> + Localization.lang("Images, pictures, photographs, and similar media."); + case Jurisdiction -> + Localization.lang("Court decisions, court recordings, and similar things."); + case Legislation -> + Localization.lang("Laws, bills, legislative proposals, and similar things."); + case Legal -> + Localization.lang("Legal documents such as treaties."); + case Letter -> + Localization.lang("Personal correspondence such as letters, emails, memoranda, etc."); + case Movie -> + Localization.lang("Motion pictures."); + case Music -> + Localization.lang("Musical recordings. This is a more specific variant of \"Audio\"."); + case Performance -> + Localization.lang("Musical and theatrical performances as well as other works of the performing arts. This type refers to the event as opposed to a recording, a score, or a printed play."); + case Review -> + Localization.lang("Reviews of some other work. This is a more specific variant of the \"Article\" type."); + case Standard -> + Localization.lang("National and international standards issued by a standards body such as the International Organization for Standardization."); + case Video -> + Localization.lang("Audiovisual recordings, typically on dvd, vhs cassette, or similar media."); + }; + } + private static IdBasedFetcher fetcherFromName(String fetcherName, List fetchers) { for (IdBasedFetcher fetcher : fetchers) { if (fetcher.getName().equals(fetcherName)) { 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 3579e0d57ae..1baf378d441 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java @@ -393,6 +393,7 @@ public void clear() throws BackingStoreException { getUnlinkedFilesDialogPreferences().setAll(UnlinkedFilesDialogPreferences.getDefault()); getNewEntryPreferences().setAll(NewEntryPreferences.getDefault()); getSpecialFieldsPreferences().setAll(SpecialFieldsPreferences.getDefault()); + getMainTablePreferences().setAll(MainTablePreferences.getDefault()); } @Override @@ -406,6 +407,7 @@ public void importPreferences(Path path) throws JabRefException { getUnlinkedFilesDialogPreferences().setAll(UnlinkedFilesDialogPreferences.getDefault()); getNewEntryPreferences().setAll(getNewEntryPreferencesFromBackingStore(getNewEntryPreferences())); getSpecialFieldsPreferences().setAll(getSpecialFieldsPreferencesFromBackingStore(getSpecialFieldsPreferences())); + getMainTablePreferences().setAll(getMainTablePreferencesFromBackingStore(getMainTablePreferences())); } // region EntryEditorPreferences @@ -996,10 +998,7 @@ public MainTablePreferences getMainTablePreferences() { return mainTablePreferences; } - mainTablePreferences = new MainTablePreferences( - getMainTableColumnPreferences(), - getBoolean(AUTO_RESIZE_MODE), - getBoolean(EXTRA_FILE_COLUMNS)); + mainTablePreferences = getMainTablePreferencesFromBackingStore(MainTablePreferences.getDefault()); EasyBind.listen(mainTablePreferences.resizeColumnsToFitProperty(), (obs, oldValue, newValue) -> putBoolean(AUTO_RESIZE_MODE, newValue)); @@ -1009,6 +1008,14 @@ public MainTablePreferences getMainTablePreferences() { return mainTablePreferences; } + private MainTablePreferences getMainTablePreferencesFromBackingStore(MainTablePreferences defaults) { + return new MainTablePreferences( + getMainTableColumnPreferences(), + getBoolean(AUTO_RESIZE_MODE, defaults.getResizeColumnsToFit()), + getBoolean(EXTRA_FILE_COLUMNS, defaults.getExtraFileColumnsEnabled()) + ); + } + public ColumnPreferences getMainTableColumnPreferences() { if (mainTableColumnPreferences != null) { return mainTableColumnPreferences; diff --git a/jabgui/src/main/resources/org/jabref/gui/newentry/NewEntry.fxml b/jabgui/src/main/resources/org/jabref/gui/newentry/NewEntry.fxml index 842ba6f29c1..e13b82caf36 100644 --- a/jabgui/src/main/resources/org/jabref/gui/newentry/NewEntry.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/newentry/NewEntry.fxml @@ -48,6 +48,11 @@ + + + + + diff --git a/jabgui/src/main/resources/org/jabref/gui/preferences/websearch/WebSearchTab.fxml b/jabgui/src/main/resources/org/jabref/gui/preferences/websearch/WebSearchTab.fxml index 16e24877297..9277cae5e5d 100644 --- a/jabgui/src/main/resources/org/jabref/gui/preferences/websearch/WebSearchTab.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/preferences/websearch/WebSearchTab.fxml @@ -71,6 +71,6 @@ -