package org.jabref.gui.externalfiles; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.JabRefDialog; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.worker.AbstractWorker; import org.jabref.logic.l10n.Localization; import org.jabref.logic.xmp.XMPUtil; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import com.jgoodies.forms.builder.ButtonBarBuilder; /** * * This action goes through all selected entries in the BasePanel, and attempts * to write the XMP data to the external pdf. */ public class WriteXMPAction extends AbstractWorker { private final BasePanel panel; private Collection<BibEntry> entries; private BibDatabase database; private OptionsDialog optDiag; private boolean goOn = true; private int skipped; private int entriesChanged; private int errors; public WriteXMPAction(BasePanel panel) { this.panel = panel; } @Override public void init() { database = panel.getDatabase(); // Get entries and check if it makes sense to perform this operation entries = panel.getSelectedEntries(); if (entries.isEmpty()) { entries = database.getEntries(); if (entries.isEmpty()) { JOptionPane.showMessageDialog(panel, Localization.lang("This operation requires one or more entries to be selected."), Localization.lang("Write XMP-metadata"), JOptionPane.ERROR_MESSAGE); goOn = false; return; } else { int response = JOptionPane.showConfirmDialog(panel, Localization.lang("Write XMP-metadata for all PDFs in current library?"), Localization.lang("Write XMP-metadata"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (response != JOptionPane.YES_OPTION) { goOn = false; return; } } } errors = entriesChanged = skipped = 0; if (optDiag == null) { optDiag = new OptionsDialog(panel.frame()); } optDiag.open(); panel.output(Localization.lang("Writing XMP-metadata...")); } @Override public void run() { if (!goOn) { return; } for (BibEntry entry : entries) { // Make a list of all PDFs linked from this entry: List<Path> files = entry.getFiles().stream() .filter(file -> file.getFileType().equalsIgnoreCase("pdf")) .map(file -> file.findIn(panel.getBibDatabaseContext(), Globals.prefs.getFileDirectoryPreferences())) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); SwingUtilities.invokeLater(() -> optDiag.getProgressArea() .append(entry.getCiteKeyOptional().orElse(Localization.lang("undefined")) + "\n")); if (files.isEmpty()) { skipped++; SwingUtilities.invokeLater(() -> optDiag.getProgressArea() .append(" " + Localization.lang("Skipped - No PDF linked") + ".\n")); } else { for (Path file : files) { if (Files.exists(file)) { try { XMPUtil.writeXMP(file.toFile(), entry, database, Globals.prefs.getXMPPreferences()); SwingUtilities.invokeLater( () -> optDiag.getProgressArea().append(" " + Localization.lang("OK") + ".\n")); entriesChanged++; } catch (Exception e) { SwingUtilities.invokeLater(() -> { optDiag.getProgressArea().append(" " + Localization.lang("Error while writing") + " '" + file.toString() + "':\n"); optDiag.getProgressArea().append(" " + e.getLocalizedMessage() + "\n"); }); errors++; } } else { skipped++; SwingUtilities.invokeLater(() -> { optDiag.getProgressArea() .append(" " + Localization.lang("Skipped - PDF does not exist") + ":\n"); optDiag.getProgressArea().append(" " + file.toString() + "\n"); }); } } } if (optDiag.isCanceled()) { SwingUtilities.invokeLater( () -> optDiag.getProgressArea().append("\n" + Localization.lang("Operation canceled.") + "\n")); break; } } SwingUtilities.invokeLater(() -> { optDiag.getProgressArea() .append("\n" + Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", String .valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); optDiag.done(); }); } @Override public void update() { if (!goOn) { return; } panel.output(Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); } class OptionsDialog extends JabRefDialog { private final JButton okButton = new JButton(Localization.lang("OK")); private final JButton cancelButton = new JButton(Localization.lang("Cancel")); private boolean canceled; private final JTextArea progressArea; public OptionsDialog(JFrame parent) { super(parent, Localization.lang("Writing XMP-metadata for selected entries..."), false, OptionsDialog.class); okButton.setEnabled(false); okButton.addActionListener(e -> dispose()); AbstractAction cancel = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { canceled = true; } }; cancelButton.addActionListener(cancel); InputMap im = cancelButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = cancelButton.getActionMap(); im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); am.put("close", cancel); progressArea = new JTextArea(15, 60); JScrollPane scrollPane = new JScrollPane(progressArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); Dimension d = progressArea.getPreferredSize(); d.height += scrollPane.getHorizontalScrollBar().getHeight() + 15; d.width += scrollPane.getVerticalScrollBar().getWidth() + 15; panel.setSize(d); progressArea.setBackground(Color.WHITE); progressArea.setEditable(false); progressArea.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); progressArea.setText(""); JPanel tmpPanel = new JPanel(); tmpPanel.setBorder(BorderFactory.createEmptyBorder(3, 2, 3, 2)); tmpPanel.add(scrollPane); // progressArea.setPreferredSize(new Dimension(300, 300)); ButtonBarBuilder bb = new ButtonBarBuilder(); bb.addGlue(); bb.addButton(okButton); bb.addRelatedGap(); bb.addButton(cancelButton); bb.addGlue(); JPanel bbPanel = bb.getPanel(); bbPanel.setBorder(BorderFactory.createEmptyBorder(0, 3, 3, 3)); getContentPane().add(tmpPanel, BorderLayout.CENTER); getContentPane().add(bbPanel, BorderLayout.SOUTH); pack(); this.setResizable(false); } public void done() { okButton.setEnabled(true); cancelButton.setEnabled(false); } public void open() { progressArea.setText(""); canceled = false; optDiag.setLocationRelativeTo(panel.frame()); okButton.setEnabled(false); cancelButton.setEnabled(true); okButton.requestFocus(); optDiag.setVisible(true); } public boolean isCanceled() { return canceled; } public JTextArea getProgressArea() { return progressArea; } } }