diff --git a/CHANGELOG.md b/CHANGELOG.md
index 406e02ed7fe..b7db5173292 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,7 +35,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- `log.txt` does not contain entries for non-found files during PDF indexing. [#9678](https://github.com/JabRef/jabref/pull/9678)
- We improved the Medline importer to correctly import ISO dates for `revised`. [#9536](https://github.com/JabRef/jabref/issues/9536)
- To avoid cluttering of the directory, We always delete the `.sav` file upon successful write. [#9675](https://github.com/JabRef/jabref/pull/9675)
-
+- We improved the unlinking/deletion of multiple linked files of an entry using the Delete key [#9473](https://github.com/JabRef/jabref/issues/9473)
### Fixed
diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java
index f18975dad88..1e3daafffe7 100644
--- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java
+++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java
@@ -385,7 +385,7 @@ public boolean delete() {
Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences());
if (file.isEmpty()) {
- LOGGER.warn("Could not find file " + linkedFile.getLink());
+ LOGGER.warn("Could not find file {}", linkedFile.getLink());
return true;
}
@@ -407,7 +407,7 @@ public boolean delete() {
return true;
} catch (IOException ex) {
dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete file"), Localization.lang("File permission error"));
- LOGGER.warn("File permission error while deleting: " + linkedFile, ex);
+ LOGGER.warn("File permission error while deleting: {}", linkedFile, ex);
}
}
}
diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java
index e93c3952858..b59d1a3164d 100644
--- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java
+++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java
@@ -1,6 +1,5 @@
package org.jabref.gui.fieldeditors;
-import java.util.List;
import java.util.Optional;
import javafx.beans.binding.Bindings;
@@ -41,6 +40,7 @@
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.importer.GrobidOptInDialogHelper;
import org.jabref.gui.keyboard.KeyBinding;
+import org.jabref.gui.linkedfile.DeleteFileAction;
import org.jabref.gui.util.BindingsHelper;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.gui.util.ViewModelListCellFactory;
@@ -218,10 +218,8 @@ private void setUpKeyBindings() {
if (keyBinding.isPresent()) {
switch (keyBinding.get()) {
case DELETE_ENTRY:
- List toBeDeleted = List.copyOf(listView.getSelectionModel().getSelectedItems());
- for (LinkedFileViewModel selectedItem : toBeDeleted) {
- viewModel.deleteFile(selectedItem);
- }
+ new DeleteFileAction(dialogService, preferencesService, databaseContext,
+ viewModel, listView).execute();
event.consume();
break;
default:
diff --git a/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java
new file mode 100644
index 00000000000..a1f9ef3bb15
--- /dev/null
+++ b/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java
@@ -0,0 +1,138 @@
+package org.jabref.gui.linkedfile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonBar;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.ListView;
+
+import org.jabref.gui.DialogService;
+import org.jabref.gui.actions.SimpleCommand;
+import org.jabref.gui.fieldeditors.LinkedFileViewModel;
+import org.jabref.gui.fieldeditors.LinkedFilesEditorViewModel;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.LinkedFile;
+import org.jabref.preferences.PreferencesService;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DeleteFileAction extends SimpleCommand {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DeleteFileAction.class);
+
+ private final DialogService dialogService;
+ private final PreferencesService preferences;
+ private final BibDatabaseContext databaseContext;
+ private final LinkedFilesEditorViewModel viewModel;
+ private final ListView listView;
+
+ public DeleteFileAction(DialogService dialogService,
+ PreferencesService preferences,
+ BibDatabaseContext databaseContext,
+ LinkedFilesEditorViewModel viewModel,
+ ListView listView) {
+ this.dialogService = dialogService;
+ this.preferences = preferences;
+ this.databaseContext = databaseContext;
+ this.viewModel = viewModel;
+ this.listView = listView;
+ }
+
+ @Override
+ public void execute() {
+ List toBeDeleted = List.copyOf(listView.getSelectionModel().getSelectedItems());
+
+ if (toBeDeleted.isEmpty()) {
+ dialogService.notify(Localization.lang("This operation requires selected linked files."));
+ return;
+ }
+
+ String dialogTitle;
+ String dialogContent;
+
+ if (toBeDeleted.size() != 1) {
+ dialogTitle = Localization.lang("Delete %0 files", toBeDeleted.size());
+ dialogContent = Localization.lang("Delete %0 files permanently from disk, or just remove the files from the entry? " +
+ "Pressing Delete will delete the files permanently from disk.", toBeDeleted.size());
+ } else {
+ Optional file = toBeDeleted.get(0).getFile().findIn(databaseContext, preferences.getFilePreferences());
+
+ if (file.isPresent()) {
+ dialogTitle = Localization.lang("Delete '%0'", file.get().getFileName().toString());
+ dialogContent = Localization.lang("Delete '%0' permanently from disk, or just remove the file from the entry? " +
+ "Pressing Delete will delete the file permanently from disk.", file.get().toString());
+ } else {
+ dialogService.notify(Localization.lang("Error accessing file '%0'.", toBeDeleted.get(0).getFile().getLink()));
+ return;
+ }
+ }
+
+ ButtonType removeFromEntry = new ButtonType(Localization.lang("Remove from entry"), ButtonBar.ButtonData.YES);
+ ButtonType deleteFromEntry = new ButtonType(Localization.lang("Delete from disk"));
+ Optional buttonType = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.INFORMATION,
+ dialogTitle, dialogContent, removeFromEntry, deleteFromEntry, ButtonType.CANCEL);
+
+ if (buttonType.isPresent()) {
+ if (buttonType.get().equals(removeFromEntry)) {
+ deleteFiles(toBeDeleted, false);
+ }
+
+ if (buttonType.get().equals(deleteFromEntry)) {
+ deleteFiles(toBeDeleted, true);
+ }
+ }
+ }
+
+ /**
+ * Deletes the files from the entry and optionally from disk.
+ *
+ * @param toBeDeleted the files to be deleted
+ * @param deleteFromDisk if true, the files are deleted from disk, otherwise they are only removed from the entry
+ */
+ private void deleteFiles(List toBeDeleted, boolean deleteFromDisk) {
+ for (LinkedFileViewModel fileViewModel : toBeDeleted) {
+ if (fileViewModel.getFile().isOnlineLink()) {
+ viewModel.removeFileLink(fileViewModel);
+ } else {
+ if (deleteFromDisk) {
+ deleteFileFromDisk(fileViewModel);
+ }
+ viewModel.getFiles().remove(fileViewModel);
+ }
+ }
+ }
+
+ /**
+ * Deletes the file from disk without asking the user for confirmation.
+ *
+ * @param fileViewModel the file to be deleted
+ */
+ public void deleteFileFromDisk(LinkedFileViewModel fileViewModel) {
+ LinkedFile linkedFile = fileViewModel.getFile();
+
+ Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences());
+
+ if (file.isEmpty()) {
+ LOGGER.warn("Could not find file {}", linkedFile.getLink());
+ }
+
+ if (file.isPresent()) {
+ try {
+ Files.delete(file.get());
+ } catch (
+ IOException ex) {
+ dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete file"), Localization.lang("File permission error"));
+ LOGGER.warn("File permission error while deleting: {}", linkedFile, ex);
+ }
+ } else {
+ dialogService.notify(Localization.lang("Error accessing file '%0'.", linkedFile.getLink()));
+ }
+ }
+}
diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties
index 035b291a4fd..c772cf06f87 100644
--- a/src/main/resources/l10n/JabRef_en.properties
+++ b/src/main/resources/l10n/JabRef_en.properties
@@ -2539,3 +2539,8 @@ Please\ select\ a\ valid\ main\ directory\ under=Please select a valid main dire
Search\ from\ history...=Search from history...
your\ search\ history\ is\ empty=your search history is empty
Clear\ history =Clear history
+
+Delete\ %0\ files=Delete %0 files
+Delete\ %0\ files\ permanently\ from\ disk,\ or\ just\ remove\ the\ files\ from\ the\ entry?\ Pressing\ Delete\ will\ delete\ the\ files\ permanently\ from\ disk.=Delete %0 files permanently from disk, or just remove the files from the entry? Pressing Delete will delete the files permanently from disk.
+Error\ accessing\ file\ '%0'.=Error accessing file '%0'.
+This\ operation\ requires\ selected\ linked\ files.=This operation requires selected linked files.