net.sf.jabref.openoffice.StyleSelectDialog.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.openoffice.StyleSelectDialog.java

Source

/*  Copyright (C) 2003-2015 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.openoffice;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.TableColumnModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.jabref.gui.keyboard.KeyBinding;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.gui.actions.BrowseAction;
import net.sf.jabref.Globals;
import net.sf.jabref.model.entry.IdGenerator;
import net.sf.jabref.JabRef;
import net.sf.jabref.JabRefPreferences;
import net.sf.jabref.gui.JabRefFrame;
import net.sf.jabref.MetaData;
import net.sf.jabref.gui.PreviewPanel;
import net.sf.jabref.external.ExternalFileType;
import net.sf.jabref.external.ExternalFileTypes;
import net.sf.jabref.external.UnknownExternalFileType;
import net.sf.jabref.logic.l10n.Localization;
import net.sf.jabref.gui.desktop.JabRefDesktop;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.DefaultEventSelectionModel;
import ca.odell.glazedlists.swing.DefaultEventTableModel;
import ca.odell.glazedlists.swing.GlazedListsSwing;

import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;

/**
 * This class produces a dialog box for choosing a style file.
 */
class StyleSelectDialog {

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

    private static final String STYLE_FILE_EXTENSION = ".jstyle";
    private final JabRefFrame frame;
    private EventList<OOBibStyle> styles;
    private JDialog diag;
    private JTable table;
    private final JSplitPane contentPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
    private DefaultEventTableModel<OOBibStyle> tableModel;
    private DefaultEventSelectionModel<OOBibStyle> selectionModel;
    private final JPopupMenu popup = new JPopupMenu();
    private final JMenuItem edit = new JMenuItem(Localization.lang("Edit"));
    private final JRadioButton useDefaultAuthoryear = new JRadioButton(
            Localization.lang("Default style (author-year citations)"));
    private final JRadioButton useDefaultNumerical = new JRadioButton(
            Localization.lang("Default style (numerical citations)"));
    private final JRadioButton chooseDirectly = new JRadioButton(
            Localization.lang("Choose style file directly") + ":");
    private final JRadioButton setDirectory = new JRadioButton(Localization.lang("Choose from a directory") + ":");
    private final JTextField directFile = new JTextField();
    private final JTextField styleDir = new JTextField();
    private final JButton browseDirectFile = new JButton(Localization.lang("Browse"));
    private final JButton browseStyleDir = new JButton(Localization.lang("Browse"));
    private final JButton showDefaultAuthoryearStyle = new JButton(Localization.lang("View"));
    private final JButton showDefaultNumericalStyle = new JButton(Localization.lang("View"));

    private PreviewPanel preview;

    private final Rectangle toRect = new Rectangle(0, 0, 1, 1);
    private final JButton ok = new JButton(Localization.lang("OK"));
    private final JButton cancel = new JButton(Localization.lang("Cancel"));
    private final BibEntry prevEntry = new BibEntry(IdGenerator.next());

    private boolean okPressed;
    private String initSelection;

    public StyleSelectDialog(JabRefFrame frame, String initSelection) {

        this.frame = frame;
        setupPrevEntry();
        init(initSelection);
    }

