net.sf.jabref.gui.PreviewPanel.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.gui.PreviewPanel.java

Source

/*  Copyright (C) 2003-2016 JabRef contributors.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.print.PrinterException;
import java.io.IOException;
import java.io.StringReader;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.JobName;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;

import net.sf.jabref.BibDatabaseContext;
import net.sf.jabref.Globals;
import net.sf.jabref.JabRefExecutorService;
import net.sf.jabref.gui.desktop.JabRefDesktop;
import net.sf.jabref.gui.fieldeditors.PreviewPanelTransferHandler;
import net.sf.jabref.gui.keyboard.KeyBinding;
import net.sf.jabref.logic.exporter.ExportFormats;
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.logic.search.SearchQueryHighlightListener;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.FieldName;
import net.sf.jabref.model.event.FieldChangedEvent;
import net.sf.jabref.preferences.JabRefPreferences;

import com.google.common.eventbus.Subscribe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Displays an BibEntry using the given layout format.
 */
public class PreviewPanel extends JPanel implements SearchQueryHighlightListener, EntryContainer {

    private static final Log LOGGER = LogFactory.getLog(PreviewPanel.class);

    /**
     * The bibtex entry currently shown
     */
    private Optional<BibEntry> entry = Optional.empty();

    /**
     * If a database is set, the preview will attempt to resolve strings in the
     * previewed entry using that database.
     */
    private Optional<BibDatabaseContext> databaseContext = Optional.empty();

    private Optional<Layout> layout = Optional.empty();

    /**
     * must not be null, must always be set during constructor, but can change over time
     */
    private String layoutFile;

    private final Optional<BasePanel> basePanel;

    private JEditorPane previewPane;

    private final JScrollPane scrollPane;

    private final PrintAction printAction;

    private final CloseAction closeAction;

    private final CopyPreviewAction copyPreviewAction;

    private Optional<Pattern> highlightPattern = Optional.empty();

    /**
     * @param databaseContext
     *            (may be null) Optionally used to resolve strings and for resolving pdf directories for links.
     * @param entry
     *            (may be null) If given this entry is shown otherwise you have
     *            to call setEntry to make something visible.
     * @param panel
     *            (may be null) If not given no toolbar is shown on the right
     *            hand side.
     * @param layoutFile
     *            (must be given) Used for layout
     */
    public PreviewPanel(BibDatabaseContext databaseContext, BibEntry entry, BasePanel panel, String layoutFile) {
        this(panel, databaseContext, layoutFile);
        setEntry(entry);
    }

