diff --git a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java index cdc64cecd4a..7616bfc004b 100644 --- a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java @@ -520,8 +520,6 @@ public Optional getExtra(final FieldEditor editor) { return FieldExtraComponents.getPaginationExtraComponent(editor, this); } else if (fieldExtras.contains(FieldProperty.TYPE)) { return FieldExtraComponents.getTypeExtraComponent(editor, this, "patent".equalsIgnoreCase(entry.getType())); - } else if (fieldExtras.contains(FieldProperty.CROSSREF)) { - return FieldExtraComponents.getCrossrefExtraComponent(editor, frame.getCurrentBasePanel()); } return Optional.empty(); } diff --git a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java index abc7b76db75..482057d82eb 100644 --- a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java +++ b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java @@ -27,6 +27,7 @@ import net.sf.jabref.gui.BasePanel; import net.sf.jabref.gui.JabRefFrame; import net.sf.jabref.gui.autocompleter.AutoCompleteListener; +import net.sf.jabref.gui.fieldeditors.EntryLinkListEditor; import net.sf.jabref.gui.fieldeditors.FieldEditor; import net.sf.jabref.gui.fieldeditors.FileListEditor; import net.sf.jabref.gui.fieldeditors.TextArea; @@ -142,6 +143,14 @@ private void setupPanel(JabRefFrame frame, BasePanel bPanel, boolean addKeyField GUIUtil.correctRowHeight(fileListEditor); defaultHeight = 0; + } else if (InternalBibtexFields.getFieldProperties(field).contains(FieldProperty.SINGLE_ENTRY_LINK)) { + fieldEditor = new EntryLinkListEditor(frame, bPanel.getBibDatabaseContext(), field, null, parent, + true); + defaultHeight = 0; + } else if (InternalBibtexFields.getFieldProperties(field).contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { + fieldEditor = new EntryLinkListEditor(frame, bPanel.getBibDatabaseContext(), field, null, parent, + false); + defaultHeight = 0; } else { fieldEditor = new TextArea(field, null); bPanel.frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener((TextArea) fieldEditor); diff --git a/src/main/java/net/sf/jabref/gui/entryeditor/FieldExtraComponents.java b/src/main/java/net/sf/jabref/gui/entryeditor/FieldExtraComponents.java index 5fa56d0b6ec..ecdd5b9f88b 100644 --- a/src/main/java/net/sf/jabref/gui/entryeditor/FieldExtraComponents.java +++ b/src/main/java/net/sf/jabref/gui/entryeditor/FieldExtraComponents.java @@ -549,51 +549,4 @@ public static Optional getGenderExtraComponent(FieldEditor fieldEdit } - /** - * Return a button which allows to go to the parent entry of the crossref field - * @param fieldEditor The FieldEditor component to get the entry key from - * @param panel The current BasePanel - * @return - */ - - public static Optional getCrossrefExtraComponent(FieldEditor fieldEditor, - BasePanel panel) { - JButton button = new JButton(Localization.lang("Select")); - JTextComponent crossref = (JTextComponent) fieldEditor; - - button.addActionListener( - actionEvent -> panel.getDatabase().getEntryByKey(crossref.getText()).ifPresent(e -> panel.highlightEntry(e)) - ); - - // enable/disable button - crossref.getDocument().addDocumentListener(new DocumentListener() { - - @Override - public void changedUpdate(DocumentEvent documentEvent) { - checkValidKey(); - } - - @Override - public void insertUpdate(DocumentEvent documentEvent) { - checkValidKey(); - } - - @Override - public void removeUpdate(DocumentEvent documentEvent) { - checkValidKey(); - } - - private void checkValidKey() { - if (panel.getDatabase().getEntryByKey(crossref.getText()) != null) { - button.setEnabled(true); - } else { - button.setEnabled(false); - } - } - }); - - return Optional.of(button); - - } - } diff --git a/src/main/java/net/sf/jabref/gui/fieldeditors/EntryLinkListEditor.java b/src/main/java/net/sf/jabref/gui/fieldeditors/EntryLinkListEditor.java new file mode 100644 index 00000000000..9f04b91bc46 --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/fieldeditors/EntryLinkListEditor.java @@ -0,0 +1,551 @@ +package net.sf.jabref.gui.fieldeditors; + +import java.awt.BorderLayout; +import java.awt.Color; +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.JLabel; +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 net.sf.jabref.Globals; +import net.sf.jabref.gui.IconTheme; +import net.sf.jabref.gui.JabRefFrame; +import net.sf.jabref.gui.autocompleter.AutoCompleteListener; +import net.sf.jabref.gui.entryeditor.EntryEditor; +import net.sf.jabref.gui.keyboard.KeyBinding; +import net.sf.jabref.logic.l10n.Localization; +import net.sf.jabref.logic.layout.Layout; +import net.sf.jabref.logic.layout.LayoutHelper; +import net.sf.jabref.model.database.BibDatabase; +import net.sf.jabref.model.database.BibDatabaseContext; +import net.sf.jabref.model.entry.BibEntry; +import net.sf.jabref.model.entry.EntryLinkList; +import net.sf.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 Log LOGGER = LogFactory.getLog(EntryLinkListEditor.class); + + private final FieldNameLabel label; + 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()); + + 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}"; + + 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; + label = new FieldNameLabel(fieldName); + 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()); + + FormBuilder builder = FormBuilder.create() + .layout(new FormLayout("fill:pref:grow,1dlu,fill:pref:grow", + "fill:pref,fill:pref,1dlu,fill:pref")); + + 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("Select")); + button.addActionListener(e -> selectEntry()); + 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("Select")); + menu.add(openLink); + openLink.addActionListener(e -> selectEntry()); + + // Set table row height + FontMetrics metrics = getFontMetrics(getFont()); + setRowHeight(Math.max(getRowHeight(), metrics.getHeight())); + + updateButtonStates(); + } + + + private void selectEntry() { + int selectedRow = getSelectedRow(); + + if (selectedRow != -1) { + String crossref = tableModel.getEntry(selectedRow).getKey(); + + frame.getCurrentBasePanel().getDatabase().getEntryByKey(crossref) + .ifPresent(entry -> frame.getCurrentBasePanel().highlightEntry(entry)); + } + } + + 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 JLabel getLabel() { + return label; + } + + @Override + public void setLabelColor(Color color) { + label.setForeground(color); + } + + @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 updateFont() { + // 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(); + } + + + 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()); + } + } + } + + @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 + } + + @Override + public void updateFontColor() { + // Do nothing + } + + + 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)); + } + + } + } + } + } + + 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 ""; + } + +} diff --git a/src/main/java/net/sf/jabref/gui/groups/TransferableEntrySelection.java b/src/main/java/net/sf/jabref/gui/groups/TransferableEntrySelection.java index c5ed6a0045a..23b224b72b6 100644 --- a/src/main/java/net/sf/jabref/gui/groups/TransferableEntrySelection.java +++ b/src/main/java/net/sf/jabref/gui/groups/TransferableEntrySelection.java @@ -11,7 +11,7 @@ import net.sf.jabref.model.entry.BibEntry; -class TransferableEntrySelection implements Transferable { +public class TransferableEntrySelection implements Transferable { public static final DataFlavor FLAVOR_INTERNAL; private static final DataFlavor FLAVOR_EXTERNAL; diff --git a/src/main/java/net/sf/jabref/model/entry/BibEntry.java b/src/main/java/net/sf/jabref/model/entry/BibEntry.java index ae15d95edd8..ec0822fd3d1 100644 --- a/src/main/java/net/sf/jabref/model/entry/BibEntry.java +++ b/src/main/java/net/sf/jabref/model/entry/BibEntry.java @@ -732,6 +732,15 @@ public String getUserComments() { return REMOVE_TRAILING_WHITESPACE.matcher(commentsBeforeEntry).replaceFirst(""); } + public List getEntryLinkList(String fieldName, BibDatabase database) { + return getField(fieldName).map(fieldValue -> EntryLinkList.parse(fieldValue, database)) + .orElse(Collections.emptyList()); + } + + public Optional setEntryLinkList(String fieldName, List list) { + return setField(fieldName, EntryLinkList.serialize(list)); + } + public Set getFieldAsWords(String field) { String fieldName = toLowerCase(field); Set storedList = fieldsAsWords.get(fieldName); diff --git a/src/main/java/net/sf/jabref/model/entry/EntryLinkList.java b/src/main/java/net/sf/jabref/model/entry/EntryLinkList.java new file mode 100644 index 00000000000..c0b414cac3a --- /dev/null +++ b/src/main/java/net/sf/jabref/model/entry/EntryLinkList.java @@ -0,0 +1,29 @@ +package net.sf.jabref.model.entry; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import net.sf.jabref.model.database.BibDatabase; + +public class EntryLinkList { + + private static String SEPARATOR = ","; + + + public static List parse(String fieldValue, BibDatabase database) { + List result = new ArrayList<>(); + if ((fieldValue != null) && !fieldValue.isEmpty()) { + String[] entries = fieldValue.split(SEPARATOR); + + for (String entry : entries) { + result.add(new ParsedEntryLink(entry, database)); + } + } + return result; + } + + public static String serialize(List list) { + return String.join(SEPARATOR, list.stream().map(link -> link.getKey()).collect(Collectors.toList())); + } +} diff --git a/src/main/java/net/sf/jabref/model/entry/InternalBibtexFields.java b/src/main/java/net/sf/jabref/model/entry/InternalBibtexFields.java index 2ed40700da0..74bf3b46c85 100644 --- a/src/main/java/net/sf/jabref/model/entry/InternalBibtexFields.java +++ b/src/main/java/net/sf/jabref/model/entry/InternalBibtexFields.java @@ -178,6 +178,10 @@ private InternalBibtexFields(boolean serializeSpecialFields, String timeStampFie dummy.setExtras(EnumSet.of(FieldProperty.FILE_EDITOR, FieldProperty.VERBATIM)); add(dummy); + dummy = new BibtexSingleField(FieldName.RELATED, false); + dummy.setExtras(EnumSet.of(FieldProperty.MULTIPLE_ENTRY_LINK)); + add(dummy); + // some BibLatex fields dummy = new BibtexSingleField(FieldName.GENDER, true, BibtexSingleField.SMALL_W); dummy.getFieldProperties().add(FieldProperty.GENDER); diff --git a/src/main/java/net/sf/jabref/model/entry/ParsedEntryLink.java b/src/main/java/net/sf/jabref/model/entry/ParsedEntryLink.java new file mode 100644 index 00000000000..0527dbfffc1 --- /dev/null +++ b/src/main/java/net/sf/jabref/model/entry/ParsedEntryLink.java @@ -0,0 +1,59 @@ +package net.sf.jabref.model.entry; + +import java.util.Objects; +import java.util.Optional; + +import net.sf.jabref.model.database.BibDatabase; + +public class ParsedEntryLink { + + private String key; + private Optional linkedEntry; + private BibDatabase dataBase; + + public ParsedEntryLink(String key, BibDatabase dataBase) { + this.key = key; + this.linkedEntry = dataBase.getEntryByKey(this.key); + this.dataBase = dataBase; + } + + public ParsedEntryLink(BibEntry bibEntry) { + this.key = bibEntry.getCiteKeyOptional().orElse(""); + this.linkedEntry = Optional.of(bibEntry); + } + + public String getKey() { + return key; + } + + public Optional getLinkedEntry() { + return linkedEntry; + } + + public void setKey(String newKey) { + this.key = newKey; + this.linkedEntry = getDataBase().getEntryByKey(this.key); + } + + @Override + public int hashCode() { + return Objects.hash(key, linkedEntry); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ParsedEntryLink)) { + return false; + } + ParsedEntryLink other = (ParsedEntryLink) obj; + return Objects.equals(key, other.key) && Objects.equals(linkedEntry, other.linkedEntry); + } + + public BibDatabase getDataBase() { + return dataBase; + } + +} diff --git a/src/test/java/net/sf/jabref/logic/layout/format/AuthorsTest.java b/src/test/java/net/sf/jabref/logic/layout/format/AuthorsTest.java index 2059a3711d5..f5f5dd2a206 100644 --- a/src/test/java/net/sf/jabref/logic/layout/format/AuthorsTest.java +++ b/src/test/java/net/sf/jabref/logic/layout/format/AuthorsTest.java @@ -130,6 +130,22 @@ public void testNoPeriod() { a.format("Bruce, Bob Croydon and Charles Kermit von Manson and Jumper, Jolly")); } + @Test + public void testEtAl() { + ParamLayoutFormatter a = new Authors(); + a.setArgument("2,1"); + Assert.assertEquals("B. C. Bruce et al.", + a.format("Bruce, Bob Croydon and Charles Kermit von Manson and Jumper, Jolly")); + } + + @Test + public void testEtAlNotEnoughAuthors() { + ParamLayoutFormatter a = new Authors(); + a.setArgument("2,1"); + Assert.assertEquals("B. C. Bruce and C. K. von Manson", + a.format("Bruce, Bob Croydon and Charles Kermit von Manson")); + } + @Test public void testEmptyEtAl() { ParamLayoutFormatter a = new Authors(); @@ -137,4 +153,5 @@ public void testEmptyEtAl() { Assert.assertEquals("Bruce, Bob Croydon", a.format("Bob Croydon Bruce and Charles Manson and Jolly Jumper and Chuck Chuckles")); } + } diff --git a/src/test/resources/testbib/crossref.bib b/src/test/resources/testbib/crossref.bib new file mode 100644 index 00000000000..7f3c1645696 --- /dev/null +++ b/src/test/resources/testbib/crossref.bib @@ -0,0 +1,19 @@ +% Encoding: UTF-8 + +@Article{Article2016, + author = {Hans Mustermann}, + title = {Title of the article}, + journal = {Journal}, + year = {2016}, + related = {InProceedings2015}, + test = {xy}, +} + +@InProceedings{InProceedings2015, + author = {Anna Musterfrau}, + title = {Paper title}, + booktitle = {Book title}, + year = {2015}, +} + +@Comment{jabref-meta: databaseType:bibtex;}