diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css index b2b5147a6e9..79002efc95f 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css @@ -11,5 +11,5 @@ -fx-hgap: 10; -fx-vgap: 6; -fx-background-color: text-area-background; - -fx-padding: 0 0 0 5; + -fx-padding: 5 0 0 5; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/EntryLinkListEditor.java b/src/main/java/org/jabref/gui/fieldeditors/EntryLinkListEditor.java deleted file mode 100644 index 41a079d39de..00000000000 --- a/src/main/java/org/jabref/gui/fieldeditors/EntryLinkListEditor.java +++ /dev/null @@ -1,535 +0,0 @@ -package org.jabref.gui.fieldeditors; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.FontMetrics; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import javax.swing.AbstractAction; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.KeyStroke; -import javax.swing.SwingUtilities; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableCellRenderer; - -import org.jabref.Globals; -import org.jabref.gui.IconTheme; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.autocompleter.AutoCompleteListener; -import org.jabref.gui.entryeditor.EntryEditor; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.layout.Layout; -import org.jabref.logic.layout.LayoutHelper; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.EntryLinkList; -import org.jabref.model.entry.ParsedEntryLink; - -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class EntryLinkListEditor extends JTable implements FieldEditor { - private static final String layoutFormat = "\\begin{author}\\format[Authors(2,1),LatexToUnicode]{\\author}\\end{author}\\begin{title}, \"\\format[LatexToUnicode]{\\title}\"\\end{title}\\begin{year}, \\year\\end{year}"; - - private static final Log LOGGER = LogFactory.getLog(EntryLinkListEditor.class); - private final JabRefFrame frame; - private final BibDatabaseContext databaseContext; - private final String fieldName; - private final EntryEditor entryEditor; - private final JPanel panel; - private final EntryLinkListTableModel tableModel; - private final JPopupMenu menu = new JPopupMenu(); - private final boolean singleEntry; - private final JButton add = new JButton(IconTheme.JabRefIcon.ADD_NOBOX.getSmallIcon()); - - private final JButton remove = new JButton(IconTheme.JabRefIcon.REMOVE_NOBOX.getSmallIcon()); - - public EntryLinkListEditor(JabRefFrame frame, BibDatabaseContext databaseContext, String fieldName, String content, - EntryEditor entryEditor, boolean singleEntry) { - this.frame = frame; - this.databaseContext = databaseContext; - this.fieldName = fieldName; - this.entryEditor = entryEditor; - this.singleEntry = singleEntry; - tableModel = new EntryLinkListTableModel(EntryLinkList.parse(content, databaseContext.getDatabase())); - setText(content); - setModel(tableModel); - JScrollPane sPane = new JScrollPane(this); - setTableHeader(null); - addMouseListener(new TableClickListener()); - - add.setToolTipText(("New entry link (INSERT)")); - remove.setToolTipText(("Remove entry link (DELETE)")); - add.setMargin(new Insets(0, 0, 0, 0)); - remove.setMargin(new Insets(0, 0, 0, 0)); - add.addActionListener(e -> addEntry()); - remove.addActionListener(e -> removeEntries()); - - FormLayout layout = new FormLayout( - "fill:pref:grow,1dlu,fill:pref:grow", - "fill:pref,fill:pref,1dlu,fill:pref" - ); - FormBuilder builder = FormBuilder.create().layout(layout); - - if (!singleEntry) { - JButton up = new JButton(IconTheme.JabRefIcon.UP.getSmallIcon()); - JButton down = new JButton(IconTheme.JabRefIcon.DOWN.getSmallIcon()); - up.setMargin(new Insets(0, 0, 0, 0)); - down.setMargin(new Insets(0, 0, 0, 0)); - up.addActionListener(e -> moveEntry(-1)); - down.addActionListener(e -> moveEntry(1)); - builder.add(up).xy(1, 1); - builder.add(down).xy(1, 2); - } - builder.add(add).xy(3, 1); - builder.add(remove).xy(3, 2); - JButton button = new JButton(Localization.lang("Jump to entry")); - button.addActionListener(e -> jumpToEntry()); - builder.add(button).xyw(1, 4, 3); - - panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(sPane, BorderLayout.CENTER); - panel.add(builder.getPanel(), BorderLayout.EAST); - - // Add an input/action pair for deleting entries: - getInputMap().put(KeyStroke.getKeyStroke("DELETE"), "delete"); - getActionMap().put("delete", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent actionEvent) { - int row = getSelectedRow(); - removeEntries(); - row = Math.min(row, getRowCount() - 1); - if (row >= 0) { - setRowSelectionInterval(row, row); - } - } - }); - - // Add an input/action pair for inserting an entry: - getInputMap().put(KeyStroke.getKeyStroke("INSERT"), "insert"); - getActionMap().put("insert", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent actionEvent) { - int row = getSelectedRow(); - addEntry(); - setRowSelectionInterval(row, row); - } - }); - - // Add input/action pair for moving an entry up: - getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.FILE_LIST_EDITOR_MOVE_ENTRY_UP), "move up"); - getActionMap().put("move up", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent actionEvent) { - moveEntry(-1); - } - }); - - // Add input/action pair for moving an entry down: - getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.FILE_LIST_EDITOR_MOVE_ENTRY_DOWN), "move down"); - getActionMap().put("move down", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent actionEvent) { - moveEntry(1); - } - }); - - JMenuItem openLink = new JMenuItem(Localization.lang("Jump to entry")); - menu.add(openLink); - openLink.addActionListener(e -> jumpToEntry()); - - // Set table row height - FontMetrics metrics = getFontMetrics(getFont()); - setRowHeight(Math.max(getRowHeight(), metrics.getHeight())); - - updateButtonStates(); - } - - private static String formatEntry(BibEntry entry, BibDatabase database) { - StringReader sr = new StringReader(layoutFormat); - try { - Layout layout = new LayoutHelper(sr, - Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)) - .getLayoutFromText(); - return layout.doLayout(entry, database); - } catch (IOException e) { - LOGGER.warn("Problem generating entry layout", e); - } - return ""; - } - - private void jumpToEntry() { - String entryKey = null; - - if (singleEntry) { - ParsedEntryLink firstEntry = tableModel.getEntry(0); - - if (firstEntry != null) { - entryKey = firstEntry.getKey(); - } - } else { - int selectedRow = getSelectedRow(); - - if (selectedRow != -1) { - entryKey = tableModel.getEntry(selectedRow).getKey(); - } - } - - if (entryKey != null) { - frame.getCurrentBasePanel().getDatabase().getEntryByKey(entryKey).ifPresent( - e -> frame.getCurrentBasePanel().highlightEntry(e) - ); - } - } - - public void adjustColumnWidth() { - for (int column = 0; column < this.getColumnCount(); column++) { - int width = 0; - for (int row = 0; row < this.getRowCount(); row++) { - TableCellRenderer renderer = this.getCellRenderer(row, column); - Component comp = this.prepareRenderer(renderer, row, column); - width = Math.max(comp.getPreferredSize().width, width); - } - this.columnModel.getColumn(column).setPreferredWidth(width); - } - } - - @Override - public String getFieldName() { - return fieldName; - } - - /* - * Returns the component to be added to a container. Might be a JScrollPane - * or the component itself. - */ - @Override - public JComponent getPane() { - return panel; - } - - /* - * Returns the text component itself. - */ - @Override - public JComponent getTextComponent() { - return this; - } - - @Override - public String getText() { - return tableModel.getText(); - } - - @Override - public void setText(String newText) { - tableModel.setContent(EntryLinkList.parse(newText, databaseContext.getDatabase())); - adjustColumnWidth(); - updateButtonStates(); - } - - @Override - public void append(String text) { - // Do nothing - } - - @Override - public void paste(String textToInsert) { - // Do nothing - } - - @Override - public String getSelectedText() { - return null; - } - - private void addEntry() { - int row = getSelectedRow(); - if (row == -1) { - row = 0; - } - ParsedEntryLink entry = new ParsedEntryLink("", databaseContext.getDatabase()); - tableModel.addEntry(row, entry); - //entryEditor.updateField(this); - adjustColumnWidth(); - updateButtonStates(); - } - - private void removeEntries() { - int[] rows = getSelectedRows(); - if (rows != null) { - for (int i = rows.length - 1; i >= 0; i--) { - tableModel.removeEntry(rows[i]); - } - } - //entryEditor.updateField(this); - adjustColumnWidth(); - updateButtonStates(); - } - - private void updateButtonStates() { - if (singleEntry) { - if (tableModel.isEmpty()) { - add.setEnabled(true); - remove.setEnabled(false); - } else { - add.setEnabled(false); - remove.setEnabled(true); - } - } - } - - private void moveEntry(int i) { - int[] sel = getSelectedRows(); - if ((sel.length != 1) || (tableModel.getRowCount() < 2)) { - return; - } - int toIdx = sel[0] + i; - if (toIdx >= tableModel.getRowCount()) { - toIdx -= tableModel.getRowCount(); - } - if (toIdx < 0) { - toIdx += tableModel.getRowCount(); - } - ParsedEntryLink entry = tableModel.getEntry(sel[0]); - tableModel.removeEntry(sel[0]); - tableModel.addEntry(toIdx, entry); - //entryEditor.updateField(this); - setRowSelectionInterval(toIdx, toIdx); - adjustColumnWidth(); - } - - @Override - public void undo() { - // Do nothing - } - - @Override - public void redo() { - // Do nothing - } - - @Override - public void setAutoCompleteListener(AutoCompleteListener listener) { - // Do nothing - } - - @Override - public void clearAutoCompleteSuggestion() { - // Do nothing - } - - @Override - public void setActiveBackgroundColor() { - // Do nothing - } - - @Override - public void setValidBackgroundColor() { - // Do nothing - } - - @Override - public void setInvalidBackgroundColor() { - // Do nothing - } - - class TableClickListener extends MouseAdapter { - - @Override - public void mouseClicked(MouseEvent e) { - if ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 2)) { - int row = rowAtPoint(e.getPoint()); - if (row >= 0) { - Optional entry = tableModel.getEntry(row).getLinkedEntry(); - if (entry.isPresent()) { - // Select entry in main table - frame.getCurrentBasePanel().highlightEntry(entry.get()); - } else { - // Focus BibTeX key field - } - } - } else if (e.isPopupTrigger()) { - processPopupTrigger(e); - } - } - - @Override - public void mousePressed(MouseEvent e) { - if (e.isPopupTrigger()) { - processPopupTrigger(e); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (e.isPopupTrigger()) { - processPopupTrigger(e); - } - } - - private void processPopupTrigger(MouseEvent e) { - int row = rowAtPoint(e.getPoint()); - if (row >= 0) { - setRowSelectionInterval(row, row); - menu.show(EntryLinkListEditor.this, e.getX(), e.getY()); - } - } - } - - private class EntryLinkListTableModel extends DefaultTableModel { - - private final List internalList = Collections.synchronizedList(new ArrayList<>()); - - - public EntryLinkListTableModel(List originalList) { - addEntries(originalList); - } - - public String getText() { - synchronized (internalList) { - String result = EntryLinkList.serialize(internalList); - return result; - } - } - - public void addEntries(List newList) { - internalList.addAll(newList); - if (SwingUtilities.isEventDispatchThread()) { - fireTableDataChanged(); - } else { - SwingUtilities.invokeLater(() -> fireTableDataChanged()); - } - - } - - public void setContent(List newList) { - - internalList.clear(); - internalList.addAll(newList); - if (SwingUtilities.isEventDispatchThread()) { - fireTableDataChanged(); - } else { - SwingUtilities.invokeLater(() -> fireTableDataChanged()); - } - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public int getRowCount() { - if (internalList == null) { - return 0; - } - synchronized (internalList) { - return internalList.size(); - } - } - - @Override - public Class getColumnClass(int columnIndex) { - return String.class; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - synchronized (internalList) { - ParsedEntryLink entry = internalList.get(rowIndex); - switch (columnIndex) { - case 0: - return entry.getKey(); - case 1: - return entry.getLinkedEntry() - .map(bibEntry -> formatEntry(bibEntry, entry.getDataBase())) - .orElse("Unknown entry"); - default: - return null; - } - } - } - - public ParsedEntryLink getEntry(int index) { - synchronized (internalList) { - return internalList.get(index); - } - } - - public void removeEntry(int index) { - internalList.remove(index); - if (SwingUtilities.isEventDispatchThread()) { - fireTableRowsDeleted(index, index); - } else { - SwingUtilities.invokeLater(() -> fireTableRowsDeleted(index, index)); - } - } - - public boolean isEmpty() { - return internalList.isEmpty(); - } - - /** - * Add an entry to the table model, and fire a change event. The change event - * is fired on the event dispatch thread. - * @param index The row index to insert the entry at. - * @param entry The entry to insert. - */ - public void addEntry(final int index, final ParsedEntryLink entry) { - synchronized (internalList) { - internalList.add(index, entry); - if (SwingUtilities.isEventDispatchThread()) { - fireTableDataChanged(); - } else { - SwingUtilities.invokeLater(() -> fireTableDataChanged()); - } - } - } - - @Override - public boolean isCellEditable(int row, int column) { - return (column == 0); - } - - @Override - public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - synchronized (internalList) { - if (columnIndex == 0) { - internalList.get(rowIndex).setKey((String) aValue); - if (SwingUtilities.isEventDispatchThread()) { - fireTableRowsUpdated(rowIndex, rowIndex); - } else { - SwingUtilities.invokeLater(() -> fireTableRowsUpdated(rowIndex, rowIndex)); - } - - } - } - } - } - -} diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java index 9780e56f70c..ffce18ff815 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java @@ -50,6 +50,8 @@ public static FieldEditorFX getForField(String fieldName, TaskExecutor taskExecu } else { return new OptionEditor<>(fieldName, new TypeEditorViewModel()); } + } else if (fieldExtras.contains(FieldProperty.SINGLE_ENTRY_LINK) || fieldExtras.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { + return new LinkedEntriesEditor(fieldName, databaseContext); } // default diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml new file mode 100644 index 00000000000..8d76dc33e5f --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java new file mode 100644 index 00000000000..1954199372b --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java @@ -0,0 +1,44 @@ +package org.jabref.gui.fieldeditors; + +import javafx.beans.binding.Bindings; +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.layout.HBox; + +import org.jabref.gui.util.ControlHelper; +import org.jabref.gui.util.component.TagBar; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.ParsedEntryLink; + +public class LinkedEntriesEditor extends HBox implements FieldEditorFX { + + private final String fieldName; + @FXML private LinkedEntriesEditorViewModel viewModel; + @FXML private TagBar linkedEntriesBar; + + public LinkedEntriesEditor(String fieldName, BibDatabaseContext databaseContext) { + this.fieldName = fieldName; + this.viewModel = new LinkedEntriesEditorViewModel(databaseContext); + + ControlHelper.loadFXMLForControl(this); + + linkedEntriesBar.setStringConverter(viewModel.getStringConverter()); + linkedEntriesBar.setOnTagClicked((parsedEntryLink, mouseEvent) -> viewModel.jumpToEntry(parsedEntryLink)); + Bindings.bindContentBidirectional(linkedEntriesBar.tagsProperty(), viewModel.linkedEntriesProperty()); + } + + public LinkedEntriesEditorViewModel getViewModel() { + return viewModel; + } + + @Override + public void bindToEntry(BibEntry entry) { + viewModel.bindToEntry(fieldName, entry); + } + + @Override + public Parent getNode() { + return this; + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java new file mode 100644 index 00000000000..32d6d06315c --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java @@ -0,0 +1,59 @@ +package org.jabref.gui.fieldeditors; + +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; +import javafx.util.StringConverter; + +import org.jabref.gui.util.BindingsHelper; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.EntryLinkList; +import org.jabref.model.entry.ParsedEntryLink; + +public class LinkedEntriesEditorViewModel extends AbstractEditorViewModel { + + private final BibDatabaseContext databaseContext; + private final ListProperty linkedEntries; + + public LinkedEntriesEditorViewModel(BibDatabaseContext databaseContext) { + this.databaseContext = databaseContext; + linkedEntries = new SimpleListProperty<>(FXCollections.observableArrayList()); + BindingsHelper.bindContentBidirectional( + linkedEntries, + text, + EntryLinkList::serialize, + newText -> EntryLinkList.parse(newText, databaseContext.getDatabase())); + } + + public ListProperty linkedEntriesProperty() { + return linkedEntries; + } + + public StringConverter getStringConverter() { + return new StringConverter() { + @Override + public String toString(ParsedEntryLink linkedEntry) { + if (linkedEntry == null) { + return ""; + } + return linkedEntry.getKey(); + } + + @Override + public ParsedEntryLink fromString(String key) { + return databaseContext.getDatabase().getEntryByKey(key).map(ParsedEntryLink::new).orElse(null); + } + }; + } + + public void jumpToEntry(ParsedEntryLink parsedEntryLink) { + // TODO: Implement jump to entry + // TODO: Add toolitp for tag: Localization.lang("Jump to entry") + // This feature was removed while converting the linked entries editor to JavaFX + // Right now there is no nice way to re-implement it as we have no good interface to control the focus of the main table + // (except directly using the JabRefFrame class as below) + //parsedEntryLink.getLinkedEntry().ifPresent( + // e -> frame.getCurrentBasePanel().highlightEntry(e) + //); + } +} diff --git a/src/main/java/org/jabref/gui/util/component/Tag.fxml b/src/main/java/org/jabref/gui/util/component/Tag.fxml new file mode 100644 index 00000000000..868b8342601 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/component/Tag.fxml @@ -0,0 +1,25 @@ + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/util/component/Tag.java b/src/main/java/org/jabref/gui/util/component/Tag.java new file mode 100644 index 00000000000..a661f89b44c --- /dev/null +++ b/src/main/java/org/jabref/gui/util/component/Tag.java @@ -0,0 +1,64 @@ +package org.jabref.gui.util.component; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; + +import org.jabref.gui.util.ControlHelper; + +import org.fxmisc.easybind.EasyBind; + + +/** + * A tag item in a {@link TagBar}. + */ +public class Tag extends HBox { + + private ObjectProperty value; + private Consumer tagRemovedConsumer; + @FXML private Label text; + + public Tag(Function toString) { + Objects.requireNonNull(toString); + + ControlHelper.loadFXMLForControl(this); + + value = new SimpleObjectProperty<>(); + text.textProperty().bind(EasyBind.map(value, toString)); + } + + public Tag(Function toString, T value) { + this(toString); + setValue(value); + } + + public T getValue() { + return value.get(); + } + + public void setValue(T value) { + this.value.set(value); + } + + public ObjectProperty valueProperty() { + return value; + } + + @FXML + private void removeButtonClicked(ActionEvent event) { + if (tagRemovedConsumer != null) { + tagRemovedConsumer.accept(value.get()); + } + } + + public final void setOnTagRemoved(Consumer tagRemovedConsumer) { + this.tagRemovedConsumer = tagRemovedConsumer; + } +} diff --git a/src/main/java/org/jabref/gui/util/component/TagBar.css b/src/main/java/org/jabref/gui/util/component/TagBar.css new file mode 100644 index 00000000000..d940858d283 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/component/TagBar.css @@ -0,0 +1,14 @@ +.tagBar > HBox { + -fx-spacing: 3; +} + +.tagBar .tag { + -fx-background-color: transparent; + -fx-alignment: center; + -fx-border-width: 1; + -fx-border-color: darkgrey; +} + +.tagBar .tag .button { + -fx-background-color: transparent; +} diff --git a/src/main/java/org/jabref/gui/util/component/TagBar.fxml b/src/main/java/org/jabref/gui/util/component/TagBar.fxml new file mode 100644 index 00000000000..7a7d331ab02 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/component/TagBar.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/java/org/jabref/gui/util/component/TagBar.java b/src/main/java/org/jabref/gui/util/component/TagBar.java new file mode 100644 index 00000000000..175d2f939c2 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/component/TagBar.java @@ -0,0 +1,93 @@ +package org.jabref.gui.util.component; + +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.util.StringConverter; + +import org.jabref.gui.util.ControlHelper; +import org.jabref.model.strings.StringUtil; + +/** + * Field editor that provides various pre-defined options as a drop-down combobox. + */ +public class TagBar extends HBox { + + private final ListProperty tags; + private StringConverter stringConverter; + @FXML private TextField inputTextField; + @FXML private HBox tagList; + private BiConsumer onTagClicked; + + public TagBar() { + tags = new SimpleListProperty<>(FXCollections.observableArrayList()); + tags.addListener(this::onTagsChanged); + + // Load FXML + ControlHelper.loadFXMLForControl(this); + getStylesheets().add(0, TagBar.class.getResource("TagBar.css").toExternalForm()); + } + + public ObservableList getTags() { + return tags.get(); + } + + public void setTags(Collection newTags) { + this.tags.setAll(tags); + } + + public ListProperty tagsProperty() { + return tags; + } + + private void onTagsChanged(ListChangeListener.Change change) { + while (change.next()) { + if (change.wasRemoved()) { + tagList.getChildren().subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear(); + } else if (change.wasAdded()) { + tagList.getChildren().addAll(change.getFrom(), change.getAddedSubList().stream().map(this::createTag).collect(Collectors.toList())); + } + } + } + + private Tag createTag(T item) { + Tag tag = new Tag<>(stringConverter::toString); + tag.setOnTagRemoved(tags::remove); + tag.setValue(item); + if (onTagClicked != null) { + tag.setOnMouseClicked(event -> onTagClicked.accept(item, event)); + } + return tag; + } + + @FXML + private void addTextAsNewTag(ActionEvent event) { + String inputText = inputTextField.getText(); + if (StringUtil.isNotBlank(inputText)) { + T newTag = stringConverter.fromString(inputText); + if (newTag != null && !tags.contains(newTag)) { + tags.add(newTag); + inputTextField.clear(); + } + } + } + + public void setStringConverter(StringConverter stringConverter) { + this.stringConverter = stringConverter; + } + + public void setOnTagClicked(BiConsumer onTagClicked) { + this.onTagClicked = onTagClicked; + } +}