diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 760709e1504..dc1077e570e 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -67,7 +67,6 @@ import org.jabref.gui.actions.ManageCustomExportsAction; import org.jabref.gui.actions.ManageCustomImportsAction; import org.jabref.gui.actions.ManageJournalsAction; -import org.jabref.gui.actions.ManageKeywordsAction; import org.jabref.gui.actions.NewDatabaseAction; import org.jabref.gui.actions.NewEntryAction; import org.jabref.gui.actions.NewEntryFromPlainTextAction; @@ -81,6 +80,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.dialogs.AutosaveUIManager; +import org.jabref.gui.edit.ManageKeywordsAction; import org.jabref.gui.edit.MassSetFieldsAction; import org.jabref.gui.exporter.ExportCommand; import org.jabref.gui.exporter.ExportToClipboardAction; diff --git a/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java b/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java deleted file mode 100644 index 0af7a02f91c..00000000000 --- a/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java +++ /dev/null @@ -1,356 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.Enumeration; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.DefaultListModel; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JList; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JTextField; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.specialfields.SpecialFieldsUtils; -import org.jabref.model.FieldChange; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.Keyword; -import org.jabref.model.entry.KeywordList; -import org.jabref.model.strings.StringUtil; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -/** - * An Action for launching keyword managing dialog - * - */ -public class ManageKeywordsAction extends SimpleCommand { - - private final JabRefFrame frame; - private final KeywordList sortedKeywordsOfAllEntriesBeforeUpdateByUser = new KeywordList(); - private JDialog diag; - private DefaultListModel keywordListModel; - private JRadioButton intersectKeywords; - private JRadioButton mergeKeywords; - private boolean canceled; - - - public ManageKeywordsAction(JabRefFrame frame) { - this.frame = frame; - } - - private void createDialog() { - if (diag != null) { - return; - } - // keyword to add - JTextField keyword = new JTextField(); - - keywordListModel = new DefaultListModel<>(); - JList keywordList = new JList<>(keywordListModel); - keywordList.setVisibleRowCount(8); - JScrollPane kPane = new JScrollPane(keywordList); - - diag = new JDialog((JFrame) null, Localization.lang("Manage keywords"), true); - - JButton ok = new JButton(Localization.lang("OK")); - JButton cancel = new JButton(Localization.lang("Cancel")); - JButton add = new JButton(Localization.lang("Add")); - JButton remove = new JButton(Localization.lang("Remove")); - JButton replace = new JButton(Localization.lang("Replace")); - JButton join = new JButton(Localization.lang("Join")); - - join.setToolTipText(Localization.lang("Joins selected keywords and deletes selected keywords.")); - - keywordList.setVisibleRowCount(10); - - intersectKeywords = new JRadioButton(Localization.lang("Display keywords appearing in ALL entries")); - mergeKeywords = new JRadioButton(Localization.lang("Display keywords appearing in ANY entry")); - ButtonGroup group = new ButtonGroup(); - group.add(intersectKeywords); - group.add(mergeKeywords); - ActionListener stateChanged = e -> fillKeyWordList(); - intersectKeywords.addActionListener(stateChanged); - mergeKeywords.addActionListener(stateChanged); - intersectKeywords.setSelected(true); - - FormBuilder builder = FormBuilder.create().layout(new FormLayout("fill:200dlu:grow, pref, fill:pref", - "pref, 2dlu, pref, 1dlu, pref, 2dlu, fill:100dlu:grow, 4dlu, pref, 4dlu, pref, ")); - - builder.addSeparator(Localization.lang("Keywords of selected entries")).xyw(1, 1, 3); - builder.add(intersectKeywords).xyw(1, 3, 3); - builder.add(mergeKeywords).xyw(1, 5, 3); - builder.add(kPane).xywh(1, 7, 1, 3); - builder.add(join).xy(2,9); - builder.add(replace).xy(3, 9); - builder.add(keyword).xy(1, 11); - builder.add(add).xy(2, 11); - builder.add(remove).xy(3, 11); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(cancel); - bb.addGlue(); - builder.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - ok.addActionListener(e -> { - canceled = false; - diag.dispose(); - }); - - Action cancelAction = new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - canceled = true; - diag.dispose(); - } - }; - cancel.addActionListener(cancelAction); - - final ActionListener addActionListener = arg0 -> addButtonActionListener(keyword); - - add.addActionListener(addActionListener); - - final ActionListener removeActionListener = arg0 -> { - // keywordList.getSelectedIndices(); does not work, therefore we operate on the values - List values = keywordList.getSelectedValuesList(); - - for (Keyword val : values) { - keywordListModel.removeElement(val); - } - }; - - remove.addActionListener(removeActionListener); - - final ActionListener joinActionListener = arg0 -> { - List values = keywordList.getSelectedValuesList(); - String joinedKeyword = values.stream().map(currentKeyword -> currentKeyword.get()).collect(Collectors.joining(" ")); - this.addKeywordToKeywordListModel(joinedKeyword); - - for (Keyword val : values) { - this.keywordListModel.removeElement(val); - } - }; - - join.addActionListener(joinActionListener); - - final ActionListener replaceActionListener = arg0 -> { - List values = keywordList.getSelectedValuesList(); - - for (Keyword val : values) { - keywordListModel.removeElement(val); - } - addButtonActionListener(keyword); - }; - - replace.addActionListener(replaceActionListener); - - //enable a user to press Delete to delete a keyword - keywordList.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent arg0) { - if (arg0.getKeyCode() == KeyEvent.VK_DELETE) { - removeActionListener.actionPerformed(null); - } - } - }); - - //enable a user to press Enter to add a keyword - keyword.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - addActionListener.actionPerformed(null); - } - } - }); - - // Key bindings: - ActionMap am = builder.getPanel().getActionMap(); - InputMap im = builder.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - am.put("close", cancelAction); - - diag.getContentPane().add(builder.getPanel(), BorderLayout.CENTER); - diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); - } - - private void addButtonActionListener(JTextField keywordTextField) { - if (StringUtil.isBlank(keywordTextField.getText())) { - return; // nothing to add - } - addKeywordToKeywordListModel(keywordTextField.getText()); - keywordTextField.setText(null); - keywordTextField.requestFocusInWindow(); - - } - - /** - * Adds given keyword to the keyword list model - */ - private void addKeywordToKeywordListModel(String keyword) { - String keywordToAdd = Objects.requireNonNull(keyword).trim(); - Keyword newKeyword = new Keyword(keywordToAdd); - if (keywordListModel.isEmpty()) { - keywordListModel.addElement(newKeyword); - } else { - int idx = 0; - Keyword element = keywordListModel.getElementAt(idx); - while ((idx < keywordListModel.size()) && (element.compareTo(newKeyword) < 0)) { - idx++; - } - if (idx == keywordListModel.size()) { - // list is empty or word is greater than last word in list - keywordListModel.addElement(newKeyword); - } else if (element.compareTo(newKeyword) == 0) { - // nothing to do, word already in table - } else { - keywordListModel.add(idx, newKeyword); - } - } - } - - @Override - public void execute() { - BasePanel bp = frame.getCurrentBasePanel(); - if (bp == null) { - return; - } - if (bp.getSelectedEntries().isEmpty()) { - bp.output(Localization.lang("Select at least one entry to manage keywords.")); - return; - } - - // Lazy creation of the dialog: - createDialog(); - - canceled = true; - - fillKeyWordList(); - - diag.pack(); - diag.setVisible(true); - if (canceled) { - return; - } - - KeywordList keywordsToAdd = new KeywordList(); - KeywordList userSelectedKeywords = new KeywordList(); - // build keywordsToAdd and userSelectedKeywords in parallel - for (Enumeration keywords = keywordListModel.elements(); keywords.hasMoreElements();) { - Keyword keyword = keywords.nextElement(); - userSelectedKeywords.add(keyword); - if (!sortedKeywordsOfAllEntriesBeforeUpdateByUser.contains(keyword)) { - keywordsToAdd.add(keyword); - } - } - - KeywordList keywordsToRemove = new KeywordList(); - for (Keyword kword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { - if (!userSelectedKeywords.contains(kword)) { - keywordsToRemove.add(kword); - } - } - - if (keywordsToAdd.isEmpty() && keywordsToRemove.isEmpty()) { - // nothing to be done if nothing is new and nothing is obsolete - return; - } - - if (Globals.prefs.isKeywordSyncEnabled() && !keywordsToAdd.isEmpty()) { - SpecialFieldsUtils.synchronizeSpecialFields(keywordsToAdd, keywordsToRemove); - } - - NamedCompound ce = updateKeywords(bp.getSelectedEntries(), keywordsToAdd, keywordsToRemove); - bp.getUndoManager().addEdit(ce); - bp.markBaseChanged(); - } - - private NamedCompound updateKeywords(List entries, KeywordList keywordsToAdd, - KeywordList keywordsToRemove) { - NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); - for (BibEntry entry : entries) { - KeywordList keywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); - - // update keywords - keywords.removeAll(keywordsToRemove); - keywords.addAll(keywordsToAdd); - - // put keywords back - Optional change = entry.putKeywords(keywords, Globals.prefs.getKeywordDelimiter()); - if (change.isPresent()) { - ce.addEdit(new UndoableFieldChange(change.get())); - } - - if (Globals.prefs.isKeywordSyncEnabled()) { - SpecialFieldsUtils.syncSpecialFieldsFromKeywords(entry, Globals.prefs.getKeywordDelimiter()); - } - } - ce.end(); - return ce; - } - - private void fillKeyWordList() { - BasePanel bp = frame.getCurrentBasePanel(); - List entries = bp.getSelectedEntries(); - - // fill dialog with values - keywordListModel.clear(); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.clear(); - - if (mergeKeywords.isSelected()) { - for (BibEntry entry : entries) { - KeywordList separatedKeywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); - } - } else { - assert intersectKeywords.isSelected(); - - // all keywords from first entry have to be added - BibEntry firstEntry = entries.get(0); - KeywordList separatedKeywords = firstEntry.getKeywords(Globals.prefs.getKeywordDelimiter()); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); - - // for the remaining entries, intersection has to be used - // this approach ensures that one empty keyword list leads to an empty set of common keywords - for (int i = 1; i < entries.size(); i++) { - BibEntry entry = entries.get(i); - separatedKeywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.retainAll(separatedKeywords); - } - } - for (Keyword keyword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { - keywordListModel.addElement(keyword); - } - } - -} diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java new file mode 100644 index 00000000000..333e206cd02 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java @@ -0,0 +1,34 @@ +package org.jabref.gui.edit; + +import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.logic.l10n.Localization; + +/** + * An Action for launching keyword managing dialog + * + */ +public class ManageKeywordsAction extends SimpleCommand { + + private final JabRefFrame frame; + + public ManageKeywordsAction(JabRefFrame frame) { + this.frame = frame; + } + + @Override + public void execute() { + BasePanel basePanel = frame.getCurrentBasePanel(); + if (basePanel == null) { + return; + } + if (basePanel.getSelectedEntries().isEmpty()) { + basePanel.output(Localization.lang("Select at least one entry to manage keywords.")); + return; + } + + ManageKeywordsDialog dialog = new ManageKeywordsDialog(basePanel.getSelectedEntries()); + dialog.showAndWait(); + } +} diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.fxml b/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.fxml new file mode 100644 index 00000000000..609c6655bb9 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java new file mode 100644 index 00000000000..2b1201fd24e --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java @@ -0,0 +1,83 @@ +package org.jabref.gui.edit; + +import java.util.List; + +import javax.inject.Inject; + +import javafx.fxml.FXML; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.cell.TextFieldTableCell; + +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.BindingsHelper; +import org.jabref.gui.util.ValueTableCellFactory; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.PreferencesService; + +import com.airhacks.afterburner.views.ViewLoader; +import org.fxmisc.easybind.EasyBind; + +public class ManageKeywordsDialog extends BaseDialog { + private final List entries; + @FXML private TableColumn keywordsTableMainColumn; + @FXML private TableColumn keywordsTableEditColumn; + @FXML private TableColumn keywordsTableDeleteColumn; + @FXML private TableView keywordsTable; + @FXML private ToggleGroup displayType; + @Inject private PreferencesService preferences; + private ManageKeywordsViewModel viewModel; + + public ManageKeywordsDialog(List entries) { + this.entries = entries; + this.setTitle(Localization.lang("Manage keywords")); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + setResultConverter(button -> { + if (button == ButtonType.APPLY) { + viewModel.saveChanges(); + } + return null; + }); + } + + @FXML + public void initialize() { + viewModel = new ManageKeywordsViewModel(preferences, entries); + + viewModel.displayTypeProperty().bind( + EasyBind.map(displayType.selectedToggleProperty(), toggle -> { + if (toggle != null) { + return (ManageKeywordsDisplayType) toggle.getUserData(); + } else { + return ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES; + } + }) + ); + + keywordsTable.setItems(viewModel.getKeywords()); + keywordsTableMainColumn.setCellValueFactory(data -> BindingsHelper.constantOf(data.getValue())); + keywordsTableMainColumn.setOnEditCommit(event -> { + // Poor mans reverse databinding (necessary because we use a constant value above) + viewModel.getKeywords().set(event.getTablePosition().getRow(), event.getNewValue()); + }); + keywordsTableMainColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + keywordsTableEditColumn.setCellValueFactory(data -> BindingsHelper.constantOf(true)); + keywordsTableDeleteColumn.setCellValueFactory(data -> BindingsHelper.constantOf(true)); + new ValueTableCellFactory() + .withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) + .withOnMouseClickedEvent(none -> event -> keywordsTable.edit(keywordsTable.getFocusModel().getFocusedIndex(), keywordsTableMainColumn)) + .install(keywordsTableEditColumn); + new ValueTableCellFactory() + .withGraphic(none -> IconTheme.JabRefIcons.REMOVE.getGraphicNode()) + .withOnMouseClickedEvent((keyword, none) -> event -> viewModel.removeKeyword(keyword)) + .install(keywordsTableDeleteColumn); + } +} diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java new file mode 100644 index 00000000000..0af51af0734 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java @@ -0,0 +1,6 @@ +package org.jabref.gui.edit; + +public enum ManageKeywordsDisplayType { + CONTAINED_IN_ALL_ENTRIES, + CONTAINED_IN_ANY_ENTRY, +} diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java new file mode 100644 index 00000000000..c071e652033 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java @@ -0,0 +1,139 @@ +package org.jabref.gui.edit; + +import java.util.List; +import java.util.Optional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.specialfields.SpecialFieldsUtils; +import org.jabref.model.FieldChange; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Keyword; +import org.jabref.model.entry.KeywordList; +import org.jabref.preferences.PreferencesService; + +import org.fxmisc.easybind.EasyBind; + +public class ManageKeywordsViewModel { + + private final List entries; + private final KeywordList sortedKeywordsOfAllEntriesBeforeUpdateByUser = new KeywordList(); + private final PreferencesService preferences; + private final ObjectProperty displayType = new SimpleObjectProperty<>(ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES); + private final ObservableList keywords; + + public ManageKeywordsViewModel(PreferencesService preferences, List entries) { + this.preferences = preferences; + this.entries = entries; + this.keywords = FXCollections.observableArrayList(); + + EasyBind.subscribe(displayType, this::fillKeywordsList); + } + + public ManageKeywordsDisplayType getDisplayType() { + return displayType.get(); + } + + public ObjectProperty displayTypeProperty() { + return displayType; + } + + private void fillKeywordsList(ManageKeywordsDisplayType type) { + keywords.clear(); + sortedKeywordsOfAllEntriesBeforeUpdateByUser.clear(); + + if (type == ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES) { + for (BibEntry entry : entries) { + KeywordList separatedKeywords = entry.getKeywords(preferences.getKeywordDelimiter()); + sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); + } + } else if (type == ManageKeywordsDisplayType.CONTAINED_IN_ANY_ENTRY) { + + // all keywords from first entry have to be added + BibEntry firstEntry = entries.get(0); + KeywordList separatedKeywords = firstEntry.getKeywords(preferences.getKeywordDelimiter()); + sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); + + // for the remaining entries, intersection has to be used + // this approach ensures that one empty keyword list leads to an empty set of common keywords + for (BibEntry entry : entries) { + separatedKeywords = entry.getKeywords(preferences.getKeywordDelimiter()); + sortedKeywordsOfAllEntriesBeforeUpdateByUser.retainAll(separatedKeywords); + } + } else { + throw new IllegalStateException("DisplayType " + type + " not handled"); + } + for (Keyword keyword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { + keywords.add(keyword.get()); + } + } + + public ObservableList getKeywords() { + return keywords; + } + + public void removeKeyword(String keyword) { + keywords.remove(keyword); + } + + public void saveChanges() { + KeywordList keywordsToAdd = new KeywordList(); + KeywordList userSelectedKeywords = new KeywordList(); + // build keywordsToAdd and userSelectedKeywords in parallel + for (String keyword : keywords) { + userSelectedKeywords.add(keyword); + if (!sortedKeywordsOfAllEntriesBeforeUpdateByUser.contains(keyword)) { + keywordsToAdd.add(keyword); + } + } + + KeywordList keywordsToRemove = new KeywordList(); + for (Keyword kword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { + if (!userSelectedKeywords.contains(kword)) { + keywordsToRemove.add(kword); + } + } + + if (keywordsToAdd.isEmpty() && keywordsToRemove.isEmpty()) { + // nothing to be done if nothing is new and nothing is obsolete + return; + } + + if (preferences.isKeywordSyncEnabled() && !keywordsToAdd.isEmpty()) { + SpecialFieldsUtils.synchronizeSpecialFields(keywordsToAdd, keywordsToRemove); + } + + NamedCompound ce = updateKeywords(entries, keywordsToAdd, keywordsToRemove); + //TODO: bp.getUndoManager().addEdit(ce); + } + + private NamedCompound updateKeywords(List entries, KeywordList keywordsToAdd, + KeywordList keywordsToRemove) { + NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); + for (BibEntry entry : entries) { + KeywordList keywords = entry.getKeywords(preferences.getKeywordDelimiter()); + + // update keywords + keywords.removeAll(keywordsToRemove); + keywords.addAll(keywordsToAdd); + + // put keywords back + Optional change = entry.putKeywords(keywords, preferences.getKeywordDelimiter()); + if (change.isPresent()) { + ce.addEdit(new UndoableFieldChange(change.get())); + } + + if (preferences.isKeywordSyncEnabled()) { + SpecialFieldsUtils.syncSpecialFieldsFromKeywords(entry, preferences.getKeywordDelimiter()); + } + } + ce.end(); + return ce; + } +} diff --git a/src/main/java/org/jabref/model/entry/KeywordList.java b/src/main/java/org/jabref/model/entry/KeywordList.java index 2e13ce4c63f..aad112c7e87 100644 --- a/src/main/java/org/jabref/model/entry/KeywordList.java +++ b/src/main/java/org/jabref/model/entry/KeywordList.java @@ -137,6 +137,10 @@ public boolean contains(Keyword o) { return keywordChains.contains(o); } + public boolean contains(String keywordString) { + return contains(new Keyword(keywordString)); + } + public boolean remove(Keyword o) { return keywordChains.remove(o); } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 39ef552b6f0..a5aaf265c09 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -1447,9 +1447,10 @@ public FieldContentParserPreferences getFieldContentParserPreferences() { return new FieldContentParserPreferences(getStringList(NON_WRAPPABLE_FIELDS)); } + @Override public boolean isKeywordSyncEnabled() { return getBoolean(JabRefPreferences.SPECIALFIELDSENABLED) - && getBoolean(JabRefPreferences.AUTOSYNCSPECIALFIELDSTOKEYWORDS); + && getBoolean(JabRefPreferences.AUTOSYNCSPECIALFIELDSTOKEYWORDS); } public ImportFormatPreferences getImportFormatPreferences() { @@ -1725,6 +1726,7 @@ public SaveOrderConfig loadExportSaveOrder() { ); } + @Override public Character getKeywordDelimiter() { return get(KEYWORD_SEPARATOR).charAt(0); } diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index 24a33ba283c..21550d547cb 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -20,6 +20,8 @@ public interface PreferencesService { JournalAbbreviationPreferences getJournalAbbreviationPreferences(); + Character getKeywordDelimiter(); + void storeKeyBindingRepository(KeyBindingRepository keyBindingRepository); KeyBindingRepository getKeyBindingRepository(); @@ -56,6 +58,8 @@ public interface PreferencesService { LayoutFormatterPreferences getLayoutFormatterPreferences(JournalAbbreviationLoader loader); + boolean isKeywordSyncEnabled(); + SavePreferences loadForExportFromPreferences(); String getExportWorkingDirectory(); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index b5233752d16..84a2215b2e2 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -558,10 +558,6 @@ JabRef\ preferences=JabRef preferences JabRef\ requests\ recommendations\ from\ Mr.\ DLib,\ which\ is\ an\ external\ service.\ To\ enable\ Mr.\ DLib\ to\ calculate\ recommendations,\ some\ of\ your\ data\ must\ be\ shared\ with\ Mr.\ DLib.\ Generally,\ the\ more\ data\ is\ shared\ the\ better\ recommendations\ can\ be\ calculated.\ However,\ we\ understand\ that\ some\ of\ your\ data\ in\ JabRef\ is\ sensitive,\ and\ you\ may\ not\ want\ to\ share\ it.\ Therefore,\ Mr.\ DLib\ offers\ a\ choice\ of\ which\ data\ you\ would\ like\ to\ share.=JabRef requests recommendations from Mr. DLib, which is an external service. To enable Mr. DLib to calculate recommendations, some of your data must be shared with Mr. DLib. Generally, the more data is shared the better recommendations can be calculated. However, we understand that some of your data in JabRef is sensitive, and you may not want to share it. Therefore, Mr. DLib offers a choice of which data you would like to share. JabRef\ Version\ (Required\ to\ ensure\ backwards\ compatibility\ with\ Mr.\ DLib's\ Web\ Service)=JabRef Version (Required to ensure backwards compatibility with Mr. DLib's Web Service) -Join=Join - -Joins\ selected\ keywords\ and\ deletes\ selected\ keywords.=Joins selected keywords and deletes selected keywords. - Journal\ abbreviations=Journal abbreviations Keep=Keep