diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb9108a09a2..ced3f440ff3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
### Changed
+- We separated the "Clean up entries" dialog into three tabs for clarity [#13819](https://github.com/JabRef/jabref/issues/13819)
- Ctrl + Shift + L now opens the terminal in the active library directory. [#14130](https://github.com/JabRef/jabref/issues/14130)
### Fixed
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java
index b623ae39bd7..ad0281df5db 100644
--- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java
@@ -1,32 +1,16 @@
package org.jabref.gui.cleanup;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
import javax.swing.undo.UndoManager;
-import javafx.application.Platform;
-
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.gui.undo.NamedCompoundEdit;
-import org.jabref.gui.undo.UndoableFieldChange;
-import org.jabref.logic.JabRefException;
-import org.jabref.logic.cleanup.CleanupPreferences;
-import org.jabref.logic.cleanup.CleanupWorker;
-import org.jabref.logic.l10n.Localization;
import org.jabref.logic.preferences.CliPreferences;
-import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.TaskExecutor;
-import org.jabref.model.FieldChange;
-import org.jabref.model.database.BibDatabaseContext;
-import org.jabref.model.entry.BibEntry;
public class CleanupAction extends SimpleCommand {
@@ -36,10 +20,6 @@ public class CleanupAction extends SimpleCommand {
private final StateManager stateManager;
private final TaskExecutor taskExecutor;
private final UndoManager undoManager;
- private final List failures;
-
- private boolean isCanceled;
- private int modifiedEntriesCount;
public CleanupAction(Supplier tabSupplier,
CliPreferences preferences,
@@ -53,7 +33,6 @@ public CleanupAction(Supplier tabSupplier,
this.stateManager = stateManager;
this.taskExecutor = taskExecutor;
this.undoManager = undoManager;
- this.failures = new ArrayList<>();
this.executable.bind(ActionHelper.needsEntriesSelected(stateManager));
}
@@ -64,119 +43,16 @@ public void execute() {
return;
}
- if (stateManager.getSelectedEntries().isEmpty()) { // None selected. Inform the user to select entries first.
- dialogService.showInformationDialogAndWait(Localization.lang("Cleanup entry"), Localization.lang("First select entries to clean up."));
- return;
- }
-
- isCanceled = false;
- modifiedEntriesCount = 0;
-
CleanupDialog cleanupDialog = new CleanupDialog(
stateManager.getActiveDatabase().get(),
- preferences.getCleanupPreferences(),
- preferences.getFilePreferences()
- );
-
- Optional chosenPreset = dialogService.showCustomDialogAndWait(cleanupDialog);
-
- chosenPreset.ifPresent(preset -> {
- if (preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF) && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) {
- boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(Localization.lang("Autogenerate PDF Names"),
- Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"),
- Localization.lang("Autogenerate PDF Names"),
- Localization.lang("Cancel"),
- Localization.lang("Do not ask again"),
- optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut));
- if (!confirmed) {
- isCanceled = true;
- return;
- }
- }
-
- preferences.getCleanupPreferences().setActiveJobs(preset.getActiveJobs());
- preferences.getCleanupPreferences().setFieldFormatterCleanups(preset.getFieldFormatterCleanups());
-
- BackgroundTask.wrap(() -> cleanup(stateManager.getActiveDatabase().get(), preset))
- .onSuccess(result -> showResults())
- .onFailure(dialogService::showErrorDialogAndWait)
- .executeWith(taskExecutor);
- });
- }
-
- /**
- * Runs the cleanup on the entry and records the change.
- *
- * @return true iff entry was modified
- */
- private boolean doCleanup(BibDatabaseContext databaseContext, CleanupPreferences preset, BibEntry entry, NamedCompoundEdit compoundEdit) {
- // Create and run cleaner
- CleanupWorker cleaner = new CleanupWorker(
- databaseContext,
- preferences.getFilePreferences(),
- preferences.getTimestampPreferences()
+ preferences,
+ dialogService,
+ stateManager,
+ undoManager,
+ tabSupplier,
+ taskExecutor
);
- List changes = cleaner.cleanup(preset, entry);
-
- // Register undo action
- for (FieldChange change : changes) {
- compoundEdit.addEdit(new UndoableFieldChange(change));
- }
-
- failures.addAll(cleaner.getFailures());
-
- return !changes.isEmpty();
- }
-
- private void showResults() {
- if (isCanceled) {
- return;
- }
-
- if (modifiedEntriesCount > 0) {
- tabSupplier.get().markBaseChanged();
- }
-
- if (modifiedEntriesCount == 0) {
- dialogService.notify(Localization.lang("No entry needed a clean up"));
- } else if (modifiedEntriesCount == 1) {
- dialogService.notify(Localization.lang("One entry needed a clean up"));
- } else {
- dialogService.notify(Localization.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount)));
- }
- }
-
- private void cleanup(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences) {
- this.failures.clear();
-
- // undo granularity is on set of all entries
- NamedCompoundEdit compoundEdit = new NamedCompoundEdit(Localization.lang("Clean up entries"));
-
- for (BibEntry entry : List.copyOf(stateManager.getSelectedEntries())) {
- if (doCleanup(databaseContext, cleanupPreferences, entry, compoundEdit)) {
- modifiedEntriesCount++;
- }
- }
-
- compoundEdit.end();
-
- if (compoundEdit.hasEdits()) {
- undoManager.addEdit(compoundEdit);
- }
-
- if (!failures.isEmpty()) {
- showFailures(failures);
- }
- }
-
- private void showFailures(List failures) {
- String message = failures.stream()
- .map(exception -> "- " + exception.getLocalizedMessage())
- .collect(Collectors.joining("\n"));
-
- Platform.runLater(() ->
- dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), message)
- );
+ dialogService.showCustomDialogAndWait(cleanupDialog);
}
}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java
index 752b9309dc6..134714be4a4 100644
--- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java
@@ -1,35 +1,87 @@
package org.jabref.gui.cleanup;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.ScrollPane;
+import java.util.List;
+import java.util.function.Supplier;
+import javax.swing.undo.UndoManager;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+
+import org.jabref.gui.DialogService;
+import org.jabref.gui.LibraryTab;
+import org.jabref.gui.StateManager;
import org.jabref.gui.util.BaseDialog;
import org.jabref.logic.FilePreferences;
import org.jabref.logic.cleanup.CleanupPreferences;
import org.jabref.logic.l10n.Localization;
+import org.jabref.logic.preferences.CliPreferences;
+import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+
+import com.airhacks.afterburner.views.ViewLoader;
+
+public class CleanupDialog extends BaseDialog {
+
+ @FXML private TabPane tabPane;
+
+ private final CleanupDialogViewModel viewModel;
+
+ // Constructor for multiple-entry cleanup
+ public CleanupDialog(BibDatabaseContext databaseContext,
+ CliPreferences preferences,
+ DialogService dialogService,
+ StateManager stateManager,
+ UndoManager undoManager,
+ Supplier tabSupplier,
+ TaskExecutor taskExecutor) {
+
+ this.viewModel = new CleanupDialogViewModel(
+ databaseContext, preferences, dialogService,
+ stateManager, undoManager, tabSupplier, taskExecutor
+ );
+
+ init(databaseContext, preferences);
+ }
-public class CleanupDialog extends BaseDialog {
- public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreferences initialPreset, FilePreferences filePreferences) {
+ // Constructor for single-entry cleanup
+ public CleanupDialog(BibEntry targetEntry,
+ BibDatabaseContext databaseContext,
+ CliPreferences preferences,
+ DialogService dialogService,
+ StateManager stateManager,
+ UndoManager undoManager) {
+
+ this.viewModel = new CleanupDialogViewModel(
+ databaseContext, preferences, dialogService,
+ stateManager, undoManager, null, null
+ );
+
+ viewModel.setTargetEntries(List.of(targetEntry));
+
+ init(databaseContext, preferences);
+ }
+
+ private void init(BibDatabaseContext databaseContext, CliPreferences preferences) {
setTitle(Localization.lang("Clean up entries"));
- getDialogPane().setPrefSize(600, 650);
- getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
-
- CleanupPresetPanel presetPanel = new CleanupPresetPanel(databaseContext, initialPreset, filePreferences);
-
- // placing the content of the presetPanel in a scroll pane
- ScrollPane scrollPane = new ScrollPane();
- scrollPane.setFitToWidth(true);
- scrollPane.setFitToHeight(true);
- scrollPane.setContent(presetPanel);
-
- getDialogPane().setContent(scrollPane);
- setResultConverter(button -> {
- if (button == ButtonType.OK) {
- return presetPanel.getCleanupPreset();
- } else {
- return null;
- }
- });
+
+ ViewLoader.view(this)
+ .load()
+ .setAsDialogPane(this);
+
+ CleanupPreferences initialPreset = preferences.getCleanupPreferences();
+ FilePreferences filePreferences = preferences.getFilePreferences();
+
+ CleanupSingleFieldPanel singleFieldPanel = new CleanupSingleFieldPanel(initialPreset, viewModel);
+ CleanupFileRelatedPanel fileRelatedPanel = new CleanupFileRelatedPanel(databaseContext, initialPreset, filePreferences, viewModel);
+ CleanupMultiFieldPanel multiFieldPanel = new CleanupMultiFieldPanel(initialPreset, viewModel);
+
+ tabPane.getTabs().setAll(
+ new Tab(Localization.lang("Single field"), singleFieldPanel),
+ new Tab(Localization.lang("File-related"), fileRelatedPanel),
+ new Tab(Localization.lang("Multi-field"), multiFieldPanel)
+ );
}
}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java
new file mode 100644
index 00000000000..5da5cb8ccba
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java
@@ -0,0 +1,201 @@
+package org.jabref.gui.cleanup;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import javax.swing.undo.UndoManager;
+
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import org.jabref.gui.AbstractViewModel;
+import org.jabref.gui.DialogService;
+import org.jabref.gui.LibraryTab;
+import org.jabref.gui.StateManager;
+import org.jabref.gui.undo.NamedCompoundEdit;
+import org.jabref.gui.undo.UndoableFieldChange;
+import org.jabref.logic.JabRefException;
+import org.jabref.logic.cleanup.CleanupPreferences;
+import org.jabref.logic.cleanup.CleanupTabSelection;
+import org.jabref.logic.cleanup.CleanupWorker;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.logic.preferences.CliPreferences;
+import org.jabref.logic.util.BackgroundTask;
+import org.jabref.logic.util.TaskExecutor;
+import org.jabref.model.FieldChange;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+
+import org.jspecify.annotations.NonNull;
+
+public class CleanupDialogViewModel extends AbstractViewModel {
+
+ private final BibDatabaseContext databaseContext;
+ private final CliPreferences preferences;
+ private final DialogService dialogService;
+ private final StateManager stateManager;
+ private final UndoManager undoManager;
+ private final Supplier tabSupplier;
+ private final TaskExecutor taskExecutor;
+
+ private final ObservableList targetEntries = FXCollections.observableArrayList();
+ private int modifiedEntriesCount;
+
+ public CleanupDialogViewModel(
+ @NonNull BibDatabaseContext databaseContext,
+ @NonNull CliPreferences preferences,
+ @NonNull DialogService dialogService,
+ @NonNull StateManager stateManager,
+ @NonNull UndoManager undoManager,
+ Supplier tabSupplier,
+ TaskExecutor taskExecutor
+ ) {
+ this.databaseContext = databaseContext;
+ this.preferences = preferences;
+ this.dialogService = dialogService;
+ this.stateManager = stateManager;
+ this.undoManager = undoManager;
+
+ this.tabSupplier = tabSupplier; // can be null
+ this.taskExecutor = taskExecutor; // can be null
+ }
+
+ public void setTargetEntries(List entries) {
+ targetEntries.setAll(Objects.requireNonNullElse(entries, List.of()));
+ }
+
+ public void apply(CleanupTabSelection selectedTab) {
+ if (stateManager.getActiveDatabase().isEmpty()) {
+ return;
+ }
+
+ List entriesToProcess = targetEntries.isEmpty() ? List.copyOf(stateManager.getSelectedEntries()) : targetEntries;
+
+ if (entriesToProcess.isEmpty()) { // None selected. Inform the user to select entries first.
+ dialogService.showInformationDialogAndWait(Localization.lang("Clean up entry"), Localization.lang("First select entries to clean up.")
+ );
+ return;
+ }
+
+ modifiedEntriesCount = 0;
+
+ if (selectedTab.selectedJobs().contains(CleanupPreferences.CleanupStep.RENAME_PDF) && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) {
+ boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(
+ Localization.lang("Autogenerate PDF Names"),
+ Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"),
+ Localization.lang("Autogenerate PDF Names"),
+ Localization.lang("Cancel"),
+ Localization.lang("Do not ask again"),
+ optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut)
+ );
+ if (!confirmed) {
+ return;
+ }
+ }
+
+ CleanupPreferences updatedPreferences = selectedTab.updatePreferences(preferences.getCleanupPreferences());
+
+ if (selectedTab.isJobTab()) {
+ preferences.getCleanupPreferences().setActiveJobs(updatedPreferences.getActiveJobs());
+ }
+
+ if (selectedTab.isFormatterTab()) {
+ preferences.getCleanupPreferences().setFieldFormatterCleanups(updatedPreferences.getFieldFormatterCleanups());
+ }
+
+ CleanupPreferences cleanupPreset = new CleanupPreferences(EnumSet.copyOf(selectedTab.selectedJobs()));
+ selectedTab.formatters().ifPresent(cleanupPreset::setFieldFormatterCleanups);
+
+ if (taskExecutor != null) {
+ BackgroundTask.wrap(() -> cleanup(cleanupPreset, entriesToProcess))
+ .onSuccess(result -> showResults())
+ .onFailure(dialogService::showErrorDialogAndWait)
+ .executeWith(taskExecutor);
+ } else {
+ cleanup(cleanupPreset, entriesToProcess);
+ }
+ }
+
+ /**
+ * Runs the cleanup on the entry and records the change.
+ *
+ * @return true iff entry was modified
+ */
+ private boolean doCleanup(CleanupPreferences preset,
+ BibEntry entry,
+ NamedCompoundEdit compoundEdit,
+ List failures) {
+ CleanupWorker cleaner = new CleanupWorker(
+ databaseContext,
+ preferences.getFilePreferences(),
+ preferences.getTimestampPreferences()
+ );
+
+ List changes = cleaner.cleanup(preset, entry);
+
+ for (FieldChange change : changes) {
+ compoundEdit.addEdit(new UndoableFieldChange(change));
+ }
+
+ failures.addAll(cleaner.getFailures());
+
+ return !changes.isEmpty();
+ }
+
+ private void showResults() {
+ if (modifiedEntriesCount > 0 && tabSupplier != null) {
+ tabSupplier.get().markBaseChanged();
+ }
+
+ String message = switch (modifiedEntriesCount) {
+ case 0 ->
+ Localization.lang("No entry needed a clean up");
+ case 1 ->
+ Localization.lang("One entry needed a clean up");
+ default ->
+ Localization.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount));
+ };
+
+ dialogService.notify(message);
+ }
+
+ private void cleanup(CleanupPreferences cleanupPreferences, List entries) {
+ List failures = new ArrayList<>();
+
+ String editName = Localization.lang("Clean up entry(s)");
+ // undo granularity is on a set of all entries
+ NamedCompoundEdit compoundEdit = new NamedCompoundEdit(editName);
+
+ for (BibEntry entry : entries) {
+ if (doCleanup(cleanupPreferences, entry, compoundEdit, failures)) {
+ modifiedEntriesCount++;
+ }
+ }
+
+ compoundEdit.end();
+
+ if (compoundEdit.hasEdits()) {
+ undoManager.addEdit(compoundEdit);
+ }
+
+ if (!failures.isEmpty()) {
+ showFailures(failures);
+ }
+ }
+
+ private void showFailures(List failures) {
+ String message = failures.stream()
+ .map(exception -> "- " + exception.getLocalizedMessage())
+ .collect(Collectors.joining("\n"));
+
+ Platform.runLater(() ->
+ dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), message)
+ );
+ }
+}
+
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupFileRelatedPanel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupFileRelatedPanel.java
new file mode 100644
index 00000000000..4a62ba78a6a
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupFileRelatedPanel.java
@@ -0,0 +1,88 @@
+package org.jabref.gui.cleanup;
+
+import java.nio.file.Path;
+import java.util.EnumSet;
+import java.util.Optional;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Label;
+import javafx.scene.layout.VBox;
+
+import org.jabref.logic.FilePreferences;
+import org.jabref.logic.cleanup.CleanupPreferences;
+import org.jabref.logic.cleanup.CleanupTabSelection;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.field.StandardField;
+
+import com.airhacks.afterburner.views.ViewLoader;
+import org.jspecify.annotations.NonNull;
+
+public class CleanupFileRelatedPanel extends VBox {
+
+ @FXML private Label cleanupRenamePdfLabel;
+
+ @FXML private CheckBox cleanupMovePdf;
+ @FXML private CheckBox cleanupMakePathsRelative;
+ @FXML private CheckBox cleanupRenamePdf;
+ @FXML private CheckBox cleanupRenamePdfOnlyRelativePaths;
+ @FXML private CheckBox cleanupDeletedFiles;
+ @FXML private CheckBox cleanupUpgradeExternalLinks;
+
+ private final CleanupFileViewModel viewModel;
+ private final CleanupDialogViewModel dialogViewModel;
+
+ public CleanupFileRelatedPanel(@NonNull BibDatabaseContext databaseContext,
+ @NonNull CleanupPreferences cleanupPreferences,
+ @NonNull FilePreferences filePreferences,
+ @NonNull CleanupDialogViewModel dialogViewModel) {
+
+ this.dialogViewModel = dialogViewModel;
+ this.viewModel = new CleanupFileViewModel(cleanupPreferences);
+
+ ViewLoader.view(this)
+ .root(this)
+ .load();
+
+ init(databaseContext, filePreferences);
+ bindProperties();
+ }
+
+ private void init(BibDatabaseContext databaseContext, FilePreferences filePreferences) {
+ Optional firstExistingDir = databaseContext.getFirstExistingFileDir(filePreferences);
+ if (firstExistingDir.isPresent()) {
+ cleanupMovePdf.setText(Localization.lang("Move linked files to default file directory %0", firstExistingDir.get().toString()));
+ } else {
+ cleanupMovePdf.setText(Localization.lang("Move linked files to default file directory %0", "..."));
+ viewModel.movePdfEnabled.set(false);
+ viewModel.movePdfSelected.set(false);
+ }
+
+ cleanupRenamePdfOnlyRelativePaths.disableProperty().bind(cleanupRenamePdf.selectedProperty().not());
+
+ cleanupUpgradeExternalLinks.setText(Localization.lang("Upgrade external PDF/PS links to use the '%0' field.", StandardField.FILE.getName()));
+
+ String currentPattern = Localization.lang("Filename format pattern (from preferences)")
+ .concat(filePreferences.getFileNamePattern());
+ cleanupRenamePdfLabel.setText(currentPattern);
+ }
+
+ private void bindProperties() {
+ cleanupMovePdf.selectedProperty().bindBidirectional(viewModel.movePdfSelected);
+ cleanupMovePdf.disableProperty().bind(viewModel.movePdfEnabled.not());
+ cleanupMakePathsRelative.selectedProperty().bindBidirectional(viewModel.makePathsRelativeSelected);
+ cleanupRenamePdf.selectedProperty().bindBidirectional(viewModel.renamePdfSelected);
+ cleanupRenamePdfOnlyRelativePaths.selectedProperty().bindBidirectional(viewModel.renamePdfOnlyRelativeSelected);
+ cleanupDeletedFiles.selectedProperty().bindBidirectional(viewModel.deleteFilesSelected);
+ cleanupUpgradeExternalLinks.selectedProperty().bindBidirectional(viewModel.upgradeLinksSelected);
+ }
+
+ @FXML
+ private void onApply() {
+ EnumSet selectedJobs = viewModel.getSelectedJobs();
+ CleanupTabSelection selectedTab = CleanupTabSelection.ofJobs(CleanupFileViewModel.FILE_RELATED_JOBS, selectedJobs);
+ dialogViewModel.apply(selectedTab);
+ getScene().getWindow().hide();
+ }
+}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupFileViewModel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupFileViewModel.java
new file mode 100644
index 00000000000..91023ab1d3b
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupFileViewModel.java
@@ -0,0 +1,68 @@
+package org.jabref.gui.cleanup;
+
+import java.util.EnumSet;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+
+import org.jabref.logic.cleanup.CleanupPreferences;
+
+public class CleanupFileViewModel {
+
+ public static final EnumSet FILE_RELATED_JOBS = EnumSet.of(
+ CleanupPreferences.CleanupStep.MOVE_PDF,
+ CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE,
+ CleanupPreferences.CleanupStep.RENAME_PDF,
+ CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS,
+ CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS,
+ CleanupPreferences.CleanupStep.CLEAN_UP_DELETED_LINKED_FILES
+ );
+
+ public final BooleanProperty movePdfSelected = new SimpleBooleanProperty();
+ public final BooleanProperty makePathsRelativeSelected = new SimpleBooleanProperty();
+ public final BooleanProperty renamePdfSelected = new SimpleBooleanProperty();
+ public final BooleanProperty renamePdfOnlyRelativeSelected = new SimpleBooleanProperty();
+ public final BooleanProperty upgradeLinksSelected = new SimpleBooleanProperty();
+ public final BooleanProperty deleteFilesSelected = new SimpleBooleanProperty();
+
+ public final BooleanProperty movePdfEnabled = new SimpleBooleanProperty(true);
+
+ public CleanupFileViewModel(CleanupPreferences preferences) {
+ movePdfSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.MOVE_PDF));
+ makePathsRelativeSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE));
+ renamePdfSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.RENAME_PDF));
+ renamePdfOnlyRelativeSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS));
+ upgradeLinksSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS));
+ deleteFilesSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_DELETED_LINKED_FILES));
+
+ renamePdfSelected.addListener((obs, oldVal, newVal) -> {
+ if (!newVal) {
+ renamePdfOnlyRelativeSelected.set(false);
+ }
+ });
+ }
+
+ public EnumSet getSelectedJobs() {
+ EnumSet activeJobs = EnumSet.noneOf(CleanupPreferences.CleanupStep.class);
+ if (movePdfSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.MOVE_PDF);
+ }
+ if (makePathsRelativeSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE);
+ }
+ if (renamePdfSelected.get()) {
+ if (renamePdfOnlyRelativeSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS);
+ } else {
+ activeJobs.add(CleanupPreferences.CleanupStep.RENAME_PDF);
+ }
+ }
+ if (upgradeLinksSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS);
+ }
+ if (deleteFilesSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_DELETED_LINKED_FILES);
+ }
+ return activeJobs;
+ }
+}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupMultiFieldPanel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupMultiFieldPanel.java
new file mode 100644
index 00000000000..e126ee50a40
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupMultiFieldPanel.java
@@ -0,0 +1,57 @@
+package org.jabref.gui.cleanup;
+
+import java.util.EnumSet;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.CheckBox;
+import javafx.scene.layout.VBox;
+
+import org.jabref.logic.cleanup.CleanupPreferences;
+import org.jabref.logic.cleanup.CleanupTabSelection;
+
+import com.airhacks.afterburner.views.ViewLoader;
+import org.jspecify.annotations.NonNull;
+
+public class CleanupMultiFieldPanel extends VBox {
+ @FXML private CheckBox cleanupDoi;
+ @FXML private CheckBox cleanupEprint;
+ @FXML private CheckBox cleanupUrl;
+ @FXML private CheckBox cleanupBibLaTeX;
+ @FXML private CheckBox cleanupBibTeX;
+ @FXML private CheckBox cleanupTimestampToCreationDate;
+ @FXML private CheckBox cleanupTimestampToModificationDate;
+
+ private final CleanupMultiFieldViewModel viewModel;
+ private final CleanupDialogViewModel dialogViewModel;
+
+ public CleanupMultiFieldPanel(@NonNull CleanupPreferences cleanupPreferences,
+ @NonNull CleanupDialogViewModel dialogViewModel) {
+
+ this.dialogViewModel = dialogViewModel;
+ this.viewModel = new CleanupMultiFieldViewModel(cleanupPreferences);
+
+ ViewLoader.view(this)
+ .root(this)
+ .load();
+
+ bindProperties();
+ }
+
+ private void bindProperties() {
+ cleanupDoi.selectedProperty().bindBidirectional(viewModel.doiSelected);
+ cleanupEprint.selectedProperty().bindBidirectional(viewModel.eprintSelected);
+ cleanupUrl.selectedProperty().bindBidirectional(viewModel.urlSelected);
+ cleanupBibTeX.selectedProperty().bindBidirectional(viewModel.bibTexSelected);
+ cleanupBibLaTeX.selectedProperty().bindBidirectional(viewModel.bibLaTexSelected);
+ cleanupTimestampToCreationDate.selectedProperty().bindBidirectional(viewModel.timestampToCreationSelected);
+ cleanupTimestampToModificationDate.selectedProperty().bindBidirectional(viewModel.timestampToModificationSelected);
+ }
+
+ @FXML
+ private void onApply() {
+ EnumSet selectedJobs = viewModel.getSelectedJobs();
+ CleanupTabSelection selectedTab = CleanupTabSelection.ofJobs(CleanupMultiFieldViewModel.MULTI_FIELD_JOBS, selectedJobs);
+ dialogViewModel.apply(selectedTab);
+ getScene().getWindow().hide();
+ }
+}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupMultiFieldViewModel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupMultiFieldViewModel.java
new file mode 100644
index 00000000000..b401066f4e1
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupMultiFieldViewModel.java
@@ -0,0 +1,94 @@
+package org.jabref.gui.cleanup;
+
+import java.util.EnumSet;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+
+import org.jabref.logic.cleanup.CleanupPreferences;
+
+public class CleanupMultiFieldViewModel {
+
+ public static final EnumSet MULTI_FIELD_JOBS = EnumSet.of(
+ CleanupPreferences.CleanupStep.CLEAN_UP_DOI,
+ CleanupPreferences.CleanupStep.CLEANUP_EPRINT,
+ CleanupPreferences.CleanupStep.CLEAN_UP_URL,
+ CleanupPreferences.CleanupStep.CONVERT_TO_BIBLATEX,
+ CleanupPreferences.CleanupStep.CONVERT_TO_BIBTEX,
+ CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE,
+ CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE
+ );
+
+ public final BooleanProperty doiSelected = new SimpleBooleanProperty();
+ public final BooleanProperty eprintSelected = new SimpleBooleanProperty();
+ public final BooleanProperty urlSelected = new SimpleBooleanProperty();
+ public final BooleanProperty bibTexSelected = new SimpleBooleanProperty();
+ public final BooleanProperty bibLaTexSelected = new SimpleBooleanProperty();
+ public final BooleanProperty timestampToCreationSelected = new SimpleBooleanProperty();
+ public final BooleanProperty timestampToModificationSelected = new SimpleBooleanProperty();
+
+ public CleanupMultiFieldViewModel(CleanupPreferences preferences) {
+ doiSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_DOI));
+ eprintSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CLEANUP_EPRINT));
+ urlSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_URL));
+ bibTexSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CONVERT_TO_BIBTEX));
+ bibLaTexSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CONVERT_TO_BIBLATEX));
+ timestampToCreationSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE));
+ timestampToModificationSelected.set(preferences.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE));
+
+ bibTexSelected.addListener(
+ (obs, oldVal, newVal) -> {
+ if (newVal) {
+ bibLaTexSelected.set(false);
+ }
+ }
+ );
+ bibLaTexSelected.addListener(
+ (obs, oldVal, newVal) -> {
+ if (newVal) {
+ bibTexSelected.set(false);
+ }
+ }
+ );
+ timestampToCreationSelected.addListener(
+ (obs, oldVal, newVal) -> {
+ if (newVal) {
+ timestampToModificationSelected.set(false);
+ }
+ }
+ );
+ timestampToModificationSelected.addListener(
+ (obs, oldVal, newVal) -> {
+ if (newVal) {
+ timestampToCreationSelected.set(false);
+ }
+ }
+ );
+ }
+
+ public EnumSet getSelectedJobs() {
+ EnumSet activeJobs = EnumSet.noneOf(CleanupPreferences.CleanupStep.class);
+ if (doiSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_DOI);
+ }
+ if (eprintSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CLEANUP_EPRINT);
+ }
+ if (urlSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_URL);
+ }
+ if (bibTexSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TO_BIBTEX);
+ }
+ if (bibLaTexSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TO_BIBLATEX);
+ }
+ if (timestampToCreationSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE);
+ }
+ if (timestampToModificationSelected.get()) {
+ activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE);
+ }
+ return activeJobs;
+ }
+}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java
deleted file mode 100644
index c8c1cae125e..00000000000
--- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.jabref.gui.cleanup;
-
-import java.nio.file.Path;
-import java.util.EnumSet;
-import java.util.Optional;
-
-import javafx.collections.FXCollections;
-import javafx.fxml.FXML;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.Label;
-import javafx.scene.layout.VBox;
-
-import org.jabref.gui.commonfxcontrols.FieldFormatterCleanupsPanel;
-import org.jabref.logic.FilePreferences;
-import org.jabref.logic.cleanup.CleanupPreferences;
-import org.jabref.logic.cleanup.FieldFormatterCleanups;
-import org.jabref.logic.l10n.Localization;
-import org.jabref.model.database.BibDatabaseContext;
-import org.jabref.model.entry.field.FieldTextMapper;
-import org.jabref.model.entry.field.StandardField;
-
-import com.airhacks.afterburner.views.ViewLoader;
-import org.jspecify.annotations.NonNull;
-
-public class CleanupPresetPanel extends VBox {
-
- private final BibDatabaseContext databaseContext;
- @FXML private Label cleanupRenamePDFLabel;
- @FXML private CheckBox cleanUpDOI;
- @FXML private CheckBox cleanUpEprint;
- @FXML private CheckBox cleanUpURL;
- @FXML private CheckBox cleanUpMovePDF;
- @FXML private CheckBox cleanUpMakePathsRelative;
- @FXML private CheckBox cleanUpRenamePDF;
- @FXML private CheckBox cleanUpRenamePDFonlyRelativePaths;
- @FXML private CheckBox cleanUpDeletedFiles;
- @FXML private CheckBox cleanUpUpgradeExternalLinks;
- @FXML private CheckBox cleanUpBiblatex;
- @FXML private CheckBox cleanUpBibtex;
- @FXML private CheckBox cleanUpTimestampToCreationDate;
- @FXML private CheckBox cleanUpTimestampToModificationDate;
- @FXML private FieldFormatterCleanupsPanel formatterCleanupsPanel;
-
- public CleanupPresetPanel(@NonNull BibDatabaseContext databaseContext,
- CleanupPreferences cleanupPreferences,
- FilePreferences filePreferences) {
- this.databaseContext = databaseContext;
-
- ViewLoader.view(this)
- .root(this)
- .load();
-
- init(cleanupPreferences, filePreferences);
- }
-
- private void init(CleanupPreferences cleanupPreferences, FilePreferences filePreferences) {
- Optional firstExistingDir = databaseContext.getFirstExistingFileDir(filePreferences);
- if (firstExistingDir.isPresent()) {
- cleanUpMovePDF.setText(Localization.lang("Move linked files to default file directory %0", firstExistingDir.get().toString()));
- } else {
- cleanUpMovePDF.setText(Localization.lang("Move linked files to default file directory %0", "..."));
-
- // Since the directory does not exist, we cannot move it to there. So, this option is not checked - regardless of the presets stored in the preferences.
- cleanUpMovePDF.setDisable(true);
- cleanUpMovePDF.setSelected(false);
- }
-
- cleanUpRenamePDFonlyRelativePaths.disableProperty().bind(cleanUpRenamePDF.selectedProperty().not());
-
- cleanUpUpgradeExternalLinks.setText(Localization.lang("Upgrade external PDF/PS links to use the '%0' field.", FieldTextMapper.getDisplayName(StandardField.FILE)));
-
- String currentPattern = Localization.lang("Filename format pattern (from preferences)")
- .concat(filePreferences.getFileNamePattern());
- cleanupRenamePDFLabel.setText(currentPattern);
-
- cleanUpBibtex.selectedProperty().addListener(
- (observable, oldValue, newValue) -> {
- if (newValue) {
- cleanUpBiblatex.selectedProperty().setValue(false);
- }
- });
- cleanUpBiblatex.selectedProperty().addListener(
- (observable, oldValue, newValue) -> {
- if (newValue) {
- cleanUpBibtex.selectedProperty().setValue(false);
- }
- });
-
- cleanUpTimestampToCreationDate.selectedProperty().addListener(
- (observable, oldValue, newValue) -> {
- if (newValue) {
- cleanUpTimestampToModificationDate.selectedProperty().setValue(false);
- }
- });
- cleanUpTimestampToModificationDate.selectedProperty().addListener(
- (observable, oldValue, newValue) -> {
- if (newValue) {
- cleanUpTimestampToCreationDate.selectedProperty().setValue(false);
- }
- });
- updateDisplay(cleanupPreferences);
- }
-
- private void updateDisplay(CleanupPreferences preset) {
- cleanUpDOI.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_DOI));
- cleanUpEprint.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEANUP_EPRINT));
- cleanUpURL.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_URL));
- if (!cleanUpMovePDF.isDisabled()) {
- cleanUpMovePDF.setSelected(preset.isActive(CleanupPreferences.CleanupStep.MOVE_PDF));
- }
- cleanUpMakePathsRelative.setSelected(preset.isActive(CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE));
- cleanUpRenamePDF.setSelected(preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF));
- cleanUpRenamePDFonlyRelativePaths.setSelected(preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS));
- cleanUpUpgradeExternalLinks.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS));
- cleanUpDeletedFiles.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_DELETED_LINKED_FILES));
- cleanUpBiblatex.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TO_BIBLATEX));
- cleanUpBibtex.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TO_BIBTEX));
- cleanUpTimestampToCreationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE));
- cleanUpTimestampToModificationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE));
- cleanUpTimestampToModificationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.DO_NOT_CONVERT_TIMESTAMP));
- formatterCleanupsPanel.cleanupsDisableProperty().setValue(!preset.getFieldFormatterCleanups().isEnabled());
- formatterCleanupsPanel.cleanupsProperty().setValue(FXCollections.observableArrayList(preset.getFieldFormatterCleanups().getConfiguredActions()));
- }
-
- public CleanupPreferences getCleanupPreset() {
- EnumSet activeJobs = EnumSet.noneOf(CleanupPreferences.CleanupStep.class);
-
- if (cleanUpMovePDF.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.MOVE_PDF);
- }
- if (cleanUpDOI.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_DOI);
- }
- if (cleanUpEprint.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CLEANUP_EPRINT);
- }
- if (cleanUpURL.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_URL);
- }
- if (cleanUpMakePathsRelative.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE);
- }
- if (cleanUpRenamePDF.isSelected()) {
- if (cleanUpRenamePDFonlyRelativePaths.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS);
- } else {
- activeJobs.add(CleanupPreferences.CleanupStep.RENAME_PDF);
- }
- }
- if (cleanUpUpgradeExternalLinks.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS);
- }
- if (cleanUpDeletedFiles.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_DELETED_LINKED_FILES);
- }
- if (cleanUpBiblatex.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TO_BIBLATEX);
- }
- if (cleanUpBibtex.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TO_BIBTEX);
- }
- if (cleanUpTimestampToCreationDate.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE);
- }
- if (cleanUpTimestampToModificationDate.isSelected()) {
- activeJobs.add(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE);
- }
-
- activeJobs.add(CleanupPreferences.CleanupStep.FIX_FILE_LINKS);
-
- return new CleanupPreferences(activeJobs, new FieldFormatterCleanups(
- !formatterCleanupsPanel.cleanupsDisableProperty().getValue(),
- formatterCleanupsPanel.cleanupsProperty()));
- }
-}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java
index ccec9065541..d2834cfd935 100644
--- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java
@@ -1,25 +1,12 @@
package org.jabref.gui.cleanup;
-import java.util.List;
-import java.util.Optional;
-
import javax.swing.undo.UndoManager;
-import javafx.application.Platform;
-
import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.actions.ActionHelper;
import org.jabref.gui.actions.SimpleCommand;
-import org.jabref.gui.undo.NamedCompoundEdit;
-import org.jabref.gui.undo.UndoableFieldChange;
-import org.jabref.logic.JabRefException;
-import org.jabref.logic.cleanup.CleanupPreferences;
-import org.jabref.logic.cleanup.CleanupWorker;
-import org.jabref.logic.l10n.Localization;
import org.jabref.logic.preferences.CliPreferences;
-import org.jabref.model.FieldChange;
-import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
public class CleanupSingleAction extends SimpleCommand {
@@ -30,10 +17,11 @@ public class CleanupSingleAction extends SimpleCommand {
private final BibEntry entry;
private final UndoManager undoManager;
- private boolean isCanceled;
- private int modifiedEntriesCount;
-
- public CleanupSingleAction(BibEntry entry, CliPreferences preferences, DialogService dialogService, StateManager stateManager, UndoManager undoManager) {
+ public CleanupSingleAction(BibEntry entry,
+ CliPreferences preferences,
+ DialogService dialogService,
+ StateManager stateManager,
+ UndoManager undoManager) {
this.entry = entry;
this.preferences = preferences;
this.dialogService = dialogService;
@@ -45,79 +33,19 @@ public CleanupSingleAction(BibEntry entry, CliPreferences preferences, DialogSer
@Override
public void execute() {
- isCanceled = false;
+ if (stateManager.getActiveDatabase().isEmpty()) {
+ return;
+ }
CleanupDialog cleanupDialog = new CleanupDialog(
+ entry,
stateManager.getActiveDatabase().get(),
- preferences.getCleanupPreferences(),
- preferences.getFilePreferences()
- );
-
- Optional chosenPreset = dialogService.showCustomDialogAndWait(cleanupDialog);
-
- chosenPreset.ifPresent(preset -> {
- if (preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF) && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) {
- boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(Localization.lang("Autogenerate PDF Names"),
- Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"),
- Localization.lang("Autogenerate PDF Names"),
- Localization.lang("Cancel"),
- Localization.lang("Do not ask again"),
- optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut));
- if (!confirmed) {
- isCanceled = true;
- return;
- }
- }
-
- preferences.getCleanupPreferences().setActiveJobs(preset.getActiveJobs());
- preferences.getCleanupPreferences().setFieldFormatterCleanups(preset.getFieldFormatterCleanups());
-
- cleanup(stateManager.getActiveDatabase().get(), preset);
- });
- }
-
- /**
- * Runs the cleanup on the entry and records the change.
- */
- private void doCleanup(BibDatabaseContext databaseContext, CleanupPreferences preset, BibEntry entry, NamedCompoundEdit compoundEdit) {
- // Create and run cleaner
- CleanupWorker cleaner = new CleanupWorker(
- databaseContext,
- preferences.getFilePreferences(),
- preferences.getTimestampPreferences()
+ preferences,
+ dialogService,
+ stateManager,
+ undoManager
);
- List changes = cleaner.cleanup(preset, entry);
-
- // Register undo action
- for (FieldChange change : changes) {
- compoundEdit.addEdit(new UndoableFieldChange(change));
- }
-
- if (!cleaner.getFailures().isEmpty()) {
- this.showFailures(cleaner.getFailures());
- }
- }
-
- private void cleanup(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences) {
- // undo granularity is on entry level
- NamedCompoundEdit compoundEdit = new NamedCompoundEdit(Localization.lang("Cleanup entry"));
-
- doCleanup(databaseContext, cleanupPreferences, entry, compoundEdit);
-
- compoundEdit.end();
- if (compoundEdit.hasEdits()) {
- undoManager.addEdit(compoundEdit);
- }
- }
-
- private void showFailures(List failures) {
- StringBuilder sb = new StringBuilder();
- for (JabRefException exception : failures) {
- sb.append("- ").append(exception.getLocalizedMessage()).append("\n");
- }
- Platform.runLater(() ->
- dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), sb.toString())
- );
+ dialogService.showCustomDialogAndWait(cleanupDialog);
}
}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleFieldPanel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleFieldPanel.java
new file mode 100644
index 00000000000..cbb26482fd1
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleFieldPanel.java
@@ -0,0 +1,46 @@
+package org.jabref.gui.cleanup;
+
+import javafx.fxml.FXML;
+import javafx.scene.layout.VBox;
+
+import org.jabref.gui.commonfxcontrols.FieldFormatterCleanupsPanel;
+import org.jabref.logic.cleanup.CleanupPreferences;
+import org.jabref.logic.cleanup.CleanupTabSelection;
+import org.jabref.logic.cleanup.FieldFormatterCleanups;
+
+import com.airhacks.afterburner.views.ViewLoader;
+import org.jspecify.annotations.NonNull;
+
+public class CleanupSingleFieldPanel extends VBox {
+
+ @FXML private FieldFormatterCleanupsPanel formatterCleanupsPanel;
+
+ private final CleanupSingleFieldViewModel viewModel;
+ private final CleanupDialogViewModel dialogViewModel;
+
+ public CleanupSingleFieldPanel(@NonNull CleanupPreferences cleanupPreferences,
+ @NonNull CleanupDialogViewModel dialogViewModel) {
+
+ this.dialogViewModel = dialogViewModel;
+ this.viewModel = new CleanupSingleFieldViewModel(cleanupPreferences.getFieldFormatterCleanups());
+
+ ViewLoader.view(this)
+ .root(this)
+ .load();
+
+ bindProperties();
+ }
+
+ private void bindProperties() {
+ formatterCleanupsPanel.cleanupsDisableProperty().bind(viewModel.cleanupsEnabled.not());
+ formatterCleanupsPanel.cleanupsProperty().bindBidirectional(viewModel.cleanups);
+ }
+
+ @FXML
+ private void onApply() {
+ FieldFormatterCleanups selectedFormatters = viewModel.getSelectedFormatters();
+ CleanupTabSelection selectedTab = CleanupTabSelection.ofFormatters(selectedFormatters);
+ dialogViewModel.apply(selectedTab);
+ getScene().getWindow().hide();
+ }
+}
diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleFieldViewModel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleFieldViewModel.java
new file mode 100644
index 00000000000..f41770cb31e
--- /dev/null
+++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleFieldViewModel.java
@@ -0,0 +1,24 @@
+package org.jabref.gui.cleanup;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleListProperty;
+import javafx.collections.FXCollections;
+
+import org.jabref.logic.cleanup.FieldFormatterCleanup;
+import org.jabref.logic.cleanup.FieldFormatterCleanups;
+
+public class CleanupSingleFieldViewModel {
+ public final BooleanProperty cleanupsEnabled = new SimpleBooleanProperty(true);
+ public final ListProperty cleanups = new SimpleListProperty<>(FXCollections.observableArrayList());
+
+ public CleanupSingleFieldViewModel(FieldFormatterCleanups initialCleanups) {
+ cleanupsEnabled.set(initialCleanups.isEnabled());
+ cleanups.setAll(initialCleanups.getConfiguredActions());
+ }
+
+ public FieldFormatterCleanups getSelectedFormatters() {
+ return new FieldFormatterCleanups(cleanupsEnabled.get(), cleanups.get());
+ }
+}
diff --git a/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupDialog.fxml b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupDialog.fxml
new file mode 100644
index 00000000000..9243c5ee63e
--- /dev/null
+++ b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupDialog.fxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupFileRelatedPanel.fxml b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupFileRelatedPanel.fxml
new file mode 100644
index 00000000000..b9c42f66dd6
--- /dev/null
+++ b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupFileRelatedPanel.fxml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupMultiFieldPanel.fxml b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupMultiFieldPanel.fxml
new file mode 100644
index 00000000000..f82498ea08f
--- /dev/null
+++ b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupMultiFieldPanel.fxml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupPresetPanel.fxml b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupPresetPanel.fxml
deleted file mode 100644
index f6421d75a4d..00000000000
--- a/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupPresetPanel.fxml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupSingleFieldPanel.fxml b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupSingleFieldPanel.fxml
new file mode 100644
index 00000000000..e4f44d188d7
--- /dev/null
+++ b/jabgui/src/main/resources/org/jabref/gui/cleanup/CleanupSingleFieldPanel.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jablib/src/main/java/org/jabref/logic/cleanup/CleanupTabSelection.java b/jablib/src/main/java/org/jabref/logic/cleanup/CleanupTabSelection.java
new file mode 100644
index 00000000000..daaf467cbbe
--- /dev/null
+++ b/jablib/src/main/java/org/jabref/logic/cleanup/CleanupTabSelection.java
@@ -0,0 +1,50 @@
+package org.jabref.logic.cleanup;
+
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Optional;
+
+public record CleanupTabSelection(
+ EnumSet allJobs,
+ EnumSet selectedJobs,
+ Optional formatters) {
+
+ public CleanupTabSelection {
+ allJobs = allJobs == null ? EnumSet.noneOf(CleanupPreferences.CleanupStep.class) : EnumSet.copyOf(allJobs);
+ selectedJobs = selectedJobs == null ? EnumSet.noneOf(CleanupPreferences.CleanupStep.class) : EnumSet.copyOf(selectedJobs);
+ formatters = Objects.requireNonNullElse(formatters, Optional.empty());
+ }
+
+ public static CleanupTabSelection ofJobs(EnumSet allJobs, EnumSet selectedJobs) {
+ return new CleanupTabSelection(allJobs, selectedJobs, Optional.empty());
+ }
+
+ public static CleanupTabSelection ofFormatters(FieldFormatterCleanups cleanups) {
+ return new CleanupTabSelection(EnumSet.noneOf(CleanupPreferences.CleanupStep.class), EnumSet.noneOf(CleanupPreferences.CleanupStep.class), Optional.of(cleanups));
+ }
+
+ public boolean isFormatterTab() {
+ return formatters.isPresent();
+ }
+
+ public boolean isJobTab() {
+ return !allJobs.isEmpty();
+ }
+
+ public CleanupPreferences updatePreferences(CleanupPreferences currentPreferences) {
+ EnumSet updatedJobs = EnumSet.copyOf(currentPreferences.getActiveJobs());
+
+ if (!allJobs.isEmpty()) {
+ updatedJobs.removeAll(allJobs);
+ }
+ if (!selectedJobs.isEmpty()) {
+ updatedJobs.addAll(selectedJobs);
+ }
+
+ CleanupPreferences updated = new CleanupPreferences(updatedJobs);
+
+ updated.setFieldFormatterCleanups(formatters.orElse(currentPreferences.getFieldFormatterCleanups()));
+
+ return updated;
+ }
+}
diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties
index 14d987aad0c..c8adf47b1cb 100644
--- a/jablib/src/main/resources/l10n/JabRef_en.properties
+++ b/jablib/src/main/resources/l10n/JabRef_en.properties
@@ -1247,6 +1247,8 @@ Use\ abbreviated\ firstname\ whenever\ possible=Use abbreviated firstname whenev
Use\ abbreviated\ and\ full\ firstname=Use abbreviated and full firstname
Name\ format=Name format
First\ names=First names
+Clean\ up\ entry=Clean up entry
+Clean\ up\ entry(s)=Clean up entry(s)
Clean\ up\ entries=Clean up entries
Automatically\ assign\ new\ entry\ to\ selected\ groups=Automatically assign new entry to selected groups
%0\ mode=%0 mode
@@ -2863,6 +2865,8 @@ Writing\ metadata\ to\ %0=Writing metadata to %0
Get\ more\ themes...=Get more themes...
Miscellaneous=Miscellaneous
+Single\ field =Single field
+Multi-field=Multi-field
File-related=File-related
Add\ selected\ entry(s)\ to\ library=Add selected entry(s) to library
diff --git a/jablib/src/test/java/org/jabref/logic/cleanup/CleanupTabSelectionTest.java b/jablib/src/test/java/org/jabref/logic/cleanup/CleanupTabSelectionTest.java
new file mode 100644
index 00000000000..5a97f1e3817
--- /dev/null
+++ b/jablib/src/test/java/org/jabref/logic/cleanup/CleanupTabSelectionTest.java
@@ -0,0 +1,67 @@
+package org.jabref.logic.cleanup;
+
+import java.util.EnumSet;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class CleanupTabSelectionTest {
+
+ private CleanupPreferences cleanupPreferences;
+
+ @BeforeEach
+ void setUp() {
+ cleanupPreferences = new CleanupPreferences(EnumSet.of(
+ CleanupPreferences.CleanupStep.CLEAN_UP_DOI
+ ));
+ }
+
+ @Test
+ void updatePreferencesReplacesStepsInSameCategory() {
+ EnumSet allJobs = EnumSet.of(
+ CleanupPreferences.CleanupStep.CLEAN_UP_DOI,
+ CleanupPreferences.CleanupStep.CLEANUP_EPRINT
+ );
+ EnumSet selectedJobs = EnumSet.of(
+ CleanupPreferences.CleanupStep.CLEANUP_EPRINT
+ );
+
+ CleanupTabSelection selection = CleanupTabSelection.ofJobs(allJobs, selectedJobs);
+ CleanupPreferences result = selection.updatePreferences(cleanupPreferences);
+
+ EnumSet expected = EnumSet.of(CleanupPreferences.CleanupStep.CLEANUP_EPRINT);
+ assertEquals(expected, result.getActiveJobs());
+ }
+
+ @Test
+ void updatePreferencesAddsStepInDifferentCategory() {
+ EnumSet allJobs = EnumSet.of(
+ CleanupPreferences.CleanupStep.MOVE_PDF,
+ CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE
+ );
+ EnumSet selectedJobs = EnumSet.of(
+ CleanupPreferences.CleanupStep.MOVE_PDF
+ );
+
+ CleanupTabSelection selection = CleanupTabSelection.ofJobs(allJobs, selectedJobs);
+ CleanupPreferences result = selection.updatePreferences(cleanupPreferences);
+
+ EnumSet expected = EnumSet.of(
+ CleanupPreferences.CleanupStep.CLEAN_UP_DOI,
+ CleanupPreferences.CleanupStep.MOVE_PDF
+ );
+ assertEquals(expected, result.getActiveJobs());
+ }
+
+ @Test
+ void updatePreferencesAppliesFormatterCleanups() {
+ FieldFormatterCleanups formatter = new FieldFormatterCleanups(true, FieldFormatterCleanups.parse("title[identity]"));
+
+ CleanupTabSelection selection = CleanupTabSelection.ofFormatters(formatter);
+ CleanupPreferences result = selection.updatePreferences(cleanupPreferences);
+
+ assertEquals(formatter, result.getFieldFormatterCleanups());
+ }
+}