From 76e2ce773050429564a538e7f3d4fe0ae1b040ac Mon Sep 17 00:00:00 2001 From: NiD133 <91547740+NiD133@users.noreply.github.com> Date: Tue, 6 Jun 2023 14:38:53 +0100 Subject: [PATCH] User specific comment (#9727) * Create user specific comment field * User can make user-specific comments * User specific comment will not display in other fields * Refactor the code * Make changes * Force to make user specific comments * Fix error * Test if to show the other user's comments * Delete useless code * Add changes in CHANGELOG * Make changes only author can edit user specific comment * Specify the user specific comment does not belong to current user * Add test case for comments tab * Refactor code based on the feedback * Refactor tests about the CommentsTab * Add missing localization keys in the English language file. * Modify the failing checkstyle tests * Modify the order of import * Separate the UndoManager import * Make changes * Update the user specific comment field * Remove the dependency on the entry type for presence of comments * Change code for checkstyle * Additional logic to parseField to better handle user specific comment. * Fetch the username using PreferencesService to improve simplicity. * Merge "All-Comments" into tab "Comments" * Minor code beautification * Simplify tests (and add one more test) * Always list "Comment" field * Refine CHANGELOG entry * Remove tool tip (not containing much additional information) --------- Co-authored-by: Christoph Co-authored-by: Oliver Kopp --- CHANGELOG.md | 1 + .../jabref/gui/entryeditor/CommentsTab.java | 91 ++++++++++++ .../gui/entryeditor/DeprecatedFieldsTab.java | 1 + .../jabref/gui/entryeditor/EntryEditor.java | 36 +++-- .../gui/entryeditor/FieldsEditorTab.java | 8 +- .../gui/entryeditor/LatexCitationsTab.java | 1 + .../jabref/gui/entryeditor/MathSciNetTab.java | 2 + .../gui/entryeditor/OptionalFields2Tab.java | 3 + .../gui/entryeditor/OptionalFieldsTab.java | 3 + .../gui/entryeditor/OtherFieldsTab.java | 9 +- .../jabref/gui/entryeditor/PreviewTab.java | 1 + .../gui/entryeditor/RelatedArticlesTab.java | 1 + .../gui/entryeditor/RequiredFieldsTab.java | 2 +- .../fileannotationtab/FileAnnotationTab.java | 1 + .../FulltextSearchResultsTab.java | 1 + .../model/entry/field/FieldFactory.java | 6 + .../model/entry/field/FieldProperty.java | 3 +- .../model/entry/field/StandardField.java | 2 +- .../entry/field/UserSpecificCommentField.java | 53 +++++++ .../gui/entryeditor/CommentsTabTest.java | 132 ++++++++++++++++++ .../model/entry/field/FieldFactoryTest.java | 18 +++ 21 files changed, 352 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/jabref/gui/entryeditor/CommentsTab.java create mode 100644 src/main/java/org/jabref/model/entry/field/UserSpecificCommentField.java create mode 100644 src/test/java/org/jabref/gui/entryeditor/CommentsTabTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bad38b0248..54e89137cbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added - We added a field showing the BibTeX/biblatex source for added and deleted entries in the "External Changes Resolver" dialog. [#9509](https://github.com/JabRef/jabref/issues/9509) +- We added user-specific comment field so that multiple users can make separate comments. [#543](https://github.com/koppor/jabref/issues/543) - We added a search history list in the search field's right click menu. [#7906](https://github.com/JabRef/jabref/issues/7906) - We added a full text fetcher for IACR eprints. [#9651](https://github.com/JabRef/jabref/pull/9651) - We added "Attach file from URL" to right-click context menu to download and store a file with the reference library. [#9646](https://github.com/JabRef/jabref/issues/9646) diff --git a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java new file mode 100644 index 00000000000..269ab381a83 --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java @@ -0,0 +1,91 @@ +package org.jabref.gui.entryeditor; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.autocompleter.SuggestionProviders; +import org.jabref.gui.fieldeditors.FieldEditorFX; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.pdf.search.indexing.IndexingTaskManager; +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.StandardField; +import org.jabref.model.entry.field.UserSpecificCommentField; +import org.jabref.preferences.PreferencesService; + +public class CommentsTab extends FieldsEditorTab { + public static final String NAME = "Comments"; + + private final String defaultOwner; + public CommentsTab(PreferencesService preferences, + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + DialogService dialogService, + StateManager stateManager, + ThemeManager themeManager, + IndexingTaskManager indexingTaskManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { + super( + false, + databaseContext, + suggestionProviders, + undoManager, + dialogService, + preferences, + stateManager, + themeManager, + taskExecutor, + journalAbbreviationRepository, + indexingTaskManager + ); + this.defaultOwner = preferences.getOwnerPreferences().getDefaultOwner(); + setText(Localization.lang("Comments")); + setGraphic(IconTheme.JabRefIcons.COMMENT.getGraphicNode()); + } + + @Override + protected Set determineFieldsToShow(BibEntry entry) { + UserSpecificCommentField defaultCommentField = new UserSpecificCommentField(defaultOwner); + + // As default: Show BibTeX comment field and the user-specific comment field of the default owner + Set comments = new LinkedHashSet<>(Set.of(defaultCommentField, StandardField.COMMENT)); + + comments.addAll(entry.getFields().stream() + .filter(field -> field instanceof UserSpecificCommentField || + field.getName().toLowerCase().contains("comment")) + .collect(Collectors.toSet())); + + return comments; + } + + @Override + protected void setupPanel(BibEntry entry, boolean compressed) { + super.setupPanel(entry, compressed); + + for (Map.Entry fieldEditorEntry : editors.entrySet()) { + Field field = fieldEditorEntry.getKey(); + FieldEditorFX editor = fieldEditorEntry.getValue(); + + if (field instanceof UserSpecificCommentField) { + if (field.getName().contains(defaultOwner)) { + editor.getNode().setDisable(false); + } + } else { + editor.getNode().setDisable(!field.getName().equals(StandardField.COMMENT.getName())); + } + } + } +} diff --git a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index bc24872ba1d..a40302d54c6 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -30,6 +30,7 @@ public class DeprecatedFieldsTab extends FieldsEditorTab { + public static final String NAME = "Deprecated fields"; private final BibEntryTypesManager entryTypesManager; public DeprecatedFieldsTab(BibDatabaseContext databaseContext, diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 516c0db9a26..cdd7457b1cd 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -3,6 +3,7 @@ import java.io.File; import java.nio.file.Path; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -245,31 +246,43 @@ private void navigateToNextEntry() { } private List createTabs() { - // Preview tab entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager())); - // Required fields + // Required, optional, deprecated, and "other" fields entryEditorTabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); - - // Optional fields entryEditorTabs.add(new OptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); entryEditorTabs.add(new OptionalFields2Tab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); - - // Other fields entryEditorTabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); + // Comment Tab: Tab for general and user-specific comments + entryEditorTabs.add(new CommentsTab(preferencesService, databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor, journalAbbreviationRepository)); + // General fields from preferences - for (Map.Entry> tab : entryEditorPreferences.getEntryEditorTabList().entrySet()) { + // First, remove all tabs that are already handled above or below; except for the source tab (which has different titles for BibTeX and BibLaTeX mode) + Map> entryEditorTabList = new HashMap<>(entryEditorPreferences.getEntryEditorTabList()); + entryEditorTabList.remove(PreviewTab.NAME); + entryEditorTabList.remove(RequiredFieldsTab.NAME); + entryEditorTabList.remove(OptionalFieldsTab.NAME); + entryEditorTabList.remove(OptionalFields2Tab.NAME); + entryEditorTabList.remove(DeprecatedFieldsTab.NAME); + entryEditorTabList.remove(CommentsTab.NAME); + entryEditorTabList.remove(MathSciNetTab.NAME); + entryEditorTabList.remove(FileAnnotationTab.NAME); + entryEditorTabList.remove(RelatedArticlesTab.NAME); + entryEditorTabList.remove(LatexCitationsTab.NAME); + entryEditorTabList.remove(FulltextSearchResultsTab.NAME); + entryEditorTabList.remove("Comments"); + // Then show the remaining configured + for (Map.Entry> tab : entryEditorTabList.entrySet()) { entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor, journalAbbreviationRepository)); } - // Special tabs + // "Special" tabs entryEditorTabs.add(new MathSciNetTab()); entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); entryEditorTabs.add(new RelatedArticlesTab(this, entryEditorPreferences, preferencesService, dialogService)); - // Source tab sourceTab = new SourceTab( databaseContext, undoManager, @@ -281,7 +294,6 @@ private List createTabs() { keyBindingRepository); entryEditorTabs.add(sourceTab); - // LaTeX citations tab entryEditorTabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor, dialogService)); entryEditorTabs.add(new FulltextSearchResultsTab(stateManager, preferencesService, dialogService)); @@ -408,8 +420,8 @@ private void fetchAndMerge(EntryBasedFetcher fetcher) { public void setFocusToField(Field field) { DefaultTaskExecutor.runInJavaFXThread(() -> { for (Tab tab : tabbed.getTabs()) { - if ((tab instanceof FieldsEditorTab fieldsEditorTab) && fieldsEditorTab.getShownFields() - .contains(field)) { + if ((tab instanceof FieldsEditorTab fieldsEditorTab) + && fieldsEditorTab.getShownFields().contains(field)) { tabbed.getSelectionModel().select(tab); fieldsEditorTab.requestFocus(field); } diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 49fc1d8715b..8ab2b955eae 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -46,7 +46,7 @@ */ abstract class FieldsEditorTab extends EntryEditorTab { protected final BibDatabaseContext databaseContext; - private final Map editors = new LinkedHashMap<>(); + protected final Map editors = new LinkedHashMap<>(); private final boolean isCompressed; private final SuggestionProviders suggestionProviders; private final DialogService dialogService; @@ -93,7 +93,7 @@ private static void addColumn(GridPane gridPane, int columnIndex, Stream gridPane.addColumn(columnIndex, nodes.toArray(Node[]::new)); } - private void setupPanel(BibEntry entry, boolean compressed) { + protected void setupPanel(BibEntry entry, boolean compressed) { // The preferences might be not initialized in tests -> return immediately // TODO: Replace this ugly workaround by proper injection propagation if (preferences == null) { @@ -184,9 +184,6 @@ private void setCompressedRowLayout(GridPane gridPane, int rows) { } } - /** - * Focuses the given field. - */ public void requestFocus(Field fieldName) { if (editors.containsKey(fieldName)) { editors.get(fieldName).focus(); @@ -202,7 +199,6 @@ public boolean shouldShow(BibEntry entry) { protected void bindToEntry(BibEntry entry) { initPanel(); setupPanel(entry, isCompressed); - if (previewPanel != null) { previewPanel.setEntry(entry); } diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java index 87658d4e46c..fd3b47a43f9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java @@ -27,6 +27,7 @@ public class LatexCitationsTab extends EntryEditorTab { + public static final String NAME = "LaTeX Citations"; private final LatexCitationsTabViewModel viewModel; private final GridPane searchPane; private final ProgressIndicator progressIndicator; diff --git a/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java b/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java index 3113d016542..9f4ed909090 100644 --- a/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java @@ -14,6 +14,8 @@ public class MathSciNetTab extends EntryEditorTab { + public static final String NAME = "MathSciNet Review"; + public MathSciNetTab() { setText(Localization.lang("MathSciNet Review")); } diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java b/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java index 97c38da58dd..da37540e513 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java @@ -15,6 +15,9 @@ import org.jabref.preferences.PreferencesService; public class OptionalFields2Tab extends OptionalFieldsTabBase { + + public static final String NAME = "Optional fields 2"; + public OptionalFields2Tab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java index 93a6ff4b6f1..d511ef268cf 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java @@ -15,6 +15,9 @@ import org.jabref.preferences.PreferencesService; public class OptionalFieldsTab extends OptionalFieldsTabBase { + + public static final String NAME = "Optional fields"; + public OptionalFieldsTab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, diff --git a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java index 180ea465c53..d06b6b41a89 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java @@ -28,10 +28,13 @@ import org.jabref.model.entry.field.BibField; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.InternalField; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UserSpecificCommentField; import org.jabref.preferences.PreferencesService; public class OtherFieldsTab extends FieldsEditorTab { + public static final String NAME = "Other fields"; private final List customTabFieldNames; private final BibEntryTypesManager entryTypesManager; @@ -72,8 +75,10 @@ protected Set determineFieldsToShow(BibEntry entry) { Optional entryType = entryTypesManager.enrich(entry.getType(), mode); if (entryType.isPresent()) { Set allKnownFields = entryType.get().getAllFields(); - Set otherFields = entry.getFields().stream().filter(field -> !allKnownFields.contains(field)).collect(Collectors.toCollection(LinkedHashSet::new)); - + Set otherFields = entry.getFields().stream() + .filter(field -> !allKnownFields.contains(field) && + !(field.equals(StandardField.COMMENT) || field instanceof UserSpecificCommentField)) + .collect(Collectors.toCollection(LinkedHashSet::new)); otherFields.removeAll(entryType.get().getDeprecatedFields(mode)); otherFields.removeAll(entryType.get().getOptionalFields().stream().map(BibField::field).collect(Collectors.toSet())); otherFields.remove(InternalField.KEY_FIELD); diff --git a/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java b/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java index 4212506b8f6..908349bc0ad 100644 --- a/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java @@ -12,6 +12,7 @@ import org.jabref.preferences.PreferencesService; public class PreviewTab extends EntryEditorTab { + public static final String NAME = "Preview"; private final DialogService dialogService; private final BibDatabaseContext databaseContext; private final PreferencesService preferences; diff --git a/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java b/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java index f0031c15653..ec1faf68ca2 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java @@ -40,6 +40,7 @@ */ public class RelatedArticlesTab extends EntryEditorTab { + public static final String NAME = "Related articles"; private static final Logger LOGGER = LoggerFactory.getLogger(RelatedArticlesTab.class); private final EntryEditorPreferences preferences; private final DialogService dialogService; diff --git a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java index 3451f2c8f53..9034a76f0c4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java @@ -28,6 +28,7 @@ public class RequiredFieldsTab extends FieldsEditorTab { + public static final String NAME = "Required fields"; private final BibEntryTypesManager entryTypesManager; public RequiredFieldsTab(BibDatabaseContext databaseContext, @@ -44,7 +45,6 @@ public RequiredFieldsTab(BibDatabaseContext databaseContext, super(false, databaseContext, suggestionProviders, undoManager, dialogService, preferences, stateManager, themeManager, taskExecutor, journalAbbreviationRepository, indexingTaskManager); this.entryTypesManager = entryTypesManager; - setText(Localization.lang("Required fields")); setTooltip(new Tooltip(Localization.lang("Show required fields"))); setGraphic(IconTheme.JabRefIcons.REQUIRED.getGraphicNode()); diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java index 9ba9c1b23e8..338cc91872b 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java @@ -13,6 +13,7 @@ public class FileAnnotationTab extends EntryEditorTab { + public static final String NAME = "File annotations"; private final FileAnnotationCache fileAnnotationCache; public FileAnnotationTab(FileAnnotationCache cache) { diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java index 8fe5c146b5b..539307e3152 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java @@ -38,6 +38,7 @@ public class FulltextSearchResultsTab extends EntryEditorTab { + public static final String NAME = "Search results"; private static final Logger LOGGER = LoggerFactory.getLogger(FulltextSearchResultsTab.class); private final StateManager stateManager; diff --git a/src/main/java/org/jabref/model/entry/field/FieldFactory.java b/src/main/java/org/jabref/model/entry/field/FieldFactory.java index 2eceb820c10..5da3e3fb428 100644 --- a/src/main/java/org/jabref/model/entry/field/FieldFactory.java +++ b/src/main/java/org/jabref/model/entry/field/FieldFactory.java @@ -74,6 +74,11 @@ public static String serializeFieldsList(Collection fields) { } public static Field parseField(T type, String fieldName) { + // Check if the field name starts with "comment-" which indicates it's a UserSpecificCommentField + if (fieldName.startsWith("comment-")) { + String username = fieldName.substring("comment-".length()); + return new UserSpecificCommentField(username); + } return OptionalUtil.orElse( OptionalUtil.orElse( OptionalUtil.orElse( @@ -156,6 +161,7 @@ private static Set getAllFields() { fields.addAll(EnumSet.allOf(InternalField.class)); fields.addAll(EnumSet.allOf(SpecialField.class)); fields.addAll(EnumSet.allOf(StandardField.class)); + fields.removeIf(field -> field instanceof UserSpecificCommentField); return fields; } diff --git a/src/main/java/org/jabref/model/entry/field/FieldProperty.java b/src/main/java/org/jabref/model/entry/field/FieldProperty.java index 2eae6e18b3a..cba4a76714b 100644 --- a/src/main/java/org/jabref/model/entry/field/FieldProperty.java +++ b/src/main/java/org/jabref/model/entry/field/FieldProperty.java @@ -23,5 +23,6 @@ public enum FieldProperty { SINGLE_ENTRY_LINK, TYPE, VERBATIM, - YES_NO + YES_NO, + COMMENT } diff --git a/src/main/java/org/jabref/model/entry/field/StandardField.java b/src/main/java/org/jabref/model/entry/field/StandardField.java index 2ca594674fa..c4cd9f4e612 100644 --- a/src/main/java/org/jabref/model/entry/field/StandardField.java +++ b/src/main/java/org/jabref/model/entry/field/StandardField.java @@ -31,7 +31,7 @@ public enum StandardField implements Field { BOOKTITLEADDON("booktitleaddon"), CHAPTER("chapter"), COMMENTATOR("commentator", FieldProperty.PERSON_NAMES), - COMMENT("comment"), + COMMENT("comment", FieldProperty.COMMENT), CROSSREF("crossref", FieldProperty.SINGLE_ENTRY_LINK), DATE("date", FieldProperty.DATE), DAY("day"), diff --git a/src/main/java/org/jabref/model/entry/field/UserSpecificCommentField.java b/src/main/java/org/jabref/model/entry/field/UserSpecificCommentField.java new file mode 100644 index 00000000000..5066ecdb335 --- /dev/null +++ b/src/main/java/org/jabref/model/entry/field/UserSpecificCommentField.java @@ -0,0 +1,53 @@ +package org.jabref.model.entry.field; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +public class UserSpecificCommentField implements Field { + private static final Set PROPERTIES = EnumSet.of(FieldProperty.COMMENT); + private final String name; + + public UserSpecificCommentField(String username) { + this.name = "comment-" + username; + } + + @Override + public Set getProperties() { + return PROPERTIES; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isStandardField() { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Field)) { + return false; + } + Field other = (Field) o; + return name.equals(other.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "UnknownCommentField{" + + "name='" + name + '\'' + + '}'; + } +} diff --git a/src/test/java/org/jabref/gui/entryeditor/CommentsTabTest.java b/src/test/java/org/jabref/gui/entryeditor/CommentsTabTest.java new file mode 100644 index 00000000000..1d255cf7536 --- /dev/null +++ b/src/test/java/org/jabref/gui/entryeditor/CommentsTabTest.java @@ -0,0 +1,132 @@ +package org.jabref.gui.entryeditor; + +import java.util.Optional; +import java.util.Set; + +import javax.swing.undo.UndoManager; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.autocompleter.SuggestionProviders; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.pdf.search.indexing.IndexingTaskManager; +import org.jabref.logic.preferences.OwnerPreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UserSpecificCommentField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.preferences.PreferencesService; +import org.jabref.testutils.category.GUITest; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testfx.framework.junit5.ApplicationExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@GUITest +@ExtendWith(ApplicationExtension.class) +class CommentsTabTest { + + private final String ownerName = "user1"; + + private CommentsTab commentsTab; + + @Mock + private BibEntryTypesManager entryTypesManager; + @Mock + private BibDatabaseContext databaseContext; + @Mock + private SuggestionProviders suggestionProviders; + @Mock + private UndoManager undoManager; + @Mock + private DialogService dialogService; + @Mock + private PreferencesService preferences; + @Mock + private StateManager stateManager; + @Mock + private ThemeManager themeManager; + @Mock + private TaskExecutor taskExecutor; + @Mock + private JournalAbbreviationRepository journalAbbreviationRepository; + @Mock + private IndexingTaskManager indexingTaskManager; + @Mock + private OwnerPreferences ownerPreferences; + + @BeforeEach + void setUp() { + MockitoAnnotations.initMocks(this); + + when(preferences.getOwnerPreferences()).thenReturn(ownerPreferences); + when(ownerPreferences.getDefaultOwner()).thenReturn(ownerName); + when(databaseContext.getMode()).thenReturn(BibDatabaseMode.BIBLATEX); + BibEntryType entryTypeMock = mock(BibEntryType.class); + when(entryTypesManager.enrich(any(), any())).thenReturn(Optional.of(entryTypeMock)); + + commentsTab = new CommentsTab( + preferences, + databaseContext, + suggestionProviders, + undoManager, + dialogService, + stateManager, + themeManager, + indexingTaskManager, + taskExecutor, + journalAbbreviationRepository + ); + } + + @Test + void testDetermineFieldsToShowWorksForASingleUser() { + final UserSpecificCommentField ownerComment = new UserSpecificCommentField(ownerName); + + BibEntry entry = new BibEntry(StandardEntryType.Book) + .withField(StandardField.COMMENT, "Standard comment text") + .withField(ownerComment, "User-specific comment text"); + + Set fields = commentsTab.determineFieldsToShow(entry); + + assertEquals(Set.of(StandardField.COMMENT, ownerComment), fields); + } + + @Test + void testDetermineFieldsToShowWorksForMultipleUsers() { + final UserSpecificCommentField ownerComment = new UserSpecificCommentField(ownerName); + final UserSpecificCommentField otherUsersComment = new UserSpecificCommentField("other-user-id"); + + BibEntry entry = new BibEntry(StandardEntryType.Book) + .withField(StandardField.COMMENT, "Standard comment text") + .withField(ownerComment, "User-specific comment text") + .withField(otherUsersComment, "other-user-id comment text"); + + Set fields = commentsTab.determineFieldsToShow(entry); + + assertEquals(Set.of(StandardField.COMMENT, ownerComment, otherUsersComment), fields); + } + + @Test + public void testDifferentiateCaseInUserName() { + UserSpecificCommentField field1 = new UserSpecificCommentField("USER"); + UserSpecificCommentField field2 = new UserSpecificCommentField("user"); + assertNotEquals(field1, field2, "Two UserSpecificCommentField instances with usernames that differ only by case should be considered different"); + } +} diff --git a/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java b/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java index ddb60cb61a7..33aa2e18555 100644 --- a/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java +++ b/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java @@ -1,8 +1,13 @@ package org.jabref.model.entry.field; +import java.util.stream.Stream; + import org.jabref.model.entry.types.BiblatexApaEntryType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -18,6 +23,19 @@ void testOrFieldsThreeTerms() { assertEquals("aaa/bbb/ccc", FieldFactory.serializeOrFields(new UnknownField("aaa"), new UnknownField("bbb"), new UnknownField("ccc"))); } + private static Stream commentFields() { + return Stream.of( + Arguments.of(new UserSpecificCommentField("user1"), "comment-user1"), + Arguments.of(new UserSpecificCommentField("other-user-id"), "comment-other-user-id") + ); + } + + @ParameterizedTest + @MethodSource + void commentFields(Field expected, String name) { + assertEquals(expected, FieldFactory.parseField(name)); + } + @Test void testDoesNotParseApaFieldWithoutEntryType() { assertNotEquals(BiblatexApaField.ARTICLE, FieldFactory.parseField("article"));