    /**
     *
     * @param panel
     *            (may be null) If not given no toolbar is shown on the right
     *            hand side.
     * @param databaseContext
     *            (may be null) Used for resolving pdf directories for links.
     * @param layoutFile
     *            (must be given) Used for layout
     */
    public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, String layoutFile) {
        super(new BorderLayout(), true);

        this.databaseContext = Optional.ofNullable(databaseContext);
        this.layoutFile = Objects.requireNonNull(layoutFile);
        updateLayout();

        this.closeAction = new CloseAction();
        this.printAction = new PrintAction();
        this.copyPreviewAction = new CopyPreviewAction();

        this.basePanel = Optional.ofNullable(panel);

        createPreviewPane();

        if (panel != null) {
            // dropped files handler only created for main window
            // not for Windows as like the search results window
            this.previewPane.setTransferHandler(
                    new PreviewPanelTransferHandler(panel.frame(), this, this.previewPane.getTransferHandler()));
        }

        // Set up scroll pane for preview pane
        scrollPane = new JScrollPane(previewPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setBorder(null);

        /*
         * If we have been given a panel and the preference option
         * previewPrintButton is set, show the tool bar
         */
        if (this.basePanel.isPresent()
                && JabRefPreferences.getInstance().getBoolean(JabRefPreferences.PREVIEW_PRINT_BUTTON)) {
            add(createToolBar(), BorderLayout.LINE_START);
        }

        add(scrollPane, BorderLayout.CENTER);

        this.createKeyBindings();
    }

    private void createKeyBindings() {
        ActionMap actionMap = this.getActionMap();
        InputMap inputMap = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

        final String close = "close";
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), close);
        actionMap.put(close, this.closeAction);

        final String copy = "copy";
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.COPY_PREVIEW), copy);
        actionMap.put(copy, this.copyPreviewAction);

        final String print = "print";
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.PRINT_ENTRY_PREVIEW), print);
        actionMap.put(print, this.printAction);
    }

    private JPopupMenu createPopupMenu() {
        JPopupMenu menu = new JPopupMenu();
        menu.add(this.printAction);
        menu.add(this.copyPreviewAction);
        this.basePanel.ifPresent(p -> menu.add(p.frame().getSwitchPreviewAction()));
        return menu;
    }

    private JToolBar createToolBar() {
        JToolBar toolBar = new OSXCompatibleToolbar(SwingConstants.VERTICAL);
        toolBar.setMargin(new Insets(0, 0, 0, 2));
        toolBar.setFloatable(false);

        // Add actions (and thus buttons)
        toolBar.add(this.closeAction);
        toolBar.addSeparator();
        toolBar.add(this.copyPreviewAction);
        toolBar.addSeparator();
        toolBar.add(this.printAction);

        Component[] comps = toolBar.getComponents();

        for (Component comp : comps) {
            ((JComponent) comp).setOpaque(false);
        }

        return toolBar;
    }

    private void createPreviewPane() {
        previewPane = new JEditorPane() {
            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return getPreferredSize();
            }

        };
        previewPane.setMargin(new Insets(3, 3, 3, 3));

        previewPane.setComponentPopupMenu(createPopupMenu());

        previewPane.setEditable(false);
        previewPane.setDragEnabled(true); // this has an effect only, if no custom transfer handler is registered. We keep the statement if the transfer handler is removed.
        previewPane.setContentType("text/html");
        previewPane.addHyperlinkListener(hyperlinkEvent -> {
            if ((hyperlinkEvent.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
                    && PreviewPanel.this.databaseContext.isPresent()) {
                try {
                    String address = hyperlinkEvent.getURL().toString();
                    JabRefDesktop.openExternalViewer(PreviewPanel.this.databaseContext.get(), address,
                            FieldName.URL);
                } catch (IOException e) {
                    LOGGER.warn("Could not open external viewer", e);
                }
            }
        });

    }

    public void setDatabaseContext(BibDatabaseContext databaseContext) {
        this.databaseContext = Optional.ofNullable(databaseContext);
    }

    public void updateLayout(String layoutFormat) {
        layoutFile = layoutFormat;
        updateLayout();
    }

    private void updateLayout() {
        StringReader sr = new StringReader(layoutFile.replace("__NEWLINE__", "\n"));
        try {
            layout = Optional
                    .of(new LayoutHelper(sr, Globals.prefs, Globals.journalAbbreviationLoader).getLayoutFromText());
        } catch (IOException e) {
            layout = Optional.empty();
            LOGGER.debug("no layout could be set", e);
        }
    }

    public void setLayout(Layout layout) {
        this.layout = Optional.of(layout);
    }

    public void setEntry(BibEntry newEntry) {

        entry.filter(e -> e != newEntry).ifPresent(e -> e.unregisterListener(this));
        entry = Optional.ofNullable(newEntry);
        entry.ifPresent(e -> e.registerListener(this));

        updateLayout();
        update();
    }

    /**
    * Listener for ChangedFieldEvent.
    */
    @Subscribe
    public void listen(FieldChangedEvent fieldChangedEvent) {
        update();
    }

    @Override
    public BibEntry getEntry() {
        return this.entry.orElse(null);
    }

    public void update() {
        StringBuilder sb = new StringBuilder();
        ExportFormats.entryNumber = 1; // Set entry number in case that is included in the preview layout.
        entry.ifPresent(entry -> layout.ifPresent(layout -> sb.append(layout.doLayout(entry,
                databaseContext.map(BibDatabaseContext::getDatabase).orElse(null), highlightPattern))));
        String newValue = sb.toString();

        previewPane.setText(newValue);
        previewPane.revalidate();

        // Scroll to top:
        scrollToTop();
    }

    private void scrollToTop() {
        SwingUtilities.invokeLater(() -> scrollPane.getVerticalScrollBar().setValue(0));
    }

    @Override
    public void highlightPattern(Optional<Pattern> newPattern) {
        this.highlightPattern = newPattern;
        update();
    }

    class PrintAction extends AbstractAction {
        public PrintAction() {
            super(Localization.lang("Print entry preview"), IconTheme.JabRefIcon.PRINTED.getIcon());

            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Print entry preview"));
            putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.PRINT_ENTRY_PREVIEW));
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {

            // Background this, as it takes a while.
            JabRefExecutorService.INSTANCE.execute(() -> {
                try {
                    PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
                    pras.add(new JobName(entry.map(BibEntry::getCiteKey).orElse("NO ENTRY"), null));
                    previewPane.print(null, null, true, null, pras, false);

                } catch (PrinterException e) {
                    // Inform the user... we don't know what to do.
                    JOptionPane.showMessageDialog(PreviewPanel.this,
                            Localization.lang("Could not print preview") + ".\n" + e.getMessage(),
                            Localization.lang("Print entry preview"), JOptionPane.ERROR_MESSAGE);
                    LOGGER.info("Could not print preview", e);
                }
            });
        }

    }

    class CloseAction extends AbstractAction {

        public CloseAction() {
            super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon());
            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close window"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            basePanel.ifPresent(BasePanel::hideBottomComponent);
        }

    }

    class CopyPreviewAction extends AbstractAction {

        public CopyPreviewAction() {
            super(Localization.lang("Copy preview"), IconTheme.JabRefIcon.COPY.getSmallIcon());
            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Copy preview"));
            putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.COPY_PREVIEW));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            previewPane.selectAll();
            previewPane.copy();
            previewPane.select(0, -1);
        }

    }

}