diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile
new file mode 100644
index 00000000000..007108a0f49
--- /dev/null
+++ b/.gitpod.Dockerfile
@@ -0,0 +1,8 @@
+# See https://www.gitpod.io/docs/java-in-gitpod/ for a full documentation of Java in GitPod
+
+FROM gitpod/workspace-full
+
+# All available versions can be listed using sdk ls java
+# More information about SDKMAN available at https://github.com/sdkman/sdkman-cli#sdkman-cli
+RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \
+ && sdk install java 18.0.1.1-open"
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 00000000000..a0aa262ea90
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,16 @@
+image:
+ file: .gitpod.Dockerfile
+tasks:
+ - init: ./gradlew assemble
+jetbrains:
+ intellij:
+ plugins:
+ - CheckStyle-IDEA
+ - zielu.gittoolbox
+vscode:
+ extensions:
+ - redhat.java
+ - richardwillis.vscode-gradle
+ - vscjava.vscode-java-debug
+ - kiteco.kite
+ - DavidAnson.vscode-markdownlint
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0fb2cba24c..4bad6edb909 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- The Medline/Pubmed search now also supports the [default fields and operators for searching](https://docs.jabref.org/collect/import-using-online-bibliographic-database#search-syntax). [forum#3554](https://discourse.jabref.org/t/native-pubmed-search/3354)
- We improved group expansion arrow that prevent it from activating group when expanding or collapsing. [#7982](https://github.com/JabRef/jabref/issues/7982), [#3176](https://github.com/JabRef/jabref/issues/3176)
- When configured SSL certificates changed, JabRef warns the user to restart to apply the configuration.
+- We improved the appearances and logic of the "Manage field names & content" dialog, and renamed it to "Automatic field editor". [#6536](https://github.com/JabRef/jabref/issues/6536)
- We fixed an issue that caused JabRef to sometimes open multiple instances when "Remote Operation" is enabled. [#8653](https://github.com/JabRef/jabref/issues/8653)
### Fixed
diff --git a/README.md b/README.md
index 78a90d2785d..08b68588cfb 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ An explanation of donation possibilities and usage of donations is available at
[![OpenHub](https://www.openhub.net/p/jabref/widgets/project_thin_badge.gif)](https://www.openhub.net/p/jabref)
[![Deployment Status](https://github.com/JabRef/jabref/workflows/Deployment/badge.svg)](https://github.com/JabRef/jabref/actions?query=workflow%3ADeployment)
[![Test Status](https://github.com/JabRef/jabref/workflows/Tests/badge.svg)](https://github.com/JabRef/jabref/actions?query=workflow%3ATests)
-[![codecov.io](https://codecov.io/github/JabRef/jabref/coverage.svg?branch=master)](https://codecov.io/github/JabRef/jabref?branch=master)
+[![codecov.io](https://codecov.io/github/JabRef/jabref/coverage.svg?branch=master)](https://codecov.io/github/JabRef/jabref?branch=main)
Want to be part of a free and open-source project that tens of thousands of scientists use every day?
Check out the ways you can contribute, below:
diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java
index efd992dd688..2bbbf8f4412 100644
--- a/src/main/java/org/jabref/gui/JabRefFrame.java
+++ b/src/main/java/org/jabref/gui/JabRefFrame.java
@@ -67,9 +67,9 @@
import org.jabref.gui.edit.CopyMoreAction;
import org.jabref.gui.edit.EditAction;
import org.jabref.gui.edit.ManageKeywordsAction;
-import org.jabref.gui.edit.MassSetFieldsAction;
import org.jabref.gui.edit.OpenBrowserAction;
import org.jabref.gui.edit.ReplaceStringAction;
+import org.jabref.gui.edit.automaticfiededitor.AutomaticFieldEditorAction;
import org.jabref.gui.entryeditor.OpenEntryEditorAction;
import org.jabref.gui.entryeditor.PreviewSwitchAction;
import org.jabref.gui.exporter.ExportCommand;
@@ -750,8 +750,7 @@ private MenuBar createMenu() {
new SeparatorMenuItem(),
factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(stateManager)),
- factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldsAction(stateManager, dialogService, undoManager)));
-
+ factory.createMenuItem(StandardActions.AUTOMATIC_FIELD_EDITOR, new AutomaticFieldEditorAction(stateManager, dialogService)));
SeparatorMenuItem specialFieldsSeparator = new SeparatorMenuItem();
specialFieldsSeparator.visibleProperty().bind(prefs.getSpecialFieldsPreferences().specialFieldsEnabledProperty());
diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java
index aec2323eb26..b673a4e516b 100644
--- a/src/main/java/org/jabref/gui/actions/StandardActions.java
+++ b/src/main/java/org/jabref/gui/actions/StandardActions.java
@@ -78,6 +78,8 @@ public enum StandardActions implements Action {
REPLACE_ALL(Localization.lang("Find and replace"), KeyBinding.REPLACE_STRING),
MANAGE_KEYWORDS(Localization.lang("Manage keywords")),
MASS_SET_FIELDS(Localization.lang("Manage field names & content")),
+
+ AUTOMATIC_FIELD_EDITOR(Localization.lang("Automatic field editor")),
TOGGLE_GROUPS(Localization.lang("Groups"), IconTheme.JabRefIcons.TOGGLE_GROUPS, KeyBinding.TOGGLE_GROUPS_INTERFACE),
TOOGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION),
TOGGLE_WEB_SEARCH(Localization.lang("Web search"), Localization.lang("Toggle web search interface"), IconTheme.JabRefIcons.WWW, KeyBinding.WEB_SEARCH),
diff --git a/src/main/java/org/jabref/gui/edit/MassSetFieldsAction.java b/src/main/java/org/jabref/gui/edit/MassSetFieldsAction.java
deleted file mode 100644
index 392f90536fc..00000000000
--- a/src/main/java/org/jabref/gui/edit/MassSetFieldsAction.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.jabref.gui.edit;
-
-import javax.swing.undo.UndoManager;
-
-import org.jabref.gui.DialogService;
-import org.jabref.gui.StateManager;
-import org.jabref.gui.actions.SimpleCommand;
-import org.jabref.model.database.BibDatabaseContext;
-
-import static org.jabref.gui.actions.ActionHelper.needsDatabase;
-import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;
-
-/**
- * An Action for launching mass field.
- *
- * Functionality:
- *
- *
Defaults to selected entries, or all entries if none are selected.
- *
Input field name
- *
Either set field, or clear field.
- *
- */
-public class MassSetFieldsAction extends SimpleCommand {
-
- private final StateManager stateManager;
- private DialogService dialogService;
- private UndoManager undoManager;
-
- public MassSetFieldsAction(StateManager stateManager, DialogService dialogService, UndoManager undoManager) {
- this.stateManager = stateManager;
- this.dialogService = dialogService;
- this.undoManager = undoManager;
-
- this.executable.bind(needsDatabase(stateManager).and(needsEntriesSelected(stateManager)));
- }
-
- @Override
- public void execute() {
- BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null"));
- dialogService.showCustomDialogAndWait(new MassSetFieldsDialog(stateManager.getSelectedEntries(), database, dialogService, undoManager));
- }
-}
diff --git a/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java b/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java
deleted file mode 100644
index 20d5aa1f538..00000000000
--- a/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package org.jabref.gui.edit;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import javax.swing.undo.UndoManager;
-import javax.swing.undo.UndoableEdit;
-
-import javafx.application.Platform;
-import javafx.geometry.Insets;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.ComboBox;
-import javafx.scene.control.Label;
-import javafx.scene.control.RadioButton;
-import javafx.scene.control.TextField;
-import javafx.scene.control.ToggleGroup;
-import javafx.scene.control.Tooltip;
-import javafx.scene.layout.GridPane;
-
-import org.jabref.gui.DialogService;
-import org.jabref.gui.undo.NamedCompound;
-import org.jabref.gui.undo.UndoableFieldChange;
-import org.jabref.gui.util.BaseDialog;
-import org.jabref.gui.util.IconValidationDecorator;
-import org.jabref.logic.l10n.Localization;
-import org.jabref.model.database.BibDatabaseContext;
-import org.jabref.model.entry.BibEntry;
-import org.jabref.model.entry.field.Field;
-import org.jabref.model.entry.field.FieldFactory;
-import org.jabref.model.strings.StringUtil;
-
-import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator;
-import de.saxsys.mvvmfx.utils.validation.Severity;
-import de.saxsys.mvvmfx.utils.validation.ValidationMessage;
-import de.saxsys.mvvmfx.utils.validation.Validator;
-import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer;
-
-public class MassSetFieldsDialog extends BaseDialog {
-
- private final List entries;
- private final BibDatabaseContext database;
- private final DialogService dialogService;
-
- private RadioButton clearRadioButton;
- private RadioButton setRadioButton;
- private RadioButton appendRadioButton;
- private RadioButton renameRadioButton;
- private ComboBox fieldComboBox;
- private TextField setTextField;
- private TextField appendTextField;
- private TextField renameTextField;
- private CheckBox overwriteCheckBox;
- private UndoManager undoManager;
-
- MassSetFieldsDialog(List entries, BibDatabaseContext database, DialogService dialogService, UndoManager undoManager) {
- this.entries = entries;
- this.database = database;
- this.dialogService = dialogService;
- this.undoManager = undoManager;
-
- init();
- this.setTitle(Localization.lang("Manage field names & content"));
- this.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
- this.setResultConverter(button -> {
- if (button == ButtonType.OK) {
- performEdits();
- }
- return null;
- });
- }
-
- /**
- * Append a given value to a given field for all entries in a Collection. This method DOES NOT update any UndoManager,
- * but returns a relevant CompoundEdit that should be registered by the caller.
- *
- * @param entries The entries to process the operation for.
- * @param field The name of the field to append to.
- * @param textToAppend The value to set. A null in this case will simply preserve the current field state.
- * @return A CompoundEdit for the entire operation.
- */
- private static UndoableEdit massAppendField(Collection entries, Field field, String textToAppend) {
- String newValue = "";
-
- if (textToAppend != null) {
- newValue = textToAppend;
- }
-
- NamedCompound compoundEdit = new NamedCompound(Localization.lang("Append field"));
- for (BibEntry entry : entries) {
- Optional oldValue = entry.getField(field);
- entry.setField(field, oldValue.orElse("") + newValue);
- compoundEdit.addEdit(new UndoableFieldChange(entry, field, oldValue.orElse(null), newValue));
- }
- compoundEdit.end();
- return compoundEdit;
- }
-
- /**
- * Move contents from one field to another for a Collection of entries.
- *
- * @param entries The entries to do this operation for.
- * @param field The field to move contents from.
- * @param newField The field to move contents into.
- * @param overwriteValues If true, overwrites any existing values in the new field. If false, makes no change for
- * entries with existing value in the new field.
- * @return A CompoundEdit for the entire operation.
- */
- private static UndoableEdit massRenameField(Collection entries, Field field, Field newField,
- boolean overwriteValues) {
- NamedCompound compoundEdit = new NamedCompound(Localization.lang("Rename field"));
- for (BibEntry entry : entries) {
- Optional valToMove = entry.getField(field);
- // If there is no value, do nothing:
- if ((!valToMove.isPresent()) || valToMove.get().isEmpty()) {
- continue;
- }
- // If we are not allowed to overwrite values, check if there is a
- // non-empty value already for this entry for the new field:
- Optional valInNewField = entry.getField(newField);
- if (!overwriteValues && (valInNewField.isPresent()) && !valInNewField.get().isEmpty()) {
- continue;
- }
-
- entry.setField(newField, valToMove.get());
- compoundEdit.addEdit(new UndoableFieldChange(entry, newField, valInNewField.orElse(null), valToMove.get()));
- entry.clearField(field);
- compoundEdit.addEdit(new UndoableFieldChange(entry, field, valToMove.get(), null));
- }
- compoundEdit.end();
- return compoundEdit;
- }
-
- /**
- * Set a given field to a given value for all entries in a Collection. This method DOES NOT update any UndoManager,
- * but returns a relevant CompoundEdit that should be registered by the caller.
- *
- * @param entries The entries to set the field for.
- * @param field The name of the field to set.
- * @param textToSet The value to set. This value can be null, indicating that the field should be cleared.
- * @param overwriteValues Indicate whether the value should be set even if an entry already has the field set.
- * @return A CompoundEdit for the entire operation.
- */
- private static UndoableEdit massSetField(Collection entries, Field field, String textToSet,
- boolean overwriteValues) {
- NamedCompound compoundEdit = new NamedCompound(Localization.lang("Set field"));
- for (BibEntry entry : entries) {
- Optional oldValue = entry.getField(field);
- // If we are not allowed to overwrite values, check if there is a
- // nonempty
- // value already for this entry:
- if (!overwriteValues && (oldValue.isPresent()) && !oldValue.get().isEmpty()) {
- continue;
- }
- if (textToSet == null) {
- entry.clearField(field);
- } else {
- entry.setField(field, textToSet);
- }
- compoundEdit.addEdit(new UndoableFieldChange(entry, field, oldValue.orElse(null), textToSet));
- }
- compoundEdit.end();
- return compoundEdit;
- }
-
- private void init() {
- fieldComboBox = new ComboBox<>();
- fieldComboBox.setEditable(true);
- fieldComboBox.getItems().addAll(database.getDatabase().getAllVisibleFields().stream().map(Field::getName).collect(Collectors.toSet()));
-
- ToggleGroup toggleGroup = new ToggleGroup();
- clearRadioButton = new RadioButton(Localization.lang("Clear fields"));
- clearRadioButton.setToggleGroup(toggleGroup);
- renameRadioButton = new RadioButton(Localization.lang("Rename field to") + ":");
- renameRadioButton.setTooltip(new Tooltip(Localization.lang("Move contents of a field into a field with a different name")));
- renameRadioButton.setToggleGroup(toggleGroup);
- setRadioButton = new RadioButton(Localization.lang("Set fields") + ":");
- setRadioButton.setToggleGroup(toggleGroup);
- appendRadioButton = new RadioButton(Localization.lang("Append to fields") + ":");
- appendRadioButton.setToggleGroup(toggleGroup);
-
- setTextField = new TextField();
- setTextField.disableProperty().bind(setRadioButton.selectedProperty().not());
- appendTextField = new TextField();
- appendTextField.disableProperty().bind(appendRadioButton.selectedProperty().not());
- renameTextField = new TextField();
- renameTextField.disableProperty().bind(renameRadioButton.selectedProperty().not());
-
- overwriteCheckBox = new CheckBox(Localization.lang("Overwrite existing field values"));
-
- GridPane main = new GridPane();
- main.add(new Label(Localization.lang("Field name")), 0, 0);
- main.add(fieldComboBox, 1, 0);
- main.add(setRadioButton, 0, 2);
- main.add(setTextField, 1, 2);
- main.add(appendRadioButton, 0, 3);
- main.add(appendTextField, 1, 3);
- main.add(renameRadioButton, 0, 4);
- main.add(renameTextField, 1, 4);
- main.add(clearRadioButton, 0, 5);
- main.add(overwriteCheckBox, 0, 7);
-
- main.setPadding(new Insets(15, 15, 0, 15));
- main.setGridLinesVisible(false);
- main.setVgap(4);
- main.setHgap(10);
- getDialogPane().setContent(main);
-
- Validator fieldNameValidator = new FunctionBasedValidator<>(
- fieldComboBox.valueProperty(),
- StringUtil::isNotBlank,
- new ValidationMessage(Severity.ERROR, Localization.lang("You must enter at least one field name"))
- );
- Platform.runLater(() -> {
- // Need to run this async, otherwise the dialog does not work
- ControlsFxVisualizer visualizer = new ControlsFxVisualizer();
- visualizer.setDecoration(new IconValidationDecorator());
- visualizer.initVisualization(fieldNameValidator.getValidationStatus(), fieldComboBox, true);
- });
- }
-
- private void performEdits() {
- String toSet = setTextField.getText();
- if (toSet.isEmpty()) {
- toSet = null;
- }
-
- Field field = FieldFactory.parseField(fieldComboBox.getValue());
-
- NamedCompound compoundEdit = new NamedCompound(Localization.lang("Set field"));
- if (renameRadioButton.isSelected()) {
- compoundEdit.addEdit(massRenameField(entries, field, FieldFactory.parseField(renameTextField.getText()), overwriteCheckBox.isSelected()));
- } else if (appendRadioButton.isSelected()) {
- compoundEdit.addEdit(massAppendField(entries, field, appendTextField.getText()));
- } else {
- compoundEdit.addEdit(massSetField(entries, field,
- setRadioButton.isSelected() ? toSet : null,
- overwriteCheckBox.isSelected()));
- }
- compoundEdit.end();
- undoManager.addEdit(compoundEdit);
- }
-}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java
new file mode 100644
index 00000000000..2954b013f68
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java
@@ -0,0 +1,11 @@
+package org.jabref.gui.edit.automaticfiededitor;
+
+import javafx.scene.layout.AnchorPane;
+
+public abstract class AbstractAutomaticFieldEditorTabView extends AnchorPane implements AutomaticFieldEditorTab {
+
+ @Override
+ public AnchorPane getContent() {
+ return this;
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java
new file mode 100644
index 00000000000..a56af9dfa78
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java
@@ -0,0 +1,26 @@
+package org.jabref.gui.edit.automaticfiededitor;
+
+import org.jabref.gui.DialogService;
+import org.jabref.gui.StateManager;
+import org.jabref.gui.actions.SimpleCommand;
+
+import static org.jabref.gui.actions.ActionHelper.needsDatabase;
+import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;
+
+public class AutomaticFieldEditorAction extends SimpleCommand {
+ private final StateManager stateManager;
+ private final DialogService dialogService;
+
+ public AutomaticFieldEditorAction(StateManager stateManager, DialogService dialogService) {
+ this.stateManager = stateManager;
+ this.dialogService = dialogService;
+
+ this.executable.bind(needsDatabase(stateManager).and(needsEntriesSelected(stateManager)));
+ }
+
+ @Override
+ public void execute() {
+ dialogService.showCustomDialogAndWait(new AutomaticFieldEditorDialog(stateManager.getSelectedEntries(),
+ stateManager.getActiveDatabase().orElseThrow()));
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml
new file mode 100644
index 00000000000..f79462369a1
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java
new file mode 100644
index 00000000000..5ecbf06b275
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java
@@ -0,0 +1,73 @@
+package org.jabref.gui.edit.automaticfiededitor;
+
+import java.util.List;
+
+import javax.swing.undo.UndoManager;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.ButtonBar;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+
+import org.jabref.gui.Globals;
+import org.jabref.gui.util.BaseDialog;
+import org.jabref.gui.util.ControlHelper;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+
+import com.airhacks.afterburner.views.ViewLoader;
+
+public class AutomaticFieldEditorDialog extends BaseDialog {
+ @FXML public ButtonType saveButton;
+ @FXML public ButtonType cancelButton;
+ @FXML
+ private TabPane tabPane;
+
+ private final UndoManager undoManager;
+
+ private final BibDatabaseContext databaseContext;
+ private final List selectedEntries;
+ private AutomaticFieldEditorViewModel viewModel;
+
+ public AutomaticFieldEditorDialog(List selectedEntries, BibDatabaseContext databaseContext) {
+ this.selectedEntries = selectedEntries;
+ this.databaseContext = databaseContext;
+ this.undoManager = Globals.undoManager;
+
+ this.setTitle(Localization.lang("Automatic field editor"));
+
+ ViewLoader.view(this)
+ .load()
+ .setAsDialogPane(this);
+
+ ControlHelper.setAction(saveButton, getDialogPane(), event -> saveChangesAndCloseDialog());
+ ControlHelper.setAction(cancelButton, getDialogPane(), event -> cancelChangesAndCloseDialog());
+
+ // This will prevent all dialog buttons from having the same size
+ // Read more: https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes
+ getDialogPane().getButtonTypes().stream()
+ .map(getDialogPane()::lookupButton)
+ .forEach(btn-> ButtonBar.setButtonUniformSize(btn, false));
+ }
+
+ @FXML
+ public void initialize() {
+ viewModel = new AutomaticFieldEditorViewModel(selectedEntries, databaseContext, undoManager);
+
+ for (AutomaticFieldEditorTab tabModel : viewModel.getFieldEditorTabs()) {
+ tabPane.getTabs().add(new Tab(tabModel.getTabName(), tabModel.getContent()));
+ }
+ }
+
+ private void saveChangesAndCloseDialog() {
+ viewModel.saveChanges();
+ close();
+ }
+
+ private void cancelChangesAndCloseDialog() {
+ viewModel.cancelChanges();
+ close();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java
new file mode 100644
index 00000000000..7863422deff
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java
@@ -0,0 +1,9 @@
+package org.jabref.gui.edit.automaticfiededitor;
+
+import javafx.scene.layout.Pane;
+
+public interface AutomaticFieldEditorTab {
+ Pane getContent();
+
+ String getTabName();
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java
new file mode 100644
index 00000000000..0c334ddf6fa
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java
@@ -0,0 +1,49 @@
+package org.jabref.gui.edit.automaticfiededitor;
+
+import java.util.List;
+
+import javax.swing.undo.UndoManager;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import org.jabref.gui.AbstractViewModel;
+import org.jabref.gui.edit.automaticfiededitor.editfieldvalue.EditFieldValueTabView;
+import org.jabref.gui.edit.automaticfiededitor.renamefield.RenameFieldTabView;
+import org.jabref.gui.edit.automaticfiededitor.twofields.TwoFieldsTabView;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+
+public class AutomaticFieldEditorViewModel extends AbstractViewModel {
+ public static final String NAMED_COMPOUND_EDITS = "EDIT_FIELDS";
+ private final ObservableList fieldEditorTabs = FXCollections.observableArrayList();
+ private final NamedCompound dialogEdits = new NamedCompound(NAMED_COMPOUND_EDITS);
+
+ private final BibDatabaseContext databaseContext;
+ private final UndoManager undoManager;
+
+ public AutomaticFieldEditorViewModel(List selectedEntries, BibDatabaseContext databaseContext, UndoManager undoManager) {
+ fieldEditorTabs.addAll(
+ new EditFieldValueTabView(selectedEntries, databaseContext, dialogEdits),
+ new TwoFieldsTabView(selectedEntries, databaseContext, dialogEdits),
+ new RenameFieldTabView(selectedEntries, databaseContext, dialogEdits)
+ );
+ this.databaseContext = databaseContext;
+ this.undoManager = undoManager;
+ }
+
+ public ObservableList getFieldEditorTabs() {
+ return fieldEditorTabs;
+ }
+
+ public void saveChanges() {
+ dialogEdits.end();
+ undoManager.addEdit(dialogEdits);
+ }
+
+ public void cancelChanges() {
+ dialogEdits.end();
+ dialogEdits.undo();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java
new file mode 100644
index 00000000000..8863d17de78
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java
@@ -0,0 +1,42 @@
+package org.jabref.gui.edit.automaticfiededitor;
+
+import java.util.List;
+
+import org.jabref.gui.actions.SimpleCommand;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.gui.undo.UndoableFieldChange;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+import org.jabref.model.strings.StringUtil;
+
+public class MoveFieldValueAction extends SimpleCommand {
+ private final Field fromField;
+ private final Field toField;
+ private final List entries;
+
+ private final NamedCompound edits;
+
+ public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits) {
+ this.fromField = fromField;
+ this.toField = toField;
+ this.entries = entries;
+ this.edits = edits;
+ }
+
+ @Override
+ public void execute() {
+ for (BibEntry entry : entries) {
+ String fromFieldValue = entry.getField(fromField).orElse("");
+ String toFieldValue = entry.getField(toField).orElse("");
+
+ if (StringUtil.isNotBlank(fromFieldValue)) {
+ entry.setField(toField, fromFieldValue);
+ entry.setField(fromField, "");
+
+ edits.addEdit(new UndoableFieldChange(entry, fromField, fromFieldValue, null));
+ edits.addEdit(new UndoableFieldChange(entry, toField, toFieldValue, fromFieldValue));
+ }
+ }
+ edits.end();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTab.fxml b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTab.fxml
new file mode 100644
index 00000000000..4cf8a44016e
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTab.fxml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTabView.java
new file mode 100644
index 00000000000..3bd9ea57a09
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTabView.java
@@ -0,0 +1,95 @@
+package org.jabref.gui.edit.automaticfiededitor.editfieldvalue;
+
+import java.util.Comparator;
+import java.util.List;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.TextField;
+import javafx.util.StringConverter;
+
+import org.jabref.gui.edit.automaticfiededitor.AbstractAutomaticFieldEditorTabView;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+import org.jabref.model.entry.field.FieldFactory;
+
+import com.airhacks.afterburner.views.ViewLoader;
+
+public class EditFieldValueTabView extends AbstractAutomaticFieldEditorTabView {
+ public Button appendValueButton;
+ @FXML
+ private ComboBox fieldComboBox;
+
+ @FXML
+ private TextField fieldValueTextField;
+
+ @FXML
+ private CheckBox overwriteNonEmptyFieldsCheckBox;
+
+ private final List selectedEntries;
+ private final BibDatabaseContext databaseContext;
+
+ private EditFieldValueViewModel viewModel;
+
+ private final NamedCompound dialogEdits;
+
+ public EditFieldValueTabView(List selectedEntries, BibDatabaseContext databaseContext, NamedCompound dialogEdits) {
+ this.selectedEntries = selectedEntries;
+ this.databaseContext = databaseContext;
+ this.dialogEdits = dialogEdits;
+
+ ViewLoader.view(this)
+ .root(this)
+ .load();
+ }
+
+ @FXML
+ public void initialize() {
+ viewModel = new EditFieldValueViewModel(databaseContext, selectedEntries, dialogEdits);
+ fieldComboBox.setConverter(new StringConverter<>() {
+ @Override
+ public String toString(Field field) {
+ return field.getName();
+ }
+
+ @Override
+ public Field fromString(String name) {
+ return FieldFactory.parseField(name);
+ }
+ });
+ fieldComboBox.getItems().addAll(viewModel.getAllFields().sorted(Comparator.comparing(Field::getName)));
+ fieldComboBox.getSelectionModel().selectFirst();
+ viewModel.selectedFieldProperty().bindBidirectional(fieldComboBox.valueProperty());
+
+ viewModel.fieldValueProperty().bindBidirectional(fieldValueTextField.textProperty());
+
+ viewModel.overwriteNonEmptyFieldsProperty().bindBidirectional(overwriteNonEmptyFieldsCheckBox.selectedProperty());
+
+ appendValueButton.disableProperty().bind(overwriteNonEmptyFieldsCheckBox.selectedProperty().not());
+ }
+
+ @Override
+ public String getTabName() {
+ return Localization.lang("Edit field value");
+ }
+
+ @FXML
+ void appendToFieldValue() {
+ viewModel.appendToFieldValue();
+ }
+
+ @FXML
+ void clearField() {
+ viewModel.clearSelectedField();
+ }
+
+ @FXML
+ void setFieldValue() {
+ viewModel.setFieldValue();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueViewModel.java
new file mode 100644
index 00000000000..d11928ebc53
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueViewModel.java
@@ -0,0 +1,135 @@
+package org.jabref.gui.edit.automaticfiededitor.editfieldvalue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+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.gui.AbstractViewModel;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.gui.undo.UndoableFieldChange;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+
+public class EditFieldValueViewModel extends AbstractViewModel {
+ private final BibDatabaseContext databaseContext;
+ private final List selectedEntries;
+
+ private final StringProperty fieldValue = new SimpleStringProperty();
+
+ private final ObjectProperty selectedField = new SimpleObjectProperty<>();
+
+ private final BooleanProperty overwriteNonEmptyFields = new SimpleBooleanProperty();
+
+ private final ObservableList allFields = FXCollections.observableArrayList();
+
+ private final NamedCompound dialogEdits;
+
+ public EditFieldValueViewModel(BibDatabaseContext databaseContext, List selectedEntries, NamedCompound dialogEdits) {
+ this.databaseContext = databaseContext;
+ this.selectedEntries = new ArrayList<>(selectedEntries);
+ this.dialogEdits = dialogEdits;
+
+ allFields.addAll(databaseContext.getDatabase().getAllVisibleFields().stream().toList());
+ }
+
+ public void clearSelectedField() {
+ NamedCompound clearFieldEdit = new NamedCompound("CLEAR_SELECTED_FIELD");
+
+ for (BibEntry entry : selectedEntries) {
+ Optional oldFieldValue = entry.getField(selectedField.get());
+ if (oldFieldValue.isPresent()) {
+ entry.setField(selectedField.get(), "");
+
+ clearFieldEdit.addEdit(new UndoableFieldChange(entry,
+ selectedField.get(),
+ oldFieldValue.orElse(null),
+ fieldValue.get()));
+ }
+ }
+
+ if (clearFieldEdit.hasEdits()) {
+ clearFieldEdit.end();
+ dialogEdits.addEdit(clearFieldEdit);
+ }
+ }
+
+ public void setFieldValue() {
+ NamedCompound setFieldEdit = new NamedCompound("CHANGE_SELECTED_FIELD");
+ String toSetFieldValue = fieldValue.getValue();
+
+ for (BibEntry entry : selectedEntries) {
+ Optional oldFieldValue = entry.getField(selectedField.get());
+ if (oldFieldValue.isEmpty() || overwriteNonEmptyFields.get()) {
+ entry.setField(selectedField.get(), toSetFieldValue);
+
+ setFieldEdit.addEdit(new UndoableFieldChange(entry,
+ selectedField.get(),
+ oldFieldValue.orElse(null),
+ toSetFieldValue));
+ fieldValue.set("");
+ }
+ }
+
+ if (setFieldEdit.hasEdits()) {
+ setFieldEdit.end();
+ dialogEdits.addEdit(setFieldEdit);
+ }
+ }
+
+ public void appendToFieldValue() {
+ NamedCompound appendToFieldEdit = new NamedCompound("APPEND_TO_SELECTED_FIELD");
+ String toAppendFieldValue = fieldValue.getValue();
+
+ for (BibEntry entry : selectedEntries) {
+ Optional oldFieldValue = entry.getField(selectedField.get());
+ // Append button should be disabled if 'overwriteNonEmptyFields' is false
+ if (overwriteNonEmptyFields.get()) {
+ String newFieldValue = oldFieldValue.orElse("").concat(toAppendFieldValue);
+
+ entry.setField(selectedField.get(), newFieldValue);
+ appendToFieldEdit.addEdit(new UndoableFieldChange(entry,
+ selectedField.get(),
+ oldFieldValue.orElse(null),
+ newFieldValue)
+ );
+
+ fieldValue.set("");
+ }
+ }
+
+ if (appendToFieldEdit.hasEdits()) {
+ appendToFieldEdit.end();
+ dialogEdits.addEdit(appendToFieldEdit);
+ }
+ }
+
+ public ObservableList getAllFields() {
+ return allFields;
+ }
+
+ public ObjectProperty selectedFieldProperty() {
+ return selectedField;
+ }
+
+ public String getFieldValue() {
+ return fieldValue.get();
+ }
+
+ public StringProperty fieldValueProperty() {
+ return fieldValue;
+ }
+
+ public BooleanProperty overwriteNonEmptyFieldsProperty() {
+ return overwriteNonEmptyFields;
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTab.fxml b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTab.fxml
new file mode 100644
index 00000000000..388f1f567c0
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTab.fxml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java
new file mode 100644
index 00000000000..8029cfc7827
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java
@@ -0,0 +1,74 @@
+package org.jabref.gui.edit.automaticfiededitor.renamefield;
+
+import java.util.Comparator;
+import java.util.List;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.TextField;
+import javafx.util.StringConverter;
+
+import org.jabref.gui.edit.automaticfiededitor.AbstractAutomaticFieldEditorTabView;
+import org.jabref.gui.edit.automaticfiededitor.AutomaticFieldEditorTab;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+import org.jabref.model.entry.field.FieldFactory;
+
+import com.airhacks.afterburner.views.ViewLoader;
+
+public class RenameFieldTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab {
+ @FXML
+ private ComboBox fieldComboBox;
+ @FXML
+ private TextField newFieldNameTextField;
+ private final List selectedEntries;
+ private final BibDatabaseContext databaseContext;
+ private final NamedCompound dialogEdits;
+ private RenameFieldViewModel viewModel;
+
+ public RenameFieldTabView(List selectedEntries, BibDatabaseContext databaseContext, NamedCompound dialogEdits) {
+ this.selectedEntries = selectedEntries;
+ this.databaseContext = databaseContext;
+ this.dialogEdits = dialogEdits;
+
+ ViewLoader.view(this)
+ .root(this)
+ .load();
+ }
+
+ @FXML
+ public void initialize() {
+ viewModel = new RenameFieldViewModel(selectedEntries, databaseContext, dialogEdits);
+
+ fieldComboBox.getItems().addAll(viewModel.getAllFields().sorted(Comparator.comparing(Field::getName)));
+ fieldComboBox.setConverter(new StringConverter<>() {
+ @Override
+ public String toString(Field field) {
+ return field.getName();
+ }
+
+ @Override
+ public Field fromString(String name) {
+ return FieldFactory.parseField(name);
+ }
+ });
+
+ fieldComboBox.getSelectionModel().selectFirst();
+ viewModel.selectedFieldProperty().bindBidirectional(fieldComboBox.valueProperty());
+
+ viewModel.newFieldNameProperty().bindBidirectional(newFieldNameTextField.textProperty());
+ }
+
+ @Override
+ public String getTabName() {
+ return Localization.lang("Rename field");
+ }
+
+ @FXML
+ void renameField() {
+ viewModel.renameField();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java
new file mode 100644
index 00000000000..f8e430ccc51
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java
@@ -0,0 +1,71 @@
+package org.jabref.gui.edit.automaticfiededitor.renamefield;
+
+import java.util.List;
+
+import javafx.beans.property.ObjectProperty;
+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.gui.AbstractViewModel;
+import org.jabref.gui.edit.automaticfiededitor.MoveFieldValueAction;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+import org.jabref.model.entry.field.FieldFactory;
+
+public class RenameFieldViewModel extends AbstractViewModel {
+
+ private final StringProperty newFieldName = new SimpleStringProperty();
+ private final ObjectProperty selectedField = new SimpleObjectProperty<>();
+
+ private final ObservableList allFields = FXCollections.observableArrayList();
+ private final List selectedEntries;
+ private final BibDatabaseContext databaseContext;
+ private final NamedCompound dialogEdits;
+
+ public RenameFieldViewModel(List selectedEntries, BibDatabaseContext databaseContext, NamedCompound dialogEdits) {
+ this.selectedEntries = selectedEntries;
+ this.databaseContext = databaseContext;
+ this.dialogEdits = dialogEdits;
+
+ allFields.addAll(databaseContext.getDatabase().getAllVisibleFields());
+ }
+
+ public String getNewFieldName() {
+ return newFieldName.get();
+ }
+
+ public StringProperty newFieldNameProperty() {
+ return newFieldName;
+ }
+
+ public Field getSelectedField() {
+ return selectedField.get();
+ }
+
+ public ObjectProperty selectedFieldProperty() {
+ return selectedField;
+ }
+
+ public ObservableList getAllFields() {
+ return allFields;
+ }
+
+ public void renameField() {
+ NamedCompound renameEdit = new NamedCompound("RENAME_EDIT");
+
+ new MoveFieldValueAction(selectedField.get(),
+ FieldFactory.parseField(newFieldName.get()),
+ selectedEntries,
+ renameEdit).execute();
+
+ if (renameEdit.hasEdits()) {
+ renameEdit.end();
+ dialogEdits.addEdit(renameEdit);
+ }
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTab.fxml b/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTab.fxml
new file mode 100644
index 00000000000..7ac475e6cd7
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTab.fxml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTabView.java
new file mode 100644
index 00000000000..baf000ce3cb
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTabView.java
@@ -0,0 +1,118 @@
+package org.jabref.gui.edit.automaticfiededitor.twofields;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.util.StringConverter;
+
+import org.jabref.gui.edit.automaticfiededitor.AbstractAutomaticFieldEditorTabView;
+import org.jabref.gui.edit.automaticfiededitor.AutomaticFieldEditorTab;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+import org.jabref.model.entry.field.FieldFactory;
+
+import com.airhacks.afterburner.views.ViewLoader;
+
+public class TwoFieldsTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab {
+ @FXML
+ private Button moveValueButton;
+
+ @FXML
+ private Button swapValuesButton;
+
+ @FXML
+ private ComboBox fromFieldComboBox;
+ @FXML
+ private ComboBox toFieldComboBox;
+
+ @FXML
+ private CheckBox overwriteNonEmptyFields;
+
+ private TwoFieldsViewModel viewModel;
+ private final List selectedEntries;
+ private final BibDatabaseContext databaseContext;
+ private final NamedCompound dialogEdits;
+
+ public TwoFieldsTabView(List selectedEntries, BibDatabaseContext databaseContext, NamedCompound dialogEdits) {
+ this.selectedEntries = new ArrayList<>(selectedEntries);
+ this.databaseContext = databaseContext;
+ this.dialogEdits = dialogEdits;
+
+ ViewLoader.view(this)
+ .root(this)
+ .load();
+ }
+
+ public void initialize() {
+ viewModel = new TwoFieldsViewModel(selectedEntries, databaseContext, dialogEdits);
+ initializeFromAndToComboBox();
+
+ viewModel.overwriteNonEmptyFieldsProperty().bindBidirectional(overwriteNonEmptyFields.selectedProperty());
+
+ moveValueButton.disableProperty().bind(viewModel.overwriteNonEmptyFieldsProperty().not());
+ swapValuesButton.disableProperty().bind(viewModel.overwriteNonEmptyFieldsProperty().not());
+ }
+
+ private void initializeFromAndToComboBox() {
+ fromFieldComboBox.getItems().addAll(viewModel.getAllFields().sorted(Comparator.comparing(Field::getName)));
+ toFieldComboBox.getItems().addAll(viewModel.getAllFields().sorted(Comparator.comparing(Field::getName)));
+
+ fromFieldComboBox.setConverter(new StringConverter<>() {
+ @Override
+ public String toString(Field field) {
+ return field.getName();
+ }
+
+ @Override
+ public Field fromString(String name) {
+ return FieldFactory.parseField(name);
+ }
+ });
+
+ toFieldComboBox.setConverter(new StringConverter<>() {
+ @Override
+ public String toString(Field field) {
+ return field.getName();
+ }
+
+ @Override
+ public Field fromString(String name) {
+ return FieldFactory.parseField(name);
+ }
+ });
+
+ fromFieldComboBox.getSelectionModel().selectFirst();
+ toFieldComboBox.getSelectionModel().selectLast();
+
+ viewModel.fromFieldProperty().bindBidirectional(fromFieldComboBox.valueProperty());
+ viewModel.toFieldProperty().bindBidirectional(toFieldComboBox.valueProperty());
+ }
+
+ @Override
+ public String getTabName() {
+ return Localization.lang("Two fields");
+ }
+
+ @FXML
+ void copyValue() {
+ viewModel.copyValue();
+ }
+
+ @FXML
+ void moveValue() {
+ viewModel.moveValue();
+ }
+
+ @FXML
+ void swapValues() {
+ viewModel.swapValues();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsViewModel.java
new file mode 100644
index 00000000000..cf184630db9
--- /dev/null
+++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsViewModel.java
@@ -0,0 +1,141 @@
+package org.jabref.gui.edit.automaticfiededitor.twofields;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import org.jabref.gui.AbstractViewModel;
+import org.jabref.gui.edit.automaticfiededitor.MoveFieldValueAction;
+import org.jabref.gui.undo.NamedCompound;
+import org.jabref.gui.undo.UndoableFieldChange;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.Field;
+import org.jabref.model.strings.StringUtil;
+
+public class TwoFieldsViewModel extends AbstractViewModel {
+ private final ObjectProperty fromField = new SimpleObjectProperty<>();
+
+ private final ObjectProperty toField = new SimpleObjectProperty<>();
+
+ private final BooleanProperty overwriteNonEmptyFields = new SimpleBooleanProperty();
+ private final ObservableList allFields = FXCollections.observableArrayList();
+ // TODO: Create an abstraction where selectedEntries, databaseContext and dialogEdits dependencies are shared across
+ // all automatic field editors tab view models
+ private final List selectedEntries;
+ private final BibDatabaseContext databaseContext;
+ private final NamedCompound dialogEdits;
+
+ public TwoFieldsViewModel(List selectedEntries, BibDatabaseContext databaseContext, NamedCompound dialogEdits) {
+ this.selectedEntries = new ArrayList<>(selectedEntries);
+ this.databaseContext = databaseContext;
+ this.dialogEdits = dialogEdits;
+
+ allFields.addAll(databaseContext.getDatabase().getAllVisibleFields());
+ }
+
+ public ObservableList getAllFields() {
+ return allFields;
+ }
+
+ public Field getFromField() {
+ return fromField.get();
+ }
+
+ public ObjectProperty fromFieldProperty() {
+ return fromField;
+ }
+
+ public Field getToField() {
+ return toField.get();
+ }
+
+ public ObjectProperty toFieldProperty() {
+ return toField;
+ }
+
+ public boolean getOverwriteNonEmptyFields() {
+ return overwriteNonEmptyFields.get();
+ }
+
+ public BooleanProperty overwriteNonEmptyFieldsProperty() {
+ return overwriteNonEmptyFields;
+ }
+
+ public void copyValue() {
+ NamedCompound copyFieldValueEdit = new NamedCompound("COPY_FIELD_VALUE");
+
+ for (BibEntry entry : selectedEntries) {
+ String fromFieldValue = entry.getField(fromField.get()).orElse("");
+ String toFieldValue = entry.getField(toField.get()).orElse("");
+
+ if (overwriteNonEmptyFields.get() || StringUtil.isBlank(toFieldValue)) {
+ entry.setField(toField.get(), fromFieldValue);
+
+ copyFieldValueEdit.addEdit(new UndoableFieldChange(entry,
+ toField.get(),
+ toFieldValue,
+ fromFieldValue));
+ }
+ }
+
+ if (copyFieldValueEdit.hasEdits()) {
+ copyFieldValueEdit.end();
+ dialogEdits.addEdit(copyFieldValueEdit);
+ }
+ }
+
+ public void moveValue() {
+ NamedCompound moveEdit = new NamedCompound("MOVE_EDIT");
+ if (overwriteNonEmptyFields.get()) {
+ new MoveFieldValueAction(fromField.get(),
+ toField.get(),
+ selectedEntries,
+ moveEdit).execute();
+
+ if (moveEdit.hasEdits()) {
+ moveEdit.end();
+ dialogEdits.addEdit(moveEdit);
+ }
+ }
+ }
+
+ public void swapValues() {
+ NamedCompound swapFieldValuesEdit = new NamedCompound("SWAP_FIELD_VALUES");
+
+ for (BibEntry entry : selectedEntries) {
+ String fromFieldValue = entry.getField(fromField.get()).orElse("");
+ String toFieldValue = entry.getField(toField.get()).orElse("");
+
+ if (overwriteNonEmptyFields.get() && StringUtil.isNotBlank(fromFieldValue) && StringUtil.isNotBlank(toFieldValue)) {
+ entry.setField(toField.get(), fromFieldValue);
+ entry.setField(fromField.get(), toFieldValue);
+
+ swapFieldValuesEdit.addEdit(new UndoableFieldChange(
+ entry,
+ toField.get(),
+ toFieldValue,
+ fromFieldValue
+ ));
+
+ swapFieldValuesEdit.addEdit(new UndoableFieldChange(
+ entry,
+ fromField.get(),
+ fromFieldValue,
+ toFieldValue
+ ));
+ }
+ }
+
+ if (swapFieldValuesEdit.hasEdits()) {
+ swapFieldValuesEdit.end();
+ dialogEdits.addEdit(swapFieldValuesEdit);
+ }
+ }
+}
diff --git a/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java b/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java
index b4df1160ba0..2d38961064f 100644
--- a/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java
+++ b/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java
@@ -1,6 +1,7 @@
package org.jabref.gui.help;
import java.io.IOException;
+import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -57,8 +58,11 @@ public AboutDialogViewModel(DialogService dialogService, ClipBoardManager clipBo
maintainers.set(buildInfo.maintainers);
license.set(Localization.lang("License") + ":");
changelogUrl = buildInfo.version.getChangelogUrl();
+
+ String javafx_version = System.getProperty("javafx.runtime.version", BuildInfo.UNKNOWN_VERSION).toLowerCase(Locale.ROOT);
+
versionInfo = String.format("JabRef %s%n%s %s %s %nJava %s %nJavaFX %s", buildInfo.version, BuildInfo.OS,
- BuildInfo.OS_VERSION, BuildInfo.OS_ARCH, BuildInfo.JAVA_VERSION, BuildInfo.JAVAFX_VERSION);
+ BuildInfo.OS_VERSION, BuildInfo.OS_ARCH, BuildInfo.JAVA_VERSION, javafx_version);
}
public String getDevelopmentVersion() {
diff --git a/src/main/java/org/jabref/logic/util/BuildInfo.java b/src/main/java/org/jabref/logic/util/BuildInfo.java
index 7a1cf33b9af..375fc0d0bdb 100644
--- a/src/main/java/org/jabref/logic/util/BuildInfo.java
+++ b/src/main/java/org/jabref/logic/util/BuildInfo.java
@@ -16,8 +16,6 @@ public final class BuildInfo {
public static final String OS_VERSION = System.getProperty("os.version", UNKNOWN_VERSION).toLowerCase(Locale.ROOT);
public static final String OS_ARCH = System.getProperty("os.arch", UNKNOWN_VERSION).toLowerCase(Locale.ROOT);
public static final String JAVA_VERSION = System.getProperty("java.version", UNKNOWN_VERSION).toLowerCase(Locale.ROOT);
- public static final String JAVAFX_VERSION = System.getProperty("javafx.runtime.version", UNKNOWN_VERSION).toLowerCase(Locale.ROOT);
-
public final Version version;
public final String maintainers;
diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties
index db2940fd8bc..d9f4e1272f6 100644
--- a/src/main/resources/l10n/JabRef_de.properties
+++ b/src/main/resources/l10n/JabRef_de.properties
@@ -2478,4 +2478,16 @@ Error\ downloading=Fehler beim Herunterladen
Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Fehler beim Schreiben von Metadaten. Details finden Sie im Fehlerprotokoll.
Failed\ to\ write\ metadata,\ file\ %1\ not\ found.=Metadaten können nicht geschrieben werden, Datei %1 nicht gefunden.
Success\!\ Finished\ writing\ metadata.=Erfolgreich\! Das Schreiben der Metadaten ist abgeschlossen.
+Overwrite\ Non\ empty\ fields=Overwrite Non empty fields
+Set=Set
+Append=Append
+Clear\ field=Clear field
+field\ value=field value
+Edit\ field\ value\ of\ selected\ entries=Edit field value of selected entries
+Rename=Rename
+new\ field\ name=new field name
+Copy\ value=Copy value
+Move\ value=Move value
+Swap\ values=Swap values
+Copy\ or\ move\ the\ value\ of\ one\ field\ to\ another=Copy or move the value of one field to another
diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties
index 368b722e913..e66b587e6a1 100644
--- a/src/main/resources/l10n/JabRef_en.properties
+++ b/src/main/resources/l10n/JabRef_en.properties
@@ -153,8 +153,6 @@ Cite\ command=Cite command
Clear=Clear
-Clear\ fields=Clear fields
-
Open\ /\ close\ entry\ editor=Open / close entry editor
Close\ dialog=Close dialog
@@ -804,9 +802,6 @@ Select\ file\ from\ ZIP-archive=Select file from ZIP-archive
Select\ the\ tree\ nodes\ to\ view\ and\ accept\ or\ reject\ changes=Select the tree nodes to view and accept or reject changes
-Set\ field=Set field
-Set\ fields=Set fields
-
Settings=Settings
Shortcut=Shortcut
@@ -986,10 +981,6 @@ Line\ %0\:\ Found\ corrupted\ citation\ key\ %1\ (comma\ missing).=Line %0: Foun
No\ full\ text\ document\ found=No full text document found
Download\ from\ URL=Download from URL
Rename\ field=Rename field
-Append\ field=Append field
-Append\ to\ fields=Append to fields
-Rename\ field\ to=Rename field to
-Move\ contents\ of\ a\ field\ into\ a\ field\ with\ a\ different\ name=Move contents of a field into a field with a different name
Cannot\ use\ port\ %0\ for\ remote\ operation;\ another\ application\ may\ be\ using\ it.\ Try\ specifying\ another\ port.=Cannot use port %0 for remote operation; another application may be using it. Try specifying another port.
@@ -1730,7 +1721,6 @@ Protected\ terms\ file=Protected terms file
Style\ file=Style file
Open\ OpenOffice/LibreOffice\ connection=Open OpenOffice/LibreOffice connection
-You\ must\ enter\ at\ least\ one\ field\ name=You must enter at least one field name
Non-ASCII\ encoded\ character\ found=Non-ASCII encoded character found
Non-UTF-8\ encoded\ field\ found=Non-UTF-8 encoded field found
Toggle\ web\ search\ interface=Toggle web search interface
@@ -2422,7 +2412,6 @@ Entry\ Type=Entry Type
Entry\ types=Entry types
Field\ names=Field names
Others=Others
-Overwrite\ existing\ field\ values=Overwrite existing field values
Recommended=Recommended
Authors\ and\ Title=Authors and Title
@@ -2501,3 +2490,19 @@ Success\!\ Finished\ writing\ metadata.=Success! Finished writing metadata.
Custom\ API\ key=Custom API key
Check\ %0\ API\ Key\ Setting=Check %0 API Key Setting
+
+Edit\ field\ value=Edit field value
+Two\ fields=Two fields
+Overwrite\ Non\ empty\ fields=Overwrite Non empty fields
+Set=Set
+Append=Append
+Clear\ field=Clear field
+Field\ value=Field value
+Edit\ field\ value\ of\ selected\ entries=Edit field value of selected entries
+Rename=Rename
+New\ field\ name=New field name
+Copy\ value=Copy value
+Move\ value=Move value
+Swap\ values=Swap values
+Copy\ or\ move\ the\ value\ of\ one\ field\ to\ another=Copy or move the value of one field to another
+Automatic\ field\ editor=Automatic field editor
diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties
index 2971bddf631..425843db382 100644
--- a/src/main/resources/l10n/JabRef_fr.properties
+++ b/src/main/resources/l10n/JabRef_fr.properties
@@ -1037,7 +1037,7 @@ Output\ file\ missing=Fichier de sortie manquant
No\ search\ matches.=Recherche sans correspondance.
The\ output\ option\ depends\ on\ a\ valid\ input\ option.=L'option de sortie dépend d'une option d'entrée valide.
Linked\ file\ name\ conventions=Conventions pour les noms de fichiers liés
-Filename\ format\ pattern=Modèle de format de nom de fichier
+Filename\ format\ pattern=Modèle de format de nom de fichier
Additional\ parameters=Paramètres additionnels
Cite\ selected\ entries\ between\ parenthesis=Citer les entrées sélectionnées entre parenthèses
Cite\ selected\ entries\ with\ in-text\ citation=Citer les entrées sélectionnées comme incluse dans le texte
diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties
index 0a0ec18b86f..2938d9c870e 100644
--- a/src/main/resources/l10n/JabRef_ko.properties
+++ b/src/main/resources/l10n/JabRef_ko.properties
@@ -1905,7 +1905,7 @@ Add\ new\ String=문자열 추가
Must\ not\ be\ empty\!=비워둘 수 없습니다\!
Open\ Help\ page=도움말 열기
Add\ new\ field\ name=새 필드 이름 추가
-Field\ name\:=필드 이름\:
+Field\ name\:=필드 이름\:
Field\ name\ "%0"\ already\ exists=필드 이름 "%0"이 이미 존재합니다
No\ field\ name\ selected\!=필드 이름을 선택하지 않았습니다
Remove\ field\ name=필드 이름 제거
diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties
index 1e60b6d3438..4f2f56445c7 100644
--- a/src/main/resources/l10n/JabRef_ru.properties
+++ b/src/main/resources/l10n/JabRef_ru.properties
@@ -2234,7 +2234,7 @@ This\ entry\ type\ is\ intended\ for\ sources\ such\ as\ web\ sites\ which\ are\
A\ single-volume\ work\ of\ reference\ such\ as\ an\ encyclopedia\ or\ a\ dictionary.=Неделимая работа или ссылка, как энциклопедия или словарь.
A\ technical\ report,\ research\ report,\ or\ white\ paper\ published\ by\ a\ university\ or\ some\ other\ institution.=Технический отчет, исследовательский отчет, или белая книга, выпущенная институтом или другим учреждением.
An\ entry\ set\ is\ a\ group\ of\ entries\ which\ are\ cited\ as\ a\ single\ reference\ and\ listed\ as\ a\ single\ item\ in\ the\ bibliography.=Набор записей представляет собой группу записей, которые приводятся в виде единой ссылки и перечислены в виде одного элемента в библиографии.
-Supplemental\ material\ in\ a\ "Book".\ This\ type\ is\ provided\ for\ elements\ such\ as\ prefaces,\ introductions,\ forewords,\ afterwords,\ etc.\ which\ often\ have\ a\ generic\ title\ only.=Дополнительный материал в "Книге" предназначен для таких элементов, как предисловия, введения, послесловия и т.д.
+Supplemental\ material\ in\ a\ "Book".\ This\ type\ is\ provided\ for\ elements\ such\ as\ prefaces,\ introductions,\ forewords,\ afterwords,\ etc.\ which\ often\ have\ a\ generic\ title\ only.=Дополнительный материал в "Книге" предназначен для таких элементов, как предисловия, введения, послесловия и т.д.
Supplemental\ material\ in\ a\ "Collection".=Дополнительные материалы в "Коллекции".
Supplemental\ material\ in\ a\ "Periodical".\ This\ type\ may\ be\ useful\ when\ referring\ to\ items\ such\ as\ regular\ columns,\ obituaries,\ letters\ to\ the\ editor,\ etc.\ which\ only\ have\ a\ generic\ title.=Дополнительные материалы в "Периодическом издании". Этот тип может быть полезен при обращении к таким элементам, как обычные колонки, некрологи, письма к редактору и т.д., которые имеют только общее название.
A\ thesis\ written\ for\ an\ educational\ institution\ to\ satisfy\ the\ requirements\ for\ a\ degree.=Тезис, написанный для учебного заведения с целью удовлетворения требований к степени.
diff --git a/src/main/resources/l10n/JabRef_zh_TW.properties b/src/main/resources/l10n/JabRef_zh_TW.properties
index 450e0d622f0..588f8dd4d77 100644
--- a/src/main/resources/l10n/JabRef_zh_TW.properties
+++ b/src/main/resources/l10n/JabRef_zh_TW.properties
@@ -984,7 +984,7 @@ Set\ rank\ to\ five=設定評分為 5 級
Order=順序
-Affected\ fields\:=影響欄位\:
+Affected\ fields\:=影響欄位\:
Show\ preview\ as\ a\ tab\ in\ entry\ editor=在條目編輯器中以分頁形式顯示預覽
Font=字型
Visual\ theme=界面主題