    private void init(String inSelection) {
        this.initSelection = inSelection;

        ButtonGroup bg = new ButtonGroup();
        bg.add(useDefaultAuthoryear);
        bg.add(useDefaultNumerical);
        bg.add(chooseDirectly);
        bg.add(setDirectory);
        if (Globals.prefs.getBoolean(JabRefPreferences.OO_USE_DEFAULT_AUTHORYEAR_STYLE)) {
            useDefaultAuthoryear.setSelected(true);
        } else if (Globals.prefs.getBoolean(JabRefPreferences.OO_USE_DEFAULT_NUMERICAL_STYLE)) {
            useDefaultNumerical.setSelected(true);
        } else {
            if (Globals.prefs.getBoolean(JabRefPreferences.OO_CHOOSE_STYLE_DIRECTLY)) {
                chooseDirectly.setSelected(true);
            } else {
                setDirectory.setSelected(true);
            }
        }

        directFile.setText(Globals.prefs.get(JabRefPreferences.OO_DIRECT_FILE));
        styleDir.setText(Globals.prefs.get(JabRefPreferences.OO_STYLE_DIRECTORY));
        directFile.setEditable(false);
        styleDir.setEditable(false);

        popup.add(edit);

        BrowseAction dfBrowse = BrowseAction.buildForFile(directFile, directFile);
        browseDirectFile.addActionListener(dfBrowse);

        BrowseAction sdBrowse = BrowseAction.buildForDir(styleDir, setDirectory);
        browseStyleDir.addActionListener(sdBrowse);

        showDefaultAuthoryearStyle.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                displayDefaultStyle(true);
            }
        });
        showDefaultNumericalStyle.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                displayDefaultStyle(false);
            }
        });
        // Add action listener to "Edit" menu item, which is supposed to open the style file in an external editor:
        edit.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                int i = table.getSelectedRow();
                if (i == -1) {
                    return;
                }
                ExternalFileType type = ExternalFileTypes.getInstance().getExternalFileTypeByExt("jstyle");
                String link = tableModel.getElementAt(i).getFile().getPath();
                try {
                    if (type == null) {
                        JabRefDesktop.openExternalFileUnknown(frame, new BibEntry(), new MetaData(), link,
                                new UnknownExternalFileType("jstyle"));
                    } else {
                        JabRefDesktop.openExternalFileAnyFormat(new MetaData(), link, type);
                    }
                } catch (IOException e) {
                    LOGGER.warn("Problem open style file editor", e);
                }
            }
        });

        diag = new JDialog(frame, Localization.lang("Styles"), true);

        styles = new BasicEventList<>();
        EventList<OOBibStyle> sortedStyles = new SortedList<>(styles);

        // Create a preview panel for previewing styles:
        preview = new PreviewPanel(null, new MetaData(), "");
        // Use the test entry from the Preview settings tab in Preferences:
        preview.setEntry(prevEntry);

        tableModel = (DefaultEventTableModel<OOBibStyle>) GlazedListsSwing
                .eventTableModelWithThreadProxyList(sortedStyles, new StyleTableFormat());
        table = new JTable(tableModel);
        TableColumnModel cm = table.getColumnModel();
        cm.getColumn(0).setPreferredWidth(100);
        cm.getColumn(1).setPreferredWidth(200);
        cm.getColumn(2).setPreferredWidth(80);
        selectionModel = (DefaultEventSelectionModel<OOBibStyle>) GlazedListsSwing
                .eventSelectionModelWithThreadProxyList(sortedStyles);
        table.setSelectionModel(selectionModel);
        table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent mouseEvent) {
                if (mouseEvent.isPopupTrigger()) {
                    tablePopup(mouseEvent);
                }
            }

            @Override
            public void mouseReleased(MouseEvent mouseEvent) {
                if (mouseEvent.isPopupTrigger()) {
                    tablePopup(mouseEvent);
                }
            }
        });

        selectionModel.getSelected().addListEventListener(new EntrySelectionListener());

        styleDir.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent documentEvent) {
                readStyles();
                setDirectory.setSelected(true);
            }

            @Override
            public void removeUpdate(DocumentEvent documentEvent) {
                readStyles();
                setDirectory.setSelected(true);
            }

            @Override
            public void changedUpdate(DocumentEvent documentEvent) {
                readStyles();
                setDirectory.setSelected(true);
            }
        });
        directFile.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent documentEvent) {
                chooseDirectly.setSelected(true);
            }

            @Override
            public void removeUpdate(DocumentEvent documentEvent) {
                chooseDirectly.setSelected(true);
            }

            @Override
            public void changedUpdate(DocumentEvent documentEvent) {
                chooseDirectly.setSelected(true);
            }
        });

        contentPane.setTopComponent(new JScrollPane(table));
        contentPane.setBottomComponent(preview);

        readStyles();

        DefaultFormBuilder b = new DefaultFormBuilder(
                new FormLayout("fill:pref,4dlu,fill:150dlu,4dlu,fill:pref", ""));
        b.append(useDefaultAuthoryear, 3);
        b.append(showDefaultAuthoryearStyle);
        b.nextLine();
        b.append(useDefaultNumerical, 3);
        b.append(showDefaultNumericalStyle);
        b.nextLine();
        b.append(chooseDirectly);
        b.append(directFile);
        b.append(browseDirectFile);
        b.nextLine();
        b.append(setDirectory);
        b.append(styleDir);
        b.append(browseStyleDir);
        b.nextLine();
        DefaultFormBuilder b2 = new DefaultFormBuilder(
                new FormLayout("fill:1dlu:grow", "fill:pref, fill:pref, fill:270dlu:grow"));

        b2.nextLine();
        b2.append(new JLabel("<html>"
                + Localization.lang("This is the list of available styles. Select the one you want to use.")
                + "</html>"));
        b2.nextLine();
        b2.append(contentPane);
        b.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        b2.getPanel().setBorder(BorderFactory.createEmptyBorder(15, 5, 5, 5));
        diag.add(b.getPanel(), BorderLayout.NORTH);
        diag.add(b2.getPanel(), BorderLayout.CENTER);

        AbstractAction okListener = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent event) {
                if (!useDefaultAuthoryear.isSelected() && !useDefaultNumerical.isSelected()) {
                    if (chooseDirectly.isSelected()) {
                        File f = new File(directFile.getText());
                        if (!f.exists()) {
                            JOptionPane.showMessageDialog(diag,
                                    Localization.lang(
                                            "You must select either a valid style file, or use a default style."),
                                    Localization.lang("Style selection"), JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                    } else {
                        if ((table.getRowCount() == 0) || (table.getSelectedRowCount() == 0)) {
                            JOptionPane.showMessageDialog(diag,
                                    Localization.lang(
                                            "You must select either a valid style file, or use a default style."),
                                    Localization.lang("Style selection"), JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                    }
                }
                okPressed = true;
                storeSettings();
                diag.dispose();
            }
        };
        ok.addActionListener(okListener);

        Action cancelListener = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent event) {
                diag.dispose();
            }
        };
        cancel.addActionListener(cancelListener);

        ButtonBarBuilder bb = new ButtonBarBuilder();
        bb.addGlue();
        bb.addButton(ok);
        bb.addButton(cancel);
        bb.addGlue();
        bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        diag.add(bb.getPanel(), BorderLayout.SOUTH);

        ActionMap am = bb.getPanel().getActionMap();
        InputMap im = bb.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close");
        am.put("close", cancelListener);
        im.put(KeyStroke.getKeyStroke("ENTER"), "enterOk");
        am.put("enterOk", okListener);

        diag.pack();
        diag.setLocationRelativeTo(frame);
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                contentPane.setDividerLocation(contentPane.getSize().height - 150);
            }
        });

    }

    public void setVisible(boolean visible) {
        okPressed = false;
        diag.setVisible(visible);
    }

    /**
     * Read all style files or directories of style files indicated by the current
     * settings, and add the styles to the list of styles.
     */
    private void readStyles() {
        table.clearSelection();

        styles.getReadWriteLock().writeLock().lock();
        styles.clear();
        if (!styleDir.getText().isEmpty()) {
            addStyles(styleDir.getText(), true);
        }
        styles.getReadWriteLock().writeLock().unlock();

        selectLastUsed();
    }

    /**
     * This method scans the current list of styles, and looks for the styles
     * that was last used. If found, that style is selected. If not found,
     * the first style is selected provided there are >0 styles.
     */
    private void selectLastUsed() {
        // Set the initial selection of the table:
        if (initSelection == null) {
            if (table.getRowCount() > 0) {
                table.setRowSelectionInterval(0, 0);
            }
        } else {
            boolean found = false;
            for (int i = 0; i < table.getRowCount(); i++) {
                if (tableModel.getElementAt(i).getFile().getPath().equals(initSelection)) {
                    table.setRowSelectionInterval(i, i);
                    found = true;
                    break;
                }
            }
            if (!found && (table.getRowCount() > 0)) {
                table.setRowSelectionInterval(0, 0);
            }
        }
    }

    /**
     * If the string dir indicates a file, parse it and add it to the list of styles if
     * successful. If the string dir indicates a directory, parse all files looking like
     * style files, and add them. The parameter recurse determines whether we should
     * recurse into subdirectories.
     * @param dir the directory or file to handle.
     * @param recurse true indicates that we should recurse into subdirectories.
     */
    private void addStyles(String dir, boolean recurse) {
        File dirF = new File(dir);
        if (dirF.isDirectory()) {
            File[] fileArray = dirF.listFiles();
            List<File> files;
            if (fileArray == null) {
                files = Collections.emptyList();
            } else {
                files = Arrays.asList(fileArray);
            }
            for (File file : files) {
                // If the file looks like a style file, parse it:
                if (!file.isDirectory() && (file.getName().endsWith(StyleSelectDialog.STYLE_FILE_EXTENSION))) {
                    addSingleFile(file, Globals.prefs.getDefaultEncoding());
                } else if (file.isDirectory() && recurse) {
                    // If the file is a directory, and we should recurse, do:
                    addStyles(file.getPath(), recurse);
                }
            }
        } else {
            // The file wasn't a directory, so we simply parse it:
            addSingleFile(dirF, Globals.prefs.getDefaultEncoding());
        }
    }

    /**
     * Parse a single file, and add it to the list of styles if parse was successful.
     * @param file the file to parse.
     */
    private void addSingleFile(File file, Charset encoding) {
        try {
            OOBibStyle style = new OOBibStyle(file, Globals.journalAbbreviationLoader.getRepository(), encoding);
            // Check if the parse was successful before adding it:
            if (style.isValid() && !styles.contains(style)) {
                styles.add(style);
            }
        } catch (IOException e) {
            LOGGER.warn("Unable to read style file: '" + file.getPath() + "'", e);
        }
    }

    private void storeSettings() {
        OOBibStyle selected = getSelectedStyle();
        Globals.prefs.putBoolean(JabRefPreferences.OO_USE_DEFAULT_AUTHORYEAR_STYLE,
                useDefaultAuthoryear.isSelected());
        Globals.prefs.putBoolean(JabRefPreferences.OO_USE_DEFAULT_NUMERICAL_STYLE,
                useDefaultNumerical.isSelected());
        Globals.prefs.putBoolean(JabRefPreferences.OO_CHOOSE_STYLE_DIRECTLY, chooseDirectly.isSelected());
        Globals.prefs.put(JabRefPreferences.OO_DIRECT_FILE, directFile.getText());
        Globals.prefs.put(JabRefPreferences.OO_STYLE_DIRECTORY, styleDir.getText());
        if (chooseDirectly.isSelected()) {
            Globals.prefs.put(JabRefPreferences.OO_BIBLIOGRAPHY_STYLE_FILE, directFile.getText());
        } else if (setDirectory.isSelected() && (selected != null)) {
            Globals.prefs.put(JabRefPreferences.OO_BIBLIOGRAPHY_STYLE_FILE, selected.getFile().getPath());
        }

    }

    /**
     * Get the currently selected style.
     * @return the selected style, or null if no style is selected.
     */
    private OOBibStyle getSelectedStyle() {
        if (!selectionModel.getSelected().isEmpty()) {
            return selectionModel.getSelected().get(0);
        }
        return null;
    }

    private void setupPrevEntry() {
        prevEntry.setField("author", "Smith, Bill and Jones, Bob and Williams, Jeff");
        prevEntry.setField("editor", "Taylor, Phil");
        prevEntry.setField("title", "Title of the test entry for reference styles");
        prevEntry.setField("volume", "34");
        prevEntry.setField("year", "2008");
        prevEntry.setField("journal", "BibTeX journal");
        prevEntry.setField("publisher", "JabRef publishing");
        prevEntry.setField("address", "Trondheim");
        prevEntry.setField("www", "https://github.com/JabRef");
    }

    static class StyleTableFormat implements TableFormat<OOBibStyle> {

        @Override
        public int getColumnCount() {
            return 3;
        }

        @Override
        public String getColumnName(int i) {
            switch (i) {
            case 0:
                return Localization.lang("Name");
            case 1:
                return Localization.lang("Journals");
            case 2:
                return Localization.lang("File");
            default:
                return "";
            }
        }

        @Override
        public Object getColumnValue(OOBibStyle style, int i) {
            switch (i) {
            case 0:
                return style.getName();
            case 1:
                return String.join(", ", style.getJournals());
            case 2:
                return style.getFile().getName();
            default:
                return "";
            }
        }
    }

    public boolean isOkPressed() {
        return okPressed;
    }

    private void tablePopup(MouseEvent e) {
        popup.show(e.getComponent(), e.getX(), e.getY());
    }

    private void displayDefaultStyle(boolean authoryear) {
        try {
            // Read the contents of the default style file:
            URL defPath = authoryear ? JabRef.class.getResource(OpenOfficePanel.DEFAULT_AUTHORYEAR_STYLE_PATH)
                    : JabRef.class.getResource(OpenOfficePanel.DEFAULT_NUMERICAL_STYLE_PATH);
            BufferedReader r = new BufferedReader(new InputStreamReader(defPath.openStream()));
            String line;
            StringBuilder sb = new StringBuilder();
            while ((line = r.readLine()) != null) {
                sb.append(line);
                sb.append('\n');
            }

            // Make a dialog box to display the contents:
            final JDialog dd = new JDialog(diag, Localization.lang("Default style"), true);
            JLabel header = new JLabel(
                    "<html>" + Localization.lang("The panel below shows the definition of the default style.")
                    //+"<br>"
                            + Localization.lang(
                                    "If you want to use it as a template for a new style, you can copy the contents into a new .jstyle file")
                            + "</html>");

            header.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            dd.getContentPane().add(header, BorderLayout.NORTH);
            JTextArea ta = new JTextArea(sb.toString());
            ta.setEditable(false);
            JScrollPane sp = new JScrollPane(ta);
            sp.setPreferredSize(new Dimension(700, 500));
            dd.getContentPane().add(sp, BorderLayout.CENTER);
            JButton okButton = new JButton(Localization.lang("OK"));
            ButtonBarBuilder bb = new ButtonBarBuilder();
            bb.addGlue();
            bb.addButton(okButton);
            bb.addGlue();
            bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            dd.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);
            okButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent actionEvent) {
                    dd.dispose();
                }
            });
            dd.pack();
            dd.setLocationRelativeTo(diag);
            dd.setVisible(true);
        } catch (IOException ex) {
            LOGGER.warn("Problem showing default style", ex);
        }
    }

    /**
     * The listener for the Glazed list monitoring the current selection.
     * When selection changes, we need to update the preview panel.
     */
    private class EntrySelectionListener implements ListEventListener<OOBibStyle> {

        @Override
        public void listChanged(ListEvent<OOBibStyle> listEvent) {
            if (listEvent.getSourceList().size() == 1) {
                OOBibStyle style = listEvent.getSourceList().get(0);
                initSelection = style.getFile().getPath();
                preview.setLayout(style.getReferenceFormat("default"));
                // Update the preview's entry:
                contentPane.setDividerLocation(contentPane.getSize().height - 150);
                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        preview.update();
                        preview.scrollRectToVisible(toRect);
                    }
                });
            }
        }
    }

}