From 7965eaa4f2e874772cf00f72cfcf6d5320eee550 Mon Sep 17 00:00:00 2001 From: leaf-soba Date: Sun, 8 Sep 2024 16:47:11 +0800 Subject: [PATCH 01/34] Add abstract param layout formatter test (#11713) * Rename "Show 'Ai Chat' tab" to "Show tab 'AI Chat'" solve #11708 * add unit test and refactor parseArgument add unit test and refactor AbstractParamLayoutFormatter.parseArgument * rewrite the test method name rewrite the test method name --- .../layout/AbstractParamLayoutFormatter.java | 52 ++++++----- .../AbstractParamLayoutFormatterTest.java | 88 +++++++++++++++++++ 2 files changed, 116 insertions(+), 24 deletions(-) create mode 100644 src/test/java/org/jabref/logic/layout/format/AbstractParamLayoutFormatterTest.java diff --git a/src/main/java/org/jabref/logic/layout/AbstractParamLayoutFormatter.java b/src/main/java/org/jabref/logic/layout/AbstractParamLayoutFormatter.java index bbd1344ddf9..44209654bb6 100644 --- a/src/main/java/org/jabref/logic/layout/AbstractParamLayoutFormatter.java +++ b/src/main/java/org/jabref/logic/layout/AbstractParamLayoutFormatter.java @@ -26,34 +26,38 @@ protected static List parseArgument(String arg) { StringBuilder current = new StringBuilder(); boolean escaped = false; for (int i = 0; i < arg.length(); i++) { - if ((arg.charAt(i) == AbstractParamLayoutFormatter.SEPARATOR) && !escaped) { - parts.add(current.toString()); - current = new StringBuilder(); - } else if (arg.charAt(i) == '\\') { - if (escaped) { - escaped = false; - current.append(arg.charAt(i)); - } else { - escaped = true; - } - } else if (escaped) { - // Handle newline and tab: - if (arg.charAt(i) == 'n') { - current.append('\n'); - } else if (arg.charAt(i) == 't') { - current.append('\t'); - } else { - if ((arg.charAt(i) != ',') && (arg.charAt(i) != '"')) { - current.append('\\'); - } - current.append(arg.charAt(i)); - } + char currentChar = arg.charAt(i); + if (escaped) { + handleEscapedCharacter(current, currentChar); escaped = false; + } else if (currentChar == '\\') { + escaped = true; + } else if (currentChar == AbstractParamLayoutFormatter.SEPARATOR) { + addPart(parts, current); } else { - current.append(arg.charAt(i)); + current.append(currentChar); } } - parts.add(current.toString()); + addPart(parts, current); return parts; } + + private static void handleEscapedCharacter(StringBuilder current, char currentChar) { + switch (currentChar) { + case 'n' -> current.append('\n'); + case 't' -> current.append('\t'); + case ',' -> current.append(','); + case '"' -> current.append('"'); + case '\\' -> current.append('\\'); + default -> { + current.append('\\'); + current.append(currentChar); + } + } + } + + private static void addPart(List parts, StringBuilder current) { + parts.add(current.toString()); + current.setLength(0); + } } diff --git a/src/test/java/org/jabref/logic/layout/format/AbstractParamLayoutFormatterTest.java b/src/test/java/org/jabref/logic/layout/format/AbstractParamLayoutFormatterTest.java new file mode 100644 index 00000000000..6cc2ede6ed0 --- /dev/null +++ b/src/test/java/org/jabref/logic/layout/format/AbstractParamLayoutFormatterTest.java @@ -0,0 +1,88 @@ +package org.jabref.logic.layout.format; + +import java.util.List; + +import org.jabref.logic.layout.AbstractParamLayoutFormatter; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AbstractParamLayoutFormatterTest { + static class ParseArgumentTester extends AbstractParamLayoutFormatter { + public static List callParseArgument(String arg) { + return parseArgument(arg); + } + + @Override + public String format(String fieldText) { + return null; + } + + @Override + public void setArgument(String arg) { + } + } + + @Test + void simpleArguments() { + String input = "arg1,arg2,arg3"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("arg1", "arg2", "arg3"), result, + "Simple arguments should be split correctly by commas"); + } + + @Test + void escapedCommas() { + String input = "arg1\\,arg2,arg3"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("arg1,arg2", "arg3"), result, + "Escaped commas should be treated as literal commas"); + } + + @Test + void escapedBackslash() { + String input = "arg1\\\\,arg2"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("arg1\\", "arg2"), result, "Escaped backslashes should be treated correctly"); + } + + @Test + void emptyArgument() { + String input = ""; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of(""), result, "Empty string should return a list with an empty string"); + } + + @Test + void singleArgument() { + String input = "singleArg"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("singleArg"), result, + "Single argument should return a list with the argument itself"); + } + + @Test + void newlineAndTabEscapeSequences() { + String input = "arg1\\narg2\\targ3"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("arg1\narg2\targ3"), result, + "Escape sequences like newline and tab should be handled correctly"); + } + + @Test + void multipleEscapedCharacters() { + String input = "arg1\\n,arg2\\t,arg3\\,arg4"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("arg1\n", "arg2\t", "arg3,arg4"), result, + "Multiple escaped characters should be handled correctly"); + } + + @Test + void mixedCases() { + String input = "arg1,arg2\\,withComma,arg3\\nnewline,arg4\\\\backslash"; + List result = ParseArgumentTester.callParseArgument(input); + assertEquals(List.of("arg1", "arg2,withComma", "arg3\nnewline", "arg4\\backslash"), result, + "Mixed cases should be parsed correctly"); + } +} From d8b935e76d9cc5e24572e3e663fbc0b9fca25f59 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 8 Sep 2024 11:13:26 +0200 Subject: [PATCH 02/34] Adds OpenFastTrace for requirements tracking (#11710) * Initial requirement * Initial requirement * Add OpenFastTrace * Fix linting * Remove wrong disable rule --- .github/workflows/tests.yml | 16 ++++++ build.gradle | 6 +++ docs/contributing.md | 2 + docs/requirements/ai.md | 16 ++++++ docs/requirements/index.md | 49 +++++++++++++++++++ .../chatprompt/ChatPromptComponent.java | 1 + 6 files changed, 90 insertions(+) create mode 100644 docs/requirements/ai.md create mode 100644 docs/requirements/index.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3edf09d599c..b4fd2c565f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -332,6 +332,22 @@ jobs: env: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + requirements_coverage: + name: "Validate requiremenet coverage" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 21.0.2 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - run: ./gradlew traceRequirements + # This is https://github.com/marketplace/actions/gradle-wrapper-validation # It ensures that the jar file is from gradle and not by a strange third party. gradlevalidation: diff --git a/build.gradle b/build.gradle index 9bd82d66fb8..064c2cbf97a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,8 @@ plugins { id 'idea' id 'org.openrewrite.rewrite' version '6.20.0' + + id "org.itsallcode.openfasttrace" version "3.0.0" } // Enable following for debugging @@ -898,3 +900,7 @@ jmh { iterations = 10 fork = 2 } + +requirementTracing { + inputDirectories = files('docs', 'src/main/java', 'src/test/java') +} diff --git a/docs/contributing.md b/docs/contributing.md index 0e64dfe09b0..d827129b175 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -112,3 +112,5 @@ If you want to indicate that a pull request is not yet complete **before** creat For improving developer's documentation, go on at the [docs/ subdirectory of JabRef's code](https://github.com/JabRef/jabref/tree/main/docs) and edit the file. GitHub offers a good guide at [Editing files in another user's repository](https://help.github.com/en/github/managing-files-in-a-repository/editing-files-in-another-users-repository). + +One can also add [callouts](https://just-the-docs.github.io/just-the-docs-tests/components/callouts/). diff --git a/docs/requirements/ai.md b/docs/requirements/ai.md new file mode 100644 index 00000000000..dfe6be9f004 --- /dev/null +++ b/docs/requirements/ai.md @@ -0,0 +1,16 @@ +--- +parent: Requirements +--- +# AI + +## User Interface + +### Chatting with AI +`req~ai.chat.new-message-based-on-previous~1` + +To enable simple editing and resending of previous messages, Cursor Up should show last message. +This should only happen if the current text field is empty. + +Needs: impl + + diff --git a/docs/requirements/index.md b/docs/requirements/index.md new file mode 100644 index 00000000000..bee1af1c590 --- /dev/null +++ b/docs/requirements/index.md @@ -0,0 +1,49 @@ +--- +nav_order: 7 +has_children: true +--- +# Requirements + +This part of the documentation collects requirements using [OpenFastTrace](https://github.com/itsallcode/openfasttrace). + +## Specifying requirements + +One writes directly below a Markdown heading a requirement identifier. + +Example: + +```markdown +### Example +`req~ai.example~1` +``` + +It is important that there is no empty line directly after the heading. + +{: note} +One needs to add `` to the end of the file, because the id of the requirement needs to follow the heading directly. + +## Linking implementations + +Then, one writes down at the requirement. +Directly at the end, one writes that it requires an implementation: + +```markdown +Needs: impl +``` + +One can also state that there should be detailed design document (`dsn`). +However, typically in JabRef, we go from the requirement directly to the implementation. + +Then, at the implementation, a comment is added this implementation is covered: + +```java +// [impl->req~ai.example~1] +``` + +When executing the gradle task `traceRequirements`, `build/tracing.txt` is generated. +In case of a tracing error, one can inspect this file to see which requirements were not covered. + +## More Information + +- [User manual of OpenFastTrace](https://github.com/itsallcode/openfasttrace/blob/main/doc/user_guide.md) +- We cannot copy and paste real examples here, because of [openfasttrace#280](https://github.com/itsallcode/openfasttrace/issues/280). diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java index d89fbb6be05..8bb590103ac 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java @@ -88,6 +88,7 @@ private void initialize() { // will be true (this is important). } } else if (keyEvent.getCode() == KeyCode.UP) { + // [impl->req~ai.chat.new-message-based-on-previous~1] if ((currentUserMessageScroll.get() < history.get().size() - 1) && (userPromptTextArea.getText().isEmpty() || showingHistoryMessage.get())) { // 1. We should not go up the maximum number of user messages. // 2. We can scroll history only on two conditions: From 17b7697f7eb75a13b29147a801c9fe9918c94523 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sun, 8 Sep 2024 13:32:39 +0200 Subject: [PATCH 03/34] Show only one notification when cutting an entry (#11724) * Move cut and copy out of MainTable into LibraryTab * Move ImportHandler to LibraryTab and Reword methods * Seperated notifications * l10n * CHANGELOG.md * Wording consistency --- CHANGELOG.md | 3 +- src/main/java/org/jabref/gui/LibraryTab.java | 254 +++++++++++------- .../java/org/jabref/gui/edit/EditAction.java | 8 +- .../jabref/gui/entryeditor/EntryEditor.java | 2 +- .../org/jabref/gui/frame/JabRefFrame.java | 1 - .../jabref/gui/importer/NewEntryAction.java | 2 +- .../importer/actions/OpenDatabaseAction.java | 1 - .../org/jabref/gui/maintable/MainTable.java | 88 +----- .../gui/openoffice/OpenOfficePanel.java | 1 - .../gui/shared/SharedDatabaseUIManager.java | 1 - src/main/resources/l10n/JabRef_en.properties | 3 + 11 files changed, 169 insertions(+), 195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c307ed39c5..e2a090ea191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where the full-text search results were incomplete. [#8626](https://github.com/JabRef/jabref/issues/8626) - We fixed an issue where search result highlighting was incorrectly highlighting the boolean operators. [#11595](https://github.com/JabRef/jabref/issues/11595) - We fixed an issue where search result highlighting was broken at complex searches. [#8067](https://github.com/JabRef/jabref/issues/8067) -- We fixed an issue where unescaped braces in the arXiv fetcher were not treated [#11704](https://github.com/JabRef/jabref/issues/11704) +- We fixed an issue where two contradicting notifications were shown when cutting an entry in the main table. [#11724](https://github.com/JabRef/jabref/pull/11724) +- We fixed an issue where unescaped braces in the arXiv fetcher were not treated. [#11704](https://github.com/JabRef/jabref/issues/11704) ### Removed diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 0542c7023fe..d17a0a1871e 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1,5 +1,6 @@ package org.jabref.gui; +import java.io.IOException; import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -46,6 +47,7 @@ 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; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.linkedfile.DeleteFileAction; @@ -63,15 +65,16 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.UiTaskExecutor; -import org.jabref.logic.ai.AiService; import org.jabref.logic.citationstyle.CitationStyleCache; +import org.jabref.logic.importer.FetcherClientException; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.FetcherServerException; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; import org.jabref.logic.search.LuceneManager; import org.jabref.logic.shared.DatabaseLocation; -import org.jabref.logic.util.UpdateField; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabase; @@ -82,6 +85,7 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.BibtexString; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.FieldChangedEvent; @@ -118,7 +122,6 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private final CountingUndoManager undoManager; private final DialogService dialogService; private final PreferencesService preferencesService; - private final AiService aiService; private final FileUpdateMonitor fileUpdateMonitor; private final StateManager stateManager; private final BibEntryTypesManager entryTypesManager; @@ -162,6 +165,8 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private final ClipBoardManager clipBoardManager; private final TaskExecutor taskExecutor; private final DirectoryMonitorManager directoryMonitorManager; + + private ImportHandler importHandler; private LuceneManager luceneManager; /** @@ -174,7 +179,6 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext, LibraryTabContainer tabContainer, DialogService dialogService, PreferencesService preferencesService, - AiService aiService, StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, @@ -187,7 +191,6 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext, this.undoManager = undoManager; this.dialogService = dialogService; this.preferencesService = Objects.requireNonNull(preferencesService); - this.aiService = Objects.requireNonNull(aiService); this.stateManager = Objects.requireNonNull(stateManager); this.fileUpdateMonitor = fileUpdateMonitor; this.entryTypesManager = entryTypesManager; @@ -222,6 +225,14 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { new CitationStyleCache(bibDatabaseContext); annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences()); + importHandler = new ImportHandler( + bibDatabaseContext, + preferencesService, + fileUpdateMonitor, + undoManager, + stateManager, + dialogService, + taskExecutor); setupMainPanel(); setupAutoCompletion(); @@ -449,63 +460,6 @@ public SuggestionProviders getSuggestionProviders() { return suggestionProviders; } - /** - * Removes the selected entries and files linked to selected entries from the database - * - * @param mode If DELETE_ENTRY the user will get asked if he really wants to delete the entries, and it will be localized as "deleted". If true the action will be localized as "cut" - */ - public void delete(StandardActions mode) { - delete(mode, mainTable.getSelectedEntries()); - } - - /** - * Removes the selected entries and files linked to selected entries from the database - * - * @param mode If DELETE_ENTRY the user will get asked if he really wants to delete the entries, and it will be localized as "deleted". If true the action will be localized as "cut" - */ - private void delete(StandardActions mode, List entries) { - if (entries.isEmpty()) { - return; - } - if (mode == StandardActions.DELETE_ENTRY && !showDeleteConfirmationDialog(entries.size())) { - return; - } - - // Delete selected entries - getUndoManager().addEdit(new UndoableRemoveEntries(bibDatabaseContext.getDatabase(), entries, mode == StandardActions.CUT)); - bibDatabaseContext.getDatabase().removeEntries(entries); - - if (mode != StandardActions.CUT) { - List linkedFileList = entries.stream() - .flatMap(entry -> entry.getFiles().stream()) - .distinct() - .toList(); - - if (!linkedFileList.isEmpty()) { - List viewModels = linkedFileList.stream() - .map(linkedFile -> linkedFile.toModel(null, bibDatabaseContext, null, null, preferencesService)) - .collect(Collectors.toList()); - - new DeleteFileAction(dialogService, preferencesService.getFilePreferences(), bibDatabaseContext, viewModels).execute(); - } - } - - ensureNotShowingBottomPanel(entries); - - this.changedProperty.setValue(true); - switch (mode) { - case StandardActions.CUT -> dialogService.notify(Localization.lang("Cut %0 entry(ies)", entries.size())); - case StandardActions.DELETE_ENTRY -> dialogService.notify(Localization.lang("Deleted %0 entry(ies)", entries.size())); - } - - // prevent the main table from loosing focus - mainTable.requestFocus(); - } - - public void delete(BibEntry entry) { - delete(StandardActions.DELETE_ENTRY, Collections.singletonList(entry)); - } - public void registerUndoableChanges(List changes) { NamedCompound ce = new NamedCompound(Localization.lang("Save actions")); for (FieldChange change : changes) { @@ -517,31 +471,6 @@ public void registerUndoableChanges(List changes) { } } - public void insertEntry(final BibEntry bibEntry) { - if (bibEntry != null) { - insertEntries(Collections.singletonList(bibEntry)); - } - } - - public void insertEntries(final List entries) { - if (!entries.isEmpty()) { - bibDatabaseContext.getDatabase().insertEntries(entries); - - // Set owner and timestamp - UpdateField.setAutomaticFields(entries, - preferencesService.getOwnerPreferences(), - preferencesService.getTimestampPreferences()); - // Create an UndoableInsertEntries object. - getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); - - this.changedProperty.setValue(true); // The database just changed. - if (preferencesService.getEntryEditorPreferences().shouldOpenOnNewEntry()) { - showAndEdit(entries.getFirst()); - } - clearAndSelect(entries.getFirst()); - } - } - public void editEntryAndFocusField(BibEntry entry, Field field) { showAndEdit(entry); Platform.runLater(() -> { @@ -563,7 +492,7 @@ private void createMainTable() { clipBoardManager, entryTypesManager, taskExecutor, - fileUpdateMonitor); + 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 -> { @@ -949,20 +878,151 @@ public void resetChangeMonitor() { stateManager)); } - public void copy() { - mainTable.copy(); + public void insertEntry(final BibEntry bibEntry) { + insertEntries(List.of(bibEntry)); + } + + public void insertEntries(final List entries) { + if (!entries.isEmpty()) { + importHandler.importCleanedEntries(entries); + + // Create an UndoableInsertEntries object. + getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); + + markBaseChanged(); + if (preferencesService.getEntryEditorPreferences().shouldOpenOnNewEntry()) { + showAndEdit(entries.getFirst()); + } + clearAndSelect(entries.getFirst()); + } + } + + public void copyEntry() { + int entriesCopied = doCopyEntry(getSelectedEntries()); + if (entriesCopied >= 0) { + dialogService.notify(Localization.lang("Copied %0 entry(ies)", entriesCopied)); + } else { + dialogService.notify(Localization.lang("Copy failed", entriesCopied)); + } } - public void paste() { - mainTable.paste(); + private int doCopyEntry(List selectedEntries) { + if (!selectedEntries.isEmpty()) { + List stringConstants = bibDatabaseContext.getDatabase().getUsedStrings(selectedEntries); + try { + if (stringConstants.isEmpty()) { + clipBoardManager.setContent(selectedEntries, entryTypesManager); + } else { + clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants); + } + return selectedEntries.size(); + } catch (IOException e) { + LOGGER.error("Error while copying selected entries to clipboard.", e); + return -1; + } + } + + return 0; + } + + public void pasteEntry() { + List entriesToAdd; + String content = ClipBoardManager.getContents(); + entriesToAdd = importHandler.handleBibTeXData(content); + if (entriesToAdd.isEmpty()) { + entriesToAdd = handleNonBibTeXStringData(content); + } + if (entriesToAdd.isEmpty()) { + return; + } + + importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); + } + + private List handleNonBibTeXStringData(String data) { + try { + return this.importHandler.handleStringData(data); + } catch ( + FetcherException exception) { + if (exception instanceof FetcherClientException) { + dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("No data was found for the identifier")); + } else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("Server not available")); + } else { + dialogService.showErrorDialogAndWait(exception); + } + return List.of(); + } } public void dropEntry(List entriesToAdd) { - mainTable.dropEntry(entriesToAdd); + importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); } - public void cut() { - mainTable.cut(); + public void cutEntry() { + int entriesCopied = doCopyEntry(getSelectedEntries()); + int entriesDeleted = doDeleteEntry(StandardActions.CUT, mainTable.getSelectedEntries()); + + if (entriesCopied == entriesDeleted) { + dialogService.notify(Localization.lang("Cut %0 entry(ies)", entriesCopied)); + } else { + dialogService.notify(Localization.lang("Cut failed", entriesCopied)); + undoManager.undo(); + clipBoardManager.setContent(""); + } + } + + /** + * Removes the selected entries and files linked to selected entries from the database + */ + public void deleteEntry() { + int entriesDeleted = doDeleteEntry(StandardActions.DELETE_ENTRY, mainTable.getSelectedEntries()); + dialogService.notify(Localization.lang("Deleted %0 entry(ies)", entriesDeleted)); + } + + public void deleteEntry(BibEntry entry) { + doDeleteEntry(StandardActions.DELETE_ENTRY, Collections.singletonList(entry)); + } + + /** + * Removes the selected entries and files linked to selected entries from the database + * + * @param mode If DELETE_ENTRY the user will get asked if he really wants to delete the entries, and it will be localized as "deleted". If true the action will be localized as "cut" + */ + private int doDeleteEntry(StandardActions mode, List entries) { + if (entries.isEmpty()) { + return 0; + } + if (mode == StandardActions.DELETE_ENTRY && !showDeleteConfirmationDialog(entries.size())) { + return -1; + } + + // Delete selected entries + getUndoManager().addEdit(new UndoableRemoveEntries(bibDatabaseContext.getDatabase(), entries, mode == StandardActions.CUT)); + bibDatabaseContext.getDatabase().removeEntries(entries); + + if (mode != StandardActions.CUT) { + List linkedFileList = entries.stream() + .flatMap(entry -> entry.getFiles().stream()) + .distinct() + .toList(); + + if (!linkedFileList.isEmpty()) { + List viewModels = linkedFileList.stream() + .map(linkedFile -> linkedFile.toModel(null, bibDatabaseContext, null, null, preferencesService)) + .collect(Collectors.toList()); + + new DeleteFileAction(dialogService, preferencesService.getFilePreferences(), bibDatabaseContext, viewModels).execute(); + } + } + + ensureNotShowingBottomPanel(entries); + markBaseChanged(); + + // prevent the main table from loosing focus + mainTable.requestFocus(); + + return entries.size(); } public boolean isModified() { @@ -993,7 +1053,6 @@ public static LibraryTab createLibraryTab(BackgroundTask dataLoadi Path file, DialogService dialogService, PreferencesService preferencesService, - AiService aiService, StateManager stateManager, LibraryTabContainer tabContainer, FileUpdateMonitor fileUpdateMonitor, @@ -1009,7 +1068,6 @@ public static LibraryTab createLibraryTab(BackgroundTask dataLoadi tabContainer, dialogService, preferencesService, - aiService, stateManager, fileUpdateMonitor, entryTypesManager, @@ -1031,7 +1089,6 @@ public static LibraryTab createLibraryTab(BibDatabaseContext databaseContext, LibraryTabContainer tabContainer, DialogService dialogService, PreferencesService preferencesService, - AiService aiService, StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, @@ -1045,7 +1102,6 @@ public static LibraryTab createLibraryTab(BibDatabaseContext databaseContext, tabContainer, dialogService, preferencesService, - aiService, stateManager, fileUpdateMonitor, entryTypesManager, diff --git a/src/main/java/org/jabref/gui/edit/EditAction.java b/src/main/java/org/jabref/gui/edit/EditAction.java index 2c870a821cb..c6be070e0e8 100644 --- a/src/main/java/org/jabref/gui/edit/EditAction.java +++ b/src/main/java/org/jabref/gui/edit/EditAction.java @@ -78,10 +78,10 @@ public void execute() { // Not sure what is selected -> copy/paste/cut selected entries except for Preview and CodeArea switch (action) { - case COPY -> tabSupplier.get().copy(); - case CUT -> tabSupplier.get().cut(); - case PASTE -> tabSupplier.get().paste(); - case DELETE_ENTRY -> tabSupplier.get().delete(StandardActions.DELETE_ENTRY); + case COPY -> tabSupplier.get().copyEntry(); + case CUT -> tabSupplier.get().cutEntry(); + case PASTE -> tabSupplier.get().pasteEntry(); + case DELETE_ENTRY -> tabSupplier.get().deleteEntry(); case UNDO -> { if (undoManager.canUndo()) { undoManager.undo(); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 67be8a5f908..e13eb0ecb6c 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -249,7 +249,7 @@ public void close() { @FXML private void deleteEntry() { - libraryTab.delete(currentlyEditedEntry); + libraryTab.deleteEntry(currentlyEditedEntry); } @FXML diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 499b47162bc..15e98498ef8 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -434,7 +434,6 @@ public void addTab(@NonNull BibDatabaseContext databaseContext, boolean raisePan this, dialogService, prefs, - aiService, stateManager, fileUpdateMonitor, entryTypesManager, diff --git a/src/main/java/org/jabref/gui/importer/NewEntryAction.java b/src/main/java/org/jabref/gui/importer/NewEntryAction.java index a47e1a9f6c6..6a3e70cc249 100644 --- a/src/main/java/org/jabref/gui/importer/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/importer/NewEntryAction.java @@ -43,7 +43,7 @@ public NewEntryAction(Supplier tabSupplier, DialogService dialogServ public NewEntryAction(Supplier tabSupplier, EntryType type, DialogService dialogService, PreferencesService preferences, StateManager stateManager) { this(tabSupplier, dialogService, preferences, stateManager); - this.type = Optional.of(type); + this.type = Optional.ofNullable(type); } @Override diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index 97ccf23758d..2074b7f4081 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -200,7 +200,6 @@ private void openTheFile(Path file) { file, dialogService, preferencesService, - aiService, stateManager, tabContainer, fileUpdateMonitor, diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index ecce71476de..6b8fcd4d53b 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -1,7 +1,6 @@ package org.jabref.gui.maintable; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -45,17 +44,11 @@ import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.gui.util.ViewModelTableRowFactory; -import org.jabref.logic.importer.FetcherClientException; -import org.jabref.logic.importer.FetcherException; -import org.jabref.logic.importer.FetcherServerException; import org.jabref.logic.journals.JournalAbbreviationRepository; -import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.EntriesAddedEvent; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.entry.BibtexString; -import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.FilePreferences; import org.jabref.preferences.PreferencesService; @@ -74,18 +67,14 @@ public class MainTable extends TableView { private static final PseudoClass NOT_MATCHING_SEARCH_AND_GROUPS = PseudoClass.getPseudoClass("not-matching-search-and-groups"); private final LibraryTab libraryTab; - private final DialogService dialogService; private final StateManager stateManager; private final BibDatabaseContext database; private final MainTableDataModel model; - - private final ImportHandler importHandler; private final CustomLocalDragboard localDragboard; - private final ClipBoardManager clipBoardManager; - private final BibEntryTypesManager entryTypesManager; private final TaskExecutor taskExecutor; private final UndoManager undoManager; private final FilePreferences filePreferences; + private final ImportHandler importHandler; private long lastKeyPressTime; private String columnSearchTerm; @@ -100,31 +89,20 @@ public MainTable(MainTableDataModel model, ClipBoardManager clipBoardManager, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, - FileUpdateMonitor fileUpdateMonitor) { + ImportHandler importHandler) { super(); this.libraryTab = libraryTab; - this.dialogService = dialogService; this.stateManager = stateManager; this.database = Objects.requireNonNull(database); this.model = model; - this.clipBoardManager = clipBoardManager; - this.entryTypesManager = entryTypesManager; this.taskExecutor = taskExecutor; this.undoManager = libraryTab.getUndoManager(); this.filePreferences = preferencesService.getFilePreferences(); + this.importHandler = importHandler; MainTablePreferences mainTablePreferences = preferencesService.getMainTablePreferences(); - importHandler = new ImportHandler( - database, - preferencesService, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); - localDragboard = stateManager.getLocalDragboard(); this.setOnDragOver(this::handleOnDragOverTableView); @@ -272,29 +250,6 @@ public void clearAndSelect(BibEntry bibEntry) { }); } - public void copy() { - List selectedEntries = getSelectedEntries(); - - if (!selectedEntries.isEmpty()) { - List stringConstants = getUsedStringValues(selectedEntries); - try { - if (stringConstants.isEmpty()) { - clipBoardManager.setContent(selectedEntries, entryTypesManager); - } else { - clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants); - } - dialogService.notify(Localization.lang("Copied %0 entry(ies)", selectedEntries.size())); - } catch (IOException e) { - LOGGER.error("Error while copying selected entries to clipboard.", e); - } - } - } - - public void cut() { - copy(); - libraryTab.delete(StandardActions.CUT); - } - private void scrollToNextMatchCategory() { BibEntryTableViewModel selectedEntry = getSelectionModel().getSelectedItem(); if (selectedEntry == null) { @@ -403,39 +358,6 @@ private void clearAndSelectLast() { scrollTo(getItems().size() - 1); } - public void paste() { - List entriesToAdd; - String content = ClipBoardManager.getContents(); - entriesToAdd = importHandler.handleBibTeXData(content); - if (entriesToAdd.isEmpty()) { - entriesToAdd = handleNonBibTeXStringData(content); - } - if (entriesToAdd.isEmpty()) { - return; - } - - importHandler.importEntriesWithDuplicateCheck(database, entriesToAdd); - } - - private List handleNonBibTeXStringData(String data) { - try { - return this.importHandler.handleStringData(data); - } catch (FetcherException exception) { - if (exception instanceof FetcherClientException) { - dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("No data was found for the identifier")); - } else if (exception instanceof FetcherServerException) { - dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("Server not available")); - } else { - dialogService.showErrorDialogAndWait(exception); - } - return List.of(); - } - } - - public void dropEntry(List entriesToAdd) { - importHandler.importEntriesWithDuplicateCheck(database, entriesToAdd); - } - private void handleOnDragOver(TableRow row, BibEntryTableViewModel item, DragEvent event) { if (event.getDragboard().hasFiles()) { event.acceptTransferModes(TransferMode.ANY); @@ -558,8 +480,4 @@ private Optional findEntry(BibEntry entry) { .filter(viewModel -> viewModel.getEntry().equals(entry)) .findFirst(); } - - private List getUsedStringValues(List entries) { - return database.getDatabase().getUsedStrings(entries); - } } diff --git a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index c4416876ed8..4eb29ff872f 100644 --- a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -310,7 +310,6 @@ private void exportEntries() { tabContainer, dialogService, preferencesService, - aiService, stateManager, fileUpdateMonitor, entryTypesManager, diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index d6ef1dce709..480ecab64ff 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -176,7 +176,6 @@ public LibraryTab openNewSharedDatabaseTab(DBMSConnectionProperties dbmsConnecti tabContainer, dialogService, preferencesService, - aiService, stateManager, fileUpdateMonitor, entryTypesManager, diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 5ff0d0364d7..990890c3fc4 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2765,6 +2765,9 @@ Ask\ every\ time=Ask every time Value\ is\ not\ in\ Unicode's\ Normalization\ Form\ "Canonical\ Composition"\ (NFC)\ format=Value is not in Unicode's Normalization Form "Canonical Composition" (NFC) format +Copy\ failed=Copy failed +Cut\ failed=Cut failed + Group\ icons=Group icons Redownload\ file=Redownload file Redownload\ missing\ files=Redownload missing files From 0b305abaa99f589e7d32b4b6c20d43fb4d03f57e Mon Sep 17 00:00:00 2001 From: Ruslan Date: Sun, 8 Sep 2024 14:48:04 +0300 Subject: [PATCH 04/34] Remove unnecessary AI dependencies (#11727) * Remove unnecessary AI dependencies * Remove AiApiKeyProvider.java * Fix checkers --- src/main/java/org/jabref/Launcher.java | 2 - src/main/java/org/jabref/gui/JabRefGUI.java | 2 - .../jabref/gui/entryeditor/EntryEditor.java | 2 - .../org/jabref/gui/preferences/ai/AiTab.java | 6 +- .../gui/preferences/ai/AiTabViewModel.java | 21 ++--- .../java/org/jabref/logic/ai/AiService.java | 4 +- .../model/JabRefChatLanguageModel.java | 14 ++-- .../model/JvmOpenAiChatLanguageModel.java | 5 +- .../jabref/preferences/JabRefPreferences.java | 35 +------- .../preferences/PreferencesService.java | 9 --- .../preferences/ai/AiApiKeyProvider.java | 7 -- .../jabref/preferences/ai/AiPreferences.java | 80 +++++++++++-------- 12 files changed, 66 insertions(+), 121 deletions(-) delete mode 100644 src/main/java/org/jabref/preferences/ai/AiApiKeyProvider.java diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index 117c9fd5c1c..fa891388f98 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -36,7 +36,6 @@ import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreferencesService; -import org.jabref.preferences.ai.AiApiKeyProvider; import com.airhacks.afterburner.injection.Injector; import org.apache.commons.cli.ParseException; @@ -64,7 +63,6 @@ public static void main(String[] args) { // Initialize preferences final JabRefPreferences preferences = JabRefPreferences.getInstance(); Injector.setModelOrService(PreferencesService.class, preferences); - Injector.setModelOrService(AiApiKeyProvider.class, preferences); // Early exit in case another instance is already running if (!handleMultipleAppInstances(args, preferences.getRemotePreferences())) { diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 3e09d3f5c39..159d8bf5bea 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -40,7 +40,6 @@ import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.GuiPreferences; import org.jabref.preferences.JabRefPreferences; -import org.jabref.preferences.ai.AiApiKeyProvider; import com.airhacks.afterburner.injection.Injector; import com.tobiasdiez.easybind.EasyBind; @@ -161,7 +160,6 @@ public void initialize() { preferencesService.getAiPreferences(), preferencesService.getFilePreferences(), preferencesService.getCitationKeyPatternPreferences(), - Injector.instantiateModelOrService(AiApiKeyProvider.class), dialogService, taskExecutor); Injector.setModelOrService(AiService.class, aiService); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index e13eb0ecb6c..4caed630210 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -63,7 +63,6 @@ import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; -import org.jabref.preferences.ai.AiApiKeyProvider; import com.airhacks.afterburner.views.ViewLoader; import com.tobiasdiez.easybind.EasyBind; @@ -111,7 +110,6 @@ public class EntryEditor extends BorderPane { @Inject private DialogService dialogService; @Inject private TaskExecutor taskExecutor; @Inject private PreferencesService preferencesService; - @Inject private AiApiKeyProvider aiApiKeyProvider; @Inject private StateManager stateManager; @Inject private ThemeManager themeManager; @Inject private FileUpdateMonitor fileMonitor; diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTab.java b/src/main/java/org/jabref/gui/preferences/ai/AiTab.java index daff2c9999e..d557ef919c3 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTab.java +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTab.java @@ -15,7 +15,6 @@ import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.ai.AiApiKeyProvider; import org.jabref.preferences.ai.AiProvider; import org.jabref.preferences.ai.EmbeddingModel; @@ -23,15 +22,12 @@ import com.dlsc.gemsfx.ResizableTextArea; import com.dlsc.unitfx.IntegerInputField; import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; import org.controlsfx.control.SearchableComboBox; import org.controlsfx.control.textfield.CustomPasswordField; public class AiTab extends AbstractPreferenceTabView implements PreferencesTab { private static final String HUGGING_FACE_CHAT_MODEL_PROMPT = "TinyLlama/TinyLlama_v1.1 (or any other model name)"; - @Inject private AiApiKeyProvider aiApiKeyProvider; - @FXML private CheckBox enableAi; @FXML private ComboBox aiProviderComboBox; @@ -73,7 +69,7 @@ public AiTab() { } public void initialize() { - this.viewModel = new AiTabViewModel(preferencesService, aiApiKeyProvider); + this.viewModel = new AiTabViewModel(preferencesService); enableAi.selectedProperty().bindBidirectional(viewModel.enableAi()); diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java index b8bc2d4bc3f..031803d9f73 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java @@ -24,7 +24,6 @@ import org.jabref.logic.util.LocalizedNumbers; import org.jabref.model.strings.StringUtil; import org.jabref.preferences.PreferencesService; -import org.jabref.preferences.ai.AiApiKeyProvider; import org.jabref.preferences.ai.AiPreferences; import org.jabref.preferences.ai.AiProvider; import org.jabref.preferences.ai.EmbeddingModel; @@ -83,7 +82,6 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final BooleanProperty disableExpertSettings = new SimpleBooleanProperty(true); private final AiPreferences aiPreferences; - private final AiApiKeyProvider aiApiKeyProvider; private final Validator apiKeyValidator; private final Validator chatModelValidator; @@ -99,11 +97,10 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final Validator ragMinScoreTypeValidator; private final Validator ragMinScoreRangeValidator; - public AiTabViewModel(PreferencesService preferencesService, AiApiKeyProvider aiApiKeyProvider) { + public AiTabViewModel(PreferencesService preferencesService) { this.oldLocale = Locale.getDefault(); this.aiPreferences = preferencesService.getAiPreferences(); - this.aiApiKeyProvider = aiApiKeyProvider; this.enableAi.addListener((observable, oldValue, newValue) -> { disableBasicSettings.set(!newValue); @@ -266,9 +263,9 @@ public AiTabViewModel(PreferencesService preferencesService, AiApiKeyProvider ai @Override public void setValues() { - openAiApiKey.setValue(aiApiKeyProvider.getApiKeyForAiProvider(AiProvider.OPEN_AI)); - mistralAiApiKey.setValue(aiApiKeyProvider.getApiKeyForAiProvider(AiProvider.MISTRAL_AI)); - huggingFaceApiKey.setValue(aiApiKeyProvider.getApiKeyForAiProvider(AiProvider.HUGGING_FACE)); + openAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.OPEN_AI)); + mistralAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.MISTRAL_AI)); + huggingFaceApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.HUGGING_FACE)); openAiApiBaseUrl.setValue(aiPreferences.getOpenAiApiBaseUrl()); mistralAiApiBaseUrl.setValue(aiPreferences.getMistralAiApiBaseUrl()); @@ -305,9 +302,9 @@ public void storeSettings() { aiPreferences.setMistralAiChatModel(mistralAiChatModel.get() == null ? "" : mistralAiChatModel.get()); aiPreferences.setHuggingFaceChatModel(huggingFaceChatModel.get() == null ? "" : huggingFaceChatModel.get()); - aiApiKeyProvider.storeAiApiKeyInKeyring(AiProvider.OPEN_AI, openAiApiKey.get() == null ? "" : openAiApiKey.get()); - aiApiKeyProvider.storeAiApiKeyInKeyring(AiProvider.MISTRAL_AI, mistralAiApiKey.get() == null ? "" : mistralAiApiKey.get()); - aiApiKeyProvider.storeAiApiKeyInKeyring(AiProvider.HUGGING_FACE, huggingFaceApiKey.get() == null ? "" : huggingFaceApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.OPEN_AI, openAiApiKey.get() == null ? "" : openAiApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.MISTRAL_AI, mistralAiApiKey.get() == null ? "" : mistralAiApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.HUGGING_FACE, huggingFaceApiKey.get() == null ? "" : huggingFaceApiKey.get()); // We notify in all cases without a real check if something was changed aiPreferences.apiKeyUpdated(); @@ -389,10 +386,6 @@ public BooleanProperty enableAi() { return enableAi; } - public boolean getEnableAi() { - return enableAi.get(); - } - public ReadOnlyListProperty aiProvidersProperty() { return aiProvidersList; } diff --git a/src/main/java/org/jabref/logic/ai/AiService.java b/src/main/java/org/jabref/logic/ai/AiService.java index 0503307df27..a33e254c722 100644 --- a/src/main/java/org/jabref/logic/ai/AiService.java +++ b/src/main/java/org/jabref/logic/ai/AiService.java @@ -28,7 +28,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.preferences.FilePreferences; -import org.jabref.preferences.ai.AiApiKeyProvider; import org.jabref.preferences.ai.AiPreferences; import com.airhacks.afterburner.injection.Injector; @@ -79,7 +78,6 @@ public class AiService implements AutoCloseable { public AiService(AiPreferences aiPreferences, FilePreferences filePreferences, CitationKeyPatternPreferences citationKeyPatternPreferences, - AiApiKeyProvider aiApiKeyProvider, DialogService dialogService, TaskExecutor taskExecutor ) { @@ -88,7 +86,7 @@ public AiService(AiPreferences aiPreferences, this.dialogService = dialogService; this.taskExecutor = taskExecutor; - this.jabRefChatLanguageModel = new JabRefChatLanguageModel(aiPreferences, aiApiKeyProvider); + this.jabRefChatLanguageModel = new JabRefChatLanguageModel(aiPreferences); this.mvStoreEmbeddingStore = new MVStoreEmbeddingStore(JabRefDesktop.getAiFilesDirectory().resolve(EMBEDDINGS_FILE_NAME), dialogService); this.mvStoreFullyIngestedDocumentsTracker = new MVStoreFullyIngestedDocumentsTracker(JabRefDesktop.getAiFilesDirectory().resolve(FULLY_INGESTED_FILE_NAME), dialogService); diff --git a/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java b/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java index 8bd71112cb8..5e9c424e3ab 100644 --- a/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java +++ b/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java @@ -9,7 +9,6 @@ import org.jabref.logic.ai.chatting.AiChatLogic; import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.ai.AiApiKeyProvider; import org.jabref.preferences.ai.AiPreferences; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -24,13 +23,13 @@ /** * Wrapper around langchain4j chat language model. *

- * This class listens to preferences changes. + * Notice, that the real chat model is created lazily, when it's needed. This is done, so API key is fetched only, + * when user wants to chat with AI. */ public class JabRefChatLanguageModel implements ChatLanguageModel, AutoCloseable { private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(5); private final AiPreferences aiPreferences; - private final AiApiKeyProvider apiKeyProvider; private final HttpClient httpClient; private final ExecutorService executorService = Executors.newSingleThreadExecutor( @@ -39,9 +38,8 @@ public class JabRefChatLanguageModel implements ChatLanguageModel, AutoCloseable private Optional langchainChatModel = Optional.empty(); - public JabRefChatLanguageModel(AiPreferences aiPreferences, AiApiKeyProvider apiKeyProvider) { + public JabRefChatLanguageModel(AiPreferences aiPreferences) { this.aiPreferences = aiPreferences; - this.apiKeyProvider = apiKeyProvider; this.httpClient = HttpClient.newBuilder().connectTimeout(CONNECTION_TIMEOUT).executor(executorService).build(); setupListeningToPreferencesChanges(); @@ -54,7 +52,7 @@ public JabRefChatLanguageModel(AiPreferences aiPreferences, AiApiKeyProvider api * and see {@link org.jabref.logic.ai.chatting.chathistory.ChatHistoryStorage}. */ private void rebuild() { - String apiKey = apiKeyProvider.getApiKeyForAiProvider(aiPreferences.getAiProvider()); + String apiKey = aiPreferences.getApiKeyForAiProvider(aiPreferences.getAiProvider()); if (!aiPreferences.getEnableAi() || apiKey.isEmpty()) { langchainChatModel = Optional.empty(); return; @@ -62,7 +60,7 @@ private void rebuild() { switch (aiPreferences.getAiProvider()) { case OPEN_AI -> { - langchainChatModel = Optional.of(new JvmOpenAiChatLanguageModel(aiPreferences, apiKeyProvider, httpClient)); + langchainChatModel = Optional.of(new JvmOpenAiChatLanguageModel(aiPreferences, httpClient)); } case MISTRAL_AI -> { @@ -118,7 +116,7 @@ public Response generate(List list) { if (langchainChatModel.isEmpty()) { if (!aiPreferences.getEnableAi()) { throw new RuntimeException(Localization.lang("In order to use AI chat, you need to enable chatting with attached PDF files in JabRef preferences (AI tab).")); - } else if (apiKeyProvider.getApiKeyForAiProvider(aiPreferences.getAiProvider()).isEmpty()) { + } else if (aiPreferences.getApiKeyForAiProvider(aiPreferences.getAiProvider()).isEmpty()) { throw new RuntimeException(Localization.lang("In order to use AI chat, set an API key inside JabRef preferences (AI tab).")); } else { rebuild(); diff --git a/src/main/java/org/jabref/logic/ai/chatting/model/JvmOpenAiChatLanguageModel.java b/src/main/java/org/jabref/logic/ai/chatting/model/JvmOpenAiChatLanguageModel.java index f38435aaf8b..06b029b6551 100644 --- a/src/main/java/org/jabref/logic/ai/chatting/model/JvmOpenAiChatLanguageModel.java +++ b/src/main/java/org/jabref/logic/ai/chatting/model/JvmOpenAiChatLanguageModel.java @@ -3,7 +3,6 @@ import java.net.http.HttpClient; import java.util.List; -import org.jabref.preferences.ai.AiApiKeyProvider; import org.jabref.preferences.ai.AiPreferences; import dev.langchain4j.data.message.AiMessage; @@ -30,11 +29,11 @@ public class JvmOpenAiChatLanguageModel implements ChatLanguageModel { private final ChatClient chatClient; - public JvmOpenAiChatLanguageModel(AiPreferences aiPreferences, AiApiKeyProvider aiApiKeyProvider, HttpClient httpClient) { + public JvmOpenAiChatLanguageModel(AiPreferences aiPreferences, HttpClient httpClient) { this.aiPreferences = aiPreferences; OpenAI openAI = OpenAI - .newBuilder(aiApiKeyProvider.getApiKeyForAiProvider(aiPreferences.getAiProvider())) + .newBuilder(aiPreferences.getApiKeyForAiProvider(aiPreferences.getAiProvider())) .httpClient(httpClient) .baseUrl(aiPreferences.getSelectedApiBaseUrl()) .build(); diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 7a370e2dd49..9e069357f5c 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -127,7 +127,6 @@ import org.jabref.model.metadata.SelfContainedSaveOrder; import org.jabref.model.search.SearchFlags; import org.jabref.model.strings.StringUtil; -import org.jabref.preferences.ai.AiApiKeyProvider; import org.jabref.preferences.ai.AiPreferences; import org.jabref.preferences.ai.AiProvider; import org.jabref.preferences.ai.EmbeddingModel; @@ -155,7 +154,7 @@ */ @Singleton @Service -public class JabRefPreferences implements PreferencesService, AiApiKeyProvider { +public class JabRefPreferences implements PreferencesService { // Push to application preferences public static final String PUSH_EMACS_PATH = "emacsPath"; @@ -485,9 +484,6 @@ public class JabRefPreferences implements PreferencesService, AiApiKeyProvider { private static final String AI_RAG_MAX_RESULTS_COUNT = "aiRagMaxResultsCount"; private static final String AI_RAG_MIN_SCORE = "aiRagMinScore"; - private static final String KEYRING_AI_SERVICE = "org.jabref.ai"; - private static final String KEYRING_AI_SERVICE_ACCOUNT = "apiKey"; - private static final Logger LOGGER = LoggerFactory.getLogger(JabRefPreferences.class); private static final Preferences PREFS_NODE = Preferences.userRoot().node("/org/jabref"); @@ -2793,7 +2789,6 @@ public AiPreferences getAiPreferences() { boolean aiEnabled = getBoolean(AI_ENABLED); aiPreferences = new AiPreferences( - this, aiEnabled, AiProvider.valueOf(get(AI_PROVIDER)), get(AI_OPEN_AI_CHAT_MODEL), @@ -2838,34 +2833,6 @@ public AiPreferences getAiPreferences() { return aiPreferences; } - public String getApiKeyForAiProvider(AiProvider aiProvider) { - try (final Keyring keyring = Keyring.create()) { - return keyring.getPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name()); - } catch (PasswordAccessException e) { - LOGGER.debug("No API key stored for provider {}. Returning an empty string", aiProvider.getLabel()); - return ""; - } catch (Exception e) { - LOGGER.warn("JabRef could not open keyring for retrieving {} API token", aiProvider.getLabel(), e); - return ""; - } - } - - public void storeAiApiKeyInKeyring(AiProvider aiProvider, String newKey) { - try (final Keyring keyring = Keyring.create()) { - if (StringUtil.isNullOrEmpty(newKey)) { - try { - keyring.deletePassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name()); - } catch (PasswordAccessException ex) { - LOGGER.debug("API key for provider {} not stored in keyring. JabRef does not store an empty key.", aiProvider.getLabel()); - } - } else { - keyring.setPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name(), newKey); - } - } catch (Exception e) { - LOGGER.warn("JabRef could not open keyring for storing {} API token", aiProvider.getLabel(), e); - } - } - //************************************************************************************************************* // Misc preferences //************************************************************************************************************* diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index f7aff7fc0b1..ea7e99c99fd 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -34,7 +34,6 @@ import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.preferences.ai.AiPreferences; -import org.jabref.preferences.ai.AiProvider; import org.jvnet.hk2.annotations.Contract; @@ -152,12 +151,4 @@ public interface PreferencesService { UnlinkedFilesDialogPreferences getUnlinkedFilesDialogPreferences(); AiPreferences getAiPreferences(); - - /** - * Retrieves the API key for the specified AI provider. - * - * @param provider the AI provider for which the API key is requested - * @return the API key for the specified AI provider, or empty string if no key is found - */ - String getApiKeyForAiProvider(AiProvider provider); } diff --git a/src/main/java/org/jabref/preferences/ai/AiApiKeyProvider.java b/src/main/java/org/jabref/preferences/ai/AiApiKeyProvider.java deleted file mode 100644 index cedec6b8606..00000000000 --- a/src/main/java/org/jabref/preferences/ai/AiApiKeyProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.jabref.preferences.ai; - -public interface AiApiKeyProvider { - String getApiKeyForAiProvider(AiProvider provider); - - void storeAiApiKeyInKeyring(AiProvider aiProvider, String newKey); -} diff --git a/src/main/java/org/jabref/preferences/ai/AiPreferences.java b/src/main/java/org/jabref/preferences/ai/AiPreferences.java index a3ae3b2fefb..53f13f210a1 100644 --- a/src/main/java/org/jabref/preferences/ai/AiPreferences.java +++ b/src/main/java/org/jabref/preferences/ai/AiPreferences.java @@ -1,11 +1,13 @@ package org.jabref.preferences.ai; +import java.util.List; import java.util.Objects; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; @@ -14,10 +16,18 @@ import javafx.beans.property.StringProperty; import org.jabref.logic.ai.AiDefaultPreferences; -import org.jabref.preferences.PreferencesService; +import org.jabref.model.strings.StringUtil; + +import com.github.javakeyring.Keyring; +import com.github.javakeyring.PasswordAccessException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class AiPreferences { - private final PreferencesService preferencesService; + private static final Logger LOGGER = LoggerFactory.getLogger(AiPreferences.class); + + private static final String KEYRING_AI_SERVICE = "org.jabref.ai"; + private static final String KEYRING_AI_SERVICE_ACCOUNT = "apiKey"; private final BooleanProperty enableAi; @@ -44,8 +54,7 @@ public class AiPreferences { private Runnable apiKeyChangeListener; - public AiPreferences(PreferencesService preferencesService, - boolean enableAi, + public AiPreferences(boolean enableAi, AiProvider aiProvider, String openAiChatModel, String mistralAiChatModel, @@ -63,8 +72,6 @@ public AiPreferences(PreferencesService preferencesService, int ragMaxResultsCount, double ragMinScore ) { - this.preferencesService = preferencesService; - this.enableAi = new SimpleBooleanProperty(enableAi); this.aiProvider = new SimpleObjectProperty<>(aiProvider); @@ -89,6 +96,35 @@ public AiPreferences(PreferencesService preferencesService, this.ragMinScore = new SimpleDoubleProperty(ragMinScore); } + public String getApiKeyForAiProvider(AiProvider aiProvider) { + try (final Keyring keyring = Keyring.create()) { + return keyring.getPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name()); + } catch ( + PasswordAccessException e) { + LOGGER.debug("No API key stored for provider {}. Returning an empty string", aiProvider.getLabel()); + return ""; + } catch (Exception e) { + LOGGER.warn("JabRef could not open keyring for retrieving {} API token", aiProvider.getLabel(), e); + return ""; + } + } + + public void storeAiApiKeyInKeyring(AiProvider aiProvider, String newKey) { + try (final Keyring keyring = Keyring.create()) { + if (StringUtil.isNullOrEmpty(newKey)) { + try { + keyring.deletePassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name()); + } catch (PasswordAccessException ex) { + LOGGER.debug("API key for provider {} not stored in keyring. JabRef does not store an empty key.", aiProvider.getLabel()); + } + } else { + keyring.setPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name(), newKey); + } + } catch (Exception e) { + LOGGER.warn("JabRef could not open keyring for storing {} API token", aiProvider.getLabel(), e); + } + } + public BooleanProperty enableAiProperty() { return enableAi; } @@ -355,43 +391,23 @@ public void addListenerToEmbeddingsParametersChange(Runnable runnable) { } public void addListenerToChatModels(Runnable runnable) { - openAiChatModel.addListener((observableValue, oldValue, newValue) -> { - if (!newValue.equals(oldValue)) { - runnable.run(); - } - }); - - mistralAiChatModel.addListener((observableValue, oldValue, newValue) -> { - if (!newValue.equals(oldValue)) { - runnable.run(); - } - }); + List> observables = List.of(openAiChatModel, mistralAiChatModel, huggingFaceChatModel); - huggingFaceChatModel.addListener((observableValue, oldValue, newValue) -> { + observables.forEach(obs -> obs.addListener((observableValue, oldValue, newValue) -> { if (!newValue.equals(oldValue)) { runnable.run(); } - }); + })); } public void addListenerToApiBaseUrls(Runnable runnable) { - openAiApiBaseUrl.addListener((observableValue, oldValue, newValue) -> { - if (!newValue.equals(oldValue)) { - runnable.run(); - } - }); + List> observables = List.of(openAiApiBaseUrl, mistralAiApiBaseUrl, huggingFaceApiBaseUrl); - mistralAiApiBaseUrl.addListener((observableValue, oldValue, newValue) -> { + observables.forEach(obs -> obs.addListener((observableValue, oldValue, newValue) -> { if (!newValue.equals(oldValue)) { runnable.run(); } - }); - - huggingFaceApiBaseUrl.addListener((observableValue, oldValue, newValue) -> { - if (!newValue.equals(oldValue)) { - runnable.run(); - } - }); + })); } public String getSelectedChatModel() { From f49dfac901843b82f8d32b743bd4001dafdd21ff Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 8 Sep 2024 18:26:20 +0200 Subject: [PATCH 05/34] Fix ArgumentProcessor (#11728) * Refine JavaDoc * Add TODOs Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Fix typos * Reoder settings * Fix arch flaw Co-authored-by: Christoph Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Fix checkstyle and formatting --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Co-authored-by: Christoph --- .../org/jabref/cli/ArgumentProcessor.java | 40 +++++++++++-------- .../externalfiles/AutoLinkFilesAction.java | 19 ++++++++- .../externalfiles/AutoSetFileLinksUtil.java | 32 ++++----------- .../gui/frame/JabRefFrameViewModel.java | 17 +++++++- .../gui/maintable/BibEntryTableViewModel.java | 2 +- src/main/java/org/jabref/logic/UiCommand.java | 2 + .../jabref/logic/search/DatabaseSearcher.java | 1 + 7 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 1ac2d50da67..6d4649c7739 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -14,7 +14,6 @@ import java.util.prefs.BackingStoreException; import org.jabref.gui.externalfiles.AutoSetFileLinksUtil; -import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.CurrentThreadTaskExecutor; import org.jabref.logic.JabRefException; import org.jabref.logic.UiCommand; @@ -99,16 +98,13 @@ public ArgumentProcessor(String[] args, } /** - * Will open a file (like importFile), but will also request JabRef to focus on this database + * Will open a file (like {@link #importFile(String)}, but will also request JabRef to focus on this database. * - * @param argument See importFile. * @return ParserResult with setToOpenTab(true) */ - private Optional importToOpenBase(String argument) { - Optional result = importFile(argument); - + private Optional importToOpenBase(String importArguments) { + Optional result = importFile(importArguments); result.ifPresent(ParserResult::setToOpenTab); - return result; } @@ -125,9 +121,13 @@ private Optional importBibtexToOpenBase(String argument, ImportFor } } - private Optional importFile(String argument) { - LOGGER.debug("Importing file {}", argument); - String[] data = argument.split(","); + /** + * + * @param importArguments Format: fileName[,format] + */ + private Optional importFile(String importArguments) { + LOGGER.debug("Importing file {}", importArguments); + String[] data = importArguments.split(","); String address = data[0]; Path file; @@ -295,7 +295,7 @@ public void processArguments() { } private void writeMetadataToPdf(List loaded, - String filesAndCitekeys, + String filesAndCiteKeys, XmpPreferences xmpPreferences, FilePreferences filePreferences, BibDatabaseMode databaseMode, @@ -314,7 +314,7 @@ private void writeMetadataToPdf(List loaded, XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences); EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldPreferences); - if ("all".equals(filesAndCitekeys)) { + if ("all".equals(filesAndCiteKeys)) { for (BibEntry entry : databaseContext.getEntries()) { writeMetadataToPDFsOfEntry( databaseContext, @@ -332,7 +332,7 @@ private void writeMetadataToPdf(List loaded, List citeKeys = new ArrayList<>(); List pdfs = new ArrayList<>(); - for (String fileOrCiteKey : filesAndCitekeys.split(",")) { + for (String fileOrCiteKey : filesAndCiteKeys.split(",")) { if (fileOrCiteKey.toLowerCase(Locale.ROOT).endsWith(".pdf")) { pdfs.add(fileOrCiteKey); } else { @@ -368,7 +368,7 @@ private void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, JournalAbbreviationRepository abbreviationRepository, boolean writeXMP, - boolean embeddBibfile) { + boolean embedBibfile) { try { if (writeXMP) { if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { @@ -377,7 +377,7 @@ private void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, System.err.printf("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); } } - if (embeddBibfile) { + if (embedBibfile) { if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { System.out.printf("Successfully embedded metadata on at least one linked file of %s%n", citeKey); } else { @@ -461,6 +461,7 @@ private boolean exportMatches(List loaded) { List matches; try { + // extract current thread task executor from luceneManager matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), preferencesService.getFilePreferences()).getMatches(); } catch (IOException e) { LOGGER.error("Error occurred when searching", e); @@ -729,12 +730,17 @@ private void resetPreferences(String value) { private void automaticallySetFileLinks(List loaded) { for (ParserResult parserResult : loaded) { BibDatabase database = parserResult.getDatabase(); - LOGGER.info(Localization.lang("Automatically setting file links")); + LOGGER.info("Automatically setting file links for {}", + parserResult.getDatabaseContext().getDatabasePath() + .map(Path::getFileName) + .map(Path::toString).orElse("UNKNOWN")); + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( parserResult.getDatabaseContext(), preferencesService.getFilePreferences(), preferencesService.getAutoLinkPreferences()); - util.linkAssociatedFiles(database.getEntries(), new NamedCompound("")); + + util.linkAssociatedFiles(database.getEntries(), (linkedFile, bibEntry) -> bibEntry.addFile(linkedFile)); } } diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java b/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java index 689d79dca69..41f7257732a 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java @@ -1,6 +1,7 @@ package org.jabref.gui.externalfiles; import java.util.List; +import java.util.function.BiConsumer; import javax.swing.undo.UndoManager; @@ -10,11 +11,15 @@ import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.util.BindingsHelper; import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.bibtex.FileFieldWriter; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.field.StandardField; import org.jabref.preferences.PreferencesService; import static org.jabref.gui.actions.ActionHelper.needsDatabase; @@ -47,16 +52,26 @@ public AutoLinkFilesAction(DialogService dialogService, PreferencesService prefe public void execute() { final BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); final List entries = stateManager.getSelectedEntries(); - final AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( + + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( database, preferences.getFilePreferences(), preferences.getAutoLinkPreferences()); final NamedCompound nc = new NamedCompound(Localization.lang("Automatically set file links")); Task linkFilesTask = new Task<>() { + final BiConsumer onLinkedFile = (linkedFile, entry) -> { + // lambda for gui actions that are relevant when setting the linked file entry when ui is opened + String newVal = FileFieldWriter.getStringRepresentation(linkedFile); + String oldVal = entry.getField(StandardField.FILE).orElse(null); + UndoableFieldChange fieldChange = new UndoableFieldChange(entry, StandardField.FILE, oldVal, newVal); + nc.addEdit(fieldChange); // push to undo manager is in succeeded + entry.addFile(linkedFile); + }; + @Override protected AutoSetFileLinksUtil.LinkFilesResult call() { - return util.linkAssociatedFiles(entries, nc); + return util.linkAssociatedFiles(entries, onLinkedFile); } @Override diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java index 7733bedfb07..da256e45125 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -6,14 +6,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.BiConsumer; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.externalfiletype.UnknownExternalFileType; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.gui.util.UiTaskExecutor; -import org.jabref.logic.bibtex.FileFieldWriter; import org.jabref.logic.util.io.AutoLinkPreferences; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; @@ -21,7 +18,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; -import org.jabref.model.entry.field.StandardField; import org.jabref.preferences.FilePreferences; import org.slf4j.Logger; @@ -66,7 +62,7 @@ private AutoSetFileLinksUtil(List directories, FilePreferences filePrefere this.filePreferences = filePreferences; } - public LinkFilesResult linkAssociatedFiles(List entries, NamedCompound ce) { + public LinkFilesResult linkAssociatedFiles(List entries, BiConsumer onAddLinkedFile) { LinkFilesResult result = new LinkFilesResult(); for (BibEntry entry : entries) { @@ -79,26 +75,12 @@ public LinkFilesResult linkAssociatedFiles(List entries, NamedCompound LOGGER.error("Problem finding files", e); } - if (ce != null) { - boolean changed = false; - - for (LinkedFile linkedFile : linkedFiles) { - // store undo information - String newVal = FileFieldWriter.getStringRepresentation(linkedFile); - String oldVal = entry.getField(StandardField.FILE).orElse(null); - UndoableFieldChange fieldChange = new UndoableFieldChange(entry, StandardField.FILE, oldVal, newVal); - ce.addEdit(fieldChange); - changed = true; - - UiTaskExecutor.runInJavaFXThread(() -> { - entry.addFile(linkedFile); - }); - } - - if (changed) { - result.addBibEntry(entry); - } + for (LinkedFile linkedFile : linkedFiles) { + // store undo information + onAddLinkedFile.accept(linkedFile, entry); } + + result.addBibEntry(entry); } return result; } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java b/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java index 924a8baa675..42f7f0957fc 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java @@ -21,6 +21,7 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; +import org.jabref.gui.externalfiles.AutoLinkFilesAction; import org.jabref.gui.importer.ImportEntriesDialog; import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; @@ -158,7 +159,14 @@ public void handleUiCommands(List uiCommands) { .forEach(command -> openDatabases(command.parserResults())); } + // Handle automatically setting file links + uiCommands.stream() + .filter(UiCommand.AutoSetFileLinks.class::isInstance).findAny() + .map(UiCommand.AutoSetFileLinks.class::cast) + .ifPresent(autoSetFileLinks -> autoSetFileLinks(autoSetFileLinks.parserResults())); + // Handle jumpToEntry + // Needs to go last, because it requires all libraries opened uiCommands.stream() .filter(UiCommand.JumpToEntryKey.class::isInstance) .map(UiCommand.JumpToEntryKey.class::cast) @@ -172,14 +180,13 @@ public void handleUiCommands(List uiCommands) { } private void openDatabases(List parserResults) { - final List failed = new ArrayList<>(); final List toOpenTab = new ArrayList<>(); // Remove invalid databases List invalidDatabases = parserResults.stream() .filter(ParserResult::isInvalid) .toList(); - failed.addAll(invalidDatabases); + final List failed = new ArrayList<>(invalidDatabases); parserResults.removeAll(invalidDatabases); // passed file (we take the first one) should be focused @@ -397,4 +404,10 @@ void addImportedEntries(final LibraryTab tab, final ParserResult parserResult) { dialog.setTitle(Localization.lang("Import")); dialogService.showCustomDialogAndWait(dialog); } + + void autoSetFileLinks(List loaded) { + for (ParserResult parserResult : loaded) { + new AutoLinkFilesAction(dialogService, preferences, stateManager, undoManager, taskExecutor).execute(); + } + } } diff --git a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java index eded204b933..aa8fdc38ec3 100644 --- a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java +++ b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java @@ -60,12 +60,12 @@ public class BibEntryTableViewModel { public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseContext, ObservableValue fieldValueFormatter) { this.entry = entry; + this.bibDatabaseContext = bibDatabaseContext; this.fieldValueFormatter = fieldValueFormatter; this.linkedFiles = getField(StandardField.FILE).mapOpt(FileFieldParser::parse).orElseOpt(Collections.emptyList()); this.linkedIdentifiers = createLinkedIdentifiersBinding(entry); this.matchedGroups = createMatchedGroupsBinding(bibDatabaseContext, entry); - this.bibDatabaseContext = bibDatabaseContext; } private static EasyBinding> createLinkedIdentifiersBinding(BibEntry entry) { diff --git a/src/main/java/org/jabref/logic/UiCommand.java b/src/main/java/org/jabref/logic/UiCommand.java index e409d65b0be..1fc8a699ce5 100644 --- a/src/main/java/org/jabref/logic/UiCommand.java +++ b/src/main/java/org/jabref/logic/UiCommand.java @@ -10,4 +10,6 @@ record BlankWorkspace() implements UiCommand { } record JumpToEntryKey(String citationKey) implements UiCommand { } record OpenDatabases(List parserResults) implements UiCommand { } + + record AutoSetFileLinks(List parserResults) implements UiCommand { } } diff --git a/src/main/java/org/jabref/logic/search/DatabaseSearcher.java b/src/main/java/org/jabref/logic/search/DatabaseSearcher.java index bf5e74e070b..1a8bc4e8945 100644 --- a/src/main/java/org/jabref/logic/search/DatabaseSearcher.java +++ b/src/main/java/org/jabref/logic/search/DatabaseSearcher.java @@ -22,6 +22,7 @@ public class DatabaseSearcher { private final SearchQuery query; private final LuceneManager luceneManager; + // get rid of task executor here or add a constuctor overload? public DatabaseSearcher(SearchQuery query, BibDatabaseContext databaseContext, TaskExecutor taskExecutor, FilePreferences filePreferences) throws IOException { this.databaseContext = databaseContext; this.query = Objects.requireNonNull(query); From 28164eba12394bf09ef686447764082f0b5f5ae3 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 8 Sep 2024 20:19:37 +0200 Subject: [PATCH 06/34] New Crowdin updates (#11730) * New translations jabref_en.properties (French) * New translations jabref_en.properties (Polish) * New translations jabref_en.properties (Portuguese, Brazilian) * New translations jabref_en.properties (Spanish) * New translations jabref_en.properties (Arabic) * New translations jabref_en.properties (Danish) * New translations jabref_en.properties (German) * New translations jabref_en.properties (Greek) * New translations jabref_en.properties (Finnish) * New translations jabref_en.properties (Italian) * New translations jabref_en.properties (Japanese) * New translations jabref_en.properties (Korean) * New translations jabref_en.properties (Dutch) * New translations jabref_en.properties (Norwegian) * New translations jabref_en.properties (Portuguese) * New translations jabref_en.properties (Russian) * New translations jabref_en.properties (Swedish) * New translations jabref_en.properties (Turkish) * New translations jabref_en.properties (Ukrainian) * New translations jabref_en.properties (Chinese Simplified) * New translations jabref_en.properties (Chinese Traditional) * New translations jabref_en.properties (Vietnamese) * New translations jabref_en.properties (Indonesian) * New translations jabref_en.properties (Persian) * New translations jabref_en.properties (Tagalog) --- src/main/resources/l10n/JabRef_ar.properties | 1 + src/main/resources/l10n/JabRef_da.properties | 1 + src/main/resources/l10n/JabRef_de.properties | 1 + src/main/resources/l10n/JabRef_el.properties | 1 + src/main/resources/l10n/JabRef_es.properties | 1 + src/main/resources/l10n/JabRef_fa.properties | 1 + src/main/resources/l10n/JabRef_fi.properties | 1 + src/main/resources/l10n/JabRef_fr.properties | 3 +++ src/main/resources/l10n/JabRef_id.properties | 1 + src/main/resources/l10n/JabRef_it.properties | 3 +++ src/main/resources/l10n/JabRef_ja.properties | 1 + src/main/resources/l10n/JabRef_ko.properties | 1 + src/main/resources/l10n/JabRef_nl.properties | 1 + src/main/resources/l10n/JabRef_no.properties | 1 + src/main/resources/l10n/JabRef_pl.properties | 3 +++ src/main/resources/l10n/JabRef_pt.properties | 1 + src/main/resources/l10n/JabRef_pt_BR.properties | 3 +++ src/main/resources/l10n/JabRef_ru.properties | 1 + src/main/resources/l10n/JabRef_sv.properties | 1 + src/main/resources/l10n/JabRef_tl.properties | 1 + src/main/resources/l10n/JabRef_tr.properties | 1 + src/main/resources/l10n/JabRef_uk.properties | 1 + src/main/resources/l10n/JabRef_vi.properties | 1 + src/main/resources/l10n/JabRef_zh_CN.properties | 1 + src/main/resources/l10n/JabRef_zh_TW.properties | 1 + 25 files changed, 33 insertions(+) diff --git a/src/main/resources/l10n/JabRef_ar.properties b/src/main/resources/l10n/JabRef_ar.properties index 996ce6bf593..44275e54d6b 100644 --- a/src/main/resources/l10n/JabRef_ar.properties +++ b/src/main/resources/l10n/JabRef_ar.properties @@ -716,5 +716,6 @@ File\ not\ found=لم يتم العثور على الملف + diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 296ce587c92..234153c4fae 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -1011,3 +1011,4 @@ Path\ to\ %0=Sti til %0 + diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index b3ae803b34e..6689fcc5d94 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2745,6 +2745,7 @@ Ask\ every\ time=Immer nachfragen Value\ is\ not\ in\ Unicode's\ Normalization\ Form\ "Canonical\ Composition"\ (NFC)\ format=Wert liegt nicht in der Unicode-Normalform "Kanonische Komposition" (NFC) Format vor + Group\ icons=Gruppensymbole Redownload\ file=Datei erneut herunterladen Redownload\ missing\ files=Fehlende Dateien erneut herunterladen diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 9fdc68de6f4..2e7ec4c0f82 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -1794,3 +1794,4 @@ Related\ articles=Σχετικά άρθρα + diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index fa623b1d75d..510461198e9 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2475,6 +2475,7 @@ Ask\ every\ time=Preguntar siempre Value\ is\ not\ in\ Unicode's\ Normalization\ Form\ "Canonical\ Composition"\ (NFC)\ format=El valor no está en el formato «composición canónica» (NFC) de la forma de normalización de Unicode + Redownload\ file=Volver a descargar archivo Redownload\ missing\ files=Volver a descargar archivos faltantes Redownload\ missing\ files\ for\ current\ library?=¿Quiere volver a descargar los archivos faltantes en la biblioteca actual? diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 2901bb40930..a579532fa30 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -652,5 +652,6 @@ Auto\ complete\ enabled.=تکمیل خودکار غیرفعال شد. + diff --git a/src/main/resources/l10n/JabRef_fi.properties b/src/main/resources/l10n/JabRef_fi.properties index 5850a7ae7c7..66442149920 100644 --- a/src/main/resources/l10n/JabRef_fi.properties +++ b/src/main/resources/l10n/JabRef_fi.properties @@ -596,5 +596,6 @@ Proxy\ requires\ password=Välityspalvelin vaatii salasanan + diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 379b261e014..0e53c04e124 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2765,6 +2765,9 @@ Ask\ every\ time=Toujours demander Value\ is\ not\ in\ Unicode's\ Normalization\ Form\ "Canonical\ Composition"\ (NFC)\ format=Une valeur n'est pas au format de Normalisation de l'Unicode "Composition Canonique" (NFC) +Copy\ failed=Échec de la copie +Cut\ failed=Échec de la coupe + Group\ icons=Icônes de groupe Redownload\ file=Télécharger à nouveau le fichier Redownload\ missing\ files=Télécharger à nouveau les fichiers manquants diff --git a/src/main/resources/l10n/JabRef_id.properties b/src/main/resources/l10n/JabRef_id.properties index 6692e127384..6ee10e99a5f 100644 --- a/src/main/resources/l10n/JabRef_id.properties +++ b/src/main/resources/l10n/JabRef_id.properties @@ -1558,3 +1558,4 @@ Related\ articles=Artikel terkait + diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 5c45d339556..2d88ccb5727 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2731,6 +2731,9 @@ Ask\ every\ time=Chiedi ogni volta Value\ is\ not\ in\ Unicode's\ Normalization\ Form\ "Canonical\ Composition"\ (NFC)\ format=Il valore non è nel formato di Normalizzazione della "Composizione Canonica" (NFC) di Unicode +Copy\ failed=Copia non riuscita +Cut\ failed=Taglio fallito + Group\ icons=Icone di gruppo Redownload\ file=Scarica nuovamente il file Redownload\ missing\ files=Scarica nuovamente i file mancanti diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 8c2787d36d0..42e975a2cd9 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2364,3 +2364,4 @@ Related\ articles=関連文献 + diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties index 9599e486a08..331a92dd02e 100644 --- a/src/main/resources/l10n/JabRef_ko.properties +++ b/src/main/resources/l10n/JabRef_ko.properties @@ -2198,3 +2198,4 @@ Related\ articles=관련 글 + diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 906ab442817..8b67bace406 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2464,3 +2464,4 @@ Related\ articles=Gerelateerde artikelen + diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index f1a5bf336f6..3b5c8239b23 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -1122,3 +1122,4 @@ Path\ to\ %0=Sti til %0 + diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index cf265d2535d..a9e12512966 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -1848,6 +1848,9 @@ Copied\ %0\ entry(ies)=Skopiowano %0 wpis(ów) Ask\ every\ time=Pytaj za każdym razem +Copy\ failed=Nie udało się skopiować +Cut\ failed=Nie udało się wyciąć + Redownload\ file=Pobierz ponownie plik Redownload\ missing\ files=Pobierz ponownie brakujące pliki Redownload\ missing\ files\ for\ current\ library?=Pobrać ponownie brakujące pliki dla bieżącej biblioteki? diff --git a/src/main/resources/l10n/JabRef_pt.properties b/src/main/resources/l10n/JabRef_pt.properties index 5f2bfd52712..f7a4a838b7b 100644 --- a/src/main/resources/l10n/JabRef_pt.properties +++ b/src/main/resources/l10n/JabRef_pt.properties @@ -1373,3 +1373,4 @@ Related\ articles=Artigos relacionados + diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 69e456b44f8..5896c114742 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2760,6 +2760,9 @@ Ask\ every\ time=Sempre perguntar Value\ is\ not\ in\ Unicode's\ Normalization\ Form\ "Canonical\ Composition"\ (NFC)\ format=O valor não está no formato de Composição Canônica de Normalização do Unicode (NFC) +Copy\ failed=Falha ao copiar +Cut\ failed=Cortar falhou + Group\ icons=Ícones de grupo Redownload\ file=Baixar novamente o arquivo Redownload\ missing\ files=Baixar novamente arquivos ausentes diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index ad69bad8a89..bbb5fe20c57 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2332,3 +2332,4 @@ Related\ articles=Связанные статьи + diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index b8b99413bbe..25ee8df12dd 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -1519,6 +1519,7 @@ Related\ articles=Relaterade artiklar + Warning\:\ The\ selected\ directory\ is\ not\ empty.=Varning\: Den valda mappen är inte tom. Warning\:\ Failed\ to\ check\ if\ the\ directory\ is\ empty.=Varning\: Det gick inte att kontrollera om katalogen är tom. Warning\:\ The\ selected\ directory\ is\ not\ a\ valid\ directory.=Varning\: Den valda mappen är inte en giltig mapp. diff --git a/src/main/resources/l10n/JabRef_tl.properties b/src/main/resources/l10n/JabRef_tl.properties index d536a38d6f6..8fbeea796c8 100644 --- a/src/main/resources/l10n/JabRef_tl.properties +++ b/src/main/resources/l10n/JabRef_tl.properties @@ -1251,3 +1251,4 @@ Related\ articles=Kaugnay na mga artikulo + diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 85414b55085..a751e569474 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2519,3 +2519,4 @@ More\ options...=Daha fazla seçenekler... + diff --git a/src/main/resources/l10n/JabRef_uk.properties b/src/main/resources/l10n/JabRef_uk.properties index 4b0335667d1..2d6106c3c6a 100644 --- a/src/main/resources/l10n/JabRef_uk.properties +++ b/src/main/resources/l10n/JabRef_uk.properties @@ -663,5 +663,6 @@ Proxy\ requires\ password=Потрібен пароль проксі-серве + diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 69c20026bfc..3caae272168 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -1055,3 +1055,4 @@ Path\ to\ %0=Đường dẫn đến %0 + diff --git a/src/main/resources/l10n/JabRef_zh_CN.properties b/src/main/resources/l10n/JabRef_zh_CN.properties index 9b391a81922..5c1970d7b26 100644 --- a/src/main/resources/l10n/JabRef_zh_CN.properties +++ b/src/main/resources/l10n/JabRef_zh_CN.properties @@ -2451,3 +2451,4 @@ Related\ articles=相关文章 + diff --git a/src/main/resources/l10n/JabRef_zh_TW.properties b/src/main/resources/l10n/JabRef_zh_TW.properties index a7df93ce477..48b91b8829d 100644 --- a/src/main/resources/l10n/JabRef_zh_TW.properties +++ b/src/main/resources/l10n/JabRef_zh_TW.properties @@ -1044,3 +1044,4 @@ Related\ articles=相關文章 + From 3df3b4fd427b27e9ac8ef603c10996d7118f1f9a Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 8 Sep 2024 22:43:30 +0200 Subject: [PATCH 07/34] Fixed an exception when searching for unlinked files. (#11731) * Fixed an exception when searching for unlinked files. Co-authored-by: Christoph * Update CHANGELOG.md --------- Co-authored-by: Christoph --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/util/FileNodeViewModel.java | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a090ea191..a61a78fddcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where the full-text search results were incomplete. [#8626](https://github.com/JabRef/jabref/issues/8626) - We fixed an issue where search result highlighting was incorrectly highlighting the boolean operators. [#11595](https://github.com/JabRef/jabref/issues/11595) - We fixed an issue where search result highlighting was broken at complex searches. [#8067](https://github.com/JabRef/jabref/issues/8067) +- We fixed an exception when searching for unlinked files. [#11731](https://github.com/JabRef/jabref/issues/11731) - We fixed an issue where two contradicting notifications were shown when cutting an entry in the main table. [#11724](https://github.com/JabRef/jabref/pull/11724) - We fixed an issue where unescaped braces in the arXiv fetcher were not treated. [#11704](https://github.com/JabRef/jabref/issues/11704) diff --git a/src/main/java/org/jabref/gui/util/FileNodeViewModel.java b/src/main/java/org/jabref/gui/util/FileNodeViewModel.java index b4d23c1a140..253ea20a69f 100644 --- a/src/main/java/org/jabref/gui/util/FileNodeViewModel.java +++ b/src/main/java/org/jabref/gui/util/FileNodeViewModel.java @@ -64,7 +64,7 @@ public static String formatDateTime(FileTime fileTime) { */ public String getDisplayText() { if (path.toFile().isDirectory()) { - return "%s (%s %s)".formatted(path.getFileName(), Localization.lang("%0 file(s)", fileCount)); + return "%s (%s)".formatted(path.getFileName(), Localization.lang("%0 file(s)", fileCount)); } return path.getFileName().toString(); } @@ -75,7 +75,7 @@ public String getDisplayText() { */ public String getDisplayTextWithEditDate() { if (path.toFile().isDirectory()) { - return "%s (%s %s)".formatted(path.getFileName(), Localization.lang("%0 file(s)", fileCount)); + return "%s (%s)".formatted(path.getFileName(), Localization.lang("%0 file(s)", fileCount)); } FileTime lastEditedTime = null; try { @@ -83,7 +83,7 @@ public String getDisplayTextWithEditDate() { } catch (IOException e) { LOGGER.error("Could not get last modified time", e); } - return "%s (%s: %s)".formatted(path.getFileName().toString(), Localization.lang("last edited"), formatDateTime(lastEditedTime)); + return "%s (%s: %s)".formatted(path.getFileName(), Localization.lang("last edited"), formatDateTime(lastEditedTime)); } @Override @@ -104,10 +104,9 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (!(obj instanceof FileNodeViewModel)) { + if (!(obj instanceof FileNodeViewModel other)) { return false; } - FileNodeViewModel other = (FileNodeViewModel) obj; return Objects.equals(children, other.children) && (fileCount == other.fileCount) && Objects.equals(path, other.path); } } From 85d8d8ac2ed1bb25729a7e2b14e94680788d179b Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 9 Sep 2024 12:30:16 +0200 Subject: [PATCH 08/34] Add pref switch to not store url (#11735) * Add option to disable keeping download url Refs https://discourse.jabref.org/t/import-adds-pdf-link-and-url-to-file-entry/4484/ * Fix checkstyle * Compilefix * add default * Update CHANGELOG.md * add addtional check * fix ui * fix test * fix fcking test --------- Co-authored-by: Oliver Kopp --- CHANGELOG.md | 1 + .../linkedfile/DownloadLinkedFileAction.java | 4 ++-- .../gui/preferences/websearch/WebSearchTab.fxml | 1 + .../gui/preferences/websearch/WebSearchTab.java | 2 ++ .../websearch/WebSearchTabViewModel.java | 9 +++++++-- .../org/jabref/preferences/FilePreferences.java | 17 ++++++++++++++++- .../jabref/preferences/JabRefPreferences.java | 6 +++++- src/main/resources/l10n/JabRef_en.properties | 1 + .../DownloadLinkedFileActionTest.java | 2 ++ 9 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61a78fddcb..b20c6bf2e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We enabled creating a new file link manually. [#11017](https://github.com/JabRef/jabref/issues/11017) - We added a toggle button to invert the selected groups. [#9073](https://github.com/JabRef/jabref/issues/9073) - We reintroduced the floating search in the main table. [#4237](https://github.com/JabRef/jabref/issues/4237) +- We added a switch not to store the linked file URL, because it caused troubles at other apps. [#11735](https://github.com/JabRef/jabref/pull/11735) - When starting a new SLR, the selected catalogs now persist within and across JabRef sessions. [koppor#614](https://github.com/koppor/jabref/issues/614) - We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658) diff --git a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java index 490da28bdca..ea434197f9c 100644 --- a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java @@ -169,9 +169,9 @@ private void onSuccess(Path targetDirectory, Path downloadedFile) { if (newLinkedFile.getDescription().isEmpty() && !linkedFile.getDescription().isEmpty()) { newLinkedFile.setDescription((linkedFile.getDescription())); } - if (linkedFile.getSourceUrl().isEmpty() && LinkedFile.isOnlineLink(linkedFile.getLink())) { + if (linkedFile.getSourceUrl().isEmpty() && LinkedFile.isOnlineLink(linkedFile.getLink()) && filePreferences.shouldKeepDownloadUrl()) { newLinkedFile.setSourceURL(linkedFile.getLink()); - } else { + } else if (filePreferences.shouldKeepDownloadUrl()) { newLinkedFile.setSourceURL(linkedFile.getSourceUrl()); } diff --git a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml index 57cd5a4a449..463debd9327 100644 --- a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml @@ -20,6 +20,7 @@ + - + @@ -37,16 +37,25 @@ - + + + + + + + + + + - + diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java index cb257d4cf80..724ea24ded7 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java @@ -10,8 +10,10 @@ import org.jabref.gui.DialogService; import org.jabref.gui.desktop.JabRefDesktop; +import org.jabref.logic.ai.AiDefaultPreferences; import org.jabref.preferences.FilePreferences; import org.jabref.preferences.ai.AiPreferences; +import org.jabref.preferences.ai.AiProvider; import com.airhacks.afterburner.views.ViewLoader; import org.slf4j.Logger; @@ -22,6 +24,7 @@ public class PrivacyNoticeComponent extends ScrollPane { @FXML private TextFlow openAiPrivacyTextFlow; @FXML private TextFlow mistralAiPrivacyTextFlow; + @FXML private TextFlow geminiPrivacyTextFlow; @FXML private TextFlow huggingFacePrivacyTextFlow; @FXML private Text embeddingModelText; @@ -43,9 +46,10 @@ public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButt @FXML private void initialize() { - initPrivacyHyperlink(openAiPrivacyTextFlow, "https://openai.com/policies/privacy-policy/"); - initPrivacyHyperlink(mistralAiPrivacyTextFlow, "https://mistral.ai/terms/#privacy-policy"); - initPrivacyHyperlink(huggingFacePrivacyTextFlow, "https://huggingface.co/privacy"); + initPrivacyHyperlink(openAiPrivacyTextFlow, AiProvider.OPEN_AI); + initPrivacyHyperlink(mistralAiPrivacyTextFlow, AiProvider.MISTRAL_AI); + initPrivacyHyperlink(geminiPrivacyTextFlow, AiProvider.GEMINI); + initPrivacyHyperlink(huggingFacePrivacyTextFlow, AiProvider.HUGGING_FACE); String newEmbeddingModelText = embeddingModelText.getText().replaceAll("%0", aiPreferences.getEmbeddingModel().sizeInfo()); embeddingModelText.setText(newEmbeddingModelText); @@ -56,20 +60,19 @@ private void initialize() { embeddingModelText.wrappingWidthProperty().bind(this.widthProperty()); } - private void initPrivacyHyperlink(TextFlow textFlow, String link) { + private void initPrivacyHyperlink(TextFlow textFlow, AiProvider aiProvider) { if (textFlow.getChildren().isEmpty() || !(textFlow.getChildren().getFirst() instanceof Text text)) { return; } - String[] stringArray = text.getText().split("%0"); + String replacedText = text.getText().replaceAll("%0", aiProvider.getLabel()).replace("%1", ""); - if (stringArray.length != 2) { - return; - } + replacedText = replacedText.endsWith(".") ? replacedText.substring(0, replacedText.length() - 1) : replacedText; + text.setText(replacedText); text.wrappingWidthProperty().bind(this.widthProperty()); - text.setText(stringArray[0]); + String link = AiDefaultPreferences.PROVIDERS_PRIVACY_POLICIES.get(aiProvider); Hyperlink hyperlink = new Hyperlink(link); hyperlink.setWrapText(true); hyperlink.setFont(text.getFont()); @@ -79,11 +82,11 @@ private void initPrivacyHyperlink(TextFlow textFlow, String link) { textFlow.getChildren().add(hyperlink); - Text postText = new Text(stringArray[1]); - postText.setFont(text.getFont()); - postText.wrappingWidthProperty().bind(this.widthProperty()); + Text dot = new Text("."); + dot.setFont(text.getFont()); + dot.wrappingWidthProperty().bind(this.widthProperty()); - textFlow.getChildren().add(postText); + textFlow.getChildren().add(dot); } @FXML diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java index 031803d9f73..995fb277a21 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java @@ -49,12 +49,14 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final StringProperty openAiChatModel = new SimpleStringProperty(); private final StringProperty mistralAiChatModel = new SimpleStringProperty(); + private final StringProperty geminiChatModel = new SimpleStringProperty(); private final StringProperty huggingFaceChatModel = new SimpleStringProperty(); private final StringProperty currentApiKey = new SimpleStringProperty(); private final StringProperty openAiApiKey = new SimpleStringProperty(); private final StringProperty mistralAiApiKey = new SimpleStringProperty(); + private final StringProperty geminiAiApiKey = new SimpleStringProperty(); private final StringProperty huggingFaceApiKey = new SimpleStringProperty(); private final BooleanProperty customizeExpertSettings = new SimpleBooleanProperty(); @@ -64,10 +66,11 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final ObjectProperty selectedEmbeddingModel = new SimpleObjectProperty<>(); private final StringProperty currentApiBaseUrl = new SimpleStringProperty(); - private final BooleanProperty disableApiBaseUrl = new SimpleBooleanProperty(true); // {@link HuggingFaceChatModel} doesn't support setting API base URL + private final BooleanProperty disableApiBaseUrl = new SimpleBooleanProperty(true); // {@link HuggingFaceChatModel} and {@link GoogleAiGeminiChatModel} doesn't support setting API base URL private final StringProperty openAiApiBaseUrl = new SimpleStringProperty(); private final StringProperty mistralAiApiBaseUrl = new SimpleStringProperty(); + private final StringProperty geminiApiBaseUrl = new SimpleStringProperty(); private final StringProperty huggingFaceApiBaseUrl = new SimpleStringProperty(); private final StringProperty instruction = new SimpleStringProperty(); @@ -120,7 +123,7 @@ public AiTabViewModel(PreferencesService preferencesService) { String oldChatModel = currentChatModel.get(); chatModelsList.setAll(models); - disableApiBaseUrl.set(newValue == AiProvider.HUGGING_FACE); + disableApiBaseUrl.set(newValue == AiProvider.HUGGING_FACE || newValue == AiProvider.GEMINI); if (oldValue != null) { switch (oldValue) { @@ -134,6 +137,11 @@ public AiTabViewModel(PreferencesService preferencesService) { mistralAiApiKey.set(currentApiKey.get()); mistralAiApiBaseUrl.set(currentApiBaseUrl.get()); } + case GEMINI -> { + geminiChatModel.set(oldChatModel); + geminiAiApiKey.set(currentApiKey.get()); + geminiApiBaseUrl.set(currentApiBaseUrl.get()); + } case HUGGING_FACE -> { huggingFaceChatModel.set(oldChatModel); huggingFaceApiKey.set(currentApiKey.get()); @@ -153,6 +161,11 @@ public AiTabViewModel(PreferencesService preferencesService) { currentApiKey.set(mistralAiApiKey.get()); currentApiBaseUrl.set(mistralAiApiBaseUrl.get()); } + case GEMINI -> { + currentChatModel.set(geminiChatModel.get()); + currentApiKey.set(geminiAiApiKey.get()); + currentApiBaseUrl.set(geminiApiBaseUrl.get()); + } case HUGGING_FACE -> { currentChatModel.set(huggingFaceChatModel.get()); currentApiKey.set(huggingFaceApiKey.get()); @@ -165,6 +178,7 @@ public AiTabViewModel(PreferencesService preferencesService) { switch (selectedAiProvider.get()) { case OPEN_AI -> openAiChatModel.set(newValue); case MISTRAL_AI -> mistralAiChatModel.set(newValue); + case GEMINI -> geminiChatModel.set(newValue); case HUGGING_FACE -> huggingFaceChatModel.set(newValue); } @@ -182,6 +196,7 @@ public AiTabViewModel(PreferencesService preferencesService) { switch (selectedAiProvider.get()) { case OPEN_AI -> openAiApiKey.set(newValue); case MISTRAL_AI -> mistralAiApiKey.set(newValue); + case GEMINI -> geminiAiApiKey.set(newValue); case HUGGING_FACE -> huggingFaceApiKey.set(newValue); } }); @@ -190,6 +205,7 @@ public AiTabViewModel(PreferencesService preferencesService) { switch (selectedAiProvider.get()) { case OPEN_AI -> openAiApiBaseUrl.set(newValue); case MISTRAL_AI -> mistralAiApiBaseUrl.set(newValue); + case GEMINI -> geminiApiBaseUrl.set(newValue); case HUGGING_FACE -> huggingFaceApiBaseUrl.set(newValue); } }); @@ -265,14 +281,17 @@ public AiTabViewModel(PreferencesService preferencesService) { public void setValues() { openAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.OPEN_AI)); mistralAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.MISTRAL_AI)); + geminiAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.GEMINI)); huggingFaceApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.HUGGING_FACE)); openAiApiBaseUrl.setValue(aiPreferences.getOpenAiApiBaseUrl()); mistralAiApiBaseUrl.setValue(aiPreferences.getMistralAiApiBaseUrl()); + geminiApiBaseUrl.setValue(aiPreferences.getGeminiApiBaseUrl()); huggingFaceApiBaseUrl.setValue(aiPreferences.getHuggingFaceApiBaseUrl()); openAiChatModel.setValue(aiPreferences.getOpenAiChatModel()); mistralAiChatModel.setValue(aiPreferences.getMistralAiChatModel()); + geminiChatModel.setValue(aiPreferences.getGeminiChatModel()); huggingFaceChatModel.setValue(aiPreferences.getHuggingFaceChatModel()); enableAi.setValue(aiPreferences.getEnableAi()); @@ -282,7 +301,6 @@ public void setValues() { customizeExpertSettings.setValue(aiPreferences.getCustomizeExpertSettings()); selectedEmbeddingModel.setValue(aiPreferences.getEmbeddingModel()); - instruction.setValue(aiPreferences.getInstruction()); temperature.setValue(LocalizedNumbers.doubleToString(aiPreferences.getTemperature())); contextWindowSize.setValue(aiPreferences.getContextWindowSize()); @@ -300,10 +318,12 @@ public void storeSettings() { aiPreferences.setOpenAiChatModel(openAiChatModel.get() == null ? "" : openAiChatModel.get()); aiPreferences.setMistralAiChatModel(mistralAiChatModel.get() == null ? "" : mistralAiChatModel.get()); + aiPreferences.setGeminiChatModel(geminiChatModel.get() == null ? "" : geminiChatModel.get()); aiPreferences.setHuggingFaceChatModel(huggingFaceChatModel.get() == null ? "" : huggingFaceChatModel.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.OPEN_AI, openAiApiKey.get() == null ? "" : openAiApiKey.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.MISTRAL_AI, mistralAiApiKey.get() == null ? "" : mistralAiApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.GEMINI, geminiAiApiKey.get() == null ? "" : geminiAiApiKey.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.HUGGING_FACE, huggingFaceApiKey.get() == null ? "" : huggingFaceApiKey.get()); // We notify in all cases without a real check if something was changed aiPreferences.apiKeyUpdated(); @@ -314,6 +334,7 @@ public void storeSettings() { aiPreferences.setOpenAiApiBaseUrl(openAiApiBaseUrl.get() == null ? "" : openAiApiBaseUrl.get()); aiPreferences.setMistralAiApiBaseUrl(mistralAiApiBaseUrl.get() == null ? "" : mistralAiApiBaseUrl.get()); + aiPreferences.setGeminiApiBaseUrl(geminiApiBaseUrl.get() == null ? "" : geminiApiBaseUrl.get()); aiPreferences.setHuggingFaceApiBaseUrl(huggingFaceApiBaseUrl.get() == null ? "" : huggingFaceApiBaseUrl.get()); aiPreferences.setInstruction(instruction.get()); diff --git a/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java b/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java index 206fa0ae57d..e3fd6238667 100644 --- a/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java +++ b/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java @@ -11,12 +11,21 @@ public class AiDefaultPreferences { AiProvider.OPEN_AI, List.of("gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-turbo", "gpt-3.5-turbo"), // "mistral" and "mixtral" are not language mistakes. AiProvider.MISTRAL_AI, List.of("open-mistral-nemo", "open-mistral-7b", "open-mixtral-8x7b", "open-mixtral-8x22b", "mistral-large-latest"), + AiProvider.GEMINI, List.of("gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.0-pro"), AiProvider.HUGGING_FACE, List.of() ); + public static final Map PROVIDERS_PRIVACY_POLICIES = Map.of( + AiProvider.OPEN_AI, "https://openai.com/policies/privacy-policy/", + AiProvider.MISTRAL_AI, "https://mistral.ai/terms/#privacy-policy", + AiProvider.GEMINI, "https://ai.google.dev/gemini-api/terms", + AiProvider.HUGGING_FACE, "https://huggingface.co/privacy" + ); + public static final Map PROVIDERS_API_URLS = Map.of( AiProvider.OPEN_AI, "https://api.openai.com/v1", AiProvider.MISTRAL_AI, "https://api.mistral.ai/v1", + AiProvider.GEMINI, "https://generativelanguage.googleapis.com/v1beta/", AiProvider.HUGGING_FACE, "https://huggingface.co/api" ); @@ -34,6 +43,11 @@ public class AiDefaultPreferences { "open-mistral-7b", 32000, "open-mixtral-8x7b", 32000, "open-mixtral-8x22b", 64000 + ), + AiProvider.GEMINI, Map.of( + "gemini-1.5-flash", 1048576, + "gemini-1.5-pro", 2097152, + "gemini-1.0-pro", 32000 ) ); @@ -44,6 +58,7 @@ public class AiDefaultPreferences { public static final Map CHAT_MODELS = Map.of( AiProvider.OPEN_AI, "gpt-4o-mini", AiProvider.MISTRAL_AI, "open-mixtral-8x22b", + AiProvider.GEMINI, "gemini-1.5-flash", AiProvider.HUGGING_FACE, "" ); diff --git a/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java b/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java index 5e9c424e3ab..fa4cccf0135 100644 --- a/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java +++ b/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java @@ -16,6 +16,7 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel; import dev.langchain4j.model.huggingface.HuggingFaceChatModel; import dev.langchain4j.model.mistralai.MistralAiChatModel; import dev.langchain4j.model.output.Response; @@ -76,8 +77,20 @@ private void rebuild() { ); } + case GEMINI -> { + // NOTE: {@link GoogleAiGeminiChatModel} doesn't support API base url. + langchainChatModel = Optional.of(GoogleAiGeminiChatModel + .builder() + .apiKey(apiKey) + .modelName(aiPreferences.getSelectedChatModel()) + .temperature(aiPreferences.getTemperature()) + .logRequestsAndResponses(true) + .build() + ); + } + case HUGGING_FACE -> { - // NOTE: {@link HuggingFaceChatModel} doesn't support API base url :( + // NOTE: {@link HuggingFaceChatModel} doesn't support API base url. langchainChatModel = Optional.of(HuggingFaceChatModel .builder() .accessToken(apiKey) diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index f5beb092a4b..71010a99881 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -471,11 +471,13 @@ public class JabRefPreferences implements PreferencesService { private static final String AI_PROVIDER = "aiProvider"; private static final String AI_OPEN_AI_CHAT_MODEL = "aiOpenAiChatModel"; private static final String AI_MISTRAL_AI_CHAT_MODEL = "aiMistralAiChatModel"; + private static final String AI_GEMINI_CHAT_MODEL = "aiGeminiChatModel"; private static final String AI_HUGGING_FACE_CHAT_MODEL = "aiHuggingFaceChatModel"; private static final String AI_CUSTOMIZE_SETTINGS = "aiCustomizeSettings"; private static final String AI_EMBEDDING_MODEL = "aiEmbeddingModel"; private static final String AI_OPEN_AI_API_BASE_URL = "aiOpenAiApiBaseUrl"; private static final String AI_MISTRAL_AI_API_BASE_URL = "aiMistralAiApiBaseUrl"; + private static final String AI_GEMINI_API_BASE_URL = "aiGeminiApiBaseUrl"; private static final String AI_HUGGING_FACE_API_BASE_URL = "aiHuggingFaceApiBaseUrl"; private static final String AI_SYSTEM_MESSAGE = "aiSystemMessage"; private static final String AI_TEMPERATURE = "aiTemperature"; @@ -894,11 +896,13 @@ private JabRefPreferences() { defaults.put(AI_PROVIDER, AiDefaultPreferences.PROVIDER.name()); defaults.put(AI_OPEN_AI_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.OPEN_AI)); defaults.put(AI_MISTRAL_AI_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.MISTRAL_AI)); + defaults.put(AI_GEMINI_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.GEMINI)); defaults.put(AI_HUGGING_FACE_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.HUGGING_FACE)); defaults.put(AI_CUSTOMIZE_SETTINGS, AiDefaultPreferences.CUSTOMIZE_SETTINGS); defaults.put(AI_EMBEDDING_MODEL, AiDefaultPreferences.EMBEDDING_MODEL.name()); defaults.put(AI_OPEN_AI_API_BASE_URL, AiDefaultPreferences.PROVIDERS_API_URLS.get(AiProvider.OPEN_AI)); defaults.put(AI_MISTRAL_AI_API_BASE_URL, AiDefaultPreferences.PROVIDERS_API_URLS.get(AiProvider.MISTRAL_AI)); + defaults.put(AI_GEMINI_API_BASE_URL, AiDefaultPreferences.PROVIDERS_API_URLS.get(AiProvider.GEMINI)); defaults.put(AI_HUGGING_FACE_API_BASE_URL, AiDefaultPreferences.PROVIDERS_API_URLS.get(AiProvider.HUGGING_FACE)); defaults.put(AI_SYSTEM_MESSAGE, AiDefaultPreferences.SYSTEM_MESSAGE); defaults.put(AI_TEMPERATURE, AiDefaultPreferences.TEMPERATURE); @@ -2797,10 +2801,12 @@ public AiPreferences getAiPreferences() { AiProvider.valueOf(get(AI_PROVIDER)), get(AI_OPEN_AI_CHAT_MODEL), get(AI_MISTRAL_AI_CHAT_MODEL), + get(AI_GEMINI_CHAT_MODEL), get(AI_HUGGING_FACE_CHAT_MODEL), getBoolean(AI_CUSTOMIZE_SETTINGS), get(AI_OPEN_AI_API_BASE_URL), get(AI_MISTRAL_AI_API_BASE_URL), + get(AI_GEMINI_API_BASE_URL), get(AI_HUGGING_FACE_API_BASE_URL), EmbeddingModel.valueOf(get(AI_EMBEDDING_MODEL)), get(AI_SYSTEM_MESSAGE), @@ -2817,12 +2823,14 @@ public AiPreferences getAiPreferences() { EasyBind.listen(aiPreferences.openAiChatModelProperty(), (obs, oldValue, newValue) -> put(AI_OPEN_AI_CHAT_MODEL, newValue)); EasyBind.listen(aiPreferences.mistralAiChatModelProperty(), (obs, oldValue, newValue) -> put(AI_MISTRAL_AI_CHAT_MODEL, newValue)); + EasyBind.listen(aiPreferences.geminiChatModelProperty(), (obs, oldValue, newValue) -> put(AI_GEMINI_CHAT_MODEL, newValue)); EasyBind.listen(aiPreferences.huggingFaceChatModelProperty(), (obs, oldValue, newValue) -> put(AI_HUGGING_FACE_CHAT_MODEL, newValue)); EasyBind.listen(aiPreferences.customizeExpertSettingsProperty(), (obs, oldValue, newValue) -> putBoolean(AI_CUSTOMIZE_SETTINGS, newValue)); EasyBind.listen(aiPreferences.openAiApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_OPEN_AI_API_BASE_URL, newValue)); EasyBind.listen(aiPreferences.mistralAiApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_MISTRAL_AI_API_BASE_URL, newValue)); + EasyBind.listen(aiPreferences.geminiApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_GEMINI_API_BASE_URL, newValue)); EasyBind.listen(aiPreferences.huggingFaceApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_HUGGING_FACE_API_BASE_URL, newValue)); EasyBind.listen(aiPreferences.embeddingModelProperty(), (obs, oldValue, newValue) -> put(AI_EMBEDDING_MODEL, newValue.name())); diff --git a/src/main/java/org/jabref/preferences/ai/AiPreferences.java b/src/main/java/org/jabref/preferences/ai/AiPreferences.java index 53f13f210a1..aaab39cb72b 100644 --- a/src/main/java/org/jabref/preferences/ai/AiPreferences.java +++ b/src/main/java/org/jabref/preferences/ai/AiPreferences.java @@ -35,12 +35,14 @@ public class AiPreferences { private final StringProperty openAiChatModel; private final StringProperty mistralAiChatModel; + private final StringProperty geminiChatModel; private final StringProperty huggingFaceChatModel; private final BooleanProperty customizeExpertSettings; private final StringProperty openAiApiBaseUrl; private final StringProperty mistralAiApiBaseUrl; + private final StringProperty geminiApiBaseUrl; private final StringProperty huggingFaceApiBaseUrl; private final ObjectProperty embeddingModel; @@ -58,10 +60,12 @@ public AiPreferences(boolean enableAi, AiProvider aiProvider, String openAiChatModel, String mistralAiChatModel, + String geminiChatModel, String huggingFaceChatModel, boolean customizeExpertSettings, String openAiApiBaseUrl, String mistralAiApiBaseUrl, + String geminiApiBaseUrl, String huggingFaceApiBaseUrl, EmbeddingModel embeddingModel, String instruction, @@ -78,12 +82,14 @@ public AiPreferences(boolean enableAi, this.openAiChatModel = new SimpleStringProperty(openAiChatModel); this.mistralAiChatModel = new SimpleStringProperty(mistralAiChatModel); + this.geminiChatModel = new SimpleStringProperty(geminiChatModel); this.huggingFaceChatModel = new SimpleStringProperty(huggingFaceChatModel); this.customizeExpertSettings = new SimpleBooleanProperty(customizeExpertSettings); this.openAiApiBaseUrl = new SimpleStringProperty(openAiApiBaseUrl); this.mistralAiApiBaseUrl = new SimpleStringProperty(mistralAiApiBaseUrl); + this.geminiApiBaseUrl = new SimpleStringProperty(geminiApiBaseUrl); this.huggingFaceApiBaseUrl = new SimpleStringProperty(huggingFaceApiBaseUrl); this.embeddingModel = new SimpleObjectProperty<>(embeddingModel); @@ -99,8 +105,7 @@ public AiPreferences(boolean enableAi, public String getApiKeyForAiProvider(AiProvider aiProvider) { try (final Keyring keyring = Keyring.create()) { return keyring.getPassword(KEYRING_AI_SERVICE, KEYRING_AI_SERVICE_ACCOUNT + "-" + aiProvider.name()); - } catch ( - PasswordAccessException e) { + } catch (PasswordAccessException e) { LOGGER.debug("No API key stored for provider {}. Returning an empty string", aiProvider.getLabel()); return ""; } catch (Exception e) { @@ -173,6 +178,18 @@ public void setMistralAiChatModel(String mistralAiChatModel) { this.mistralAiChatModel.set(mistralAiChatModel); } + public StringProperty geminiChatModelProperty() { + return geminiChatModel; + } + + public String getGeminiChatModel() { + return geminiChatModel.get(); + } + + public void setGeminiChatModel(String geminiChatModel) { + this.geminiChatModel.set(geminiChatModel); + } + public StringProperty huggingFaceChatModelProperty() { return huggingFaceChatModel; } @@ -237,6 +254,18 @@ public void setMistralAiApiBaseUrl(String mistralAiApiBaseUrl) { this.mistralAiApiBaseUrl.set(mistralAiApiBaseUrl); } + public StringProperty geminiApiBaseUrlProperty() { + return geminiApiBaseUrl; + } + + public String getGeminiApiBaseUrl() { + return geminiApiBaseUrl.get(); + } + + public void setGeminiApiBaseUrl(String geminiApiBaseUrl) { + this.geminiApiBaseUrl.set(geminiApiBaseUrl); + } + public StringProperty huggingFaceApiBaseUrlProperty() { return huggingFaceApiBaseUrl; } @@ -293,6 +322,7 @@ public int getContextWindowSize() { case OPEN_AI -> AiDefaultPreferences.getContextWindowSize(AiProvider.OPEN_AI, openAiChatModel.get()); case MISTRAL_AI -> AiDefaultPreferences.getContextWindowSize(AiProvider.MISTRAL_AI, mistralAiChatModel.get()); case HUGGING_FACE -> AiDefaultPreferences.getContextWindowSize(AiProvider.HUGGING_FACE, huggingFaceChatModel.get()); + case GEMINI -> AiDefaultPreferences.getContextWindowSize(AiProvider.GEMINI, geminiChatModel.get()); }; } } @@ -418,6 +448,8 @@ public String getSelectedChatModel() { mistralAiChatModel.get(); case HUGGING_FACE -> huggingFaceChatModel.get(); + case GEMINI -> + geminiChatModel.get(); }; } @@ -430,6 +462,8 @@ public String getSelectedApiBaseUrl() { mistralAiApiBaseUrl.get(); case HUGGING_FACE -> huggingFaceApiBaseUrl.get(); + case GEMINI -> + geminiApiBaseUrl.get(); }; } else { return AiDefaultPreferences.PROVIDERS_API_URLS.get(aiProvider.get()); diff --git a/src/main/java/org/jabref/preferences/ai/AiProvider.java b/src/main/java/org/jabref/preferences/ai/AiProvider.java index 69c3405249d..b682035dc07 100644 --- a/src/main/java/org/jabref/preferences/ai/AiProvider.java +++ b/src/main/java/org/jabref/preferences/ai/AiProvider.java @@ -5,6 +5,7 @@ public enum AiProvider implements Serializable { OPEN_AI("OpenAI"), MISTRAL_AI("Mistral AI"), + GEMINI("Gemini"), HUGGING_FACE("Hugging Face"); private final String label; diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index a60384b6812..80d0c559891 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2570,9 +2570,6 @@ Enable\ AI\ functionality\ (summary\ generation\ and\ chatting)\ in\ JabRef=Enab Customize\ expert\ settings=Customize expert settings These\ parameters\ affect\ how\ the\ AI\ will\ answer\ your\ questions.=These parameters affect how the AI will answer your questions. Chat\ with\ AI\ about\ content\ of\ attached\ file(s)=Chat with AI about content of attached file(s) -If\ you\ have\ chosen\ the\ Hugging\ Face\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Hugging\ Face\ applies.\ You\ find\ it\ at\ %0.=If you have chosen the Hugging Face as AI provider, the privacy policy of Hugging Face applies. You find it at %0. -If\ you\ have\ chosen\ the\ Mistral\ AI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Mistral\ AI\ applies.\ You\ find\ it\ at\ %0.=If you have chosen the Mistral AI as AI provider, the privacy policy of Mistral AI applies. You find it at %0. -If\ you\ have\ chosen\ the\ OpenAI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ OpenAI\ applies.\ You\ find\ it\ at\ %0.=If you have chosen the OpenAI as AI provider, the privacy policy of OpenAI applies. You find it at %0. In\ order\ to\ use\ AI\ chat,\ you\ need\ to\ enable\ chatting\ with\ attached\ PDF\ files\ in\ JabRef\ preferences\ (AI\ tab).=In order to use AI chat, you need to enable chatting with attached PDF files in JabRef preferences (AI tab). In\ order\ to\ use\ AI\ chat,\ set\ an\ API\ key\ inside\ JabRef\ preferences\ (AI\ tab).=In order to use AI chat, set an API key inside JabRef preferences (AI tab). Unable\ to\ chat\ with\ AI.=Unable to chat with AI. @@ -2632,6 +2629,7 @@ Generating\ embeddings\ for\ %0=Generating embeddings for %0 RAG\ minimum\ score\ must\ be\ a\ number=RAG minimum score must be a number RAG\ minimum\ score\ must\ be\ greater\ than\ 0\ and\ less\ than\ 1=RAG minimum score must be greater than 0 and less than 1 Temperature\ must\ be\ a\ number=Temperature must be a number +If\ you\ have\ chosen\ %0\ as\ an\ AI\ provider,\ the\ privacy\ policy\ of\ %0\ applies.\ You\ find\ it\ at\ %1.=If you have chosen %0 as an AI provider, the privacy policy of %0 applies. You find it at %1. Link=Link Source\ URL=Source URL From f7fde15943ab52c153649046c6de967672128916 Mon Sep 17 00:00:00 2001 From: Nihar Thakkar <63655579+18bce133@users.noreply.github.com> Date: Fri, 13 Sep 2024 05:52:56 -0700 Subject: [PATCH 25/34] Refactor EprintCleanup to handle institution, version, and EID fields (#11627) * Refactor EprintCleanup to handle institution, version, and EID fields * Add test for cleanup with VERSION, INSTITUTION, and EID * Add version to EPRINT if not present * Modify test to retain INSTITUTION as "tbd"; include documentation link * clear institution field if it is "arXiv" else keep it as it is. * Complied to OpenRewrite to ensure "modern" Java coding practices. * Update CHANGELOG.md * Add "real" hyperlink --------- Co-authored-by: Subhramit Basu Bhowmick Co-authored-by: Oliver Kopp --- CHANGELOG.md | 1 + .../jabref/logic/cleanup/EprintCleanup.java | 21 ++++++++++- .../logic/cleanup/EprintCleanupTest.java | 37 +++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b20c6bf2e94..49785c7fc89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We enabled creating a new file link manually. [#11017](https://github.com/JabRef/jabref/issues/11017) - We added a toggle button to invert the selected groups. [#9073](https://github.com/JabRef/jabref/issues/9073) - We reintroduced the floating search in the main table. [#4237](https://github.com/JabRef/jabref/issues/4237) +- We improved [cleanup](https://docs.jabref.org/finding-sorting-and-cleaning-entries/cleanupentries) of `arXiv` IDs in distributed in the fields `note`, `version`, `institution`, and `eid` fields. [#11306](https://github.com/JabRef/jabref/issues/11306) - We added a switch not to store the linked file URL, because it caused troubles at other apps. [#11735](https://github.com/JabRef/jabref/pull/11735) - When starting a new SLR, the selected catalogs now persist within and across JabRef sessions. [koppor#614](https://github.com/koppor/jabref/issues/614) - We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658) diff --git a/src/main/java/org/jabref/logic/cleanup/EprintCleanup.java b/src/main/java/org/jabref/logic/cleanup/EprintCleanup.java index 27ab856c499..abc56b9db6a 100644 --- a/src/main/java/org/jabref/logic/cleanup/EprintCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/EprintCleanup.java @@ -13,6 +13,8 @@ /** * Formats the DOI (e.g. removes http part) and also moves DOIs from note, url or ee field to the doi field. + * + * Background information on tex.stackexchange. */ public class EprintCleanup implements CleanupJob { @@ -20,11 +22,25 @@ public class EprintCleanup implements CleanupJob { public List cleanup(BibEntry entry) { List changes = new ArrayList<>(); - for (Field field : Arrays.asList(StandardField.URL, StandardField.JOURNAL, StandardField.JOURNALTITLE, StandardField.NOTE)) { + Optional version = entry.getField(StandardField.VERSION); + Optional institution = entry.getField(StandardField.INSTITUTION); + + for (Field field : Arrays.asList(StandardField.URL, StandardField.JOURNAL, StandardField.JOURNALTITLE, StandardField.NOTE, StandardField.EID)) { Optional arXivIdentifier = entry.getField(field).flatMap(ArXivIdentifier::parse); if (arXivIdentifier.isPresent()) { - entry.setField(StandardField.EPRINT, arXivIdentifier.get().getNormalized()) + String normalizedEprint = arXivIdentifier.get().getNormalized(); + + if (version.isPresent() && !normalizedEprint.contains("v" + version.get())) { + normalizedEprint += "v" + version.get(); + } + + if (institution.isPresent() && "arxiv".equalsIgnoreCase(institution.get())) { + entry.clearField(StandardField.INSTITUTION) + .ifPresent(changes::add); + } + + entry.setField(StandardField.EPRINT, normalizedEprint) .ifPresent(changes::add); entry.setField(StandardField.EPRINTTYPE, "arxiv") @@ -45,6 +61,7 @@ public List cleanup(BibEntry entry) { } } } + entry.clearField(StandardField.VERSION).ifPresent(changes::add); return changes; } diff --git a/src/test/java/org/jabref/logic/cleanup/EprintCleanupTest.java b/src/test/java/org/jabref/logic/cleanup/EprintCleanupTest.java index d27a98e59fa..f391a4bd44f 100644 --- a/src/test/java/org/jabref/logic/cleanup/EprintCleanupTest.java +++ b/src/test/java/org/jabref/logic/cleanup/EprintCleanupTest.java @@ -27,4 +27,41 @@ void cleanupCompleteEntry() { assertEquals(expected, input); } + + @Test + void cleanupEntryWithVersionAndInstitutionAndEid() { + BibEntry input = new BibEntry() + .withField(StandardField.NOTE, "arXiv: 1503.05173") + .withField(StandardField.VERSION, "1") + .withField(StandardField.INSTITUTION, "arXiv") + .withField(StandardField.EID, "arXiv:1503.05173"); + + BibEntry expected = new BibEntry() + .withField(StandardField.EPRINT, "1503.05173v1") + .withField(StandardField.EPRINTTYPE, "arxiv"); + + EprintCleanup cleanup = new EprintCleanup(); + cleanup.cleanup(input); + + assertEquals(expected, input); + } + + @Test + void cleanupEntryWithOtherInstitution() { + BibEntry input = new BibEntry() + .withField(StandardField.NOTE, "arXiv: 1503.05173") + .withField(StandardField.VERSION, "1") + .withField(StandardField.INSTITUTION, "OtherInstitution") + .withField(StandardField.EID, "arXiv:1503.05173"); + + BibEntry expected = new BibEntry() + .withField(StandardField.EPRINT, "1503.05173v1") + .withField(StandardField.EPRINTTYPE, "arxiv") + .withField(StandardField.INSTITUTION, "OtherInstitution"); + + EprintCleanup cleanup = new EprintCleanup(); + cleanup.cleanup(input); + + assertEquals(expected, input); + } } From 939f6961a9eafbac17c95bc0b97311b85d3262d2 Mon Sep 17 00:00:00 2001 From: leaf-soba Date: Fri, 13 Sep 2024 20:54:03 +0800 Subject: [PATCH 26/34] add unit test to generateInstitutionKey (#11756) * add unit test to generateInstitutionKey prepare to refactor this method * Update BracketedPatternTest.java --- .../citationkeypattern/BracketedPattern.java | 8 +++--- .../BracketedPatternTest.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java b/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java index 2407cf0fe7f..0447f748cef 100644 --- a/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java +++ b/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java @@ -36,6 +36,7 @@ import org.jabref.model.strings.LatexToUnicodeAdapter; import org.jabref.model.strings.StringUtil; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1218,14 +1219,14 @@ protected static List parseFieldAndModifiers(String arg) { *

  • null if content is null
  • * */ - private static String generateInstitutionKey(String content) { + @VisibleForTesting + static String generateInstitutionKey(String content) { if (content == null) { return null; } if (content.isBlank()) { return ""; } - Matcher matcher = INLINE_ABBREVIATION.matcher(content); if (matcher.find()) { return LatexToUnicodeAdapter.format(matcher.group()); @@ -1309,8 +1310,7 @@ private static String generateInstitutionKey(String content) { // Putting parts together. return (university == null ? Objects.toString(rest, "") : university) + (school == null ? "" : school) - + ((department == null) - || ((school != null) && department.equals(school)) ? "" : department); + + ((department == null) || (department.equals(school)) ? "" : department); } /** diff --git a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java index cc466d6b90e..4d1b3649778 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -734,4 +735,31 @@ void editorFieldMarkers(String expectedCitationKey, String pattern, String edito BracketedPattern bracketedPattern = new BracketedPattern(pattern); assertEquals(expectedCitationKey, bracketedPattern.expand(bibEntry)); } + + @ParameterizedTest + @CsvSource({ + "'', ''", + "The Attributed Graph Grammar System ({AGG}),AGG", + "'The University of Science',UniScience", + "'School of Business, Department of Management',BM", + "'Graph Systems Research Group',GSRG", + "'The Great Institute, 123 Main Street, Springfield',GreatInstitute", + "'Invalid {\\Unicode}',Invalid", + "'School of Electrical Engineering ({SEE}), Department of Computer Science',SEE", + "'{The Attributed Graph Grammar System ({AGG})}',AGG", + "'{The Attributed Graph Grammar System}',AGGS", + "'{University of Example, Department of Computer Science, Some Address}',UniExampleCS", + "'{Example School of Engineering, Department of Computer Science, Some Address}',SomeAddressEECS", + "'{Example Institute, Computer Science Department, Some Address}',ExampleInstituteCS", + "'{Short Part, Some Address}',ShortPart", + "'{Example with Several Tokens, Some Address}',EST"}) + + void generateInstitutionKeyTest(String input, String expected) { + assertEquals(expected, BracketedPattern.generateInstitutionKey(input)); + } + + @Test + void generateInstitutionKeyNullTest() { + assertNull(BracketedPattern.generateInstitutionKey(null)); + } } From 35b0516cd1b044a720b7e1658a416692cff5cc19 Mon Sep 17 00:00:00 2001 From: Austin Schaefer Date: Sat, 14 Sep 2024 00:02:56 +0200 Subject: [PATCH 27/34] Add setting: always add "Cited on pages" text to JStyles. (#11732) * Add setting to always add "Cited on pages" text in JStyle Citations. * Add setting: always add "Cited on pages" text to JStyles. * Add changelog. * Add change to Changelog. * Fix translation key. Ensure checkstyle conformity. * Correct option text, position, & hiding behavior. The new option's presence now based on selected style, with presence being ensured only if the style is a JStyle. The option now appears always first from the top, using index based insertion to achieve this. The text has been updated to reference bibliographic entries as opposed to citations, which was factually incorrect. * Cited on pages... text now appears. Reference existing preference in OOBibBase to properly propagate setting, so that "Cited on pages" text literal will appear when user has it configured. Remove accomplished TODOs. * Shift changelog entry, fix setting string, misc. formatting * Attempt to update submodules to origin/master state. * Attempt to fix submodule hashes. * Re-ignore submodules. * Fix spacing. --------- Co-authored-by: Subhramit Basu Bhowmick --- CHANGELOG.md | 1 + .../org/jabref/gui/openoffice/OOBibBase.java | 10 ++--- .../gui/openoffice/OpenOfficePanel.java | 38 +++++++++++++++++-- .../openoffice/OpenOfficePreferences.java | 17 ++++++++- .../jabref/preferences/JabRefPreferences.java | 6 ++- src/main/resources/l10n/JabRef_en.properties | 1 + 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49785c7fc89..967c615792e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a switch not to store the linked file URL, because it caused troubles at other apps. [#11735](https://github.com/JabRef/jabref/pull/11735) - When starting a new SLR, the selected catalogs now persist within and across JabRef sessions. [koppor#614](https://github.com/koppor/jabref/issues/614) - We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658) +- We added a setting which always adds the literal "Cited on pages" text before each JStyle citation. [#11691](https://github.com/JabRef/jabref/pull/11732) ### Changed diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 40a37a69d9f..637c729a720 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -15,6 +15,7 @@ import org.jabref.logic.citationstyle.CitationStyle; import org.jabref.logic.l10n.Localization; import org.jabref.logic.openoffice.NoDocumentFoundException; +import org.jabref.logic.openoffice.OpenOfficePreferences; import org.jabref.logic.openoffice.action.EditInsert; import org.jabref.logic.openoffice.action.EditMerge; import org.jabref.logic.openoffice.action.EditSeparate; @@ -68,14 +69,13 @@ public class OOBibBase { private final DialogService dialogService; - // Shall we add "Cited on pages: ..." to resolved bibliography entries? - private final boolean alwaysAddCitedOnPages; // TODO (see comment above) + private final boolean alwaysAddCitedOnPages; private final OOBibBaseConnect connection; private CSLCitationOOAdapter cslCitationOOAdapter; - public OOBibBase(Path loPath, DialogService dialogService) + public OOBibBase(Path loPath, DialogService dialogService, OpenOfficePreferences openOfficePreferences) throws BootstrapException, CreationException { @@ -83,7 +83,7 @@ public OOBibBase(Path loPath, DialogService dialogService) this.dialogService = dialogService; this.connection = new OOBibBaseConnect(loPath, dialogService); - this.alwaysAddCitedOnPages = false; + this.alwaysAddCitedOnPages = openOfficePreferences.getAlwaysAddCitedOnPages(); } private void initializeCitationAdapter(XTextDocument doc) throws WrappedTargetException, NoSuchElementException { @@ -583,7 +583,7 @@ public void guiActionInsertEntry(List entries, } } - syncOptions.map(e -> e.setAlwaysAddCitedOnPages(this.alwaysAddCitedOnPages)); // TODO: Provide option to user: this is always false + syncOptions.map(e -> e.setAlwaysAddCitedOnPages(this.alwaysAddCitedOnPages)); try { diff --git a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index cff14afe648..fa38b9a385b 100644 --- a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -8,6 +8,7 @@ import javax.swing.undo.UndoManager; +import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Task; import javafx.geometry.Insets; import javafx.geometry.Side; @@ -65,6 +66,7 @@ import com.sun.star.comp.helper.BootstrapException; import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; +import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,9 +104,12 @@ public class OpenOfficePanel { private final LibraryTabContainer tabContainer; private final FileUpdateMonitor fileUpdateMonitor; private final BibEntryTypesManager entryTypesManager; + private final OpenOfficePreferences openOfficePreferences; private OOBibBase ooBase; private OOStyle currentStyle; + private final SimpleObjectProperty currentStyleProperty; + public OpenOfficePanel(LibraryTabContainer tabContainer, PreferencesService preferencesService, KeyBindingRepository keyBindingRepository, @@ -126,6 +131,7 @@ public OpenOfficePanel(LibraryTabContainer tabContainer, this.clipBoardManager = clipBoardManager; this.undoManager = undoManager; this.currentStyle = preferencesService.getOpenOfficePreferences().getCurrentStyle(); + this.openOfficePreferences = preferencesService.getOpenOfficePreferences(); ActionFactory factory = new ActionFactory(); @@ -157,6 +163,8 @@ public OpenOfficePanel(LibraryTabContainer tabContainer, preferencesService.getLayoutFormatterPreferences(), abbreviationRepository); + currentStyleProperty = new SimpleObjectProperty<>(currentStyle); + initPanel(); } @@ -170,6 +178,7 @@ public Node getContent() { */ private boolean getOrUpdateTheStyle(String title) { currentStyle = loader.getUsedStyleUnified(); + currentStyleProperty.set(currentStyle); final boolean FAIL = true; final boolean PASS = false; @@ -215,6 +224,8 @@ private void initPanel() { dialogService.showCustomDialogAndWait(styleDialog) .ifPresent(selectedStyle -> { currentStyle = selectedStyle; + currentStyleProperty.set(currentStyle); + if (currentStyle instanceof JStyle jStyle) { try { jStyle.ensureUpToDate(); @@ -472,7 +483,7 @@ protected OOBibBase call() throws BootstrapException, CreationException { } private OOBibBase createBibBase(Path loPath) throws BootstrapException, CreationException { - return new OOBibBase(loPath, dialogService); + return new OOBibBase(loPath, dialogService, openOfficePreferences); } /** @@ -614,12 +625,27 @@ private boolean checkThatEntriesHaveKeys(List entries) { } private ContextMenu createSettingsPopup() { - OpenOfficePreferences openOfficePreferences = preferencesService.getOpenOfficePreferences(); - ContextMenu contextMenu = new ContextMenu(); CheckMenuItem autoSync = new CheckMenuItem(Localization.lang("Automatically sync bibliography when inserting citations")); - autoSync.selectedProperty().set(preferencesService.getOpenOfficePreferences().getSyncWhenCiting()); + autoSync.selectedProperty().set(openOfficePreferences.getSyncWhenCiting()); + + CheckMenuItem alwaysAddCitedOnPagesText = new CheckMenuItem(Localization.lang("Automatically add \"Cited on pages...\" at the end of bibliographic entries")); + alwaysAddCitedOnPagesText.selectedProperty().set(openOfficePreferences.getAlwaysAddCitedOnPages()); + alwaysAddCitedOnPagesText.setOnAction(e -> openOfficePreferences.setAlwaysAddCitedOnPages(alwaysAddCitedOnPagesText.isSelected())); + + EasyBind.listen(currentStyleProperty, (obs, oldValue, newValue) -> { + switch (newValue) { + case JStyle ignored -> { + if (!contextMenu.getItems().contains(alwaysAddCitedOnPagesText)) { + contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); + } + } + case CitationStyle ignored -> + contextMenu.getItems().remove(alwaysAddCitedOnPagesText); + default -> { } + } + }); ToggleGroup toggleGroup = new ToggleGroup(); RadioMenuItem useActiveBase = new RadioMenuItem(Localization.lang("Look up BibTeX entries in the active tab only")); @@ -651,6 +677,10 @@ private ContextMenu createSettingsPopup() { new SeparatorMenuItem(), clearConnectionSettings); + if (currentStyle instanceof JStyle) { + contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); + } + return contextMenu; } } diff --git a/src/main/java/org/jabref/logic/openoffice/OpenOfficePreferences.java b/src/main/java/org/jabref/logic/openoffice/OpenOfficePreferences.java index 7b0f4a2c287..b536fd48e0f 100644 --- a/src/main/java/org/jabref/logic/openoffice/OpenOfficePreferences.java +++ b/src/main/java/org/jabref/logic/openoffice/OpenOfficePreferences.java @@ -31,19 +31,22 @@ public class OpenOfficePreferences { private final ObservableList externalStyles; private final StringProperty currentJStyle; private final ObjectProperty currentStyle; + private final BooleanProperty alwaysAddCitedOnPages; public OpenOfficePreferences(String executablePath, boolean useAllDatabases, boolean syncWhenCiting, List externalStyles, String currentJStyle, - OOStyle currentStyle) { + OOStyle currentStyle, + boolean alwaysAddCitedOnPages) { this.executablePath = new SimpleStringProperty(executablePath); this.useAllDatabases = new SimpleBooleanProperty(useAllDatabases); this.syncWhenCiting = new SimpleBooleanProperty(syncWhenCiting); this.externalStyles = FXCollections.observableArrayList(externalStyles); this.currentJStyle = new SimpleStringProperty(currentJStyle); this.currentStyle = new SimpleObjectProperty<>(currentStyle); + this.alwaysAddCitedOnPages = new SimpleBooleanProperty(alwaysAddCitedOnPages); } public void clearConnectionSettings() { @@ -137,4 +140,16 @@ public ObjectProperty currentStyleProperty() { public void setCurrentStyle(OOStyle style) { this.currentStyle.set(style); } + + public boolean getAlwaysAddCitedOnPages() { + return this.alwaysAddCitedOnPages.get(); + } + + public BooleanProperty alwaysAddCitedOnPagesProperty() { + return this.alwaysAddCitedOnPages; + } + + public void setAlwaysAddCitedOnPages(boolean alwaysAddCitedOnPages) { + this.alwaysAddCitedOnPages.set(alwaysAddCitedOnPages); + } } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 71010a99881..1c4506e45a2 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -371,6 +371,7 @@ public class JabRefPreferences implements PreferencesService { public static final String OO_BIBLIOGRAPHY_STYLE_FILE = "ooBibliographyStyleFile"; public static final String OO_EXTERNAL_STYLE_FILES = "ooExternalStyleFiles"; public static final String OO_CURRENT_STYLE = "ooCurrentStyle"; + public static final String OO_ALWAYS_ADD_CITED_ON_PAGES = "ooAlwaysAddCitedOnPages"; // Special field preferences public static final String SPECIALFIELDSENABLED = "specialFieldsEnabled"; @@ -759,6 +760,7 @@ private JabRefPreferences() { } defaults.put(OO_SYNC_WHEN_CITING, Boolean.TRUE); + defaults.put(OO_ALWAYS_ADD_CITED_ON_PAGES, Boolean.FALSE); defaults.put(OO_SHOW_PANEL, Boolean.FALSE); defaults.put(OO_USE_ALL_OPEN_BASES, Boolean.TRUE); defaults.put(OO_BIBLIOGRAPHY_STYLE_FILE, StyleLoader.DEFAULT_AUTHORYEAR_STYLE_PATH); @@ -1383,10 +1385,12 @@ public OpenOfficePreferences getOpenOfficePreferences() { getBoolean(OO_SYNC_WHEN_CITING), getStringList(OO_EXTERNAL_STYLE_FILES), get(OO_BIBLIOGRAPHY_STYLE_FILE), - currentStyle); + currentStyle, + getBoolean(OO_ALWAYS_ADD_CITED_ON_PAGES)); EasyBind.listen(openOfficePreferences.executablePathProperty(), (obs, oldValue, newValue) -> put(OO_EXECUTABLE_PATH, newValue)); EasyBind.listen(openOfficePreferences.useAllDatabasesProperty(), (obs, oldValue, newValue) -> putBoolean(OO_USE_ALL_OPEN_BASES, newValue)); + EasyBind.listen(openOfficePreferences.alwaysAddCitedOnPagesProperty(), (obs, oldValue, newValue) -> putBoolean(OO_ALWAYS_ADD_CITED_ON_PAGES, newValue)); EasyBind.listen(openOfficePreferences.syncWhenCitingProperty(), (obs, oldValue, newValue) -> putBoolean(OO_SYNC_WHEN_CITING, newValue)); openOfficePreferences.getExternalStyles().addListener((InvalidationListener) change -> diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 80d0c559891..ed0d5a7f9a1 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1132,6 +1132,7 @@ Unable\ to\ reload\ style\ file=Unable to reload style file Problem\ during\ separating\ cite\ markers=Problem during separating cite markers +Automatically\ add\ "Cited\ on\ pages..."\ at\ the\ end\ of\ bibliographic\ entries=Automatically add "Cited on pages..." at the end of bibliographic entries Automatically\ sync\ bibliography\ when\ inserting\ citations=Automatically sync bibliography when inserting citations Look\ up\ BibTeX\ entries\ in\ the\ active\ tab\ only=Look up BibTeX entries in the active tab only Look\ up\ BibTeX\ entries\ in\ all\ open\ libraries=Look up BibTeX entries in all open libraries From 9634a13bbc91954b7e53c88312fca8e71069f8d5 Mon Sep 17 00:00:00 2001 From: Houssem Nasri Date: Fri, 13 Sep 2024 23:29:03 +0100 Subject: [PATCH 28/34] Enable/Disable the undo and redo toolbar/menu buttons correctly (#11758) * Enable/Disable the undo and redo toolbar/menu buttons correctly * Add changelog entry --- CHANGELOG.md | 1 + .../jabref/gui/undo/CountingUndoManager.java | 62 ++++++++++++++++--- .../java/org/jabref/gui/undo/RedoAction.java | 8 +-- .../java/org/jabref/gui/undo/UndoAction.java | 8 +-- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 967c615792e..57f964c3101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - The browse button for a Custom theme now opens in the directory of the current used CSS file. [#11597](https://github.com/JabRef/jabref/pull/11597) - The browse button for a Custom exporter now opens in the directory of the current used exporter file. [#11717](https://github.com/JabRef/jabref/pull/11717) - We improved the display of long messages in the integrity check dialog. [#11619](https://github.com/JabRef/jabref/pull/11619) +- We improved the undo/redo buttons in the main toolbar and main menu to be disabled when there is nothing to undo/redo. [#8807](https://github.com/JabRef/jabref/issues/8807) ### Fixed diff --git a/src/main/java/org/jabref/gui/undo/CountingUndoManager.java b/src/main/java/org/jabref/gui/undo/CountingUndoManager.java index dfe059dc467..1e4eaa8981d 100644 --- a/src/main/java/org/jabref/gui/undo/CountingUndoManager.java +++ b/src/main/java/org/jabref/gui/undo/CountingUndoManager.java @@ -4,35 +4,81 @@ import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; + public class CountingUndoManager extends UndoManager { private int unchangedPoint; - private int current; + + /** + * Indicates the number of edits aka balance of edits on the stack +1 when an edit is added/redone and -1 when an edit is undoed. + * */ + private final IntegerProperty balanceProperty = new SimpleIntegerProperty(0); + private final BooleanProperty undoableProperty = new SimpleBooleanProperty(false); + private final BooleanProperty redoableProperty = new SimpleBooleanProperty(false); @Override public synchronized boolean addEdit(UndoableEdit edit) { - current++; - boolean result = super.addEdit(edit); - return result; + boolean editAdded = super.addEdit(edit); + if (editAdded) { + incrementBalance(); + updateUndoableStatus(); + updateRedoableStatus(); + return true; + } else { + return false; + } } @Override public synchronized void undo() throws CannotUndoException { super.undo(); - current--; + decrementBalance(); + updateUndoableStatus(); + updateRedoableStatus(); } @Override public synchronized void redo() throws CannotUndoException { super.redo(); - current++; + incrementBalance(); + updateUndoableStatus(); + updateRedoableStatus(); } public synchronized void markUnchanged() { - unchangedPoint = current; + unchangedPoint = balanceProperty.get(); } public synchronized boolean hasChanged() { - return current != unchangedPoint; + return balanceProperty.get() != unchangedPoint; + } + + private void incrementBalance() { + balanceProperty.setValue(balanceProperty.getValue() + 1); + } + + private void decrementBalance() { + balanceProperty.setValue(balanceProperty.getValue() - 1); + } + + private void updateUndoableStatus() { + undoableProperty.setValue(canUndo()); + } + + private void updateRedoableStatus() { + redoableProperty.setValue(canRedo()); + } + + public ReadOnlyBooleanProperty getUndoableProperty() { + return undoableProperty; + } + + public ReadOnlyBooleanProperty getRedoableProperty() { + return redoableProperty; } } diff --git a/src/main/java/org/jabref/gui/undo/RedoAction.java b/src/main/java/org/jabref/gui/undo/RedoAction.java index 9227017aced..27e8b23f436 100644 --- a/src/main/java/org/jabref/gui/undo/RedoAction.java +++ b/src/main/java/org/jabref/gui/undo/RedoAction.java @@ -7,7 +7,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; -import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; @@ -22,9 +21,10 @@ public RedoAction(Supplier tabSupplier, DialogService dialogService, this.tabSupplier = tabSupplier; this.dialogService = dialogService; - // TODO: Rework the UndoManager to something like the following, if it had a property. - // this.executable.bind(frame.getCurrentBasePanel().getUndoManager().canUndo()) - this.executable.bind(ActionHelper.needsDatabase(stateManager)); + stateManager.activeTabProperty().addListener((observable, oldValue, activeLibraryTab) -> { + activeLibraryTab.ifPresent(libraryTab -> + this.executable.bind(libraryTab.getUndoManager().getRedoableProperty())); + }); } @Override diff --git a/src/main/java/org/jabref/gui/undo/UndoAction.java b/src/main/java/org/jabref/gui/undo/UndoAction.java index 9e219d637df..a61551bcf6b 100644 --- a/src/main/java/org/jabref/gui/undo/UndoAction.java +++ b/src/main/java/org/jabref/gui/undo/UndoAction.java @@ -7,7 +7,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; -import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; @@ -22,9 +21,10 @@ public UndoAction(Supplier tabSupplier, DialogService dialogService, this.tabSupplier = tabSupplier; this.dialogService = dialogService; - // TODO: Rework the UndoManager to something like the following, if it had a property. - // this.executable.bind(frame.getCurrentBasePanel().getUndoManager().canUndo()) - this.executable.bind(ActionHelper.needsDatabase(stateManager)); + stateManager.activeTabProperty().addListener((observable, oldValue, activeLibraryTab) -> { + activeLibraryTab.ifPresent(libraryTab -> + this.executable.bind(libraryTab.getUndoManager().getUndoableProperty())); + }); } @Override From 67ca1743e79a66db250e9e0c74676dcd348aeb91 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 14 Sep 2024 11:49:16 +0200 Subject: [PATCH 29/34] New Crowdin updates (#11760) * New translations jabref_en.properties (French) * New translations jabref_en.properties (Polish) * New translations jabref_en.properties (Portuguese, Brazilian) * New translations jabref_en.properties (German) * New translations jabref_en.properties (Italian) --- src/main/resources/l10n/JabRef_de.properties | 3 --- src/main/resources/l10n/JabRef_fr.properties | 5 ++--- src/main/resources/l10n/JabRef_it.properties | 2 ++ src/main/resources/l10n/JabRef_pl.properties | 2 ++ src/main/resources/l10n/JabRef_pt_BR.properties | 5 ++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 6689fcc5d94..bd963be2dd0 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2554,9 +2554,6 @@ Enable\ AI\ functionality\ (summary\ generation\ and\ chatting)\ in\ JabRef=Akti Customize\ expert\ settings=Experteneinstellungen anpassen These\ parameters\ affect\ how\ the\ AI\ will\ answer\ your\ questions.=Diese Parameter beeinflussen, wie die KI Ihre Fragen beantworten wird. Chat\ with\ AI\ about\ content\ of\ attached\ file(s)=Mit der KI über den Inhalt angehängter Datei(en) chatten -If\ you\ have\ chosen\ the\ Hugging\ Face\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Hugging\ Face\ applies.\ You\ find\ it\ at\ %0.=Wenn Sie das Hugging Face als AI Anbieter gewählt haben, gilt die Datenschutzrichtlinie von Hugging Face. Sie finden sie auf %0. -If\ you\ have\ chosen\ the\ Mistral\ AI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Mistral\ AI\ applies.\ You\ find\ it\ at\ %0.=Wenn Sie Mistral AI als KI-Anbieter ausgewählt haben, gilt die Datenschutzrichtlinie von Mistral AI. Sie finden sie unter %0. -If\ you\ have\ chosen\ the\ OpenAI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ OpenAI\ applies.\ You\ find\ it\ at\ %0.=Wenn Sie OpenAI als KI-Anbieter ausgewählt haben, gilt die Datenschutzrichtlinie von OpenAI. Sie finden sie unter %0. In\ order\ to\ use\ AI\ chat,\ you\ need\ to\ enable\ chatting\ with\ attached\ PDF\ files\ in\ JabRef\ preferences\ (AI\ tab).=Um AI-Chat zu nutzen, müssen Sie in den JabRef Einstellungen (Registerkarte KI) das Chatten mit angehängten PDF-Dateien aktivieren. In\ order\ to\ use\ AI\ chat,\ set\ an\ API\ key\ inside\ JabRef\ preferences\ (AI\ tab).=Um den AI-Chat zu nutzen, setzen Sie einen API-Schlüssel in den JabRef-Einstellungen (Registerkarte KI). Unable\ to\ chat\ with\ AI.=Mit der KI kann nicht gechattet werden. diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 205b7482e02..f1ce1d7ec1f 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -1132,6 +1132,7 @@ Unable\ to\ reload\ style\ file=Impossible de recharger le fichier de style Problem\ during\ separating\ cite\ markers=Problème lors de l'individualisation des appels à référence +Automatically\ add\ "Cited\ on\ pages..."\ at\ the\ end\ of\ bibliographic\ entries=Ajouter automatiquement "Cité en pages..." à la fin des entrées bibliographiques Automatically\ sync\ bibliography\ when\ inserting\ citations=Synchroniser automatiquement la bibliographie lors de l'insertion de citations Look\ up\ BibTeX\ entries\ in\ the\ active\ tab\ only=Rechercher les entrées BibTeX uniquement dans l'onglet actif Look\ up\ BibTeX\ entries\ in\ all\ open\ libraries=Rechercher les entrées BibTeX dans tous les fichiers ouverts @@ -2570,9 +2571,6 @@ Enable\ AI\ functionality\ (summary\ generation\ and\ chatting)\ in\ JabRef=Acti Customize\ expert\ settings=Personnaliser les paramètres experts These\ parameters\ affect\ how\ the\ AI\ will\ answer\ your\ questions.=Ces paramètres affectent la façon dont l'IA répondra à vos questions. Chat\ with\ AI\ about\ content\ of\ attached\ file(s)=Tchatter avec l'IA à propos du contenu de(s) fichier(s) joint(s) -If\ you\ have\ chosen\ the\ Hugging\ Face\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Hugging\ Face\ applies.\ You\ find\ it\ at\ %0.=Si vous avez choisi Hugging Face en tant que fournisseur d'IA, la politique de confidentialité de Hugging Face s'applique. Vous le trouverez à %0. -If\ you\ have\ chosen\ the\ Mistral\ AI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Mistral\ AI\ applies.\ You\ find\ it\ at\ %0.=Si vous avez choisi Mistral AI en tant que fournisseur d'IA, la politique de confidentialité de Mistral AI s'applique. Vous la trouverez à %0. -If\ you\ have\ chosen\ the\ OpenAI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ OpenAI\ applies.\ You\ find\ it\ at\ %0.=Si vous avez choisi OpenAI comme fournisseur d'IA, la politique de confidentialité d'OpenAI s'applique. Vous le trouverez à %0. In\ order\ to\ use\ AI\ chat,\ you\ need\ to\ enable\ chatting\ with\ attached\ PDF\ files\ in\ JabRef\ preferences\ (AI\ tab).=Pour utiliser le tchat IA, vous devez activer la discussion avec les fichiers PDF joints dans les préférences de JabRef (onglet IA). In\ order\ to\ use\ AI\ chat,\ set\ an\ API\ key\ inside\ JabRef\ preferences\ (AI\ tab).=Afin d'utiliser le tchat d'IA, définissez une clé API dans les préférences de JabRef (onglet IA). Unable\ to\ chat\ with\ AI.=Impossible de tchatter avec l'IA. @@ -2632,6 +2630,7 @@ Generating\ embeddings\ for\ %0=Générer des intégrations pour %0 RAG\ minimum\ score\ must\ be\ a\ number=Le score minimum de RAG doit être un nombre RAG\ minimum\ score\ must\ be\ greater\ than\ 0\ and\ less\ than\ 1=Le score minimum de RAG doit être compris entre 0 et 1 Temperature\ must\ be\ a\ number=La température doit être un nombre +If\ you\ have\ chosen\ %0\ as\ an\ AI\ provider,\ the\ privacy\ policy\ of\ %0\ applies.\ You\ find\ it\ at\ %1.=Si vous avez choisi %0 comme fournisseur d'IA, la politique de confidentialité de %0 s'applique. Vous la trouverez sur %1. Link=Lien Source\ URL=URL de la source diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index ad2e52e1d25..f0e2705bde9 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -1132,6 +1132,7 @@ Unable\ to\ reload\ style\ file=Impossibile ricaricare il file di stile Problem\ during\ separating\ cite\ markers=Problema durante la separazione dei marcatori di citazione +Automatically\ add\ "Cited\ on\ pages..."\ at\ the\ end\ of\ bibliographic\ entries=Aggiungere automaticamente "Citato nelle pagine..." alla fine delle voci bibliografiche Automatically\ sync\ bibliography\ when\ inserting\ citations=Sincronizza automaticamente la bibliografia all'inserimento delle citazioni Look\ up\ BibTeX\ entries\ in\ the\ active\ tab\ only=Ricerca le voci BibTeX solo nella scheda attiva Look\ up\ BibTeX\ entries\ in\ all\ open\ libraries=Ricerca le voci BibTeX in tutte le librerie aperte @@ -2598,6 +2599,7 @@ Generating\ embeddings\ for\ %0=Generazione di incorporamenti per %0 RAG\ minimum\ score\ must\ be\ a\ number=Il punteggio minimo di RAG deve essere un numero RAG\ minimum\ score\ must\ be\ greater\ than\ 0\ and\ less\ than\ 1=Il punteggio minimo di RAG deve essere maggiore di 0 e minore di 1 Temperature\ must\ be\ a\ number=La temperatura deve essere un numero +If\ you\ have\ chosen\ %0\ as\ an\ AI\ provider,\ the\ privacy\ policy\ of\ %0\ applies.\ You\ find\ it\ at\ %1.=Se hai scelto %0 come fornitore di Intelligenza Artificiale, si applica la politica sulla privacy di %0. Puoi trovarla su %1. Link=Collegamento Source\ URL=URL di origine diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index dcfd08de8ff..6a8172ac4ec 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -1039,6 +1039,7 @@ Unable\ to\ reload\ style\ file=Nie można przeładować pliku stylu Problem\ during\ separating\ cite\ markers=Problem podczas oddzielania znaczników cytowania +Automatically\ add\ "Cited\ on\ pages..."\ at\ the\ end\ of\ bibliographic\ entries=Automatycznie dodaj "Cytowane na stronach..." na końcu wpisów bibliograficznych Autodetecting\ paths...=Automatyczne wykrywanie ścieżek ... Found\ more\ than\ one\ OpenOffice/LibreOffice\ executable.=Znaleziono więcej niż jeden plik wykonywalny OpenOffice/LibreOffice. Please\ choose\ which\ one\ to\ connect\ to\:=Proszę wybrać, z którym połączyć\: @@ -1772,6 +1773,7 @@ Generating\ embeddings\ for\ %0=Generowanie osadzeń dla %0 RAG\ minimum\ score\ must\ be\ a\ number=Minimalny wynik RAG musi być liczbą RAG\ minimum\ score\ must\ be\ greater\ than\ 0\ and\ less\ than\ 1=Minimalny wynik RAG musi być większy niż 0 i mniejszy niż 1 Temperature\ must\ be\ a\ number=Temperatura musi być liczbą +If\ you\ have\ chosen\ %0\ as\ an\ AI\ provider,\ the\ privacy\ policy\ of\ %0\ applies.\ You\ find\ it\ at\ %1.=Jeśli wybrałeś %0 jako dostawcę AI, zastosowanie ma polityka prywatności %0. Znajdziesz ją pod adresem %1. Link=Odnośnik Source\ URL=Źródłowy adres URL diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 32881703f65..a1b71b3c180 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -1128,6 +1128,7 @@ Unable\ to\ reload\ style\ file=Não foi possível recarregar o arquivo de estil Problem\ during\ separating\ cite\ markers=Problema ao separar marcadores de citação +Automatically\ add\ "Cited\ on\ pages..."\ at\ the\ end\ of\ bibliographic\ entries=Adicionar automaticamente "Citado nas páginas..." no final das entradas bibliográficas Automatically\ sync\ bibliography\ when\ inserting\ citations=Sincronizar bibliografia automaticamente ao inserir citações Look\ up\ BibTeX\ entries\ in\ the\ active\ tab\ only=Pesquisar por referências BibTeX apenas na aba ativa Look\ up\ BibTeX\ entries\ in\ all\ open\ libraries=Pesquisar referências BibTeX em todas as bases de dados abertas @@ -2566,9 +2567,6 @@ Enable\ AI\ functionality\ (summary\ generation\ and\ chatting)\ in\ JabRef=Ativ Customize\ expert\ settings=Personalizar configurações avançadas These\ parameters\ affect\ how\ the\ AI\ will\ answer\ your\ questions.=Estes parâmetros afetam como a IA irá responder às suas perguntas. Chat\ with\ AI\ about\ content\ of\ attached\ file(s)=Converse com IA sobre o conteúdo do(s) arquivo(s) anexado(s) -If\ you\ have\ chosen\ the\ Hugging\ Face\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Hugging\ Face\ applies.\ You\ find\ it\ at\ %0.=Se você escolheu o Hugging Face como fornecedor de IA, a política de privacidade do Hugging Face se aplica. Você encontra-o na %0. -If\ you\ have\ chosen\ the\ Mistral\ AI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ Mistral\ AI\ applies.\ You\ find\ it\ at\ %0.=Se você escolheu o Mistral AI como fornecedor de IA, a política de privacidade do Mistral AI se aplica. Você encontra-o na %0. -If\ you\ have\ chosen\ the\ OpenAI\ as\ AI\ provider,\ the\ privacy\ policy\ of\ OpenAI\ applies.\ You\ find\ it\ at\ %0.=Se você escolheu o OpenAI como fornecedor de IA, a política de privacidade do OpenAI se aplica. Você encontra-o na %0. In\ order\ to\ use\ AI\ chat,\ you\ need\ to\ enable\ chatting\ with\ attached\ PDF\ files\ in\ JabRef\ preferences\ (AI\ tab).=Para usar o chat IA, você precisa habilitar o chat com arquivos PDF anexados nas preferências do JabRef (IA). In\ order\ to\ use\ AI\ chat,\ set\ an\ API\ key\ inside\ JabRef\ preferences\ (AI\ tab).=Para usar o bate-papo com IA, defina uma chave de API dentro da aba Preferências do JabRef (aba IA). Unable\ to\ chat\ with\ AI.=Incapaz de conversar com IA. @@ -2627,6 +2625,7 @@ Generating\ embeddings\ for\ %0=Gerando incorporações para %0 RAG\ minimum\ score\ must\ be\ a\ number=A pontuação mínima de RAG deve ser um número RAG\ minimum\ score\ must\ be\ greater\ than\ 0\ and\ less\ than\ 1=Pontuação mínima de RAG deve ser maior que 0 e menor que 1 Temperature\ must\ be\ a\ number=A temperatura deve ser um número +If\ you\ have\ chosen\ %0\ as\ an\ AI\ provider,\ the\ privacy\ policy\ of\ %0\ applies.\ You\ find\ it\ at\ %1.=Se você escolheu %0 como fornecedor de IA, a política de privacidade de %0 se aplica. Você encontra-o na %1. Link=Linkar Source\ URL=URL de origem From 7c958e7dd067adbfd7244224aa551c6b3f1e9721 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 15 Sep 2024 10:55:56 +0200 Subject: [PATCH 30/34] Fix architecture gui/logic (#11729) * Refine JavaDoc * Add TODOs Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Fix typos * Reoder settings * Fix arch flaw Co-authored-by: Christoph Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Fix checkstyle and formatting * Make BackgroundTask independent from UI * Move BackgroundTask from gui to logic Co-authored-by: Christoph * protected -> public Co-authored-by: Christoph * Move CurrentThreadTaskExecutor from gui to logic Co-authored-by: Christoph * protected/package private -> public Co-authored-by: Christoph * Fix formatting * Make ArchitectureTest "real" * Fix typo in "cancelled" Co-authored-by: Christoph * Fix JavaDoc * Move execute(JavaFX Task) down to UiTaskExecutor Co-authored-by: Christoph * Fixed an exception when searching for unlinked files. Co-authored-by: Christoph * Fix CHANGELOG * Move TaskExecutor from gui to logic Co-authored-by: Christoph * Fix more checkstyle * Introduce NotifcationService Also: - Move openAiChat from AiService (logic) to GroupTree (gui) * move unlinked files filter to logic * Inline JabRefDesktop.get*Directory() (to avoid dependency to gui from logic) * move search display mode enum * Introduce "Directories" * Remove unused variables * Move ChatHistoryService to UI * move fallback exception handler to logic and add consumer for thread execution * remove dialog service from logic class * Route ChatHistoryService correctly * Do not do architecture tests for test classes * remove dialog service in test * Fix checkstyle * Sort and shelve preferences objects * Fix CHANGELOG * Checkstyle imports * Refactor NativeDesktop - Integrate OS into NativeDesktop - Integrate JabRefDesktop into NativeDesktop * Compile fix * Introduce OS (and move things from NativeDesktop) * Move FilePreferences to logic Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Fix imports * More optimize imports... * One more... * Move externalFileTypes from FilePreferences to ExternalApplicationPreferences * Adapt classes to new structure of ExternalApplicatoinPreferences Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> * Move MrDlibPreferences * Move AutoCompleteFirstNameMode to logic.preferences * Move detectProgramPath to OS * Rename "PreferenceService" to "Preferences" * Adapt name of factory * Move class - we do it differently in main JabRef * Move Preferences to logic (to prepare split up in gui / non-gui) * Infrastructore for gui / non-gui * Rename Preferences to CliPreferences * Fix names - begin to move getEntryEditorPreferences() * Rename GuiPreferences to CoreGuiPreferences * Fix CliPreferences - GuiPreferences * Move constants * Move "getMergeDialogPreferences()" to GuiPreferences * Make LinkedFile independent from Gui * Push down AutoCompletePreferences * Pull down CoreGuiPreferences and Workspace preferences * Fix empty line * Push getUnlinkedFilesDialogPreferences() down * Push down ExternalApplicationsPreferences * Adapt code * fix imports * fix more imports * checkstyle * fix more imports * rewrite * Fix imports * Push down SidePanePreferences * Adapt code * Fix variable location * Push down GroupsPreferences * Adapt classes * Push down getSpecialFieldsPreferences() * Adapt code * Push down PreviewPreferences * Adapt code * Push down PushToApplicationPreferences * Push down NameDisplayPreferences * ADapt code * Push down main table preference, main table column preferences, and search dialog column preferences * Push down KeyBindingsRepository * Add warning if file could not be deleted * Remove some calls to NativeDesktop * Remove NativeDesktop from JabRefCliPreferences * Introduce constants (and migrations may be accessed by GUI) * Fix wrong logger * Relax migrations * Move PreferencesFilter to gui * Move LuceneIndexer to logic * Remove preferences package from tests * Fix imports * Fix checkstyle * Fix compile error * Fix logger * Add workaround * Discard changes to src/test/resources/org/jabref/logic/exporter/ModsExportFormatTestOnlyRequiredFields.xml * Discard changes to src/test/resources/org/jabref/logic/exporter/ModsExportFormatTestAllFields.xml * Reset *.xml to main * Creaate LastFilesOpenedPreferences * Re-activate code for Server * NativeDesktop use is not restricted * Fix architecture test --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Co-authored-by: Christoph --- docs/code-howtos/index.md | 4 +- .../org/jabref/benchmarks/Benchmarks.java | 12 +- src/main/java/org/jabref/Launcher.java | 17 +- .../org/jabref/cli/ArgumentProcessor.java | 81 +- src/main/java/org/jabref/cli/JabRefCLI.java | 14 +- .../java/org/jabref/gui/ClipBoardManager.java | 6 +- .../CoreGuiPreferences.java} | 65 +- .../java/org/jabref/gui/DialogService.java | 10 +- .../jabref/gui/FallbackExceptionHandler.java | 28 - .../org/jabref/gui/JabRefDialogService.java | 4 +- src/main/java/org/jabref/gui/JabRefGUI.java | 100 +- src/main/java/org/jabref/gui/LibraryTab.java | 74 +- .../java/org/jabref/gui/StateManager.java | 2 +- .../jabref/gui/UpdateTimestampListener.java | 12 +- .../WorkspacePreferences.java | 2 +- .../org/jabref/gui/actions/ActionHelper.java | 6 +- .../jabref/gui/ai/ClearEmbeddingsAction.java | 4 +- .../chathistory/ChatHistoryService.java | 17 +- .../ai/components/aichat/AiChatComponent.fxml | 17 +- .../ai/components/aichat/AiChatComponent.java | 6 +- .../aichat/AiChatGuardedComponent.java | 10 +- .../ai/components/aichat/AiChatWindow.java | 14 +- .../chathistory/ChatHistoryComponent.fxml | 5 +- .../chatmessage/ChatMessageComponent.fxml | 15 +- .../chatprompt/ChatPromptComponent.fxml | 8 +- .../AiPrivacyNoticeGuardedComponent.java | 12 +- .../privacynotice/PrivacyNoticeComponent.fxml | 14 +- .../privacynotice/PrivacyNoticeComponent.java | 16 +- .../components/summary/SummaryComponent.java | 8 +- .../summary/SummaryShowingComponent.fxml | 11 +- .../util/EmbeddingModelGuardedComponent.java | 8 +- .../util/errorstate/ErrorStateComponent.fxml | 8 +- .../AutoCompletePreferences.java | 1 + .../PersonNameStringConverter.java | 1 + .../gui/autosaveandbackup/BackupManager.java | 8 +- .../jabref/gui/auximport/FromAuxDialog.java | 4 +- .../gui/auximport/FromAuxDialogViewModel.java | 10 +- .../gui/backup/BackupResolverDialog.java | 6 +- .../BibtexExtractorViewModel.java | 29 +- .../ExtractBibtexActionOnline.java | 12 +- .../bibtexextractor/ExtractBibtexDialog.java | 8 +- .../GenerateCitationKeyAction.java | 26 +- .../GenerateCitationKeySingleAction.java | 14 +- .../org/jabref/gui/cleanup/CleanupAction.java | 12 +- .../org/jabref/gui/cleanup/CleanupDialog.java | 4 +- .../gui/cleanup/CleanupPresetPanel.fxml | 4 +- .../gui/cleanup/CleanupPresetPanel.java | 4 +- .../gui/cleanup/CleanupSingleAction.java | 8 +- .../org/jabref/gui/collab/ChangeScanner.java | 12 +- .../DatabaseChangeDetailsViewFactory.java | 18 +- .../gui/collab/DatabaseChangeMonitor.java | 14 +- .../collab/DatabaseChangeResolverFactory.java | 10 +- .../collab/DatabaseChangesResolverDialog.java | 10 +- .../entrychange/EntryChangeDetailsView.java | 12 +- .../entrychange/EntryChangeResolver.java | 10 +- .../EntryWithPreviewAndSourceDetailsView.java | 6 +- .../entrychange/PreviewWithSourceTab.java | 14 +- .../CitationKeyPatternsPanel.java | 4 +- .../SaveOrderConfigPanel.java | 6 +- .../jabref/gui/copyfiles/CopyFilesAction.java | 24 +- .../jabref/gui/copyfiles/CopyFilesTask.java | 12 +- .../gui/copyfiles/CopySingleFileAction.java | 2 +- .../org/jabref/gui/desktop/JabRefDesktop.java | 344 ----- .../jabref/gui/desktop/os/DefaultDesktop.java | 12 +- .../java/org/jabref/gui/desktop/os/Linux.java | 18 +- .../jabref/gui/desktop/os/NativeDesktop.java | 402 ++++-- .../java/org/jabref/gui/desktop/os/OSX.java | 11 +- .../org/jabref/gui/desktop/os/Windows.java | 64 +- .../jabref/gui/dialogs/AutosaveUiManager.java | 6 +- .../jabref/gui/dialogs/BackupUIManager.java | 24 +- .../documentviewer/DocumentViewerControl.java | 4 +- .../documentviewer/DocumentViewerView.java | 8 +- .../DocumentViewerViewModel.java | 10 +- .../ShowDocumentViewerAction.java | 4 +- .../DuplicateResolverDialog.java | 20 +- .../duplicationFinder/DuplicateSearch.java | 14 +- .../org/jabref/gui/edit/CopyMoreAction.java | 16 +- .../jabref/gui/edit/ManageKeywordsDialog.java | 4 +- .../gui/edit/ManageKeywordsViewModel.java | 2 +- .../jabref/gui/edit/OpenBrowserAction.java | 12 +- .../CopyOrMoveFieldContentTab.fxml | 1 - .../editfieldcontent/EditFieldContentTab.fxml | 3 +- .../renamefield/RenameFieldTab.fxml | 1 - .../org/jabref/gui/entryeditor/AiChatTab.java | 34 +- .../jabref/gui/entryeditor/AiSummaryTab.java | 20 +- .../jabref/gui/entryeditor/CommentsTab.java | 6 +- .../gui/entryeditor/DeprecatedFieldsTab.java | 6 +- .../entryeditor/DetailOptionalFieldsTab.java | 6 +- .../jabref/gui/entryeditor/EntryEditor.java | 68 +- .../gui/entryeditor/FieldsEditorTab.java | 8 +- .../ImportantOptionalFieldsTab.java | 6 +- .../gui/entryeditor/LatexCitationsTab.java | 6 +- .../LatexCitationsTabViewModel.java | 20 +- .../entryeditor/OptionalFieldsTabBase.java | 6 +- .../gui/entryeditor/OtherFieldsTab.java | 6 +- .../jabref/gui/entryeditor/PreviewTab.java | 8 +- .../gui/entryeditor/RelatedArticlesTab.java | 32 +- .../gui/entryeditor/RequiredFieldsTab.java | 6 +- .../org/jabref/gui/entryeditor/SciteTab.java | 17 +- .../gui/entryeditor/SciteTabViewModel.java | 14 +- .../org/jabref/gui/entryeditor/SourceTab.java | 2 +- .../gui/entryeditor/UserDefinedFieldsTab.java | 6 +- .../CitationRelationsTab.java | 24 +- .../CitationsRelationsTabViewModel.java | 20 +- .../FileAnnotationTabViewModel.java | 2 +- .../FulltextSearchResultsTab.java | 20 +- .../jabref/gui/entrytype/EntryTypeView.java | 15 +- .../gui/entrytype/EntryTypeViewModel.java | 26 +- .../gui/errorconsole/ErrorConsoleView.java | 6 +- .../errorconsole/ErrorConsoleViewModel.java | 14 +- .../gui/errorconsole/LogEventViewModel.java | 2 +- .../CreateModifyExporterDialogView.java | 4 +- .../CreateModifyExporterDialogViewModel.java | 6 +- .../jabref/gui/exporter/ExportCommand.java | 14 +- .../gui/exporter/ExportToClipboardAction.java | 12 +- .../org/jabref/gui/exporter/SaveAction.java | 10 +- .../jabref/gui/exporter/SaveAllAction.java | 10 +- .../gui/exporter/SaveDatabaseAction.java | 12 +- .../WriteMetadataToLinkedPdfsAction.java | 8 +- .../externalfiles/AutoLinkFilesAction.java | 11 +- .../externalfiles/AutoSetFileLinksUtil.java | 18 +- .../externalfiles/DownloadFullTextAction.java | 12 +- .../ExternalFilesEntryLinker.java | 9 +- .../externalfiles/FileExtensionViewModel.java | 10 +- .../gui/externalfiles/FileFilterUtils.java | 3 + .../gui/externalfiles/ImportHandler.java | 24 +- .../externalfiles/UnlinkedFilesCrawler.java | 8 +- .../UnlinkedFilesDialogPreferences.java | 6 +- .../UnlinkedFilesDialogView.java | 23 +- .../UnlinkedFilesDialogViewModel.java | 21 +- .../externalfiles/UnlinkedPDFFileFilter.java | 2 +- .../CustomExternalFileType.java | 3 +- .../externalfiletype/ExternalFileTypes.java | 34 +- .../gui/fieldeditors/CitationKeyEditor.java | 8 +- .../CitationKeyEditorViewModel.java | 10 +- .../jabref/gui/fieldeditors/DateEditor.java | 6 +- .../gui/fieldeditors/EditorValidator.java | 6 +- .../jabref/gui/fieldeditors/FieldEditors.java | 6 +- .../jabref/gui/fieldeditors/GroupEditor.java | 4 +- .../jabref/gui/fieldeditors/ISSNEditor.java | 12 +- .../gui/fieldeditors/ISSNEditorViewModel.java | 12 +- .../gui/fieldeditors/JournalEditor.fxml | 2 +- .../gui/fieldeditors/JournalEditor.java | 10 +- .../fieldeditors/JournalEditorViewModel.java | 2 +- .../gui/fieldeditors/KeywordsEditor.java | 6 +- .../fieldeditors/KeywordsEditorViewModel.java | 6 +- .../gui/fieldeditors/LinkedFileViewModel.java | 76 +- .../gui/fieldeditors/LinkedFilesEditor.java | 51 +- .../LinkedFilesEditorViewModel.java | 23 +- .../gui/fieldeditors/MarkdownEditor.java | 4 +- .../jabref/gui/fieldeditors/OwnerEditor.java | 8 +- .../fieldeditors/OwnerEditorViewModel.java | 6 +- .../gui/fieldeditors/PersonsEditor.java | 10 +- .../jabref/gui/fieldeditors/PopOverUtil.java | 4 +- .../jabref/gui/fieldeditors/SimpleEditor.java | 4 +- .../org/jabref/gui/fieldeditors/URLUtil.java | 6 +- .../jabref/gui/fieldeditors/UrlEditor.java | 10 +- .../gui/fieldeditors/UrlEditorViewModel.java | 12 +- .../WriteMetadataToSinglePdfAction.java | 6 +- .../contextmenu/EditorContextAction.java | 2 +- .../BaseIdentifierEditorViewModel.java | 12 +- .../DoiIdentifierEditorViewModel.java | 12 +- .../EprintIdentifierEditorViewModel.java | 6 +- .../ISBNIdentifierEditorViewModel.java | 6 +- .../identifier/IdentifierEditor.java | 18 +- .../fieldeditors/journalinfo/JournalInfo.fxml | 3 +- .../ExternalApplicationsPreferences.java | 16 +- .../org/jabref/gui/frame/JabRefFrame.java | 70 +- .../gui/frame/JabRefFrameViewModel.java | 23 +- .../java/org/jabref/gui/frame/MainMenu.java | 137 +- .../org/jabref/gui/frame/MainToolBar.java | 34 +- .../jabref/gui/frame/OpenConsoleAction.java | 18 +- .../jabref/gui/frame/SendAsEMailAction.java | 20 +- .../gui/frame/SendAsKindleEmailAction.java | 14 +- .../gui/frame/SendAsStandardEmailAction.java | 18 +- .../frame}/SidePanePreferences.java | 2 +- .../jabref/gui/groups/GroupDialogView.java | 8 +- .../gui/groups/GroupDialogViewModel.java | 28 +- .../jabref/gui/groups/GroupNodeViewModel.java | 26 +- .../org/jabref/gui/groups/GroupTreeView.java | 30 +- .../jabref/gui/groups/GroupTreeViewModel.java | 46 +- .../org/jabref/gui/help/AboutDialogView.java | 6 +- .../jabref/gui/help/AboutDialogViewModel.java | 12 +- .../java/org/jabref/gui/help/HelpAction.java | 13 +- .../org/jabref/gui/help/NewVersionDialog.java | 13 +- .../gui/help/SearchForUpdateAction.java | 12 +- .../org/jabref/gui/help/VersionWorker.java | 21 +- .../importer/GenerateEntryFromIdAction.java | 22 +- .../importer/GenerateEntryFromIdDialog.fxml | 1 - .../importer/GenerateEntryFromIdDialog.java | 12 +- .../jabref/gui/importer/ImportCommand.java | 36 +- .../ImportCustomEntryTypesDialog.java | 6 +- ...ImportCustomEntryTypesDialogViewModel.java | 12 +- .../gui/importer/ImportEntriesDialog.fxml | 2 +- .../gui/importer/ImportEntriesDialog.java | 8 +- .../gui/importer/ImportEntriesViewModel.java | 12 +- .../gui/importer/NewDatabaseAction.java | 12 +- .../jabref/gui/importer/NewEntryAction.java | 8 +- .../actions/CheckForNewEntryTypesAction.java | 10 +- .../importer/actions/GUIPostOpenAction.java | 6 +- .../actions/MergeReviewIntoCommentAction.java | 6 +- .../importer/actions/OpenDatabaseAction.java | 41 +- .../actions/SearchGroupsMigrationAction.java | 6 +- .../fetcher/LookupIdentifierAction.java | 4 +- .../importer/fetcher/WebSearchPaneView.java | 8 +- .../fetcher/WebSearchPaneViewModel.java | 24 +- .../gui/integrity/IntegrityCheckAction.java | 20 +- .../jabref/gui/journals/AbbreviateAction.java | 4 +- .../gui/journals/UndoableAbbreviator.java | 21 +- .../gui/keyboard/KeyBindingRepository.java | 2 +- .../constants/ConstantsPropertiesView.java | 8 +- .../ConstantsPropertiesViewModel.java | 10 +- .../general/GeneralPropertiesView.java | 6 +- .../general/GeneralPropertiesViewModel.java | 22 +- .../keypattern/KeyPatternPropertiesView.java | 14 +- .../KeyPatternPropertiesViewModel.java | 10 +- .../saving/SavingPropertiesView.java | 6 +- .../saving/SavingPropertiesViewModel.java | 12 +- .../gui/linkedfile/AttachFileAction.java | 10 +- .../linkedfile/AttachFileFromURLAction.java | 12 +- .../gui/linkedfile/DeleteFileAction.java | 6 +- .../linkedfile/DownloadLinkedFileAction.java | 34 +- .../gui/linkedfile/LinkedFileEditDialog.java | 6 +- .../LinkedFileEditDialogViewModel.java | 14 +- .../RedownloadMissingFilesAction.java | 21 +- .../org/jabref/gui/maintable/CellFactory.java | 18 +- .../maintable/ExtractReferencesAction.java | 26 +- .../org/jabref/gui/maintable/MainTable.java | 18 +- .../gui/maintable/MainTableColumnFactory.java | 22 +- .../gui/maintable/MainTableColumnModel.java | 8 +- .../gui/maintable/MainTableDataModel.java | 20 +- .../gui/maintable/MainTableTooltip.java | 8 +- .../maintable/NewLibraryFromPdfAction.java | 12 +- .../NewLibraryFromPdfActionOffline.java | 8 +- .../NewLibraryFromPdfActionOnline.java | 10 +- .../gui/maintable/OpenExternalFileAction.java | 18 +- .../gui/maintable/OpenFolderAction.java | 20 +- .../jabref/gui/maintable/OpenUrlAction.java | 12 +- .../PersistenceVisualStateTable.java | 3 +- .../jabref/gui/maintable/RightClickMenu.java | 76 +- .../maintable/SearchShortScienceAction.java | 12 +- .../gui/maintable/columns/FileColumn.java | 22 +- .../columns/LinkedIdentifierColumn.java | 10 +- .../maintable/columns/SpecialFieldColumn.java | 12 +- .../gui/mergeentries/FetchAndMergeEntry.java | 10 +- .../mergeentries}/MergeDialogPreferences.java | 17 +- .../gui/mergeentries/MergeEntriesAction.java | 10 +- .../gui/mergeentries/MergeEntriesDialog.java | 6 +- .../MergeWithFetchedEntryAction.java | 12 +- .../gui/mergeentries/MultiMergeEntries.fxml | 13 +- .../mergeentries/MultiMergeEntriesView.java | 8 +- .../MultiMergeEntriesViewModel.java | 4 +- .../newmergedialog/FieldRowView.java | 8 +- .../PersonsNameFieldRowView.java | 6 +- .../newmergedialog/ThreeWayMergeView.java | 20 +- .../newmergedialog/cell/FieldValueCell.java | 10 +- .../cell/OpenExternalLinkAction.java | 18 +- .../fieldsmerger/CommentMerger.java | 2 +- .../fieldsmerger/FieldMergerFactory.java | 2 +- .../fieldsmerger/KeywordMerger.java | 2 +- .../toolbar/ThreeWayMergeToolbar.java | 18 +- .../DetectOpenOfficeInstallation.java | 6 +- .../gui/openoffice/OpenOfficePanel.java | 46 +- .../gui/openoffice/StyleSelectDialog.fxml | 1 - .../gui/openoffice/StyleSelectDialogView.java | 14 +- .../StyleSelectDialogViewModel.java | 29 +- .../AbstractPreferenceTabView.java | 5 +- .../gui/preferences/GuiPreferences.java | 55 + .../gui/preferences/JabRefGuiPreferences.java | 1210 ++++++++++++++++ .../preferences/PreferencesDialogView.java | 7 +- .../PreferencesDialogViewModel.java | 6 +- .../preferences/PreferencesFilter.java | 8 +- .../preferences/PreferencesFilterDialog.java | 1 - .../org/jabref/gui/preferences/ai/AiTab.fxml | 23 +- .../org/jabref/gui/preferences/ai/AiTab.java | 32 +- .../gui/preferences/ai/AiTabViewModel.java | 12 +- .../autocompletion/AutoCompletionTab.java | 2 +- .../AutoCompletionTabViewModel.java | 2 +- .../CitationKeyPatternTab.java | 8 +- .../customentrytypes/CustomEntryTypesTab.java | 6 +- .../CustomEntryTypesTabViewModel.java | 16 +- .../customexporter/CustomExporterTab.java | 2 +- .../CustomExporterTabViewModel.java | 6 +- .../customimporter/CustomImporterTab.java | 2 +- .../CustomImporterTabViewModel.java | 6 +- .../gui/preferences/entry/EntryTab.java | 4 +- .../preferences/entry/EntryTabViewModel.java | 14 +- .../entryeditor/EntryEditorTab.java | 4 +- .../entryeditor/EntryEditorTabViewModel.java | 16 +- .../gui/preferences/export/ExportTab.java | 2 +- .../export/ExportTabViewModel.java | 2 +- .../gui/preferences/external/ExternalTab.java | 4 +- .../external/ExternalTabViewModel.java | 16 +- .../EditExternalFileTypeEntryDialog.java | 4 +- .../ExternalFileTypesTab.java | 2 +- .../ExternalFileTypesTabViewModel.java | 18 +- .../gui/preferences/general/GeneralTab.java | 6 +- .../general/GeneralTabViewModel.java | 16 +- .../gui/preferences/groups/GroupsTab.java | 2 +- .../journals/JournalAbbreviationsTab.fxml | 4 +- .../journals/JournalAbbreviationsTab.java | 4 +- .../JournalAbbreviationsTabViewModel.java | 4 +- .../keybindings/KeyBindingsTab.java | 2 +- .../keybindings/KeyBindingsTabViewModel.java | 6 +- .../linkedfiles/LinkedFilesTab.java | 8 +- .../linkedfiles/LinkedFilesTabViewModel.java | 6 +- .../nameformatter/NameFormatterTab.java | 4 +- .../gui/preferences/network/NetworkTab.java | 2 +- .../network/NetworkTabViewModel.java | 10 +- .../gui/preferences/preview/PreviewTab.java | 6 +- .../preview/PreviewTabViewModel.java | 6 +- .../NewProtectedTermsFileDialog.java | 2 +- .../protectedterms/ProtectedTermsTab.java | 2 +- .../ProtectedTermsTabViewModel.java | 23 +- .../gui/preferences/table/TableTab.java | 4 +- .../preferences/table/TableTabViewModel.java | 8 +- .../preferences/websearch/WebSearchTab.java | 2 +- .../websearch/WebSearchTabViewModel.java | 34 +- .../gui/preferences/xmp/XmpPrivacyTab.java | 10 +- .../gui/preview/CopyCitationAction.java | 22 +- .../org/jabref/gui/preview/PreviewPanel.java | 15 +- .../preview}/PreviewPreferences.java | 2 +- .../org/jabref/gui/preview/PreviewViewer.java | 19 +- .../gui/push/AbstractPushToApplication.java | 21 +- .../jabref/gui/push/PushToApplication.java | 1 - .../gui/push/PushToApplicationCommand.java | 24 +- .../push}/PushToApplicationPreferences.java | 2 +- .../gui/push/PushToApplicationSettings.java | 3 +- .../jabref/gui/push/PushToApplications.java | 30 +- .../java/org/jabref/gui/push/PushToEmacs.java | 15 +- .../jabref/gui/push/PushToEmacsSettings.java | 3 +- .../java/org/jabref/gui/push/PushToLyx.java | 11 +- .../jabref/gui/push/PushToLyxSettings.java | 3 +- .../jabref/gui/push/PushToSublimeText.java | 10 +- .../org/jabref/gui/push/PushToTeXstudio.java | 6 +- .../org/jabref/gui/push/PushToTeXworks.java | 8 +- .../org/jabref/gui/push/PushToTexShop.java | 10 +- .../org/jabref/gui/push/PushToTexmaker.java | 6 +- .../java/org/jabref/gui/push/PushToVim.java | 13 +- .../jabref/gui/push/PushToVimSettings.java | 3 +- .../org/jabref/gui/push/PushToWinEdt.java | 6 +- .../jabref/gui/remote/CLIMessageHandler.java | 11 +- .../jabref/gui/search/GlobalSearchBar.java | 21 +- .../gui/search/GlobalSearchResultDialog.fxml | 2 +- .../gui/search/GlobalSearchResultDialog.java | 30 +- .../GlobalSearchResultDialogViewModel.java | 2 +- .../RebuildFulltextSearchIndexAction.java | 4 +- .../jabref/gui/search/SearchResultsTable.java | 16 +- .../search/SearchResultsTableDataModel.java | 10 +- .../shared/SharedDatabaseLoginDialogView.java | 8 +- .../SharedDatabaseLoginDialogViewModel.java | 20 +- .../gui/shared/SharedDatabaseUIManager.java | 22 +- .../org/jabref/gui/sidepane/SidePane.java | 20 +- .../gui/sidepane/SidePaneContentFactory.java | 22 +- .../gui/sidepane/SidePaneViewModel.java | 36 +- .../jabref/gui/sidepane/TogglePaneAction.java | 2 +- .../gui/slr/ExistingStudySearchAction.java | 18 +- .../gui/slr/ManageStudyDefinitionView.java | 16 +- .../slr/ManageStudyDefinitionViewModel.java | 2 +- .../jabref/gui/slr/StartNewStudyAction.java | 10 +- .../gui/specialfields/SpecialFieldAction.java | 10 +- .../SpecialFieldMenuItemFactory.java | 20 +- .../specialfields/SpecialFieldViewModel.java | 10 +- .../gui/texparser/ParseLatexDialogView.java | 8 +- .../texparser/ParseLatexDialogViewModel.java | 20 +- .../texparser/ParseLatexResultViewModel.java | 2 +- .../java/org/jabref/gui/theme/StyleSheet.java | 1 + src/main/java/org/jabref/gui/theme/Theme.java | 2 +- .../org/jabref/gui/theme/ThemeManager.java | 10 +- .../java/org/jabref/gui/util/FieldsUtil.java | 6 +- .../org/jabref/gui/util/UiTaskExecutor.java | 34 +- .../org/jabref/http/server/Application.java | 3 +- .../jabref/http/server/LibrariesResource.java | 6 +- .../jabref/http/server/LibraryResource.java | 6 +- .../http/server/PreferencesFactory.java | 17 + .../java/org/jabref/http/server/Server.java | 7 +- .../FilePreferences.java | 15 +- .../InternalPreferences.java | 2 +- .../LibraryPreferences.java | 2 +- .../jabref/logic/ai/AiDefaultPreferences.java | 4 +- .../ai/AiPreferences.java | 5 +- .../java/org/jabref/logic/ai/AiService.java | 80 +- .../jabref/logic/ai/chatting/AiChatLogic.java | 2 +- .../logic/ai/chatting/AiChatService.java | 2 +- .../storages/MVStoreChatHistoryStorage.java | 4 +- .../model/JabRefChatLanguageModel.java | 2 +- .../model/JvmOpenAiChatLanguageModel.java | 2 +- .../ai/ingestion/FileEmbeddingsManager.java | 2 +- .../GenerateEmbeddingsForSeveralTask.java | 8 +- .../ai/ingestion/GenerateEmbeddingsTask.java | 6 +- .../logic/ai/ingestion/IngestionService.java | 6 +- .../logic/ai/ingestion/LowLevelIngestor.java | 2 +- .../ai/ingestion/MVStoreEmbeddingStore.java | 4 +- .../ingestion/model/JabRefEmbeddingModel.java | 14 +- .../model/UpdateEmbeddingModelTask.java | 4 +- .../MVStoreFullyIngestedDocumentsTracker.java | 4 +- .../ai/summarization/GenerateSummaryTask.java | 8 +- .../ai/summarization/SummariesService.java | 6 +- .../logic/ai/summarization/Summary.java | 2 +- .../storages/MVStoreSummariesStorage.java | 4 +- .../org/jabref/logic/ai/util/MVStoreBase.java | 4 +- .../jabref/logic/bibtex/BibEntryWriter.java | 2 +- .../CitationStyleOutputFormat.java | 2 +- .../cleanup}/CleanupPreferences.java | 8 +- .../jabref/logic/cleanup/CleanupWorker.java | 3 +- .../logic/cleanup/MoveFilesCleanup.java | 2 +- .../logic/cleanup/RelativePathsCleanup.java | 2 +- .../RemoveLinksToNotExistentFiles.java | 2 +- .../logic/cleanup/RenamePdfCleanup.java | 2 +- .../org/jabref/logic/crawler/Crawler.java | 10 +- .../jabref/logic/crawler/StudyRepository.java | 30 +- .../jabref/logic/database/DuplicateCheck.java | 2 +- .../exporter/EmbeddedBibFilePdfExporter.java | 2 +- .../logic/exporter/EndnoteXmlExporter.java | 2 +- .../exporter}/ExportPreferences.java | 3 +- .../org/jabref/logic/exporter/Exporter.java | 2 +- .../logic/exporter/ExporterFactory.java | 18 +- .../logic/exporter/MetaDataSerializer.java | 2 +- .../logic/exporter/TemplateExporter.java | 2 +- .../externalfiles/DateRange.java | 2 +- .../externalfiles/ExternalFileSorter.java | 2 +- .../externalfiles/LinkedFileHandler.java | 2 +- .../importer/ImportFormatPreferences.java | 2 +- .../jabref/logic/importer/WebFetchers.java | 2 +- .../logic/importer/fetcher/DOAJFetcher.java | 2 +- .../jabref/logic/importer/fetcher/IEEE.java | 2 +- .../logic/importer/fetcher/LOBIDFetcher.java | 2 +- .../logic/importer/fetcher/MathSciNet.java | 2 +- .../logic/importer/fetcher/MrDLibFetcher.java | 1 - .../importer/fetcher}/MrDlibPreferences.java | 2 +- .../logic/importer/fetcher/ResearchGate.java | 2 +- .../importer/fetcher/SpringerFetcher.java | 2 +- .../importer/fileformat/BibtexParser.java | 2 +- .../importer/fileformat/CoinsParser.java | 2 +- .../fileformat/PdfContentImporter.java | 2 +- .../fileformat/PdfMergeMetadataImporter.java | 2 +- .../jabref/logic/integrity/FieldCheckers.java | 2 +- .../jabref/logic/integrity/FileChecker.java | 2 +- .../logic/integrity/IntegrityCheck.java | 2 +- .../logic/layout/format/RisAuthors.java | 2 +- .../logic/layout/format/RisKeywords.java | 2 +- .../openoffice/OpenOfficeFileSearch.java | 2 +- src/main/java/org/jabref/logic/os/OS.java | 133 ++ .../logic/pdf/EntryAnnotationImporter.java | 2 +- .../jabref/logic/pdf/FileAnnotationCache.java | 2 +- .../AutoCompleteFirstNameMode.java | 2 +- .../preferences/CliPreferences.java} | 61 +- .../preferences/JabRefCliPreferences.java} | 1250 ++--------------- .../LastFilesOpenedPreferences.java | 52 + .../protectedterms/ProtectedTermsList.java | 2 +- .../jabref/logic/search/DatabaseSearcher.java | 4 +- .../search/LuceneIndexer.java | 4 +- .../jabref/logic/search/LuceneManager.java | 33 +- .../search/SearchDisplayMode.java | 2 +- .../search}/SearchPreferences.java | 3 +- .../search/indexing/BibFieldsIndexer.java | 10 +- .../indexing/DefaultLinkedFilesIndexer.java | 8 +- .../indexing/ReadOnlyLinkedFilesIndexer.java | 4 +- .../search/retrieval/LuceneSearcher.java | 4 +- .../{gui => logic}/util/BackgroundTask.java | 67 +- .../util/CurrentThreadTaskExecutor.java | 11 +- .../org/jabref/logic/util/Directories.java | 65 + .../logic/util/FallbackExceptionHandler.java | 36 + .../logic/util/HeadlessExecutorService.java | 5 +- .../logic/util/NotificationService.java | 11 + src/main/java/org/jabref/logic/util/OS.java | 70 - .../{gui => logic}/util/TaskExecutor.java | 17 +- .../logic/util/io/DatabaseFileLookup.java | 2 +- .../logic/util/io/FileNameUniqueness.java | 11 +- .../org/jabref/logic/util/io/FileUtil.java | 2 +- .../CustomEntryTypePreferenceMigration.java | 6 +- .../migrations/PreferencesMigrations.java | 127 +- .../{preferences => model}/ai/AiProvider.java | 2 +- .../ai/EmbeddingModel.java | 2 +- .../model/database/BibDatabaseContext.java | 6 +- .../entry}/BibEntryPreferences.java | 2 +- .../org/jabref/model/entry/LinkedFile.java | 20 +- .../model/entry/field/FieldFactory.java | 3 +- .../preferences/PreferenceServiceFactory.java | 14 - .../resources/xslt/mathml_latex/entities.xsl | 6 +- .../jabref/architecture/ArchitectureTest.java | 10 +- .../architecture/MainArchitectureTest.java | 77 +- .../org/jabref/cli/ArgumentProcessorTest.java | 29 +- .../java/org/jabref/cli/JabRefCLITest.java | 2 +- .../org/jabref/gui/ClipBoardManagerTest.java | 8 +- .../gui/UpdateTimestampListenerTest.java | 6 +- .../PersonNameStringConverterTest.java | 1 + .../SuggestionProvidersTest.java | 1 + .../BackupManagerDiscardedTest.java | 12 +- .../autosaveandbackup/BackupManagerTest.java | 12 +- .../jabref/gui/edit/CopyMoreActionTest.java | 28 +- .../gui/edit/ManageKeywordsViewModelTest.java | 2 +- .../gui/entryeditor/CommentsTabTest.java | 9 +- .../jabref/gui/entryeditor/SciteTabTest.java | 14 +- .../entryeditor/SciteTabViewModelTest.java | 10 +- .../CitationsRelationsTabViewModelTest.java | 24 +- .../exporter/ExportToClipboardActionTest.java | 12 +- .../gui/exporter/SaveDatabaseActionTest.java | 20 +- .../AutoSetFileLinksUtilTest.java | 10 +- .../gui/externalfiles/ImportHandlerTest.java | 36 +- .../UnlinkedFilesCrawlerTest.java | 8 +- .../ExternalFileTypesTest.java | 22 +- .../fieldeditors/LinkedFileViewModelTest.java | 17 +- .../LinkedFilesEditorViewModelTest.java | 16 +- .../gui/groups/GroupDialogViewModelTest.java | 26 +- .../gui/groups/GroupNodeViewModelTest.java | 18 +- .../gui/groups/GroupTreeViewModelTest.java | 30 +- .../gui/importer/NewEntryActionTest.java | 8 +- .../fetcher/WebSearchPaneViewModelTest.java | 8 +- .../gui/keyboard/KeyBindingsTabModelTest.java | 10 +- .../ConstantsPropertiesViewModelTest.java | 10 +- .../DownloadLinkedFileActionTest.java | 18 +- .../maintable/MainTableColumnModelTest.java | 4 +- .../mergeentries/FieldRowViewModelTest.java | 2 +- .../CustomEntryTypesTabViewModelTest.java | 14 +- .../ExternalFileTypesTabViewModelTest.java | 6 +- .../JournalAbbreviationsViewModelTabTest.java | 4 +- .../keybindings/KeyBindingViewModelTest.java | 11 +- .../gui/preview/CopyCitationActionTest.java | 2 +- .../org/jabref/gui/push/PushToEmacsTest.java | 15 +- .../jabref/gui/push/PushToTeXworksTest.java | 13 +- .../gui/search/GlobalSearchBarTest.java | 12 +- .../gui/sidepane/SidePaneViewModelTest.java | 18 +- .../ManageStudyDefinitionViewModelTest.java | 2 +- .../jabref/gui/theme/ThemeManagerTest.java | 2 +- .../gui/util/OpenConsoleActionTest.java | 4 +- .../org/jabref/http/server/ServerTest.java | 28 +- .../MVStoreChatHistoryStorageTest.java | 4 +- ...toreFullyIngestedDocumentsTrackerTest.java | 4 +- .../MVStoreSummariesStorageTest.java | 4 +- .../summarization/SummariesStorageTest.java | 2 +- .../logic/bibtex/BibEntryWriterTest.java | 2 +- .../jabref/logic/bibtex/FieldWriterTest.java | 2 +- .../logic/cleanup/CleanupWorkerTest.java | 3 +- .../jabref/logic/cleanup/ISSNCleanupTest.java | 3 +- .../logic/cleanup/MoveFilesCleanupTest.java | 2 +- .../RemoveLinksToNotExistentFilesTest.java | 2 +- .../logic/cleanup/RenamePdfCleanupTest.java | 2 +- .../org/jabref/logic/crawler/CrawlerTest.java | 8 +- .../StudyCatalogToFetcherConverterTest.java | 12 +- .../logic/crawler/StudyRepositoryTest.java | 20 +- .../exporter/BibtexDatabaseWriterTest.java | 2 +- .../EmbeddedBibFilePdfExporterTest.java | 2 +- .../exporter/EndnoteXmlExporterFilesTest.java | 2 +- .../exporter/EndnoteXmlExporterTest.java | 2 +- .../jabref/logic/exporter/ExporterTest.java | 10 +- .../exporter/MetaDataSerializerTest.java | 2 +- .../exporter/ModsExportFormatFilesTest.java | 2 +- .../logic/exporter/XmpPdfExporterTest.java | 2 +- .../logic/importer/OpenDatabaseTest.java | 2 +- .../logic/importer/WebFetchersTest.java | 2 +- .../importer/fetcher/IssnFetcherTest.java | 2 +- .../fetcher/LibraryOfCongressTest.java | 2 +- .../importer/fetcher/MrDLibFetcherTest.java | 1 - .../importer/fileformat/BibtexParserTest.java | 2 +- .../logic/integrity/IntegrityCheckTest.java | 2 +- .../logic/layout/format/RisKeywordsTest.java | 2 +- .../pdf/EntryAnnotationImporterTest.java | 2 +- .../JabRefGuiPreferencesTest.java} | 8 +- ...est.java => RemoteGuiPreferencesTest.java} | 2 +- .../jabref/logic/remote/RemoteSetupTest.java | 2 +- .../logic/search/DatabaseSearcherTest.java | 6 +- .../DatabaseSearcherWithBibFilesTest.java | 6 +- .../indexing/LinkedFilesIndexerTest.java | 12 +- .../logic/util/io/BackupFileUtilTest.java | 10 +- .../logic/util/io/FileNameUniquenessTest.java | 19 +- .../jabref/logic/util/io/FileUtilTest.java | 4 +- .../GuiPreferencesMigrationsTest.java | 223 +++ .../migrations/PreferencesMigrationsTest.java | 222 --- .../database/BibDatabaseContextTest.java | 11 +- .../styletester/StyleTesterMain.java | 4 +- ...er_science_bibliographies_empty_result.xml | 10 +- 572 files changed, 5406 insertions(+), 5192 deletions(-) rename src/main/java/org/jabref/{preferences/GuiPreferences.java => gui/CoreGuiPreferences.java} (61%) delete mode 100644 src/main/java/org/jabref/gui/FallbackExceptionHandler.java rename src/main/java/org/jabref/{preferences => gui}/WorkspacePreferences.java (99%) rename src/main/java/org/jabref/{logic => gui}/ai/chatting/chathistory/ChatHistoryService.java (93%) delete mode 100644 src/main/java/org/jabref/gui/desktop/JabRefDesktop.java rename src/main/java/org/jabref/{preferences => gui/externalfiles}/UnlinkedFilesDialogPreferences.java (94%) rename src/main/java/org/jabref/{preferences => gui/frame}/ExternalApplicationsPreferences.java (88%) rename src/main/java/org/jabref/{preferences => gui/frame}/SidePanePreferences.java (98%) rename src/main/java/org/jabref/{preferences => gui/mergeentries}/MergeDialogPreferences.java (88%) create mode 100644 src/main/java/org/jabref/gui/preferences/GuiPreferences.java create mode 100644 src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java rename src/main/java/org/jabref/{ => gui}/preferences/PreferencesFilter.java (93%) rename src/main/java/org/jabref/{preferences => gui/preview}/PreviewPreferences.java (99%) rename src/main/java/org/jabref/{preferences => gui/push}/PushToApplicationPreferences.java (98%) create mode 100644 src/main/java/org/jabref/http/server/PreferencesFactory.java rename src/main/java/org/jabref/{preferences => logic}/FilePreferences.java (92%) rename src/main/java/org/jabref/{preferences => logic}/InternalPreferences.java (98%) rename src/main/java/org/jabref/{preferences => logic}/LibraryPreferences.java (98%) rename src/main/java/org/jabref/{preferences => logic}/ai/AiPreferences.java (99%) rename src/main/java/org/jabref/{preferences => logic/cleanup}/CleanupPreferences.java (92%) rename src/main/java/org/jabref/{preferences => logic/exporter}/ExportPreferences.java (96%) rename src/main/java/org/jabref/{gui => logic}/externalfiles/DateRange.java (94%) rename src/main/java/org/jabref/{gui => logic}/externalfiles/ExternalFileSorter.java (93%) rename src/main/java/org/jabref/{preferences => logic/importer/fetcher}/MrDlibPreferences.java (97%) create mode 100644 src/main/java/org/jabref/logic/os/OS.java rename src/main/java/org/jabref/{gui/autocompleter => logic/preferences}/AutoCompleteFirstNameMode.java (95%) rename src/main/java/org/jabref/{preferences/PreferencesService.java => logic/preferences/CliPreferences.java} (66%) rename src/main/java/org/jabref/{preferences/JabRefPreferences.java => logic/preferences/JabRefCliPreferences.java} (60%) create mode 100644 src/main/java/org/jabref/logic/preferences/LastFilesOpenedPreferences.java rename src/main/java/org/jabref/{model => logic}/search/LuceneIndexer.java (88%) rename src/main/java/org/jabref/{gui => logic}/search/SearchDisplayMode.java (63%) rename src/main/java/org/jabref/{preferences => logic/search}/SearchPreferences.java (98%) rename src/main/java/org/jabref/{gui => logic}/util/BackgroundTask.java (83%) rename src/main/java/org/jabref/{gui => logic}/util/CurrentThreadTaskExecutor.java (93%) create mode 100644 src/main/java/org/jabref/logic/util/Directories.java create mode 100644 src/main/java/org/jabref/logic/util/FallbackExceptionHandler.java create mode 100644 src/main/java/org/jabref/logic/util/NotificationService.java delete mode 100644 src/main/java/org/jabref/logic/util/OS.java rename src/main/java/org/jabref/{gui => logic}/util/TaskExecutor.java (73%) rename src/main/java/org/jabref/{preferences => model}/ai/AiProvider.java (92%) rename src/main/java/org/jabref/{preferences => model}/ai/EmbeddingModel.java (99%) rename src/main/java/org/jabref/{preferences => model/entry}/BibEntryPreferences.java (95%) delete mode 100644 src/main/java/org/jabref/preferences/PreferenceServiceFactory.java rename src/test/java/org/jabref/{preferences/JabRefPreferencesTest.java => logic/preferences/JabRefGuiPreferencesTest.java} (76%) rename src/test/java/org/jabref/logic/remote/{RemotePreferencesTest.java => RemoteGuiPreferencesTest.java} (97%) create mode 100644 src/test/java/org/jabref/migrations/GuiPreferencesMigrationsTest.java delete mode 100644 src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java diff --git a/docs/code-howtos/index.md b/docs/code-howtos/index.md index e8b49cc188f..932dcb04539 100644 --- a/docs/code-howtos/index.md +++ b/docs/code-howtos/index.md @@ -47,7 +47,7 @@ JabRef stores files relative to one of [multiple possible directories](https://d The convert the relative path to an absolute one, there is the `find` method in `FileUtil`: ```java -org.jabref.logic.util.io.FileUtil.find(org.jabref.model.database.BibDatabaseContext, java.lang.String, org.jabref.preferences.FilePreferences) +org.jabref.logic.util.io.FileUtil.find(org.jabref.model.database.BibDatabaseContext, java.lang.String, org.jabref.logic.FilePreferences) ``` `String path` Can be the files name or a relative path to it. The Preferences should only be directly accessed in the GUI. For the usage in logic pass them as parameter @@ -59,7 +59,7 @@ When adding a file to a library, the path should be stored relative to "the best This is implemented in `FileUtil`: ```java -org.jabref.logic.util.io.FileUtil.relativize(java.nio.file.Path, org.jabref.model.database.BibDatabaseContext, org.jabref.preferences.FilePreferences) +org.jabref.logic.util.io.FileUtil.relativize(java.nio.file.Path, org.jabref.model.database.BibDatabaseContext, org.jabref.logic.FilePreferences) ``` ## Setting a Directory for a .bib File diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index d2e79b24c63..e553c87ceec 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -16,7 +16,9 @@ import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.layout.format.HTMLChars; import org.jabref.logic.layout.format.LatexToUnicodeFormatter; -import org.jabref.logic.util.OS; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.preferences.JabRefCliPreferences; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -29,8 +31,6 @@ import org.jabref.model.groups.KeywordGroup; import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; -import org.jabref.preferences.JabRefPreferences; -import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; import org.openjdk.jmh.Main; @@ -52,7 +52,7 @@ public class Benchmarks { @Setup public void init() throws Exception { - Injector.setModelOrService(PreferencesService.class, JabRefPreferences.getInstance()); + Injector.setModelOrService(CliPreferences.class, JabRefCliPreferences.getInstance()); Random randomizer = new Random(); for (int i = 0; i < 1000; i++) { @@ -89,8 +89,8 @@ private StringWriter getOutputWriter() throws IOException { @Benchmark public ParserResult parse() throws IOException { - PreferencesService preferencesService = Injector.instantiateModelOrService(PreferencesService.class); - BibtexParser parser = new BibtexParser(preferencesService.getImportFormatPreferences()); + CliPreferences preferences = Injector.instantiateModelOrService(CliPreferences.class); + BibtexParser parser = new BibtexParser(preferences.getImportFormatPreferences()); return parser.parse(new StringReader(bibtexString)); } diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index fa891388f98..1d3cb9cb3e1 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -14,6 +14,8 @@ import org.jabref.cli.ArgumentProcessor; import org.jabref.cli.JabRefCLI; import org.jabref.gui.JabRefGUI; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.preferences.JabRefGuiPreferences; import org.jabref.gui.util.DefaultDirectoryMonitor; import org.jabref.gui.util.DefaultFileUpdateMonitor; import org.jabref.logic.UiCommand; @@ -24,18 +26,17 @@ import org.jabref.logic.net.ProxyRegisterer; import org.jabref.logic.net.ssl.SSLPreferences; import org.jabref.logic.net.ssl.TrustStoreManager; +import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.remote.RemotePreferences; import org.jabref.logic.remote.client.RemoteClient; import org.jabref.logic.util.BuildInfo; +import org.jabref.logic.util.Directories; import org.jabref.logic.util.HeadlessExecutorService; -import org.jabref.logic.util.OS; import org.jabref.migrations.PreferencesMigrations; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.FileUpdateMonitor; -import org.jabref.preferences.JabRefPreferences; -import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; import org.apache.commons.cli.ParseException; @@ -61,8 +62,9 @@ public static void main(String[] args) { Injector.setModelOrService(BuildInfo.class, new BuildInfo()); // Initialize preferences - final JabRefPreferences preferences = JabRefPreferences.getInstance(); - Injector.setModelOrService(PreferencesService.class, preferences); + final JabRefGuiPreferences preferences = JabRefGuiPreferences.getInstance(); + Injector.setModelOrService(CliPreferences.class, preferences); + Injector.setModelOrService(GuiPreferences.class, preferences); // Early exit in case another instance is already running if (!handleMultipleAppInstances(args, preferences.getRemotePreferences())) { @@ -95,6 +97,7 @@ public static void main(String[] args) { args, ArgumentProcessor.Mode.INITIAL_START, preferences, + preferences, fileUpdateMonitor, entryTypesManager); argumentProcessor.processArguments(); @@ -137,7 +140,7 @@ private static void initLogging(String[] args) { } // addLogToDisk - Path directory = OS.getNativeDesktop().getLogDirectory(); + Path directory = Directories.getLogDirectory(); try { Files.createDirectories(directory); } catch (IOException e) { @@ -204,7 +207,7 @@ private static void configureSSL(SSLPreferences sslPreferences) { } private static void clearOldSearchIndices() { - Path currentIndexPath = OS.getNativeDesktop().getFulltextIndexBaseDirectory(); + Path currentIndexPath = Directories.getFulltextIndexBaseDirectory(); Path appData = currentIndexPath.getParent(); try { diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 6d4649c7739..64edddac7a4 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -14,7 +14,8 @@ import java.util.prefs.BackingStoreException; import org.jabref.gui.externalfiles.AutoSetFileLinksUtil; -import org.jabref.gui.util.CurrentThreadTaskExecutor; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.logic.FilePreferences; import org.jabref.logic.JabRefException; import org.jabref.logic.UiCommand; import org.jabref.logic.bibtex.FieldPreferences; @@ -41,9 +42,12 @@ import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.search.DatabaseSearcher; +import org.jabref.logic.search.SearchPreferences; import org.jabref.logic.shared.prefs.SharedDatabasePreferences; -import org.jabref.logic.util.OS; +import org.jabref.logic.util.CurrentThreadTaskExecutor; import org.jabref.logic.util.io.FileUtil; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabase; @@ -55,9 +59,6 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; -import org.jabref.preferences.FilePreferences; -import org.jabref.preferences.PreferencesService; -import org.jabref.preferences.SearchPreferences; import com.airhacks.afterburner.injection.Injector; import com.google.common.base.Throwables; @@ -73,7 +74,8 @@ public enum Mode { INITIAL_START, REMOTE_START } private final Mode startupMode; - private final PreferencesService preferencesService; + private final CliPreferences cliPreferences; + private final GuiPreferences guiPreferences; private final FileUpdateMonitor fileUpdateMonitor; private final BibEntryTypesManager entryTypesManager; @@ -83,16 +85,20 @@ public enum Mode { INITIAL_START, REMOTE_START } /** * First call the constructor, then call {@link #processArguments()}. * Afterward, you can access the {@link #getUiCommands()}. + * + * @implNote both cli and gui preferences are passed to make the dependency to GUI parts explicit */ public ArgumentProcessor(String[] args, Mode startupMode, - PreferencesService preferencesService, + CliPreferences cliPreferences, + GuiPreferences guiPreferences, FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager) throws org.apache.commons.cli.ParseException { this.cli = new JabRefCLI(args); this.startupMode = startupMode; - this.preferencesService = preferencesService; + this.cliPreferences = cliPreferences; + this.guiPreferences = guiPreferences; this.fileUpdateMonitor = fileUpdateMonitor; this.entryTypesManager = entryTypesManager; } @@ -166,9 +172,9 @@ private Optional importFile(String importArguments) { private Optional importFile(Path file, String importFormat) { try { ImportFormatReader importFormatReader = new ImportFormatReader( - preferencesService.getImporterPreferences(), - preferencesService.getImportFormatPreferences(), - preferencesService.getCitationKeyPatternPreferences(), + cliPreferences.getImporterPreferences(), + cliPreferences.getImportFormatPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), fileUpdateMonitor ); @@ -200,7 +206,7 @@ public void processArguments() { } if ((startupMode == Mode.INITIAL_START) && cli.isHelp()) { - JabRefCLI.printUsage(preferencesService); + JabRefCLI.printUsage(cliPreferences); guiNeeded = false; return; } @@ -249,11 +255,11 @@ public void processArguments() { if (!loaded.isEmpty()) { writeMetadataToPdf(loaded, cli.getWriteMetadataToPdf(), - preferencesService.getXmpPreferences(), - preferencesService.getFilePreferences(), - preferencesService.getLibraryPreferences().getDefaultBibDatabaseMode(), + cliPreferences.getXmpPreferences(), + cliPreferences.getFilePreferences(), + cliPreferences.getLibraryPreferences().getDefaultBibDatabaseMode(), Injector.instantiateModelOrService(BibEntryTypesManager.class), - preferencesService.getFieldPreferences(), + cliPreferences.getFieldPreferences(), Injector.instantiateModelOrService(JournalAbbreviationRepository.class), cli.isWriteXmpToPdf() || cli.isWriteMetadataToPdf(), cli.isEmbedBibFileInPdf() || cli.isWriteMetadataToPdf()); @@ -271,7 +277,7 @@ public void processArguments() { if (cli.isPreferencesExport()) { try { - preferencesService.exportPreferences(Path.of(cli.getPreferencesExport())); + cliPreferences.exportPreferences(Path.of(cli.getPreferencesExport())); } catch (JabRefException ex) { LOGGER.error("Cannot export preferences", ex); } @@ -456,13 +462,13 @@ private boolean exportMatches(List loaded) { ParserResult pr = loaded.getLast(); BibDatabaseContext databaseContext = pr.getDatabaseContext(); - SearchPreferences searchPreferences = preferencesService.getSearchPreferences(); + SearchPreferences searchPreferences = cliPreferences.getSearchPreferences(); SearchQuery query = new SearchQuery(searchTerm, searchPreferences.getSearchFlags()); List matches; try { // extract current thread task executor from luceneManager - matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), preferencesService.getFilePreferences()).getMatches(); + matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), cliPreferences.getFilePreferences()).getMatches(); } catch (IOException e) { LOGGER.error("Error occurred when searching", e); return false; @@ -494,7 +500,7 @@ private boolean exportMatches(List loaded) { } else { // export new database ExporterFactory exporterFactory = ExporterFactory.create( - preferencesService, + cliPreferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); Optional exporter = exporterFactory.getExporterByName(formatName); if (exporter.isEmpty()) { @@ -554,7 +560,7 @@ private List importAndOpenFiles() { try { pr = OpenDatabase.loadDatabase( Path.of(aLeftOver), - preferencesService.getImportFormatPreferences(), + cliPreferences.getImportFormatPreferences(), fileUpdateMonitor); // In contrast to org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed, we do not execute OpenDatabaseAction.performPostOpenActions(result, dialogService); } catch (IOException ex) { @@ -594,7 +600,7 @@ private List importAndOpenFiles() { } if (!cli.isBlank() && cli.isBibtexImport()) { - importBibtexToOpenBase(cli.getBibtexImport(), preferencesService.getImportFormatPreferences()).ifPresent(loaded::add); + importBibtexToOpenBase(cli.getBibtexImport(), cliPreferences.getImportFormatPreferences()).ifPresent(loaded::add); } return loaded; @@ -630,12 +636,12 @@ private void saveDatabase(BibDatabase newBase, String subName) { try (AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), StandardCharsets.UTF_8)) { BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withReformatOnSave(preferencesService.getLibraryPreferences().shouldAlwaysReformatOnSave()); + .withReformatOnSave(cliPreferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter( bibWriter, saveConfiguration, - preferencesService.getFieldPreferences(), - preferencesService.getCitationKeyPatternPreferences(), + cliPreferences.getFieldPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), entryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); @@ -671,10 +677,10 @@ private void exportFile(List loaded, String[] data) { BibDatabaseContext databaseContext = parserResult.getDatabaseContext(); databaseContext.setDatabasePath(path); List fileDirForDatabase = databaseContext - .getFileDirectories(preferencesService.getFilePreferences()); + .getFileDirectories(cliPreferences.getFilePreferences()); System.out.println(Localization.lang("Exporting %0", data[0])); ExporterFactory exporterFactory = ExporterFactory.create( - preferencesService, + cliPreferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); Optional exporter = exporterFactory.getExporterByName(data[1]); if (exporter.isEmpty()) { @@ -697,8 +703,8 @@ private void exportFile(List loaded, String[] data) { private void importPreferences() { try { - preferencesService.importPreferences(Path.of(cli.getPreferencesImport())); - Injector.setModelOrService(BibEntryTypesManager.class, preferencesService.getCustomEntryTypesRepository()); + cliPreferences.importPreferences(Path.of(cli.getPreferencesImport())); + Injector.setModelOrService(BibEntryTypesManager.class, cliPreferences.getCustomEntryTypesRepository()); } catch (JabRefException ex) { LOGGER.error("Cannot import preferences", ex); } @@ -708,7 +714,7 @@ private void resetPreferences(String value) { if ("all".equals(value.trim())) { try { System.out.println(Localization.lang("Setting all preferences to default values.")); - preferencesService.clear(); + cliPreferences.clear(); new SharedDatabasePreferences().clear(); } catch (BackingStoreException e) { System.err.println(Localization.lang("Unable to clear preferences.")); @@ -718,7 +724,7 @@ private void resetPreferences(String value) { String[] keys = value.split(","); for (String key : keys) { try { - preferencesService.deleteKey(key.trim()); + cliPreferences.deleteKey(key.trim()); System.out.println(Localization.lang("Resetting preference key '%0'", key.trim())); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); @@ -734,11 +740,12 @@ private void automaticallySetFileLinks(List loaded) { parserResult.getDatabaseContext().getDatabasePath() .map(Path::getFileName) .map(Path::toString).orElse("UNKNOWN")); - + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( parserResult.getDatabaseContext(), - preferencesService.getFilePreferences(), - preferencesService.getAutoLinkPreferences()); + guiPreferences.getExternalApplicationsPreferences(), + cliPreferences.getFilePreferences(), + cliPreferences.getAutoLinkPreferences()); util.linkAssociatedFiles(database.getEntries(), (linkedFile, bibEntry) -> bibEntry.addFile(linkedFile)); } @@ -752,7 +759,7 @@ private void regenerateCitationKeys(List loaded) { CitationKeyGenerator keyGenerator = new CitationKeyGenerator( parserResult.getDatabaseContext(), - preferencesService.getCitationKeyPatternPreferences()); + cliPreferences.getCitationKeyPatternPreferences()); for (BibEntry entry : database.getEntries()) { keyGenerator.generateAndSetKey(entry); } @@ -777,8 +784,8 @@ private Optional fetch(String fetchCommand) { String query = split[1]; Set fetchers = WebFetchers.getSearchBasedFetchers( - preferencesService.getImportFormatPreferences(), - preferencesService.getImporterPreferences()); + cliPreferences.getImportFormatPreferences(), + cliPreferences.getImporterPreferences()); Optional selectedFetcher = fetchers.stream() .filter(fetcher -> fetcher.getName().equalsIgnoreCase(engine)) .findFirst(); diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index 4d36f61158b..0cd74a0bba4 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -8,12 +8,12 @@ import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.BuildInfo; -import org.jabref.logic.util.OS; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; -import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; import org.apache.commons.cli.CommandLine; @@ -309,13 +309,13 @@ public void displayVersion() { System.out.println(getVersionInfo()); } - public static void printUsage(PreferencesService preferencesService) { + public static void printUsage(CliPreferences preferences) { String header = ""; ImportFormatReader importFormatReader = new ImportFormatReader( - preferencesService.getImporterPreferences(), - preferencesService.getImportFormatPreferences(), - preferencesService.getCitationKeyPatternPreferences(), + preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), + preferences.getCitationKeyPatternPreferences(), new DummyFileUpdateMonitor() ); List> importFormats = importFormatReader @@ -326,7 +326,7 @@ public static void printUsage(PreferencesService preferencesService) { String importFormatsList = "%s:%n%s%n".formatted(importFormatsIntro, alignStringTable(importFormats)); ExporterFactory exporterFactory = ExporterFactory.create( - preferencesService, + preferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); List> exportFormats = exporterFactory .getExporters().stream() diff --git a/src/main/java/org/jabref/gui/ClipBoardManager.java b/src/main/java/org/jabref/gui/ClipBoardManager.java index f135aeab52e..3e1455908e0 100644 --- a/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -18,11 +18,11 @@ import org.jabref.architecture.AllowedToUseAwt; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.BibtexString; -import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; @@ -167,11 +167,11 @@ public void setContent(List entries, BibEntryTypesManager entryTypesMa } private String serializeEntries(List entries, BibEntryTypesManager entryTypesManager) throws IOException { - PreferencesService preferencesService = Injector.instantiateModelOrService(PreferencesService.class); + CliPreferences preferences = Injector.instantiateModelOrService(CliPreferences.class); // BibEntry is not Java serializable. Thus, we need to do the serialization manually // At reading of the clipboard in JabRef, we parse the plain string in all cases, so we don't need to flag we put BibEntries here // Furthermore, storing a string also enables other applications to work with the data - BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager); + BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferences.getFieldPreferences()), entryTypesManager); return writer.serializeAll(entries, BibDatabaseMode.BIBTEX); } } diff --git a/src/main/java/org/jabref/preferences/GuiPreferences.java b/src/main/java/org/jabref/gui/CoreGuiPreferences.java similarity index 61% rename from src/main/java/org/jabref/preferences/GuiPreferences.java rename to src/main/java/org/jabref/gui/CoreGuiPreferences.java index af79cc49a99..23212a62ed1 100644 --- a/src/main/java/org/jabref/preferences/GuiPreferences.java +++ b/src/main/java/org/jabref/gui/CoreGuiPreferences.java @@ -1,22 +1,13 @@ -package org.jabref.preferences; - -import java.nio.file.Path; -import java.util.List; +package org.jabref.gui; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import org.jabref.logic.util.io.FileHistory; -public class GuiPreferences { +public class CoreGuiPreferences { private final DoubleProperty positionX; private final DoubleProperty positionY; private final DoubleProperty sizeX; @@ -26,36 +17,22 @@ public class GuiPreferences { private final DoubleProperty sidePaneWidth; - // the last libraries that were open when jabref closes and should be reopened on startup - private final ObservableList lastFilesOpened; - - private final ObjectProperty lastFocusedFile; - - // observable list last files opened in the file menu - private final FileHistory fileHistory; - private final StringProperty lastSelectedIdBasedFetcher; - public GuiPreferences(double positionX, - double positionY, - double sizeX, - double sizeY, - boolean windowMaximised, - List lastFilesOpened, - Path lastFocusedFile, - FileHistory fileHistory, - String lastSelectedIdBasedFetcher, - double sidePaneWidth) { + public CoreGuiPreferences(double positionX, + double positionY, + double sizeX, + double sizeY, + boolean windowMaximised, + String lastSelectedIdBasedFetcher, + double sidePaneWidth) { this.positionX = new SimpleDoubleProperty(positionX); this.positionY = new SimpleDoubleProperty(positionY); this.sizeX = new SimpleDoubleProperty(sizeX); this.sizeY = new SimpleDoubleProperty(sizeY); this.windowMaximised = new SimpleBooleanProperty(windowMaximised); - this.lastFilesOpened = FXCollections.observableArrayList(lastFilesOpened); - this.lastFocusedFile = new SimpleObjectProperty<>(lastFocusedFile); this.lastSelectedIdBasedFetcher = new SimpleStringProperty(lastSelectedIdBasedFetcher); this.sidePaneWidth = new SimpleDoubleProperty(sidePaneWidth); - this.fileHistory = fileHistory; } public double getPositionX() { @@ -118,30 +95,6 @@ public void setWindowMaximised(boolean windowMaximised) { this.windowMaximised.set(windowMaximised); } - public ObservableList getLastFilesOpened() { - return lastFilesOpened; - } - - public void setLastFilesOpened(List files) { - lastFilesOpened.setAll(files); - } - - public Path getLastFocusedFile() { - return lastFocusedFile.get(); - } - - public ObjectProperty lastFocusedFileProperty() { - return lastFocusedFile; - } - - public void setLastFocusedFile(Path lastFocusedFile) { - this.lastFocusedFile.set(lastFocusedFile); - } - - public FileHistory getFileHistory() { - return fileHistory; - } - public String getLastSelectedIdBasedFetcher() { return lastSelectedIdBasedFetcher.get(); } diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index 03c505068a8..7248be47511 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -22,6 +22,7 @@ import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.util.NotificationService; import org.controlsfx.control.textfield.CustomPasswordField; import org.controlsfx.dialog.ProgressDialog; @@ -29,7 +30,7 @@ /** * This interface provides methods to create dialogs and show them to the user. */ -public interface DialogService { +public interface DialogService extends NotificationService { /** * This will create and display new {@link ChoiceDialog} of type T with a default choice and a collection of possible choices @@ -251,13 +252,6 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String */ Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager); - /** - * Notify the user in a non-blocking way (i.e., in form of toast in a snackbar). - * - * @param message the message to show. - */ - void notify(String message); - /** * Shows a new file save dialog. The method doesn't return until the * displayed file save dialog is dismissed. The return value specifies the diff --git a/src/main/java/org/jabref/gui/FallbackExceptionHandler.java b/src/main/java/org/jabref/gui/FallbackExceptionHandler.java deleted file mode 100644 index 2f42d2a00a1..00000000000 --- a/src/main/java/org/jabref/gui/FallbackExceptionHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.jabref.gui; - -import org.jabref.gui.util.UiTaskExecutor; - -import com.airhacks.afterburner.injection.Injector; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Catch and log any unhandled exceptions. - */ -public class FallbackExceptionHandler implements Thread.UncaughtExceptionHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(FallbackExceptionHandler.class); - - public static void installExceptionHandler() { - Thread.setDefaultUncaughtExceptionHandler(new FallbackExceptionHandler()); - } - - @Override - public void uncaughtException(Thread thread, Throwable exception) { - LOGGER.error("Uncaught exception occurred in {}", thread, exception); - UiTaskExecutor.runInJavaFXThread(() -> { - DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); - dialogService.showErrorDialogAndWait("Uncaught exception occurred in " + thread, exception); - }); - } -} diff --git a/src/main/java/org/jabref/gui/JabRefDialogService.java b/src/main/java/org/jabref/gui/JabRefDialogService.java index 9d907840e43..c3e3ce962d9 100644 --- a/src/main/java/org/jabref/gui/JabRefDialogService.java +++ b/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -41,7 +41,7 @@ import org.jabref.gui.help.ErrorConsoleAction; import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.BaseWindow; import org.jabref.gui.util.DirectoryDialogConfiguration; @@ -392,7 +392,7 @@ public Optional showBackgroundProgressDialogAndWait(String title TaskProgressView> taskProgressView = new TaskProgressView<>(); EasyBind.bindContent(taskProgressView.getTasks(), stateManager.getRunningBackgroundTasks()); taskProgressView.setRetainTasks(false); - taskProgressView.setGraphicFactory(BackgroundTask::getIcon); + taskProgressView.setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); Label message = new Label(content); diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 159d8bf5bea..4ba82c1fed5 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -14,16 +14,17 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; +import org.jabref.gui.ai.chatting.chathistory.ChatHistoryService; import org.jabref.gui.frame.JabRefFrame; import org.jabref.gui.help.VersionWorker; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.keyboard.TextInputKeyBindings; import org.jabref.gui.openoffice.OOBibBaseConnect; +import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.remote.CLIMessageHandler; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.undo.CountingUndoManager; -import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.UiCommand; import org.jabref.logic.ai.AiService; @@ -32,14 +33,14 @@ import org.jabref.logic.remote.RemotePreferences; import org.jabref.logic.remote.server.RemoteListenerServerManager; import org.jabref.logic.util.BuildInfo; +import org.jabref.logic.util.FallbackExceptionHandler; import org.jabref.logic.util.HeadlessExecutorService; +import org.jabref.logic.util.TaskExecutor; import org.jabref.logic.util.WebViewStore; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.FileUpdateMonitor; -import org.jabref.preferences.GuiPreferences; -import org.jabref.preferences.JabRefPreferences; import com.airhacks.afterburner.injection.Injector; import com.tobiasdiez.easybind.EasyBind; @@ -55,11 +56,12 @@ public class JabRefGUI extends Application { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefGUI.class); private static List uiCommands; - private static JabRefPreferences preferencesService; + private static GuiPreferences preferences; private static FileUpdateMonitor fileUpdateMonitor; // AI Service handles chat messages etc. Therefore, it is tightly coupled to the GUI. private static AiService aiService; + private static ChatHistoryService chatHistoryService; private static StateManager stateManager; private static ThemeManager themeManager; @@ -74,10 +76,10 @@ public class JabRefGUI extends Application { private Stage mainStage; public static void setup(List uiCommands, - JabRefPreferences preferencesService, + GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor) { JabRefGUI.uiCommands = uiCommands; - JabRefGUI.preferencesService = preferencesService; + JabRefGUI.preferences = preferences; JabRefGUI.fileUpdateMonitor = fileUpdateMonitor; } @@ -85,7 +87,12 @@ public static void setup(List uiCommands, public void start(Stage stage) { this.mainStage = stage; - FallbackExceptionHandler.installExceptionHandler(); + FallbackExceptionHandler.installExceptionHandler((exception, thread) -> { + UiTaskExecutor.runInJavaFXThread(() -> { + DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); + dialogService.showErrorDialogAndWait("Uncaught exception occurred in " + thread, exception); + }); + }); initialize(); @@ -93,8 +100,9 @@ public void start(Stage stage) { mainStage, dialogService, fileUpdateMonitor, - preferencesService, + preferences, aiService, + chatHistoryService, stateManager, countingUndoManager, Injector.instantiateModelOrService(BibEntryTypesManager.class), @@ -113,12 +121,12 @@ public void start(Stage stage) { } BuildInfo buildInfo = Injector.instantiateModelOrService(BuildInfo.class); - EasyBind.subscribe(preferencesService.getInternalPreferences().versionCheckEnabledProperty(), enabled -> { + EasyBind.subscribe(preferences.getInternalPreferences().versionCheckEnabledProperty(), enabled -> { if (enabled) { new VersionWorker(buildInfo.version, dialogService, taskExecutor, - preferencesService) + preferences) .checkForNewVersionDelayed(); } }); @@ -135,10 +143,10 @@ public void initialize() { JabRefGUI.stateManager = new StateManager(); Injector.setModelOrService(StateManager.class, stateManager); - Injector.setModelOrService(KeyBindingRepository.class, preferencesService.getKeyBindingRepository()); + Injector.setModelOrService(KeyBindingRepository.class, preferences.getKeyBindingRepository()); JabRefGUI.themeManager = new ThemeManager( - preferencesService.getWorkspacePreferences(), + preferences.getWorkspacePreferences(), fileUpdateMonitor, Runnable::run); Injector.setModelOrService(ThemeManager.class, themeManager); @@ -147,6 +155,7 @@ public void initialize() { Injector.setModelOrService(UndoManager.class, countingUndoManager); Injector.setModelOrService(CountingUndoManager.class, countingUndoManager); + // our Default task executor is the UITaskExecutor which can use the fx thread JabRefGUI.taskExecutor = new UiTaskExecutor(); Injector.setModelOrService(TaskExecutor.class, taskExecutor); @@ -157,23 +166,28 @@ public void initialize() { Injector.setModelOrService(ClipBoardManager.class, clipBoardManager); JabRefGUI.aiService = new AiService( - preferencesService.getAiPreferences(), - preferencesService.getFilePreferences(), - preferencesService.getCitationKeyPatternPreferences(), + preferences.getAiPreferences(), + preferences.getFilePreferences(), + preferences.getCitationKeyPatternPreferences(), dialogService, taskExecutor); Injector.setModelOrService(AiService.class, aiService); + + JabRefGUI.chatHistoryService = new ChatHistoryService( + preferences.getCitationKeyPatternPreferences(), + dialogService); + Injector.setModelOrService(ChatHistoryService.class, chatHistoryService); } private void setupProxy() { - if (!preferencesService.getProxyPreferences().shouldUseProxy() - || !preferencesService.getProxyPreferences().shouldUseAuthentication()) { + if (!preferences.getProxyPreferences().shouldUseProxy() + || !preferences.getProxyPreferences().shouldUseAuthentication()) { return; } - if (preferencesService.getProxyPreferences().shouldPersistPassword() - && StringUtil.isNotBlank(preferencesService.getProxyPreferences().getPassword())) { - ProxyRegisterer.register(preferencesService.getProxyPreferences()); + if (preferences.getProxyPreferences().shouldPersistPassword() + && StringUtil.isNotBlank(preferences.getProxyPreferences().getPassword())) { + ProxyRegisterer.register(preferences.getProxyPreferences()); return; } @@ -183,8 +197,8 @@ private void setupProxy() { Localization.lang("Password")); if (password.isPresent()) { - preferencesService.getProxyPreferences().setPassword(password.get()); - ProxyRegisterer.register(preferencesService.getProxyPreferences()); + preferences.getProxyPreferences().setPassword(password.get()); + ProxyRegisterer.register(preferences.getProxyPreferences()); } else { LOGGER.warn("No proxy password specified"); } @@ -193,24 +207,24 @@ private void setupProxy() { private void openWindow() { LOGGER.debug("Initializing frame"); - GuiPreferences guiPreferences = preferencesService.getGuiPreferences(); - LOGGER.debug("Reading from prefs: isMaximized {}", guiPreferences.isWindowMaximised()); + CoreGuiPreferences coreGuiPreferences = preferences.getGuiPreferences(); + LOGGER.debug("Reading from prefs: isMaximized {}", coreGuiPreferences.isWindowMaximised()); mainStage.setMinWidth(580); mainStage.setMinHeight(330); // maximized target state is stored, because "saveWindowState" saves x and y only if not maximized - boolean windowMaximised = guiPreferences.isWindowMaximised(); + boolean windowMaximised = coreGuiPreferences.isWindowMaximised(); LOGGER.debug("Screens: {}", Screen.getScreens()); debugLogWindowState(mainStage); if (isWindowPositionInBounds()) { LOGGER.debug("The JabRef window is inside screen bounds."); - mainStage.setX(guiPreferences.getPositionX()); - mainStage.setY(guiPreferences.getPositionY()); - mainStage.setWidth(guiPreferences.getSizeX()); - mainStage.setHeight(guiPreferences.getSizeY()); + mainStage.setX(coreGuiPreferences.getPositionX()); + mainStage.setY(coreGuiPreferences.getPositionY()); + mainStage.setWidth(coreGuiPreferences.getSizeX()); + mainStage.setHeight(coreGuiPreferences.getSizeY()); LOGGER.debug("NOT saving window positions"); } else { LOGGER.info("The JabRef window is outside of screen bounds. Position and size will be corrected to 1024x768. Primary screen will be used."); @@ -235,7 +249,7 @@ private void openWindow() { scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> TextInputKeyBindings.call( scene, event, - preferencesService.getKeyBindingRepository())); + preferences.getKeyBindingRepository())); mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); @@ -257,7 +271,7 @@ public void onShowing(WindowEvent event) { // Open last edited databases if (uiCommands.stream().noneMatch(UiCommand.BlankWorkspace.class::isInstance) - && preferencesService.getWorkspacePreferences().shouldOpenLastEdited()) { + && preferences.getWorkspacePreferences().shouldOpenLastEdited()) { mainFrame.openLastEditedDatabases(); } } @@ -270,12 +284,12 @@ public void onCloseRequest(WindowEvent event) { public void onHiding(WindowEvent event) { saveWindowState(); - preferencesService.flush(); + preferences.flush(); Platform.exit(); } private void saveWindowState() { - GuiPreferences preferences = preferencesService.getGuiPreferences(); + CoreGuiPreferences preferences = JabRefGUI.preferences.getGuiPreferences(); if (!mainStage.isMaximized()) { preferences.setPositionX(mainStage.getX()); preferences.setPositionY(mainStage.getY()); @@ -307,19 +321,19 @@ private void debugLogWindowState(Stage mainStage) { * Tests if the window coordinates are inside any screen */ private boolean isWindowPositionInBounds() { - GuiPreferences guiPreferences = preferencesService.getGuiPreferences(); + CoreGuiPreferences coreGuiPreferences = preferences.getGuiPreferences(); if (LOGGER.isDebugEnabled()) { Screen.getScreens().forEach(screen -> LOGGER.debug("Screen bounds: {}", screen.getBounds())); } - return lowerLeftIsInBounds(guiPreferences) && upperRightIsInBounds(guiPreferences); + return lowerLeftIsInBounds(coreGuiPreferences) && upperRightIsInBounds(coreGuiPreferences); } - private boolean lowerLeftIsInBounds(GuiPreferences guiPreferences) { + private boolean lowerLeftIsInBounds(CoreGuiPreferences coreGuiPreferences) { // Windows/PowerToys somehow removes 10 pixels to the left; they are re-added - double leftX = guiPreferences.getPositionX() + 10.0; - double bottomY = guiPreferences.getPositionY() + guiPreferences.getSizeY(); + double leftX = coreGuiPreferences.getPositionX() + 10.0; + double bottomY = coreGuiPreferences.getPositionY() + coreGuiPreferences.getSizeY(); LOGGER.debug("left x: {}, bottom y: {}", leftX, bottomY); boolean inBounds = Screen.getScreens().stream().anyMatch((screen -> screen.getBounds().contains(leftX, bottomY))); @@ -327,11 +341,11 @@ private boolean lowerLeftIsInBounds(GuiPreferences guiPreferences) { return inBounds; } - private boolean upperRightIsInBounds(GuiPreferences guiPreferences) { + private boolean upperRightIsInBounds(CoreGuiPreferences coreGuiPreferences) { // The upper right corner is checked as there are most probably the window controls. // Windows/PowerToys somehow adds 10 pixels to the right and top of the screen, they are removed - double rightX = guiPreferences.getPositionX() + guiPreferences.getSizeX() - 10.0; - double topY = guiPreferences.getPositionY(); + double rightX = coreGuiPreferences.getPositionX() + coreGuiPreferences.getSizeX() - 10.0; + double topY = coreGuiPreferences.getPositionY(); LOGGER.debug("right x: {}, top y: {}", rightX, topY); boolean inBounds = Screen.getScreens().stream().anyMatch((screen -> screen.getBounds().contains(rightX, topY))); @@ -341,13 +355,13 @@ private boolean upperRightIsInBounds(GuiPreferences guiPreferences) { // Background tasks public void startBackgroundTasks() { - RemotePreferences remotePreferences = preferencesService.getRemotePreferences(); + RemotePreferences remotePreferences = preferences.getRemotePreferences(); BibEntryTypesManager bibEntryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); if (remotePreferences.useRemoteServer()) { remoteListenerServerManager.openAndStart( new CLIMessageHandler( mainFrame, - preferencesService, + preferences, fileUpdateMonitor, bibEntryTypesManager), remotePreferences.getPort()); diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index d17a0a1871e..93901045848 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -54,6 +54,7 @@ import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableDataModel; +import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.RedoAction; @@ -61,9 +62,7 @@ import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.undo.UndoableInsertEntries; import org.jabref.gui.undo.UndoableRemoveEntries; -import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.OptionalObjectProperty; -import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.citationstyle.CitationStyleCache; import org.jabref.logic.importer.FetcherClientException; @@ -75,6 +74,8 @@ import org.jabref.logic.pdf.FileAnnotationCache; import org.jabref.logic.search.LuceneManager; import org.jabref.logic.shared.DatabaseLocation; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabase; @@ -97,7 +98,6 @@ import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; -import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; import com.google.common.eventbus.Subscribe; @@ -121,7 +121,7 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private final LibraryTabContainer tabContainer; private final CountingUndoManager undoManager; private final DialogService dialogService; - private final PreferencesService preferencesService; + private final GuiPreferences preferences; private final FileUpdateMonitor fileUpdateMonitor; private final StateManager stateManager; private final BibEntryTypesManager entryTypesManager; @@ -178,7 +178,7 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private LibraryTab(BibDatabaseContext bibDatabaseContext, LibraryTabContainer tabContainer, DialogService dialogService, - PreferencesService preferencesService, + GuiPreferences preferences, StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, @@ -190,7 +190,7 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext, this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); this.undoManager = undoManager; this.dialogService = dialogService; - this.preferencesService = Objects.requireNonNull(preferencesService); + this.preferences = Objects.requireNonNull(preferences); this.stateManager = Objects.requireNonNull(stateManager); this.fileUpdateMonitor = fileUpdateMonitor; this.entryTypesManager = entryTypesManager; @@ -221,13 +221,13 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { bibDatabaseContext.getMetaData().registerListener(this); this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, taskExecutor, stateManager, getLuceneManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, stateManager, getLuceneManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences()); + annotationCache = new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); importHandler = new ImportHandler( bibDatabaseContext, - preferencesService, + preferences, fileUpdateMonitor, undoManager, stateManager, @@ -245,7 +245,7 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { // ensure that all entry changes mark the panel as changed this.bibDatabaseContext.getDatabase().registerListener(this); - this.getDatabase().registerListener(new UpdateTimestampListener(preferencesService)); + this.getDatabase().registerListener(new UpdateTimestampListener(preferences)); this.entryEditor = createEntryEditor(); @@ -303,7 +303,7 @@ private void onDatabaseLoadingStarted() { } private void onDatabaseLoadingSucceed(ParserResult result) { - OpenDatabaseAction.performPostOpenActions(result, dialogService, preferencesService); + OpenDatabaseAction.performPostOpenActions(result, dialogService, preferences); if (result.getChangedOnMigration()) { this.markBaseChanged(); } @@ -316,7 +316,7 @@ private void onDatabaseLoadingSucceed(ParserResult result) { } public void createLuceneManager() { - luceneManager = new LuceneManager(bibDatabaseContext, taskExecutor, preferencesService.getFilePreferences()); + luceneManager = new LuceneManager(bibDatabaseContext, taskExecutor, preferences.getFilePreferences()); stateManager.setLuceneManager(bibDatabaseContext, luceneManager); } @@ -364,17 +364,17 @@ private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { public void installAutosaveManagerAndBackupManager() { if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { AutosaveManager autosaveManager = AutosaveManager.start(bibDatabaseContext); - autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferencesService, entryTypesManager)); + autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } - if (isDatabaseReadyForBackup(bibDatabaseContext) && preferencesService.getFilePreferences().shouldCreateBackup()) { - BackupManager.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferencesService); + if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { + BackupManager.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); } } private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { return ((context.getLocation() == DatabaseLocation.SHARED) || ((context.getLocation() == DatabaseLocation.LOCAL) - && preferencesService.getLibraryPreferences().shouldAutoSave())) + && preferences.getLibraryPreferences().shouldAutoSave())) && context.getDatabasePath().isPresent(); } @@ -390,7 +390,7 @@ private boolean isDatabaseReadyForBackup(BibDatabaseContext context) { * Example: *jabref-authors.bib – testbib */ public void updateTabTitle(boolean isChanged) { - boolean isAutosaveEnabled = preferencesService.getLibraryPreferences().shouldAutoSave(); + boolean isAutosaveEnabled = preferences.getLibraryPreferences().shouldAutoSave(); DatabaseLocation databaseLocation = bibDatabaseContext.getLocation(); Optional file = bibDatabaseContext.getDatabasePath(); @@ -485,10 +485,10 @@ private void createMainTable() { this, tabContainer, bibDatabaseContext, - preferencesService, + preferences, dialogService, stateManager, - preferencesService.getKeyBindingRepository(), + preferences.getKeyBindingRepository(), clipBoardManager, entryTypesManager, taskExecutor, @@ -539,7 +539,7 @@ public void setupMainPanel() { * Set up autocompletion for this database */ private void setupAutoCompletion() { - AutoCompletePreferences autoCompletePreferences = preferencesService.getAutoCompletePreferences(); + AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); if (autoCompletePreferences.shouldAutoComplete()) { suggestionProviders = new SuggestionProviders( getDatabase(), @@ -569,7 +569,7 @@ public void showAndEdit(BibEntry entry) { if (!splitPane.getItems().contains(entryEditor)) { splitPane.getItems().addLast(entryEditor); mode = PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR; - splitPane.setDividerPositions(preferencesService.getEntryEditorPreferences().getDividerPosition()); + splitPane.setDividerPositions(preferences.getEntryEditorPreferences().getDividerPosition()); } // We use != instead of equals because of performance reasons @@ -653,7 +653,7 @@ public BibDatabase getDatabase() { * @return true if user confirm to delete entry */ private boolean showDeleteConfirmationDialog(int numberOfEntries) { - if (preferencesService.getWorkspacePreferences().shouldConfirmDelete()) { + if (preferences.getWorkspacePreferences().shouldConfirmDelete()) { String title = Localization.lang("Delete entry"); String message = Localization.lang("Really delete the selected entry?"); String okButton = Localization.lang("Delete entry"); @@ -671,7 +671,7 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { okButton, cancelButton, Localization.lang("Do not ask again"), - optOut -> preferencesService.getWorkspacePreferences().setConfirmDelete(!optOut)); + optOut -> preferences.getWorkspacePreferences().setConfirmDelete(!optOut)); } else { return true; } @@ -682,7 +682,7 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { */ private void saveDividerLocation(Number position) { if (mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) { - preferencesService.getEntryEditorPreferences().setDividerPosition(position.doubleValue()); + preferences.getEntryEditorPreferences().setDividerPosition(position.doubleValue()); } } @@ -736,7 +736,7 @@ private boolean confirmClose() { if (buttonType.equals(saveChanges)) { try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(this, dialogService, preferencesService, Injector.instantiateModelOrService(BibEntryTypesManager.class)); + SaveDatabaseAction saveAction = new SaveDatabaseAction(this, dialogService, preferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); if (saveAction.save()) { return true; } @@ -751,7 +751,7 @@ private boolean confirmClose() { } if (buttonType.equals(discardChanges)) { - BackupManager.discardBackup(bibDatabaseContext, preferencesService.getFilePreferences().getBackupDirectory()); + BackupManager.discardBackup(bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory()); return true; } @@ -800,8 +800,8 @@ private void onClosed(Event event) { } try { BackupManager.shutdown(bibDatabaseContext, - preferencesService.getFilePreferences().getBackupDirectory(), - preferencesService.getFilePreferences().shouldCreateBackup()); + preferences.getFilePreferences().getBackupDirectory(), + preferences.getFilePreferences().shouldCreateBackup()); } catch (RuntimeException e) { LOGGER.error("Problem when shutting down backup manager", e); } @@ -872,7 +872,7 @@ public void resetChangeMonitor() { fileUpdateMonitor, taskExecutor, dialogService, - preferencesService, + preferences, databaseNotificationPane, undoManager, stateManager)); @@ -890,7 +890,7 @@ public void insertEntries(final List entries) { getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); markBaseChanged(); - if (preferencesService.getEntryEditorPreferences().shouldOpenOnNewEntry()) { + if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { showAndEdit(entries.getFirst()); } clearAndSelect(entries.getFirst()); @@ -1009,10 +1009,10 @@ private int doDeleteEntry(StandardActions mode, List entries) { if (!linkedFileList.isEmpty()) { List viewModels = linkedFileList.stream() - .map(linkedFile -> linkedFile.toModel(null, bibDatabaseContext, null, null, preferencesService)) + .map(linkedFile -> LinkedFileViewModel.fromLinkedFile(linkedFile, null, bibDatabaseContext, null, null, preferences)) .collect(Collectors.toList()); - new DeleteFileAction(dialogService, preferencesService.getFilePreferences(), bibDatabaseContext, viewModels).execute(); + new DeleteFileAction(dialogService, preferences.getFilePreferences(), bibDatabaseContext, viewModels).execute(); } } @@ -1052,7 +1052,7 @@ public void resetChangedProperties() { public static LibraryTab createLibraryTab(BackgroundTask dataLoadingTask, Path file, DialogService dialogService, - PreferencesService preferencesService, + GuiPreferences preferences, StateManager stateManager, LibraryTabContainer tabContainer, FileUpdateMonitor fileUpdateMonitor, @@ -1067,7 +1067,7 @@ public static LibraryTab createLibraryTab(BackgroundTask dataLoadi context, tabContainer, dialogService, - preferencesService, + preferences, stateManager, fileUpdateMonitor, entryTypesManager, @@ -1088,7 +1088,7 @@ public static LibraryTab createLibraryTab(BackgroundTask dataLoadi public static LibraryTab createLibraryTab(BibDatabaseContext databaseContext, LibraryTabContainer tabContainer, DialogService dialogService, - PreferencesService preferencesService, + GuiPreferences preferences, StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, @@ -1101,7 +1101,7 @@ public static LibraryTab createLibraryTab(BibDatabaseContext databaseContext, databaseContext, tabContainer, dialogService, - preferencesService, + preferences, stateManager, fileUpdateMonitor, entryTypesManager, @@ -1121,7 +1121,7 @@ public void listen(EntriesAddedEvent addedEntriesEvent) { } // Automatically add new entries to the selected group (or set of groups) - if (preferencesService.getGroupsPreferences().shouldAutoAssignGroup()) { + if (preferences.getGroupsPreferences().shouldAutoAssignGroup()) { stateManager.getSelectedGroups(bibDatabaseContext).forEach( selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); } diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 42fd8d9f1bc..cf73b00da94 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -22,11 +22,11 @@ import org.jabref.gui.edit.automaticfiededitor.LastAutomaticFieldEditorEdit; import org.jabref.gui.search.SearchType; import org.jabref.gui.sidepane.SidePaneType; -import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.DialogWindowState; import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.search.LuceneManager; +import org.jabref.logic.util.BackgroundTask; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.GroupTreeNode; diff --git a/src/main/java/org/jabref/gui/UpdateTimestampListener.java b/src/main/java/org/jabref/gui/UpdateTimestampListener.java index 2ccc4a72d3c..b975773dee8 100644 --- a/src/main/java/org/jabref/gui/UpdateTimestampListener.java +++ b/src/main/java/org/jabref/gui/UpdateTimestampListener.java @@ -1,9 +1,9 @@ package org.jabref.gui; +import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.EntryChangedEvent; import org.jabref.model.entry.field.StandardField; -import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; @@ -11,19 +11,19 @@ * Updates the timestamp of changed entries if the feature is enabled */ class UpdateTimestampListener { - private final PreferencesService preferencesService; + private final CliPreferences preferences; - UpdateTimestampListener(PreferencesService preferencesService) { - this.preferencesService = preferencesService; + UpdateTimestampListener(CliPreferences preferences) { + this.preferences = preferences; } @Subscribe public void listen(EntryChangedEvent event) { // The event source needs to be checked, since the timestamp is always updated on every change. The cleanup formatter is an exception to that behaviour, // since it just should move the contents from the timestamp field to modificationdate or creationdate. - if (preferencesService.getTimestampPreferences().shouldAddModificationDate() && event.getEntriesEventSource() != EntriesEventSource.CLEANUP_TIMESTAMP) { + if (preferences.getTimestampPreferences().shouldAddModificationDate() && event.getEntriesEventSource() != EntriesEventSource.CLEANUP_TIMESTAMP) { event.getBibEntry().setField(StandardField.MODIFICATIONDATE, - preferencesService.getTimestampPreferences().now()); + preferences.getTimestampPreferences().now()); } } } diff --git a/src/main/java/org/jabref/preferences/WorkspacePreferences.java b/src/main/java/org/jabref/gui/WorkspacePreferences.java similarity index 99% rename from src/main/java/org/jabref/preferences/WorkspacePreferences.java rename to src/main/java/org/jabref/gui/WorkspacePreferences.java index 57d97a971b6..2948be7582e 100644 --- a/src/main/java/org/jabref/preferences/WorkspacePreferences.java +++ b/src/main/java/org/jabref/gui/WorkspacePreferences.java @@ -1,4 +1,4 @@ -package org.jabref.preferences; +package org.jabref.gui; import java.util.List; diff --git a/src/main/java/org/jabref/gui/actions/ActionHelper.java b/src/main/java/org/jabref/gui/actions/ActionHelper.java index 20376f2f3e2..437d38d9bfe 100644 --- a/src/main/java/org/jabref/gui/actions/ActionHelper.java +++ b/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -12,13 +12,13 @@ import javafx.scene.control.TabPane; import org.jabref.gui.StateManager; +import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; -import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.EasyBinding; @@ -66,7 +66,7 @@ public static BooleanExpression isAnyFieldSetForSelectedEntry(List fields return BooleanExpression.booleanExpression(fieldsAreSet); } - public static BooleanExpression isFilePresentForSelectedEntry(StateManager stateManager, PreferencesService preferencesService) { + public static BooleanExpression isFilePresentForSelectedEntry(StateManager stateManager, CliPreferences preferences) { ObservableList selectedEntries = stateManager.getSelectedEntries(); Binding fileIsPresent = EasyBind.valueAt(selectedEntries, 0).mapOpt(entry -> { List files = entry.getFiles(); @@ -79,7 +79,7 @@ public static BooleanExpression isFilePresentForSelectedEntry(StateManager state Optional filename = FileUtil.find( stateManager.getActiveDatabase().get(), files.getFirst().getLink(), - preferencesService.getFilePreferences()); + preferences.getFilePreferences()); return filename.isPresent(); } else { return false; diff --git a/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java b/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java index 6b76d7af67d..91b3e926269 100644 --- a/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java +++ b/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java @@ -5,10 +5,10 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; -import org.jabref.gui.util.BackgroundTask; -import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.ai.AiService; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.LinkedFile; import static org.jabref.gui.actions.ActionHelper.needsDatabase; diff --git a/src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryService.java b/src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java similarity index 93% rename from src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryService.java rename to src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java index acbde14c3a3..872a263c7a1 100644 --- a/src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryService.java +++ b/src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java @@ -1,4 +1,4 @@ -package org.jabref.logic.ai.chatting.chathistory; +package org.jabref.gui.ai.chatting.chathistory; import java.util.Comparator; import java.util.HashSet; @@ -11,9 +11,13 @@ import javafx.collections.ObservableList; import org.jabref.gui.StateManager; +import org.jabref.logic.ai.chatting.chathistory.ChatHistoryStorage; +import org.jabref.logic.ai.chatting.chathistory.storages.MVStoreChatHistoryStorage; import org.jabref.logic.ai.util.CitationKeyCheck; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.util.Directories; +import org.jabref.logic.util.NotificationService; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.FieldChangedEvent; @@ -29,7 +33,8 @@ /** * Main class for getting and storing chat history for entries and groups. - * Use this class in the logic and UI. + * Use this class in logic and UI. + * It currently resides in the UI package because it relies on the {@link StateManager} to get the open databases and to find the correct {@link BibDatabaseContext} based on an entry. *

    * The returned chat history is a {@link ObservableList}. So chat history exists for every possible * {@link BibEntry} and {@link AbstractGroup}. The chat history is stored in runtime. @@ -51,6 +56,8 @@ public class ChatHistoryService implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(ChatHistoryService.class); + private static final String CHAT_HISTORY_FILE_NAME = "chat-histories.mv"; + private final StateManager stateManager = Injector.instantiateModelOrService(StateManager.class); private final CitationKeyPatternPreferences citationKeyPatternPreferences; @@ -72,6 +79,12 @@ private record ChatHistoryManagementRecord(Optional bibDatab return o1 == o2 ? 0 : o1.getGroup().getName().compareTo(o2.getGroup().getName()); }); + public ChatHistoryService(CitationKeyPatternPreferences citationKeyPatternPreferences, NotificationService notificationService) { + this.citationKeyPatternPreferences = citationKeyPatternPreferences; + this.implementation = new MVStoreChatHistoryStorage(Directories.getAiFilesDirectory().resolve(CHAT_HISTORY_FILE_NAME), notificationService); + configureHistoryTransfer(); + } + public ChatHistoryService(CitationKeyPatternPreferences citationKeyPatternPreferences, ChatHistoryStorage implementation) { this.citationKeyPatternPreferences = citationKeyPatternPreferences; diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml index fa24208105e..a479ace35e6 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml @@ -1,15 +1,16 @@ - - - - - - - + + + + + + + - + + chatHistory aiService, dialogService, aiPreferences, - filePreferences, + externalApplicationsPreferences, taskExecutor ), 800, diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml index b204fc467de..20836e0e3bb 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml @@ -1,9 +1,8 @@ - - - + + - - - - - - + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml b/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml index b933fc2b7bf..04c53eaab88 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml @@ -1,11 +1,7 @@ - - - - - - + + rebuildUi()); @@ -33,7 +33,7 @@ public final void rebuildUi() { new PrivacyNoticeComponent( aiPreferences, this::rebuildUi, - filePreferences, + externalApplicationsPreferences, dialogService ) ); diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml index 6ceb4fdb4ac..2f439beac31 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml @@ -1,10 +1,14 @@ - - - - - + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java index 724ea24ded7..9681379d60d 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java @@ -9,11 +9,11 @@ import javafx.scene.text.TextFlow; import org.jabref.gui.DialogService; -import org.jabref.gui.desktop.JabRefDesktop; +import org.jabref.gui.desktop.os.NativeDesktop; +import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.logic.ai.AiDefaultPreferences; -import org.jabref.preferences.FilePreferences; -import org.jabref.preferences.ai.AiPreferences; -import org.jabref.preferences.ai.AiProvider; +import org.jabref.logic.ai.AiPreferences; +import org.jabref.model.ai.AiProvider; import com.airhacks.afterburner.views.ViewLoader; import org.slf4j.Logger; @@ -31,13 +31,13 @@ public class PrivacyNoticeComponent extends ScrollPane { private final AiPreferences aiPreferences; private final Runnable onIAgreeButtonClickCallback; private final DialogService dialogService; - private final FilePreferences filePreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; - public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButtonClickCallback, FilePreferences filePreferences, DialogService dialogService) { + public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButtonClickCallback, ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService) { this.aiPreferences = aiPreferences; this.onIAgreeButtonClickCallback = onIAgreeButtonClickCallback; + this.externalApplicationsPreferences = externalApplicationsPreferences; this.dialogService = dialogService; - this.filePreferences = filePreferences; ViewLoader.view(this) .root(this) @@ -102,7 +102,7 @@ private void onDjlPrivacyPolicyClick() { private void openBrowser(String link) { try { - JabRefDesktop.openBrowser(link, filePreferences); + NativeDesktop.openBrowser(link, externalApplicationsPreferences); } catch (IOException e) { LOGGER.error("Error opening the browser to the Privacy Policy page of the AI provider.", e); dialogService.showErrorDialogAndWait(e); diff --git a/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java b/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java index 3a164c2739c..ed8e66980e0 100644 --- a/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java @@ -7,6 +7,8 @@ import org.jabref.gui.DialogService; import org.jabref.gui.ai.components.privacynotice.AiPrivacyNoticeGuardedComponent; import org.jabref.gui.ai.components.util.errorstate.ErrorStateComponent; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.logic.ai.AiPreferences; import org.jabref.logic.ai.AiService; import org.jabref.logic.ai.processingstatus.ProcessingInfo; import org.jabref.logic.ai.summarization.Summary; @@ -18,8 +20,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; -import org.jabref.preferences.FilePreferences; -import org.jabref.preferences.ai.AiPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,11 +37,11 @@ public SummaryComponent(BibDatabaseContext bibDatabaseContext, BibEntry entry, AiService aiService, AiPreferences aiPreferences, - FilePreferences filePreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, CitationKeyPatternPreferences citationKeyPatternPreferences, DialogService dialogService ) { - super(aiPreferences, filePreferences, dialogService); + super(aiPreferences, externalApplicationsPreferences, dialogService); this.bibDatabaseContext = bibDatabaseContext; this.entry = entry; diff --git a/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml b/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml index db7698eccf6..f248bccab17 100644 --- a/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml @@ -1,10 +1,11 @@ - - - - - + + + + + +