package org.jabref.gui; import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.jabref.Globals; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternUtil; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.IdBasedFetcher; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.importer.fetcher.DoiFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.EntryTypes; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BiblatexEntryTypes; import org.jabref.model.entry.BibtexEntryTypes; import org.jabref.model.entry.EntryType; import org.jabref.model.entry.IEEETranEntryTypes; import com.jgoodies.forms.builder.ButtonBarBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.swingx.VerticalLayout; /** * Dialog that prompts the user to choose a type for an entry. * Returns null if canceled. */ public class EntryTypeDialog extends JabRefDialog implements ActionListener { private static final Log LOGGER = LogFactory.getLog(EntryTypeDialog.class); private static final int COLUMN = 3; private final JabRefFrame frame; private final CancelAction cancelAction = new CancelAction(); private EntryType type; private SwingWorker<Optional<BibEntry>, Void> fetcherWorker = new FetcherWorker(); private JButton generateButton; private JTextField idTextField; private JComboBox<String> comboBox; public EntryTypeDialog(JabRefFrame frame) { // modal dialog super(frame, true, EntryTypeDialog.class); this.frame = frame; setTitle(Localization.lang("Select entry type")); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { cancelAction.actionPerformed(null); } }); getContentPane().setLayout(new BorderLayout()); getContentPane().add(createCancelButtonBarPanel(), BorderLayout.SOUTH); getContentPane().add(createEntryGroupsPanel(), BorderLayout.CENTER); pack(); setResizable(false); } private JPanel createEntryGroupsPanel() { JPanel panel = new JPanel(); panel.setLayout(new VerticalLayout()); if (frame.getCurrentBasePanel().getBibDatabaseContext().isBiblatexMode()) { panel.add(createEntryGroupPanel("biblatex", BiblatexEntryTypes.ALL)); List<EntryType> customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBLATEX); if (!customTypes.isEmpty()) { panel.add(createEntryGroupPanel(Localization.lang("Custom"), customTypes)); } } else { panel.add(createEntryGroupPanel("BibTeX", BibtexEntryTypes.ALL)); panel.add(createEntryGroupPanel("IEEETran", IEEETranEntryTypes.ALL)); List<EntryType> customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBTEX); if (!customTypes.isEmpty()) { panel.add(createEntryGroupPanel(Localization.lang("Custom"), customTypes)); } } panel.add(createIdFetcherPanel()); return panel; } private JPanel createCancelButtonBarPanel() { JButton cancel = new JButton(Localization.lang("Cancel")); cancel.addActionListener(this); // Make ESC close dialog, equivalent to clicking Cancel. cancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); cancel.getActionMap().put("close", cancelAction); JPanel buttons = new JPanel(); ButtonBarBuilder bb = new ButtonBarBuilder(buttons); bb.addGlue(); bb.addButton(cancel); bb.addGlue(); return buttons; } private JPanel createEntryGroupPanel(String groupTitle, Collection<? extends EntryType> entries) { JPanel panel = new JPanel(); GridBagLayout bagLayout = new GridBagLayout(); panel.setLayout(bagLayout); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(4, 4, 4, 4); // column count int col = 0; for (EntryType entryType : entries) { TypeButton entryButton = new TypeButton(entryType.getName(), entryType); entryButton.addActionListener(this); // Check if we should finish the row. col++; if (col == EntryTypeDialog.COLUMN) { col = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; } else { constraints.gridwidth = 1; } bagLayout.setConstraints(entryButton, constraints); panel.add(entryButton); } panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), groupTitle)); return panel; } private JPanel createIdFetcherPanel() { JLabel fetcherLabel = new JLabel(Localization.lang("ID type")); JLabel idLabel = new JLabel(Localization.lang("ID")); generateButton = new JButton(Localization.lang("Generate")); idTextField = new JTextField(""); comboBox = new JComboBox<>(); WebFetchers.getIdBasedFetchers(Globals.prefs.getImportFormatPreferences()).forEach(fetcher -> comboBox.addItem(fetcher.getName())); // set DOI as default comboBox.setSelectedItem(DoiFetcher.name); generateButton.addActionListener(action -> { fetcherWorker.execute(); }); comboBox.addActionListener(e -> { idTextField.requestFocus(); idTextField.selectAll(); }); idTextField.addActionListener(event -> fetcherWorker.execute()); JPanel jPanel = new JPanel(); GridBagConstraints constraints = new GridBagConstraints(); constraints.insets = new Insets(4, 4, 4, 4); GridBagLayout layout = new GridBagLayout(); jPanel.setLayout(layout); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 1; jPanel.add(fetcherLabel, constraints); constraints.gridx = 1; constraints.gridy = 0; constraints.weightx = 2; jPanel.add(comboBox, constraints); constraints.gridx = 0; constraints.gridy = 1; constraints.weightx = 1; jPanel.add(idLabel, constraints); constraints.gridx = 1; constraints.gridy = 1; constraints.weightx = 2; jPanel.add(idTextField, constraints); constraints.gridy = 2; constraints.gridx = 0; constraints.gridwidth = 2; constraints.fill = GridBagConstraints.NONE; jPanel.add(generateButton, constraints); jPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Localization.lang("ID-based_entry_generator"))); SwingUtilities.invokeLater(() -> idTextField.requestFocus()); return jPanel; } private void stopFetching() { if (fetcherWorker.getState() == SwingWorker.StateValue.STARTED) { fetcherWorker.cancel(true); } } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof TypeButton) { type = ((TypeButton) e.getSource()).getType(); } stopFetching(); dispose(); } public EntryType getChoice() { return type; } static class TypeButton extends JButton implements Comparable<TypeButton> { private final EntryType type; TypeButton(String label, EntryType type) { super(label); this.type = type; } @Override public int compareTo(TypeButton o) { return type.getName().compareTo(o.type.getName()); } public EntryType getType() { return type; } } class CancelAction extends AbstractAction { public CancelAction() { super("Cancel"); } @Override public void actionPerformed(ActionEvent e) { stopFetching(); dispose(); } } private class FetcherWorker extends SwingWorker<Optional<BibEntry>, Void> { private boolean fetcherException = false; private String fetcherExceptionMessage = ""; private IdBasedFetcher fetcher = null; private String searchID = ""; @Override protected Optional<BibEntry> doInBackground() throws Exception { Optional<BibEntry> bibEntry = Optional.empty(); SwingUtilities.invokeLater(() -> { generateButton.setEnabled(false); generateButton.setText(Localization.lang("Searching...")); }); searchID = idTextField.getText().trim(); fetcher = WebFetchers.getIdBasedFetchers(Globals.prefs.getImportFormatPreferences()).get(comboBox.getSelectedIndex()); if (!searchID.isEmpty()) { try { bibEntry = fetcher.performSearchById(searchID); } catch (FetcherException e) { LOGGER.error(e.getMessage(), e); fetcherException = true; fetcherExceptionMessage = e.getMessage(); } } return bibEntry; } @Override protected void done() { try { Optional<BibEntry> result = get(); if (result.isPresent()) { final BibEntry bibEntry = result.get(); // Regenerate CiteKey of imported BibEntry BibtexKeyPatternUtil.makeAndSetLabel(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern(), frame.getCurrentBasePanel().getDatabase(), bibEntry, Globals.prefs.getBibtexKeyPatternPreferences()); frame.getCurrentBasePanel().insertEntry(bibEntry); dispose(); } else if (searchID.trim().isEmpty()) { JOptionPane.showMessageDialog(frame, Localization.lang("The given search ID was empty."), Localization.lang("Empty search ID"), JOptionPane.WARNING_MESSAGE); } else if (!fetcherException) { JOptionPane.showMessageDialog(frame, Localization.lang("Fetcher_'%0'_did_not_find_an_entry_for_id_'%1'.", fetcher.getName(), searchID) + "\n" + fetcherExceptionMessage, Localization.lang("No files found."), JOptionPane.WARNING_MESSAGE); } else { JOptionPane.showMessageDialog(frame, Localization.lang("Error while fetching from %0", fetcher.getName()) + "." + "\n" + fetcherExceptionMessage, Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); } fetcherWorker = new FetcherWorker(); SwingUtilities.invokeLater(() -> { idTextField.requestFocus(); idTextField.selectAll(); generateButton.setText(Localization.lang("Generate")); generateButton.setEnabled(true); }); } catch (ExecutionException | InterruptedException e) { LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", searchID, fetcher.getName()), e); } } } }