-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented TagsField for the Keywords field (#10910)
* added selection box next to the keyword field * added selection box next to the keyword field * Update CHANGELOG.md * Rewrite keywords field: used tags field * Update src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> * Fix Suggestion Provider [#8145] * Add context menu for keywordsEditor * Fixed OpenRewrite test issue * Add typed text as first suggestion * Revert "Fix Suggestion Provider" * Remove unused imports * Bind the keywords and tags field * Update CHANGELOG.md * Remove duplicates from the suggestions list * use Labels as a tag view * Add LOGGER.debug * remove chip-view from CSS file * Update CHANGELOG.md --------- Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>
- Loading branch information
1 parent
f1c099c
commit 8374693
Showing
6 changed files
with
243 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.fxml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<?import javafx.scene.layout.HBox?> | ||
<?import com.dlsc.gemsfx.TagsField?> | ||
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="HBox" xmlns="http://javafx.com/javafx/8.0.112" | ||
fx:controller="org.jabref.gui.fieldeditors.KeywordsEditor" > | ||
<TagsField fx:id="keywordTagsField" HBox.hgrow="ALWAYS"/> | ||
</fx:root> |
141 changes: 136 additions & 5 deletions
141
src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,155 @@ | ||
package org.jabref.gui.fieldeditors; | ||
|
||
import java.util.Comparator; | ||
|
||
import javax.swing.undo.UndoManager; | ||
|
||
import javafx.beans.binding.Bindings; | ||
import javafx.fxml.FXML; | ||
import javafx.scene.Node; | ||
import javafx.scene.Parent; | ||
import javafx.scene.control.ContentDisplay; | ||
import javafx.scene.control.ContextMenu; | ||
import javafx.scene.control.Label; | ||
import javafx.scene.layout.HBox; | ||
|
||
import org.jabref.gui.ClipBoardManager; | ||
import org.jabref.gui.DialogService; | ||
import org.jabref.gui.JabRefDialogService; | ||
import org.jabref.gui.actions.ActionFactory; | ||
import org.jabref.gui.actions.SimpleCommand; | ||
import org.jabref.gui.actions.StandardActions; | ||
import org.jabref.gui.autocompleter.SuggestionProvider; | ||
import org.jabref.gui.icon.IconTheme; | ||
import org.jabref.gui.keyboard.KeyBindingRepository; | ||
import org.jabref.gui.util.ViewModelListCellFactory; | ||
import org.jabref.logic.integrity.FieldCheckers; | ||
import org.jabref.logic.l10n.Localization; | ||
import org.jabref.model.entry.BibEntry; | ||
import org.jabref.model.entry.Keyword; | ||
import org.jabref.model.entry.field.Field; | ||
import org.jabref.preferences.PreferencesService; | ||
|
||
public class KeywordsEditor extends SimpleEditor implements FieldEditorFX { | ||
import com.airhacks.afterburner.views.ViewLoader; | ||
import com.dlsc.gemsfx.TagsField; | ||
import jakarta.inject.Inject; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class KeywordsEditor extends HBox implements FieldEditorFX { | ||
private static final Logger LOGGER = LoggerFactory.getLogger(KeywordsEditor.class); | ||
|
||
@FXML private TagsField<Keyword> keywordTagsField; | ||
|
||
@Inject private PreferencesService preferencesService; | ||
@Inject private DialogService dialogService; | ||
@Inject private UndoManager undoManager; | ||
@Inject private ClipBoardManager clipBoardManager; | ||
@Inject private KeyBindingRepository keyBindingRepository; | ||
|
||
private final KeywordsEditorViewModel viewModel; | ||
|
||
public KeywordsEditor(Field field, | ||
SuggestionProvider<?> suggestionProvider, | ||
FieldCheckers fieldCheckers, | ||
PreferencesService preferences, | ||
UndoManager undoManager) { | ||
super(field, suggestionProvider, fieldCheckers, preferences, undoManager); | ||
FieldCheckers fieldCheckers) { | ||
|
||
ViewLoader.view(this) | ||
.root(this) | ||
.load(); | ||
|
||
this.viewModel = new KeywordsEditorViewModel( | ||
field, | ||
suggestionProvider, | ||
fieldCheckers, | ||
preferencesService, | ||
undoManager); | ||
|
||
keywordTagsField.setCellFactory(new ViewModelListCellFactory<Keyword>().withText(Keyword::get)); | ||
keywordTagsField.setTagViewFactory(this::createTag); | ||
|
||
keywordTagsField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); | ||
keywordTagsField.setConverter(viewModel.getStringConverter()); | ||
keywordTagsField.setMatcher((keyword, searchText) -> keyword.get().toLowerCase().startsWith(searchText.toLowerCase())); | ||
keywordTagsField.setComparator(Comparator.comparing(Keyword::get)); | ||
|
||
keywordTagsField.setNewItemProducer(searchText -> viewModel.getStringConverter().fromString(searchText)); | ||
|
||
keywordTagsField.setShowSearchIcon(false); | ||
keywordTagsField.getEditor().getStyleClass().clear(); | ||
keywordTagsField.getEditor().getStyleClass().add("tags-field-editor"); | ||
|
||
Bindings.bindContentBidirectional(keywordTagsField.getTags(), viewModel.keywordListProperty()); | ||
} | ||
|
||
private Node createTag(Keyword keyword) { | ||
Label tagLabel = new Label(); | ||
tagLabel.setText(keywordTagsField.getConverter().toString(keyword)); | ||
tagLabel.setGraphic(IconTheme.JabRefIcons.REMOVE_TAGS.getGraphicNode()); | ||
tagLabel.getGraphic().setOnMouseClicked(event -> keywordTagsField.removeTags(keyword)); | ||
tagLabel.setContentDisplay(ContentDisplay.RIGHT); | ||
ContextMenu contextMenu = new ContextMenu(); | ||
ActionFactory factory = new ActionFactory(keyBindingRepository); | ||
contextMenu.getItems().addAll( | ||
factory.createMenuItem(StandardActions.COPY, new KeywordsEditor.TagContextAction(StandardActions.COPY, keyword)), | ||
factory.createMenuItem(StandardActions.CUT, new KeywordsEditor.TagContextAction(StandardActions.CUT, keyword)), | ||
factory.createMenuItem(StandardActions.DELETE, new KeywordsEditor.TagContextAction(StandardActions.DELETE, keyword)) | ||
); | ||
tagLabel.setContextMenu(contextMenu); | ||
return tagLabel; | ||
} | ||
|
||
public KeywordsEditorViewModel getViewModel() { | ||
return viewModel; | ||
} | ||
|
||
@Override | ||
public void bindToEntry(BibEntry entry) { | ||
viewModel.bindToEntry(entry); | ||
} | ||
|
||
@Override | ||
public Parent getNode() { | ||
return this; | ||
} | ||
|
||
@Override | ||
public void requestFocus() { | ||
keywordTagsField.requestFocus(); | ||
} | ||
|
||
@Override | ||
public double getWeight() { | ||
return 2; | ||
} | ||
|
||
private class TagContextAction extends SimpleCommand { | ||
private final StandardActions command; | ||
private final Keyword keyword; | ||
|
||
public TagContextAction(StandardActions command, Keyword keyword) { | ||
this.command = command; | ||
this.keyword = keyword; | ||
} | ||
|
||
@Override | ||
public void execute() { | ||
switch (command) { | ||
case COPY -> { | ||
clipBoardManager.setContent(keyword.get()); | ||
dialogService.notify(Localization.lang("Copied '%0' to clipboard.", | ||
JabRefDialogService.shortenDialogMessage(keyword.get()))); | ||
} | ||
case CUT -> { | ||
clipBoardManager.setContent(keyword.get()); | ||
dialogService.notify(Localization.lang("Copied '%0' to clipboard.", | ||
JabRefDialogService.shortenDialogMessage(keyword.get()))); | ||
keywordTagsField.removeTags(keyword); | ||
} | ||
case DELETE -> | ||
keywordTagsField.removeTags(keyword); | ||
default -> | ||
LOGGER.info("Action {} not defined", command.getText()); | ||
} | ||
} | ||
} | ||
} |
93 changes: 93 additions & 0 deletions
93
src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package org.jabref.gui.fieldeditors; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
import javax.swing.undo.UndoManager; | ||
|
||
import javafx.beans.property.ListProperty; | ||
import javafx.beans.property.SimpleListProperty; | ||
import javafx.collections.FXCollections; | ||
import javafx.util.StringConverter; | ||
|
||
import org.jabref.gui.autocompleter.SuggestionProvider; | ||
import org.jabref.gui.util.BindingsHelper; | ||
import org.jabref.logic.integrity.FieldCheckers; | ||
import org.jabref.model.entry.Keyword; | ||
import org.jabref.model.entry.KeywordList; | ||
import org.jabref.model.entry.field.Field; | ||
import org.jabref.preferences.PreferencesService; | ||
|
||
import org.tinylog.Logger; | ||
|
||
public class KeywordsEditorViewModel extends AbstractEditorViewModel { | ||
|
||
private final ListProperty<Keyword> keywordListProperty; | ||
private final Character keywordSeparator; | ||
private final SuggestionProvider<?> suggestionProvider; | ||
|
||
public KeywordsEditorViewModel(Field field, | ||
SuggestionProvider<?> suggestionProvider, | ||
FieldCheckers fieldCheckers, | ||
PreferencesService preferencesService, | ||
UndoManager undoManager) { | ||
|
||
super(field, suggestionProvider, fieldCheckers, undoManager); | ||
|
||
keywordListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); | ||
this.keywordSeparator = preferencesService.getBibEntryPreferences().getKeywordSeparator(); | ||
this.suggestionProvider = suggestionProvider; | ||
|
||
BindingsHelper.bindContentBidirectional( | ||
keywordListProperty, | ||
text, | ||
this::serializeKeywords, | ||
this::parseKeywords); | ||
} | ||
|
||
private String serializeKeywords(List<Keyword> keywords) { | ||
return KeywordList.serialize(keywords, keywordSeparator); | ||
} | ||
|
||
private List<Keyword> parseKeywords(String newText) { | ||
return KeywordList.parse(newText, keywordSeparator).stream().toList(); | ||
} | ||
|
||
public ListProperty<Keyword> keywordListProperty() { | ||
return keywordListProperty; | ||
} | ||
|
||
public StringConverter<Keyword> getStringConverter() { | ||
return new StringConverter<>() { | ||
@Override | ||
public String toString(Keyword keyword) { | ||
if (keyword == null) { | ||
Logger.debug("Keyword is null"); | ||
return ""; | ||
} | ||
return keyword.get(); | ||
} | ||
|
||
@Override | ||
public Keyword fromString(String keywordString) { | ||
return new Keyword(keywordString); | ||
} | ||
}; | ||
} | ||
|
||
public List<Keyword> getSuggestions(String request) { | ||
List<Keyword> suggestions = suggestionProvider.getPossibleSuggestions().stream() | ||
.map(String.class::cast) | ||
.filter(keyword -> keyword.toLowerCase().contains(request.toLowerCase())) | ||
.map(Keyword::new) | ||
.distinct() | ||
.collect(Collectors.toList()); | ||
|
||
Keyword requestedKeyword = new Keyword(request); | ||
if (!suggestions.contains(requestedKeyword)) { | ||
suggestions.addFirst(requestedKeyword); | ||
} | ||
|
||
return suggestions; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters