diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index b53fab464e3..a163d9c6db1 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -206,7 +206,7 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { this.tableModel = new MainTableDataModel(getBibDatabaseContext()); citationStyleCache = new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(); + annotationCache = new FileAnnotationCache(bibDatabaseContext); setupMainPanel(); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 401590ec466..a6fc2a3b5d9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -26,6 +26,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.swing.AbstractAction; @@ -128,30 +129,50 @@ public class EntryEditor extends JPanel implements EntryContainer { private static final Log LOGGER = LogFactory.getLog(EntryEditor.class); - /** A reference to the entry this object works on. */ + /** + * A reference to the entry this object works on. + */ private final BibEntry entry; - /** The currently displayed type */ + /** + * The currently displayed type + */ private final String displayedBibEntryType; - /** The action concerned with closing the window. */ + /** + * The action concerned with closing the window. + */ private final CloseAction closeAction = new CloseAction(); - /** The action that deletes the current entry, and closes the editor. */ + /** + * The action that deletes the current entry, and closes the editor. + */ private final DeleteAction deleteAction = new DeleteAction(); - /** The action for switching to the next entry. */ + /** + * The action for switching to the next entry. + */ private final AbstractAction nextEntryAction = new NextEntryAction(); - /** The action for switching to the previous entry. */ + /** + * The action for switching to the previous entry. + */ private final AbstractAction prevEntryAction = new PrevEntryAction(); - /** The action concerned with storing a field value. */ + /** + * The action concerned with storing a field value. + */ private final StoreFieldAction storeFieldAction = new StoreFieldAction(); - /** The action for switching to the next tab */ + /** + * The action for switching to the next tab + */ private final SwitchLeftAction switchLeftAction = new SwitchLeftAction(); - /** The action for switching to the previous tab */ + /** + * The action for switching to the previous tab + */ private final SwitchRightAction switchRightAction = new SwitchRightAction(); - /** The action which generates a BibTeX key for this entry. */ + /** + * The action which generates a BibTeX key for this entry. + */ private final GenerateKeyAction generateKeyAction = new GenerateKeyAction(); // UGLY HACK to have a pointer to the fileListEditor to call autoSetLinks() @@ -160,8 +181,6 @@ public class EntryEditor extends JPanel implements EntryContainer { private final AbstractAction writeXmp; - private final SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(); - private final JPanel srcPanel = new JPanel(); private final JPanel relatedArticlePanel = new JPanel(); @@ -184,7 +203,9 @@ public class EntryEditor extends JPanel implements EntryContainer { * source couldn't be parsed, and the user is given the option to edit it. */ private boolean updateSource = true; - /** Indicates that we are about to go to the next or previous entry */ + /** + * Indicates that we are about to go to the next or previous entry + */ private boolean movingToDifferentEntry; private boolean validEntry = true; @@ -194,26 +215,29 @@ public class EntryEditor extends JPanel implements EntryContainer { private boolean lastFieldAccepted = true; /** - * This indicates whether the last attempt at parsing the source was successful. It is used to determine whether - * the dialog should close; it should stay open if the user received an error message about the source, - * whatever he or she chose to do about it. + * This indicates whether the last attempt at parsing the source was successful. It is used to determine whether + * the dialog should close; it should stay open if the user received an error message about the source, + * whatever he or she chose to do about it. */ private boolean lastSourceAccepted = true; - /** This is used to prevent double updates after editing source. */ + /** + * This is used to prevent double updates after editing source. + */ private String lastSourceStringAccepted; - /** The index the source panel has in tabbed. */ + /** + * The index the source panel has in tabbed. + */ private int sourceIndex = -1; private final HelpAction helpAction = new HelpAction(HelpFile.ENTRY_EDITOR, IconTheme.JabRefIcon.HELP.getIcon()); - private final UndoAction undoAction = new UndoAction(); - private final RedoAction redoAction = new RedoAction(); + private Action saveDatabaseAction; - private final TabListener tabListener = new TabListener(); + private final TabListener tabListener = new TabListener(); private final List searchListeners = new ArrayList<>(); public EntryEditor(JabRefFrame frame, BasePanel panel, BibEntry entry) { @@ -331,7 +355,7 @@ private void setupFieldPanels() { addSpecialTabs(); // pdf annotations tab - addPdfTab(); + addPDFAnnotationTab(); //related articles if (Globals.prefs.getBoolean(JabRefPreferences.SHOW_RECOMMENDATIONS)) { @@ -439,27 +463,24 @@ private void addOptionalTab(EntryType type) { fileListEditor = optionalPanel.fileListEditor; } tabbed.addTab(Localization.lang("Optional fields"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(), - optionalPanel - .getPane(), - Localization.lang("Show optional fields")); + optionalPanel.getPane(), Localization.lang("Show optional fields")); tabs.add(optionalPanel); } /** * Add a tab for displaying comments from a PDF */ - private void addPdfTab() { + private void addPDFAnnotationTab() { tabbed.remove(fileAnnotationTab); tabs.remove(fileAnnotationTab); Optional field = entry.getField(FieldName.FILE); if (field.isPresent()) { - fileAnnotationTab = new FileAnnotationTab(this, panel, tabbed); - tabbed.addTab(Localization.lang("File annotations"), IconTheme.JabRefIcon.COMMENT.getSmallIcon(), - fileAnnotationTab, + fileAnnotationTab = new FileAnnotationTab(this); + tabbed.addTab(Localization.lang("File annotations"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(), fileAnnotationTab, + Localization.lang("Show file annotations")); tabs.add(fileAnnotationTab); } - } public String getDisplayedBibEntryType() { @@ -495,21 +516,21 @@ private void setupToolBar() { inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_ENTRY_EDITOR), "close"); actionMap.put("close", closeAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_STORE_FIELD), "store"); - actionMap.put("store", getStoreFieldAction()); + actionMap.put("store", storeFieldAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.AUTOGENERATE_BIBTEX_KEYS), "generateKey"); - actionMap.put("generateKey", getGenerateKeyAction()); + actionMap.put("generateKey", generateKeyAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.AUTOMATICALLY_LINK_FILES), "autoLink"); actionMap.put("autoLink", autoLinkAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_PREVIOUS_ENTRY), "prev"); - actionMap.put("prev", getPrevEntryAction()); + actionMap.put("prev", prevEntryAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_ENTRY), "next"); - actionMap.put("next", getNextEntryAction()); + actionMap.put("next", nextEntryAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.UNDO), "undo"); actionMap.put("undo", undoAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.REDO), "redo"); actionMap.put("redo", redoAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help"); - actionMap.put("help", getHelpAction()); + actionMap.put("help", helpAction); toolBar.setFloatable(false); @@ -526,12 +547,13 @@ private void setupToolBar() { TypeButton typeButton = new TypeButton(); toolBar.add(typeButton); - toolBar.add(getGenerateKeyAction()); + toolBar.add(generateKeyAction); toolBar.add(autoLinkAction); toolBar.add(writeXmp); JPopupMenu fetcherPopup = new JPopupMenu(); + for (EntryBasedFetcher fetcher : EntryFetchers .getEntryBasedFetchers(Globals.prefs.getImportFormatPreferences())) { fetcherPopup.add(new JMenuItem(new AbstractAction(fetcher.getName()) { @@ -556,12 +578,12 @@ public void mousePressed(MouseEvent e) { toolBar.addSeparator(); toolBar.add(deleteAction); - toolBar.add(getPrevEntryAction()); - toolBar.add(getNextEntryAction()); + toolBar.add(prevEntryAction); + toolBar.add(nextEntryAction); toolBar.addSeparator(); - toolBar.add(getHelpAction()); + toolBar.add(helpAction); Component[] comps = toolBar.getComponents(); @@ -614,11 +636,10 @@ public Optional getExtra(final FieldEditor editor) { // Add controls for switching between abbreviated and full journal names. // If this field also has a FieldContentSelector, we need to combine these. return FieldExtraComponents.getJournalExtraComponent(frame, panel, editor, entry, contentSelectors, - getStoreFieldAction()); + storeFieldAction); } else if (!panel.getBibDatabaseContext().getMetaData().getContentSelectorValuesForField(fieldName).isEmpty()) { return FieldExtraComponents.getSelectorExtraComponent(frame, panel, editor, contentSelectors, - getStoreFieldAction()); - + storeFieldAction); } else if (fieldExtras.contains(FieldProperty.DOI)) { return FieldExtraComponents.getDoiExtraComponent(panel, this, editor); } else if (fieldExtras.contains(FieldProperty.EPRINT)) { @@ -626,7 +647,7 @@ public Optional getExtra(final FieldEditor editor) { } else if (fieldExtras.contains(FieldProperty.ISBN)) { return FieldExtraComponents.getIsbnExtraComponent(panel, this, editor); } else if (fieldExtras.contains(FieldProperty.OWNER)) { - return FieldExtraComponents.getSetOwnerExtraComponent(editor, getStoreFieldAction()); + return FieldExtraComponents.getSetOwnerExtraComponent(editor, storeFieldAction); } else if (fieldExtras.contains(FieldProperty.YES_NO)) { return FieldExtraComponents.getYesNoExtraComponent(editor, this); } else if (fieldExtras.contains(FieldProperty.MONTH)) { @@ -692,7 +713,6 @@ public void updateSource() { source.setEditable(false); LOGGER.debug("Incorrect entry", ex); } - } } @@ -716,18 +736,18 @@ private void setupJTextComponent(JTextComponent textComponent) { ActionMap actionMap = textComponent.getActionMap(); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_STORE_FIELD), "store"); - actionMap.put("store", getStoreFieldAction()); + actionMap.put("store", storeFieldAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_PANEL), "right"); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_PANEL_2), "right"); - actionMap.put("right", getSwitchRightAction()); + actionMap.put("right", switchRightAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_PREVIOUS_PANEL), "left"); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_PREVIOUS_PANEL_2), "left"); - actionMap.put("left", getSwitchLeftAction()); + actionMap.put("left", switchLeftAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help"); - actionMap.put("help", getHelpAction()); + actionMap.put("help", helpAction); inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.NEXT_TAB), "nexttab"); actionMap.put("nexttab", frame.nextTab); @@ -783,7 +803,6 @@ public void setEnabled(boolean enabled) { } } source.setEnabled(enabled); - } /** @@ -795,7 +814,7 @@ public void storeCurrentEdit() { if (comp instanceof FieldEditor) { ((FieldEditor) comp).clearAutoCompleteSuggestion(); } - getStoreFieldAction().actionPerformed(new ActionEvent(comp, 0, "")); + storeFieldAction.actionPerformed(new ActionEvent(comp, 0, "")); } } @@ -975,7 +994,6 @@ public void updateAllContentSelectors() { for (FieldContentSelector contentSelector : contentSelectors) { contentSelector.rebuildComboBox(); } - } } @@ -994,7 +1012,7 @@ public void listen(FieldChangedEvent fieldChangedEvent) { } public void updateField(final Object sourceObject) { - getStoreFieldAction().actionPerformed(new ActionEvent(sourceObject, 0, "")); + storeFieldAction.actionPerformed(new ActionEvent(sourceObject, 0, "")); } public void setMovingToDifferentEntry() { @@ -1007,9 +1025,40 @@ private void unregisterListeners() { removeSearchListeners(); } - private class TypeButton extends JButton { + public GenerateKeyAction getGenerateKeyAction() { + return generateKeyAction; + } + + public AbstractAction getPrevEntryAction() { + return prevEntryAction; + } + + public AbstractAction getNextEntryAction() { + return nextEntryAction; + } + + public StoreFieldAction getStoreFieldAction() { + return storeFieldAction; + } + + public SwitchLeftAction getSwitchLeftAction() { + return switchLeftAction; + } + + public SwitchRightAction getSwitchRightAction() { + return switchRightAction; + } - public TypeButton() { + public HelpAction getHelpAction() { + return helpAction; + } + + public Action getSaveDatabaseAction() { + return saveDatabaseAction; + } + + private class TypeButton extends JButton { + private TypeButton() { super(IconTheme.JabRefIcon.EDIT.getIcon()); setToolTipText(Localization.lang("Change entry type")); addActionListener(e -> showChangeEntryTypePopupMenu()); @@ -1022,8 +1071,7 @@ private void showChangeEntryTypePopupMenu() { } private class TypeLabel extends JLabel { - - public TypeLabel(String type) { + private TypeLabel(String type) { super(type); setUI(new VerticalLabelUI(false)); setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); @@ -1084,9 +1132,14 @@ public void stateChanged(ChangeEvent event) { Object activeTab = tabs.get(tabbed.getSelectedIndex()); if ((activeTab instanceof FileAnnotationTab) && !((FileAnnotationTab) activeTab).isInitialized()) { //Initialize by getting notes from cache if they are cached - FileAnnotationTab.initializeTab((FileAnnotationTab) activeTab, - panel.getAnnotationCache().getFromCache(Optional.of(entry))); - panel.getAnnotationCache().addToCache(entry, ((FileAnnotationTab) activeTab).getAllNotes()); + + FileAnnotationTab tab = (FileAnnotationTab) activeTab; + try { + tab.initializeTab(tab, + panel.getAnnotationCache().getFromCache(entry)); + } catch (ExecutionException e) { + tab.initializeTab((FileAnnotationTab) activeTab); + } } if (activeTab instanceof EntryEditorTab) { @@ -1102,9 +1155,9 @@ public void stateChanged(ChangeEvent event) { } } - class DeleteAction extends AbstractAction { - public DeleteAction() { + private class DeleteAction extends AbstractAction { + private DeleteAction() { super(Localization.lang("Delete"), IconTheme.JabRefIcon.DELETE_ENTRY.getIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Delete entry")); } @@ -1145,9 +1198,10 @@ public void close() { } } - class CloseAction extends AbstractAction { - public CloseAction() { + private class CloseAction extends AbstractAction { + + private CloseAction() { super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close window")); } @@ -1158,7 +1212,7 @@ public void actionPerformed(ActionEvent e) { } } - class StoreFieldAction extends AbstractAction { + public class StoreFieldAction extends AbstractAction { public StoreFieldAction() { super("Store field value"); @@ -1292,7 +1346,6 @@ public void actionPerformed(ActionEvent event) { ce.end(); panel.getUndoManager().addEdit(ce); - } else { panel.getUndoManager().addEdit(undoableFieldChange); } @@ -1324,9 +1377,10 @@ public void actionPerformed(ActionEvent event) { } } - class SwitchLeftAction extends AbstractAction { - public SwitchLeftAction() { + private class SwitchLeftAction extends AbstractAction { + + private SwitchLeftAction() { super("Switch to the panel to the left"); } @@ -1339,9 +1393,10 @@ public void actionPerformed(ActionEvent e) { } } - class SwitchRightAction extends AbstractAction { - public SwitchRightAction() { + private class SwitchRightAction extends AbstractAction { + + private SwitchRightAction() { super("Switch to the panel to the right"); } @@ -1350,13 +1405,13 @@ public void actionPerformed(ActionEvent e) { int i = tabbed.getSelectedIndex(); tabbed.setSelectedIndex(i < (tabbed.getTabCount() - 1) ? i + 1 : 0); activateVisible(); - } } - class NextEntryAction extends AbstractAction { - public NextEntryAction() { + private class NextEntryAction extends AbstractAction { + + private NextEntryAction() { super(Localization.lang("Next entry"), IconTheme.JabRefIcon.DOWN.getIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Next entry")); @@ -1368,9 +1423,10 @@ public void actionPerformed(ActionEvent e) { } } - class PrevEntryAction extends AbstractAction { - public PrevEntryAction() { + private class PrevEntryAction extends AbstractAction { + + private PrevEntryAction() { super(Localization.lang("Previous entry"), IconTheme.JabRefIcon.UP.getIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Previous entry")); @@ -1382,13 +1438,12 @@ public void actionPerformed(ActionEvent e) { } } - class GenerateKeyAction extends AbstractAction { + private class GenerateKeyAction extends AbstractAction { - public GenerateKeyAction() { + private GenerateKeyAction() { super(Localization.lang("Generate BibTeX key"), IconTheme.JabRefIcon.MAKE_KEY.getIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Generate BibTeX key")); - } @Override @@ -1443,9 +1498,9 @@ public void actionPerformed(ActionEvent e) { } } - class UndoAction extends AbstractAction { - public UndoAction() { + private class UndoAction extends AbstractAction { + private UndoAction() { super("Undo", IconTheme.JabRefIcon.UNDO.getIcon()); putValue(Action.SHORT_DESCRIPTION, "Undo"); } @@ -1456,9 +1511,9 @@ public void actionPerformed(ActionEvent e) { } } - class RedoAction extends AbstractAction { + private class RedoAction extends AbstractAction { - public RedoAction() { + private RedoAction() { super("Redo", IconTheme.JabRefIcon.REDO.getIcon()); putValue(Action.SHORT_DESCRIPTION, "Redo"); } @@ -1469,9 +1524,10 @@ public void actionPerformed(ActionEvent e) { } } - class SaveDatabaseAction extends AbstractAction { - public SaveDatabaseAction() { + private class SaveDatabaseAction extends AbstractAction { + + private SaveDatabaseAction() { super("Save library"); } @@ -1505,41 +1561,10 @@ private void warnEmptyBibtexkey() { + Localization.lang("Grouping may not work for this entry.")); } - public AbstractAction getNextEntryAction() { - return nextEntryAction; - } - - public AbstractAction getPrevEntryAction() { - return prevEntryAction; - } - - public SwitchLeftAction getSwitchLeftAction() { - return switchLeftAction; - } - - public SwitchRightAction getSwitchRightAction() { - return switchRightAction; - } - - public SaveDatabaseAction getSaveDatabaseAction() { - return saveDatabaseAction; - } - - public HelpAction getHelpAction() { - return helpAction; - } - - public GenerateKeyAction getGenerateKeyAction() { - return generateKeyAction; - } - - public StoreFieldAction getStoreFieldAction() { - return storeFieldAction; - } private class AutoLinkAction extends AbstractAction { - public AutoLinkAction() { + private AutoLinkAction() { putValue(Action.SMALL_ICON, IconTheme.JabRefIcon.AUTO_FILE_LINK.getIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Automatically set file links for this entry") + " (Alt-F)"); @@ -1570,5 +1595,4 @@ private Optional doUpdateTimeStamp() { String timestamp = DateTimeFormatter.ofPattern(timeStampFormat).format(LocalDateTime.now()); return UpdateField.updateField(entry, timeStampField, timestamp); } - } diff --git a/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java b/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java index 46d51b228a6..b0ba6a6b9a7 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java @@ -3,15 +3,11 @@ import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; -import java.io.IOException; import java.util.Arrays; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.StringJoiner; -import java.util.stream.Collectors; import javax.swing.BoxLayout; import javax.swing.DefaultListCellRenderer; @@ -22,30 +18,27 @@ import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import org.jabref.gui.BasePanel; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.GUIGlobals; import org.jabref.gui.IconTheme; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.pdf.PdfAnnotationImporterImpl; +import org.jabref.logic.pdf.EntryAnnotationImporter; import org.jabref.model.entry.FieldName; -import org.jabref.model.entry.FileField; -import org.jabref.model.entry.ParsedFileField; import org.jabref.model.pdf.FileAnnotation; import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.factories.Paddings; import org.apache.pdfbox.pdmodel.fdf.FDFAnnotationHighlight; -public class FileAnnotationTab extends JPanel { - private final JList commentList = new JList<>(); - private final JScrollPane commentScrollPane = new JScrollPane(); +class FileAnnotationTab extends JPanel { + + private final JList annotationList = new JList<>(); + private final JScrollPane annotationScrollPane = new JScrollPane(); private final JLabel fileNameLabel = new JLabel(Localization.lang("Filename"),JLabel.CENTER); private final JComboBox fileNameComboBox = new JComboBox<>(); private final JScrollPane fileNameScrollPane = new JScrollPane(); @@ -58,104 +51,93 @@ public class FileAnnotationTab extends JPanel { private final JLabel pageLabel = new JLabel(Localization.lang("Page"), JLabel.CENTER); private final JTextArea pageArea = new JTextArea("page"); private final JScrollPane pageScrollPane = new JScrollPane(); - private final JLabel commentTxtLabel = new JLabel(Localization.lang("Content"),JLabel.CENTER); + private final JLabel annotationTextLabel = new JLabel(Localization.lang("Content"), JLabel.CENTER); private final JTextArea contentTxtArea = new JTextArea(); private final JLabel highlightTxtLabel = new JLabel(Localization.lang("Highlight"), JLabel.CENTER); private final JTextArea highlightTxtArea = new JTextArea(); - private final JScrollPane commentTxtScrollPane = new JScrollPane(); + private final JScrollPane annotationTextScrollPane = new JScrollPane(); private final JScrollPane highlightScrollPane = new JScrollPane(); private final JButton copyToClipboardButton = new JButton(); private final JButton reloadAnnotationsButton = new JButton(); - DefaultListModel listModel; + private DefaultListModel listModel; private final EntryEditor parent; - private final BasePanel basePanel; - private final JTabbedPane tabbed; - private int commentListSelectedIndex = 0; + private Map> annotationsOfFiles; private boolean isInitialized; - private Map> allNotes = new HashMap<>(); - - public FileAnnotationTab(EntryEditor parent, BasePanel basePanel, JTabbedPane tabbed) { + FileAnnotationTab(EntryEditor parent) { this.parent = parent; - this.basePanel = basePanel; - this.tabbed = tabbed; setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); listModel = new DefaultListModel<>(); this.isInitialized = false; } - public static FileAnnotationTab initializeTab(FileAnnotationTab tab, Optional>> notes){ + public FileAnnotationTab initializeTab(FileAnnotationTab tab) { + if (tab.isInitialized) { + return tab; + } - if(!tab.isInitialized) { + tab.setUpGui(); + tab.isInitialized = true; + tab.parent.repaint(); + return tab; + } - try { - tab.addComments(notes); - } catch (IOException e) { - e.printStackTrace(); - } - tab.setUpGui(); + public FileAnnotationTab initializeTab(FileAnnotationTab tab, Map> cachedFileAnnotations) { + this.annotationsOfFiles = cachedFileAnnotations; - tab.isInitialized = true; - tab.parent.repaint(); + if (tab.isInitialized) { return tab; } + + tab.addAnnotations(); + tab.setUpGui(); + tab.isInitialized = true; + tab.parent.repaint(); return tab; + } /** - * Adds pdf comments from all attached pdf files belonging to the entry selected in the main table and - * shows those from the first file in the comments tab - * @throws IOException + * Adds pdf annotations from all attached pdf files belonging to the entry selected in the main table and + * shows those from the first file in the file annotations tab */ - public void addComments(Optional>> notes) throws IOException { - Optional field = parent.getEntry().getField(FieldName.FILE); - if (field.isPresent()) { - if (!commentList.getModel().equals(listModel)) { - commentList.setModel(listModel); - commentList.addListSelectionListener(new CommentListSelectionListener()); - commentList.setCellRenderer(new CommentsListCellRenderer()); + private void addAnnotations() { + if (parent.getEntry().getField(FieldName.FILE).isPresent()) { + if (!annotationList.getModel().equals(listModel)) { + annotationList.setModel(listModel); + annotationList.addListSelectionListener(new AnnotationListSelectionListener()); + annotationList.setCellRenderer(new AnnotationListCellRenderer()); } - PdfAnnotationImporterImpl annotationImporter; - - if(notes.isPresent()) { - allNotes = notes.get(); - } else { - annotationImporter = new PdfAnnotationImporterImpl(); - //import notes if the selected file is a pdf - getFilteredFileList().forEach(parsedFileField -> allNotes.put( - parsedFileField.getLink(), - annotationImporter.importAnnotations(parsedFileField.getLink(), basePanel.getDatabaseContext()))); - } //set up the comboBox for representing the selected file fileNameComboBox.removeAllItems(); - getFilteredFileList() - .forEach(((parsedField) -> fileNameComboBox.addItem(parsedField.getLink()))); - //show the annotations attached to the selected file - updateShownAnnotations(allNotes.get(fileNameComboBox.getSelectedItem() == null ? - fileNameComboBox.getItemAt(0).toString() : fileNameComboBox.getSelectedItem().toString())); + new EntryAnnotationImporter(parent.getEntry()).getFilteredFileList(). + forEach(((parsedField) -> fileNameComboBox.addItem(parsedField.getLink()))); + //show the annotationsOfFiles attached to the selected file + updateShownAnnotations(annotationsOfFiles.get(fileNameComboBox.getSelectedItem() == null ? + fileNameComboBox.getItemAt(0) : fileNameComboBox.getSelectedItem().toString())); //select the first annotation - if(commentList.isSelectionEmpty()){ - commentList.setSelectedIndex(0); + if (annotationList.isSelectionEmpty()) { + annotationList.setSelectedIndex(0); } } } /** * Updates the list model to show the given notes without those with no content - * @param importedNotes value is the comments name and the value is a pdfComment object to add to the list model + * @param annotations value is the annotation name and the value is a pdfAnnotation object to add to the list model */ - private void updateShownAnnotations(List importedNotes){ + private void updateShownAnnotations(List annotations) { listModel.clear(); - if(importedNotes.isEmpty()){ + if (annotations.isEmpty()) { listModel.addElement(new FileAnnotation("", "", "", 0, Localization.lang("File has no attached annotations"), "")); } else { - Comparator byPage = (annotation1, annotation2) -> Integer.compare(annotation1.getPage(), annotation2.getPage()); - importedNotes.stream() + Comparator byPage = Comparator.comparingInt(FileAnnotation::getPage); + annotations.stream() .filter(annotation -> !(null == annotation.getContent())) .filter(annotation -> annotation.getAnnotationType().equals(FDFAnnotationHighlight.SUBTYPE) || (null == annotation.getLinkedFileAnnotation())) @@ -165,15 +147,14 @@ private void updateShownAnnotations(List importedNotes){ } /** - * Updates the text fields showing meta data and the content from the selected comment - * @param comment pdf comment which data should be shown in the text fields + * Updates the text fields showing meta data and the content from the selected annotation + * @param annotation pdf annotation which data should be shown in the text fields */ - private void updateTextFields(FileAnnotation comment) { - authorArea.setText(comment.getAuthor()); - dateArea.setText(comment.getDate()); - pageArea.setText(String.valueOf(comment.getPage())); - updateContentAndHighlightTextfields(comment); - + private void updateTextFields(FileAnnotation annotation) { + authorArea.setText(annotation.getAuthor()); + dateArea.setText(annotation.getDate()); + pageArea.setText(String.valueOf(annotation.getPage())); + updateContentAndHighlightTextfields(annotation); } /** @@ -187,22 +168,22 @@ private void updateFileNameComboBox() { indexSelectedByComboBox = fileNameComboBox.getSelectedIndex(); } fileNameComboBox.removeAllItems(); - getFilteredFileList().stream().filter(parsedFileField -> parsedFileField.getLink().toLowerCase().endsWith(".pdf") ) + new EntryAnnotationImporter(parent.getEntry()).getFilteredFileList().stream().filter(parsedFileField -> parsedFileField.getLink().toLowerCase().endsWith(".pdf")) .forEach(((parsedField) -> fileNameComboBox.addItem(parsedField.getLink()))); fileNameComboBox.setSelectedIndex(indexSelectedByComboBox); - updateShownAnnotations(allNotes.get(fileNameComboBox.getSelectedItem().toString())); + updateShownAnnotations(annotationsOfFiles.get(fileNameComboBox.getSelectedItem().toString())); } private void setUpGui() { - JPanel commentListPanel = FormBuilder.create() + JPanel annotationPanel = FormBuilder.create() .columns("pref, $lcgap, pref:grow") .rows("pref, $lg, fill:pref:grow, $lg, pref") .padding(Paddings.DIALOG) .add(fileNameLabel).xy(1,1, "left, top") .add(fileNameScrollPane).xyw(2, 1, 2) - .add(commentScrollPane).xyw(1, 3, 3) + .add(annotationScrollPane).xyw(1, 3, 3) .build(); - commentScrollPane.setViewportView(commentList); + annotationScrollPane.setViewportView(annotationList); JPanel informationPanel = FormBuilder.create() .columns("pref, $lcgap, pref:grow") @@ -214,8 +195,8 @@ private void setUpGui() { .add(dateScrollPane).xy(3,5) .add(pageLabel).xy(1,7, "left, top") .add(pageScrollPane).xy(3,7) - .add(commentTxtLabel).xy(1,9, "left, top") - .add(commentTxtScrollPane).xywh(3,9, 1, 2) + .add(annotationTextLabel).xy(1, 9, "left, top") + .add(annotationTextScrollPane).xywh(3, 9, 1, 2) .add(highlightTxtLabel).xy(1, 11, "left, top") .add(highlightScrollPane).xywh(3, 11, 1, 2) .add(this.setUpButtons()).xyw(1, 13, 3) @@ -226,7 +207,7 @@ private void setUpGui() { authorLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); dateLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); pageLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - commentTxtLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); + annotationTextLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); highlightTxtLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); fileNameScrollPane.setBorder(null); authorScrollPane.setViewportView(authorArea); @@ -235,7 +216,7 @@ private void setUpGui() { dateScrollPane.setBorder(null); pageScrollPane.setViewportView(pageArea); pageScrollPane.setBorder(null); - commentTxtScrollPane.setViewportView(contentTxtArea); + annotationTextScrollPane.setViewportView(contentTxtArea); highlightScrollPane.setViewportView(highlightTxtArea); authorArea.setEditable(false); dateArea.setEditable(false); @@ -250,7 +231,7 @@ private void setUpGui() { this.add(FormBuilder.create() .columns("0:grow, $lcgap, 0:grow") .rows("fill:pref:grow") - .add(commentListPanel).xy(1, 1) + .add(annotationPanel).xy(1, 1) .add(informationPanel).xy(3, 1) .build()); } @@ -293,99 +274,90 @@ private void copyToClipboard(){ private void reloadAnnotations() { isInitialized = false; - Arrays.stream(this.getComponents()).forEach(component -> this.remove(component)); - initializeTab(this, Optional.empty()); + Arrays.stream(this.getComponents()).forEach(this::remove); + initializeTab(this); this.repaint(); } + /** - * Fills the highlight and comment texts and enables/disables the highlight area if there is no highlighted text + * Fills the highlight and annotation texts and enables/disables the highlight area if there is no highlighted text * - * @param comment either a text comment or a highlighting from a pdf + * @param annotation either a text annotation or a highlighting from a pdf */ - private void updateContentAndHighlightTextfields(final FileAnnotation comment){ + private void updateContentAndHighlightTextfields(final FileAnnotation annotation) { - if(comment.hasLinkedComment()){ - String textComment = ""; - String highlightedText = ""; + if (annotation.hasLinkedAnnotation()) { + String annotationText; + String highlightedText; - if(comment.getAnnotationType().equals(FDFAnnotationHighlight.SUBTYPE)){ - highlightedText = comment.getContent(); - textComment = comment.getLinkedFileAnnotation().getContent(); + if (annotation.getAnnotationType().equals(FDFAnnotationHighlight.SUBTYPE)) { + highlightedText = annotation.getContent(); + annotationText = annotation.getLinkedFileAnnotation().getContent(); } else { - highlightedText = comment.getLinkedFileAnnotation().getContent(); - textComment = comment.getContent(); + highlightedText = annotation.getLinkedFileAnnotation().getContent(); + annotationText = annotation.getContent(); } highlightTxtArea.setEnabled(true); - contentTxtArea.setText(textComment); + contentTxtArea.setText(annotationText); highlightTxtArea.setText(highlightedText); } else { - contentTxtArea.setText(comment.getContent()); + contentTxtArea.setText(annotation.getContent()); highlightTxtArea.setText("N/A"); highlightTxtArea.setEnabled(false); } } - /** - * Filter files with a web address containing "www." - * @return a list of file parsed files - */ - private List getFilteredFileList(){ - return FileField.parse(parent.getEntry().getField(FieldName.FILE).get()).stream() - .filter(parsedFileField -> parsedFileField.getLink().toLowerCase().endsWith(".pdf")) - .filter(parsedFileField -> !parsedFileField.getLink().contains("www.")).collect(Collectors.toList()); - } - private class CommentListSelectionListener implements ListSelectionListener { + private class AnnotationListSelectionListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { int index; - if (commentList.getSelectedIndex() >= 0) { - index = commentList.getSelectedIndex(); + int annotationListSelectedIndex = 0; + if (annotationList.getSelectedIndex() >= 0) { + index = annotationList.getSelectedIndex(); updateTextFields(listModel.get(index)); - commentListSelectedIndex = index; - } else { - commentListSelectedIndex = 0; + annotationListSelectedIndex = index; } - commentList.setSelectedIndex(commentListSelectedIndex); + annotationList.setSelectedIndex(annotationListSelectedIndex); //repaint the list to refresh the linked annotation highlighting - commentList.repaint(); + annotationList.repaint(); } } /** * Cell renderer that shows different icons dependent on the annotation subtype */ - class CommentsListCellRenderer extends DefaultListCellRenderer { + class AnnotationListCellRenderer extends DefaultListCellRenderer { JLabel label; - CommentsListCellRenderer() { + AnnotationListCellRenderer() { this.label = new JLabel(); } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - FileAnnotation comment = (FileAnnotation) value; + FileAnnotation annotation = (FileAnnotation) value; //call the super method so that the cell selection is done as usual label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - //If more different comment types should be reflected by icons in the list, add them here - switch(comment.getAnnotationType()){ + //If more different annotation types should be reflected by icons in the list, add them here + switch (annotation.getAnnotationType()) { case FDFAnnotationHighlight.SUBTYPE: label.setIcon(IconTheme.JabRefIcon.MARKER.getSmallIcon()); break; default: - label.setIcon(IconTheme.JabRefIcon.COMMENT.getSmallIcon()); + label.setIcon(IconTheme.JabRefIcon.OPTIONAL.getSmallIcon()); break; } - label.setToolTipText(comment.getAnnotationType()); - label.setText(comment.toString()); + label.setToolTipText(annotation.getAnnotationType()); + label.setText(annotation.toString()); return label; } @@ -394,8 +366,4 @@ public Component getListCellRendererComponent(JList list, Object value, int i public boolean isInitialized() { return isInitialized; } - - public Map> getAllNotes() { - return allNotes; - } } diff --git a/src/main/java/org/jabref/logic/pdf/AnnotationImporter.java b/src/main/java/org/jabref/logic/pdf/AnnotationImporter.java new file mode 100644 index 00000000000..ac52e0edac3 --- /dev/null +++ b/src/main/java/org/jabref/logic/pdf/AnnotationImporter.java @@ -0,0 +1,11 @@ +package org.jabref.logic.pdf; + +import java.util.List; + +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.pdf.FileAnnotation; + +public interface AnnotationImporter { + + List importAnnotations(final String path, final BibDatabaseContext context); +} diff --git a/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java b/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java new file mode 100644 index 00000000000..49ce9c196d2 --- /dev/null +++ b/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java @@ -0,0 +1,54 @@ +package org.jabref.logic.pdf; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.FieldName; +import org.jabref.model.entry.FileField; +import org.jabref.model.entry.ParsedFileField; +import org.jabref.model.pdf.FileAnnotation; + + +/** + * Here all PDF files attached to a BibEntry are scanned for annotations using a PdfAnnotationImporter. + */ +public class EntryAnnotationImporter { + + private final BibEntry entry; + + /** + * @param entry The BibEntry whose attached files are scanned for annotations. + */ + public EntryAnnotationImporter(BibEntry entry) { + this.entry = entry; + } + + /** + * Filter files with a web address containing "www." + * + * @return a list of file parsed files + */ + public List getFilteredFileList() { + return FileField.parse(this.entry.getField(FieldName.FILE).get()).stream() + .filter(parsedFileField -> parsedFileField.getLink().toLowerCase().endsWith(".pdf")) + .filter(parsedFileField -> !parsedFileField.getLink().contains("www.")).collect(Collectors.toList()); + } + + /** + * Reads the annotations from the files that are attached to a BibEntry. + * + * @param context The context is needed for the importer. + * @return Map from each PDF to a list of file annotations + */ + public Map> importAnnotationsFromFiles(BibDatabaseContext context) { + Map> annotations = new HashMap<>(); + AnnotationImporter importer = new PdfAnnotationImporter(); + //import annotationsOfFiles if the selected files are valid which is checked in getFilteredFileList() + this.getFilteredFileList().forEach(parsedFileField -> annotations.put(parsedFileField.getLink(), importer.importAnnotations(parsedFileField.getLink(), context))); + return annotations; + } +} diff --git a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java index 5f1da00058d..44bbdb633d0 100644 --- a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java +++ b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java @@ -1,11 +1,10 @@ package org.jabref.logic.pdf; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ExecutionException; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.pdf.FileAnnotation; @@ -13,6 +12,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; + public class FileAnnotationCache { //cache size in entries @@ -20,35 +20,22 @@ public class FileAnnotationCache { //the inner list holds the annotations per file, the outer collection maps this to a BibEntry. private LoadingCache>> annotationCache; - public FileAnnotationCache() { + public FileAnnotationCache(BibDatabaseContext context) { annotationCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader>>() { @Override - public Map> load(BibEntry notUsed) throws Exception { - // Automated reloading of entries is not supported. - return new HashMap<>(); + public Map> load(BibEntry entry) throws Exception { + return new EntryAnnotationImporter(entry).importAnnotationsFromFiles(context); } }); } - public void addToCache(BibEntry entry, final Map> annotations) { - annotationCache.put(entry, annotations); - } - /** * Note that entry becomes the most recent entry in the cache * * @param entry entry for which to get the annotations * @return Map containing a list of annotations in a list for each file */ - public Optional>> getFromCache(Optional entry) { - Optional>> emptyAnnotation = Optional.empty(); - try { - if (entry.isPresent() && annotationCache.get(entry.get()).size() > 0) { - return Optional.of(annotationCache.get(entry.get())); - } - } catch (ExecutionException failure) { - return emptyAnnotation; - } - return emptyAnnotation; + public Map> getFromCache(BibEntry entry) throws ExecutionException { + return annotationCache.get(entry); } } diff --git a/src/main/java/org/jabref/logic/pdf/PdfAnnotationImporterImpl.java b/src/main/java/org/jabref/logic/pdf/PdfAnnotationImporter.java similarity index 88% rename from src/main/java/org/jabref/logic/pdf/PdfAnnotationImporterImpl.java rename to src/main/java/org/jabref/logic/pdf/PdfAnnotationImporter.java index bf03ba19ec6..6489dfa174b 100644 --- a/src/main/java/org/jabref/logic/pdf/PdfAnnotationImporterImpl.java +++ b/src/main/java/org/jabref/logic/pdf/PdfAnnotationImporter.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Optional; import org.jabref.logic.util.io.FileUtil; @@ -24,14 +25,10 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.util.PDFTextStripperByArea; -public class PdfAnnotationImporterImpl implements AnnotationImporterInterface { +import static org.jabref.gui.importer.actions.OpenDatabaseAction.LOGGER; - private List pdfPages; - private PDPage page; +public class PdfAnnotationImporter implements AnnotationImporter { - public PdfAnnotationImporterImpl() { - - } /** * Imports the comments from a pdf specified by its path @@ -62,12 +59,12 @@ public List importAnnotations(final String path, final BibDataba } } catch (IOException e) { - e.printStackTrace(); + LOGGER.error(String.format("Failed to read file %s.", path) , e); } - pdfPages = document.getDocumentCatalog().getAllPages(); + List pdfPages = document.getDocumentCatalog().getAllPages(); for (int i = 0; i < pdfPages.size(); i++) { - page = (PDPage) pdfPages.get(i); + PDPage page = (PDPage) pdfPages.get(i); try { for (PDAnnotation annotation : page.getAnnotations()) { @@ -82,7 +79,10 @@ public List importAnnotations(final String path, final BibDataba PDFTextStripperByArea stripperByArea = new PDFTextStripperByArea(); COSArray quadsArray = (COSArray) annotation.getDictionary().getDictionaryObject(COSName.getPDFName("QuadPoints")); String highlightedText = null; - for (int j = 1, k = 0; j <= (quadsArray.size() / 8); j++) { + for (int j = 1, + k = 0; + j <= (quadsArray.size() / 8); + j++) { COSFloat upperLeftX = (COSFloat) quadsArray.get(k); COSFloat upperLeftY = (COSFloat) quadsArray.get(1 + k); @@ -124,14 +124,13 @@ public List importAnnotations(final String path, final BibDataba annotationsList.add(new FileAnnotation(annotation, i + 1)); } } - } catch (IOException e1) { - e1.printStackTrace(); + } catch (IOException e) { + LOGGER.error(String.format("Failed to read file %s.", path) , e); } } try { document.close(); - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException ignored) { } return annotationsList; } @@ -144,7 +143,7 @@ public List importAnnotations(final String path, final BibDataba */ public PDDocument importPdfFile(final String path) throws IOException { - if(path.toLowerCase().endsWith(".pdf")){ + if(path.toLowerCase(Locale.ROOT).endsWith(".pdf")){ return PDDocument.load("/"+ path); } return null; diff --git a/src/main/java/org/jabref/model/pdf/FileAnnotation.java b/src/main/java/org/jabref/model/pdf/FileAnnotation.java index 62a764954fe..eb0c33dda5e 100644 --- a/src/main/java/org/jabref/model/pdf/FileAnnotation.java +++ b/src/main/java/org/jabref/model/pdf/FileAnnotation.java @@ -16,7 +16,7 @@ public class FileAnnotation { private FileAnnotation linkedFileAnnotation; private final static int ABBREVIATED_ANNOTATION_NAME_LENGTH = 45; - private boolean linkedComment; + private boolean linkedAnnotation; public FileAnnotation(final String commentId, final String author, final String date, final int page, final String content, final String annotationType) { @@ -56,8 +56,8 @@ private String abbreviateAnnotationName(final String annotationName ){ public void linkComments(FileAnnotation commentToLinkTo){ linkedFileAnnotation = commentToLinkTo; commentToLinkTo.setLinkedFileAnnotation(this); - commentToLinkTo.setLinkedComment(true); - linkedComment = true; + commentToLinkTo.setLinkedAnnotation(true); + linkedAnnotation = true; } @Override @@ -117,11 +117,11 @@ public String getAnnotationType() { return annotationType; } - public boolean hasLinkedComment() { - return linkedComment; + public boolean hasLinkedAnnotation() { + return linkedAnnotation; } - public void setLinkedComment(boolean linkedComment) { - this.linkedComment = linkedComment; + public void setLinkedAnnotation(boolean linkedAnnotation) { + this.linkedAnnotation = linkedAnnotation; } }