diff --git a/CHANGELOG.md b/CHANGELOG.md index 582b7b9bb32..be389cfec4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added support for import of a Refer/BibIX file format. [#13069](https://github.com/JabRef/jabref/issues/13069) - We added markdown rendering and copy capabilities to AI chat responses. [#12234](https://github.com/JabRef/jabref/issues/12234) - We added a new `jabkit` command `pseudonymize` to pseudonymize the library. [#13109](https://github.com/JabRef/jabref/issues/13109) +- We moved the clear fields mechanic in the Automatic Field Editor from the edit content tab to a separate tab. [#13780](https://github.com/JabRef/jabref/issues/13780) - We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129) - We added a "Copy Field Content" submenu to the entry context menu, allowing users to quickly copy specific field contents including Author, Journal, Date, Keywords, and Abstract fields from selected entries. [#13280](https://github.com/JabRef/jabref/pull/13280) - We added a highlighted diff regarding changes to the Group Tree Structure of a bib file, made outside JabRef. [#11221](https://github.com/JabRef/jabref/issues/11221) diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java index 28eb1466445..608cde6e019 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java @@ -7,6 +7,7 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.StateManager; +import org.jabref.gui.edit.automaticfiededitor.clearcontent.ClearContentTabView; import org.jabref.gui.edit.automaticfiededitor.copyormovecontent.CopyOrMoveFieldContentTabView; import org.jabref.gui.edit.automaticfiededitor.editfieldcontent.EditFieldContentTabView; import org.jabref.gui.edit.automaticfiededitor.renamefield.RenameFieldTabView; @@ -25,6 +26,7 @@ public AutomaticFieldEditorViewModel(BibDatabase database, UndoManager undoManag fieldEditorTabs.addAll( new EditFieldContentTabView(database, stateManager), new CopyOrMoveFieldContentTabView(database, stateManager), + new ClearContentTabView(stateManager), new RenameFieldTabView(database, stateManager) ); } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentTabView.java new file mode 100644 index 00000000000..f42dae006ce --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentTabView.java @@ -0,0 +1,73 @@ +package org.jabref.gui.edit.automaticfiededitor.clearcontent; + +import java.util.Set; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; + +import org.jabref.gui.StateManager; +import org.jabref.gui.edit.automaticfiededitor.AbstractAutomaticFieldEditorTabView; +import org.jabref.gui.edit.automaticfiededitor.AutomaticFieldEditorTab; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.field.Field; + +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; + +public class ClearContentTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab { + + @FXML private ComboBox fieldComboBox; + @FXML private CheckBox showOnlySetFieldsCheckBox; + @FXML private Button clearButton; + private final StateManager stateManager; + private ClearContentViewModel viewModel; + + public ClearContentTabView(StateManager stateManager) { + this.stateManager = stateManager; + ViewLoader.view(this) + .root(this) + .load(); + } + + @FXML + public void initialize() { + viewModel = new ClearContentViewModel(stateManager); + + fieldComboBox.setConverter(FIELD_STRING_CONVERTER); + fieldComboBox.getItems().setAll(viewModel.getAllFields()); + if (!fieldComboBox.getItems().isEmpty()) { + fieldComboBox.getSelectionModel().selectFirst(); + } + + EasyBind.subscribe(showOnlySetFieldsCheckBox.selectedProperty(), selected -> { + Set items = selected ? viewModel.getSetFieldsOnly() + : viewModel.getAllFields(); + fieldComboBox.getItems().setAll(items); + if (!items.isEmpty()) { + fieldComboBox.getSelectionModel().selectFirst(); + } + }); + + clearButton.disableProperty().bind(fieldComboBox.valueProperty().isNull()); + + Platform.runLater(fieldComboBox::requestFocus); + } + + @FXML + private void onClear() { + Field chosen = fieldComboBox.getValue(); + if (chosen != null) { + viewModel.clearField(chosen); + } + } + + @Override + public String getTabName() { + return Localization.lang("Clear content"); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentViewModel.java new file mode 100644 index 00000000000..c98091234d4 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentViewModel.java @@ -0,0 +1,59 @@ +package org.jabref.gui.edit.automaticfiededitor.clearcontent; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.gui.StateManager; +import org.jabref.gui.edit.automaticfiededitor.LastAutomaticFieldEditorEdit; +import org.jabref.gui.undo.NamedCompoundEdit; +import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; + +public class ClearContentViewModel { + public static final int TAB_INDEX = 2; + private final StateManager stateManager; + + public ClearContentViewModel(StateManager stateManager) { + this.stateManager = stateManager; + } + + public Set getAllFields() { + return FieldFactory.getAllFieldsWithOutInternal(); + } + + public Set getSetFieldsOnly() { + return stateManager.getSelectedEntries().stream() + .flatMap(entry -> entry.getFields().stream() + .filter(f -> entry.getField(f).isPresent() && !entry.getField(f).get().isBlank())) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + public void clearField(Field field) { + NamedCompoundEdit edits = new NamedCompoundEdit("CLEAR_SELECTED_FIELD"); + List selected = stateManager.getSelectedEntries(); + int affectedEntriesCount = 0; + for (BibEntry entry : selected) { + Optional oldFieldValue = entry.getField(field); + if (oldFieldValue.isPresent()) { + entry.clearField(field) + .ifPresent(change -> edits.addEdit(new UndoableFieldChange(change))); + affectedEntriesCount++; + } + } + + if (edits.hasEdits()) { + edits.end(); + } + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit( + affectedEntriesCount, + TAB_INDEX, + edits) + ); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java index 0c97b3f81e6..b258ac58e67 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java @@ -24,8 +24,8 @@ public class EditFieldContentTabView extends AbstractAutomaticFieldEditorTabView { public Button appendValueButton; - public Button clearFieldButton; public Button setValueButton; + @FXML private ComboBox fieldComboBox; @@ -64,7 +64,7 @@ public void initialize() { fieldComboBox.getSelectionModel().selectFirst(); fieldComboBox.valueProperty().bindBidirectional(viewModel.selectedFieldProperty()); - EasyBind.listen(fieldComboBox.getEditor().textProperty(), observable -> fieldComboBox.commitValue()); + EasyBind.listen(fieldComboBox.getEditor().textProperty(), _ -> fieldComboBox.commitValue()); fieldValueTextField.textProperty().bindBidirectional(viewModel.fieldValueProperty()); @@ -72,7 +72,6 @@ public void initialize() { appendValueButton.disableProperty().bind(viewModel.canAppendProperty().not()); setValueButton.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); - clearFieldButton.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); overwriteFieldContentCheckBox.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); Platform.runLater(() -> visualizer.initVisualization(viewModel.fieldValidationStatus(), fieldComboBox, true)); @@ -88,11 +87,6 @@ void appendToFieldValue() { viewModel.appendToFieldValue(); } - @FXML - void clearField() { - viewModel.clearSelectedField(); - } - @FXML void setFieldValue() { viewModel.setFieldValue(); diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java index a8bab8e10da..5840f3a5da4 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java @@ -67,28 +67,6 @@ public BooleanBinding canAppendProperty() { return canAppend; } - public void clearSelectedField() { - NamedCompoundEdit clearFieldEdit = new NamedCompoundEdit("CLEAR_SELECTED_FIELD"); - int affectedEntriesCount = 0; - for (BibEntry entry : selectedEntries) { - Optional oldFieldValue = entry.getField(selectedField.get()); - if (oldFieldValue.isPresent()) { - entry.clearField(selectedField.get()) - .ifPresent(fieldChange -> clearFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); - affectedEntriesCount++; - } - } - - if (clearFieldEdit.hasEdits()) { - clearFieldEdit.end(); - } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, - TAB_INDEX, - clearFieldEdit - )); - } - public void setFieldValue() { NamedCompoundEdit setFieldEdit = new NamedCompoundEdit("CHANGE_SELECTED_FIELD"); String toSetFieldValue = fieldValue.getValue(); @@ -150,10 +128,6 @@ public Field getSelectedField() { return selectedFieldProperty().get(); } - public String getFieldValue() { - return fieldValue.get(); - } - public StringProperty fieldValueProperty() { return fieldValue; } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java index 18140387cbe..f1a83f8c197 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java @@ -56,7 +56,7 @@ public void initialize() { fieldComboBox.setConverter(FIELD_STRING_CONVERTER); fieldComboBox.valueProperty().bindBidirectional(viewModel.selectedFieldProperty()); - EasyBind.listen(fieldComboBox.getEditor().textProperty(), observable -> fieldComboBox.commitValue()); + EasyBind.listen(fieldComboBox.getEditor().textProperty(), _ -> fieldComboBox.commitValue()); renameButton.disableProperty().bind(viewModel.canRenameProperty().not()); diff --git a/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentTab.fxml b/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentTab.fxml new file mode 100644 index 00000000000..9a76ada3a3c --- /dev/null +++ b/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentTab.fxml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTab.fxml b/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTab.fxml index 076e645e5aa..72ddd2de3f1 100644 --- a/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTab.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTab.fxml @@ -8,9 +8,8 @@ - - diff --git a/jabgui/src/test/java/org/jabref/gui/edit/EditFieldContentTabViewModelTest.java b/jabgui/src/test/java/org/jabref/gui/edit/EditFieldContentTabViewModelTest.java index 931edb84a05..7d94693870f 100644 --- a/jabgui/src/test/java/org/jabref/gui/edit/EditFieldContentTabViewModelTest.java +++ b/jabgui/src/test/java/org/jabref/gui/edit/EditFieldContentTabViewModelTest.java @@ -39,23 +39,6 @@ void setup() { editFieldContentViewModel = new EditFieldContentViewModel(bibDatabase, List.of(entryA, entryB), stateManager); } - @Test - void clearSelectedFieldShouldClearFieldContentEvenWhenOverwriteFieldContentIsNotEnabled() { - editFieldContentViewModel.selectedFieldProperty().set(StandardField.YEAR); - editFieldContentViewModel.overwriteFieldContentProperty().set(false); - editFieldContentViewModel.clearSelectedField(); - - assertEquals(Optional.empty(), entryA.getField(StandardField.YEAR)); - } - - @Test - void clearSelectedFieldShouldDoNothingWhenFieldDoesntExistOrIsEmpty() { - editFieldContentViewModel.selectedFieldProperty().set(StandardField.FILE); - editFieldContentViewModel.clearSelectedField(); - - assertEquals(Optional.empty(), entryA.getField(StandardField.FILE)); - } - @Test void setFieldValueShouldNotDoAnythingIfOverwriteFieldContentIsNotEnabled() { editFieldContentViewModel.overwriteFieldContentProperty().set(false); diff --git a/jabgui/src/test/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentViewModelTest.java b/jabgui/src/test/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentViewModelTest.java new file mode 100644 index 00000000000..aa3d9f07b71 --- /dev/null +++ b/jabgui/src/test/java/org/jabref/gui/edit/automaticfiededitor/clearcontent/ClearContentViewModelTest.java @@ -0,0 +1,80 @@ +package org.jabref.gui.edit.automaticfiededitor.clearcontent; + +import java.util.Optional; + +import javafx.collections.FXCollections; + +import org.jabref.gui.StateManager; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ClearContentViewModelTest { + ClearContentViewModel clearContentViewModel; + BibEntry entryA; + BibEntry entryB; + + BibDatabase bibDatabase; + + StateManager stateManager = mock(StateManager.class); + + @BeforeEach + void setup() { + entryA = new BibEntry(BibEntry.DEFAULT_TYPE) + .withField(StandardField.YEAR, "2015") + .withField(StandardField.DATE, "2014"); + entryB = new BibEntry(BibEntry.DEFAULT_TYPE) + .withField(StandardField.YEAR, "2020") + .withField(StandardField.AUTHOR, "Author"); + when(stateManager.getSelectedEntries()).thenReturn(FXCollections.observableArrayList(entryA, entryB)); + + bibDatabase = new BibDatabase(); + clearContentViewModel = new ClearContentViewModel(stateManager); + } + + @Test + void clearSelectedFieldShouldDoNothingWhenFieldDoesntExistOrIsEmpty() { + clearContentViewModel.clearField(StandardField.YEAR); + + assertEquals(Optional.empty(), entryA.getField(StandardField.FILE)); + } + + @Test + void clearExistingFieldShouldRemoveFieldFromEntry() { + clearContentViewModel.clearField(StandardField.YEAR); + + assertEquals(Optional.empty(), entryA.getField(StandardField.YEAR)); + } + + @Test + void clearNonExistingFieldShouldNotAffectEntry() { + clearContentViewModel.clearField(StandardField.AUTHOR); + + assertEquals(Optional.of("2015"), entryA.getField(StandardField.YEAR)); + assertEquals(Optional.of("2014"), entryA.getField(StandardField.DATE)); + } + + @Test + void clearEmptyFieldShouldNotAffectEntry() { + clearContentViewModel.clearField(StandardField.DATE); + + assertEquals(Optional.of("2015"), entryA.getField(StandardField.YEAR)); + assertEquals(Optional.empty(), entryA.getField(StandardField.DATE)); + } + + @Test + void clearFieldAffectsOnlyEntryAForNonExistingField() { + clearContentViewModel.clearField(StandardField.FILE); + + assertEquals(Optional.of("2015"), entryA.getField(StandardField.YEAR)); + assertEquals(Optional.of("2020"), entryB.getField(StandardField.YEAR)); + assertEquals(Optional.of("Author"), entryB.getField(StandardField.AUTHOR)); + } +} diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index ade235fa98f..86a7af1763f 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -1484,6 +1484,7 @@ Show\ preferences=Show preferences Save\ actions=Save actions Other\ fields=Other fields Show\ remaining\ fields=Show remaining fields +Show\ only\ set\ fields=Show only set fields link\ should\ refer\ to\ a\ correct\ file\ path=link should refer to a correct file path abbreviation\ detected=abbreviation detected @@ -2606,6 +2607,8 @@ Overwrite\ field\ content=Overwrite field content Set=Set Append=Append Clear\ field\ content=Clear field content +Clear\ field\ content\ for\ selected\ entries=Clear field content for selected entries +Clear\ content=Clear content Set\ or\ append\ content=Set or append content Edit\ field\ content\ for\ selected\ entries=Edit field content for selected entries Rename=Rename