ca.sqlpower.architect.swingui.action.ExportHTMLPanel.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.architect.swingui.action.ExportHTMLPanel.java

Source

/*
 * Copyright (c) 2009, SQL Power Group Inc.
 *
 * This file is part of Power*Architect.
 *
 * Power*Architect 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 3 of the License, or
 * (at your option) any later version.
 *
 * Power*Architect 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, see <http://www.gnu.org/licenses/>.
 */
package ca.sqlpower.architect.swingui.action;

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.filechooser.FileFilter;

import org.apache.log4j.Logger;

import ca.sqlpower.architect.ArchitectSession;
import ca.sqlpower.architect.swingui.ArchitectFrame;
import ca.sqlpower.architect.swingui.ArchitectSwingSession;
import ca.sqlpower.architect.transformation.ReportTransformer;
import ca.sqlpower.architect.transformation.TransformerFactory;
import ca.sqlpower.architect.transformation.UnknowTemplateTypeException;
import ca.sqlpower.architect.transformation.XsltTransformation;
import ca.sqlpower.swingui.JDefaultButton;
import ca.sqlpower.swingui.SPSUtils;
import ca.sqlpower.util.BrowserUtil;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.ButtonBarFactory;
import com.jgoodies.forms.layout.FormLayout;

/**
 * A panel to select a transformation template (XSLT or Velocity) and the output file to be generated.
 * <p>
 * The panel has a start and close button so that it is self-contained.
 * showDialog() will display this panel in a non-modal JDialog.
 * <p>
 * The last 15 selected XSLT transformations will be remembered and made
 * available through a dropdown box.
 */
public class ExportHTMLPanel {

    private static final Logger logger = Logger.getLogger(ExportHTMLPanel.class);

    private static String builtinTransform = "/xsltStylesheets/architect2html.xslt";
    private static BuiltinOptionPanelFactory builtinOptions;

    private JRadioButton builtin;
    private JRadioButton external;
    private JComboBox templateFile;
    private JButton selectTemplate;
    private JButton selectOutput;
    private JButton startButton;
    private JButton closeButton;
    private JTextField outputFile;

    private JLabel statusBar;

    private final ArchitectSwingSession session;

    private JDialog dialog;

    private final JPanel panel;

    private final BuiltinOptionPanel builtinOptionPanel;

    private static final String PREF_KEY_BUILTIN = "htmlgen.builtin";
    private static final String PREF_KEY_LAST_XSLT = "htmlgen.lastxslt";
    private static final String PREF_KEY_XSLT_HISTORY = "htmlgen.xslt.recent";
    private static final String PREF_KEY_OUTPUT = "htmlgen.lastoutput";
    private static final int MAX_HISTORY_ENTRIES = 15;

    public static void setBuiltinTransform(String builtinTransform) {
        ExportHTMLPanel.builtinTransform = builtinTransform;
    }

    public static void setBuiltinOptionPanelFactory(BuiltinOptionPanelFactory builtinOptions) {
        ExportHTMLPanel.builtinOptions = builtinOptions;
    }

    public ExportHTMLPanel(ArchitectSwingSession architect) {

        session = architect;
        FormLayout layout = new FormLayout("10dlu, 3dlu, pref:grow, 3dlu, pref");
        DefaultFormBuilder builder;
        if (logger.isDebugEnabled()) {
            builder = new DefaultFormBuilder(layout, new FormDebugPanel());
        } else {
            builder = new DefaultFormBuilder(layout);
        }

        ButtonGroup group = new ButtonGroup();
        builtin = new JRadioButton(Messages.getString("XSLTSelectionPanel.labelBuiltIn"));
        external = new JRadioButton(Messages.getString("XSLTSelectionPanel.labelExternal"));
        group.add(builtin);
        group.add(external);

        // place Radio buttons
        builder.append(builtin, 5);
        builder.appendRelatedComponentsGapColumn();
        if (builtinOptions != null) {
            builtinOptionPanel = builtinOptions.createPanel(session);
            builder.append("");
            builder.append(builtinOptionPanel, 4);
        } else {
            builtinOptionPanel = null;
        }

        builder.appendUnrelatedComponentsGapRow();
        builder.nextLine();
        builder.nextLine();

        builder.append(external, 5);

        // Selection of XSLT file
        templateFile = new JComboBox();
        templateFile.setRenderer(new ComboTooltipRenderer());
        templateFile.setEditable(true);

        builder.append("");
        builder.append(templateFile);

        selectTemplate = new JButton("...");
        builder.append(selectTemplate);

        builder.appendUnrelatedComponentsGapRow();
        builder.nextLine();
        builder.nextLine();

        // Output selection
        builder.append(new JLabel(Messages.getString("XSLTSelectionPanel.labelOutput")), 5);
        builder.nextLine();

        outputFile = new JTextField(30);
        builder.append("", outputFile);

        selectOutput = new JButton("...");
        builder.append(selectOutput);

        builder.appendUnrelatedComponentsGapRow();
        builder.nextLine();
        builder.nextLine();

        // "Statusbar"
        statusBar = new JLabel(" ");
        builder.append(statusBar, 5);

        builder.appendUnrelatedComponentsGapRow();
        builder.nextLine();

        selectTemplate.addActionListener(componentStateHandler);
        selectOutput.addActionListener(componentStateHandler);
        builtin.addActionListener(componentStateHandler);
        external.addActionListener(componentStateHandler);
        builtin.setSelected(true);

        startButton = new JDefaultButton(Messages.getString("XSLTSelectionPanel.startOption"));
        startButton.addActionListener(componentStateHandler);

        closeButton = new JButton(Messages.getString("XSLTSelectionPanel.closeOption"));
        closeButton.addActionListener(componentStateHandler);

        builder.nextLine();
        JPanel bp = ButtonBarFactory.buildRightAlignedBar(startButton, closeButton);
        builder.append(bp, 5);

        builder.setDefaultDialogBorder();
        panel = builder.getPanel();

        restoreSettings();
    }

    /**
     * Displays this selection panel to the user.
     * The dialog is non-modal
     *
     */
    public void showDialog() {

        if (dialog == null) {
            ArchitectFrame frame = session.getArchitectFrame();

            dialog = new JDialog(frame, Messages.getString("XSLTSelectionPanel.dialogTitle"));
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            SPSUtils.makeJDialogCancellable(dialog, new AbstractAction() {

                public void actionPerformed(ActionEvent e) {
                    closeDialog();
                }
            });
            dialog.setContentPane(panel);
            dialog.getRootPane().setDefaultButton(startButton);
            dialog.pack();
            dialog.setLocationRelativeTo(frame);
        }
        dialog.setVisible(true);
    }

    /**
     * Return the filename the user selected. If the user
     * chose to use the built-in XSLT, this returns null
     *
     * @return the filename selected by the user, or null if the internal
     *  XSLT should be used.
     */
    public File getTemplateFile() {
        if (builtin.isSelected()) {
            return null;
        }
        Object o = templateFile.getSelectedItem();
        if (o instanceof File) {
            return (File) o;
        } else if (o instanceof String) {
            // might happen if the user entered the filename manually
            return new File((String) o);
        }
        return null;
    }

    /**
     * Returns the filename of the (user-selected) output file.
     *
     */
    public String getOutputFilename() {
        return outputFile.getText();
    }

    public File getOutputFile() {
        return new File(outputFile.getText());
    }

    private final ActionListener componentStateHandler = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == selectTemplate) {
                selectTemplate();
            } else if (e.getSource() == selectOutput) {
                selectOutput();
            } else if (e.getSource() == startButton) {
                transformFile();
            } else if (e.getSource() == closeButton) {
                closeDialog();
            } else if (e.getSource() == templateFile) {
                updateDropDownToolTip();
            } else if (e.getSource() == builtin) {
                templateFile.setEnabled(external.isSelected());
            } else if (e.getSource() == external) {
                templateFile.setEnabled(external.isSelected());
            }
        }
    };

    private void updateDropDownToolTip() {
        File f = this.getTemplateFile();
        if (f != null) {
            templateFile.setToolTipText(getFullName(f));
        }
    }

    private void setTemplateFile(File template) {
        ComboBoxFile cf = new ComboBoxFile(template);
        external.setSelected(true);
        templateFile.setEnabled(true);
        templateFile.addItem(cf);
        templateFile.setSelectedItem(cf);
        templateFile.setToolTipText(getFullName(template));
    }

    private void syncDropDown() {
        // if the user pasted the filename into the editable part
        // of the dropdown, this will not be part of the actual dropdown items
        // so I'm adding that here "manually"
        File current = getTemplateFile();

        if (current == null)
            return;
        boolean found = false;

        int numEntries = templateFile.getItemCount();
        for (int index = 0; index < numEntries; index++) {
            if (templateFile.getItemAt(index).equals(current)) {
                found = true;
                break;
            }
        }

        if (!found) {
            templateFile.addItem(new ComboBoxFile(current));
        }
    }

    private void saveSettings() {
        Preferences prefs = Preferences.userNodeForPackage(getClass());
        prefs.putBoolean(PREF_KEY_BUILTIN, builtin.isSelected());
        prefs.put(PREF_KEY_OUTPUT, outputFile.getText());

        // Add any pasted filename to the dropdown's model, so that it
        // stored correctly in the user preferences
        syncDropDown();

        File f = getTemplateFile();
        if (f != null) {
            prefs.put(PREF_KEY_LAST_XSLT, getFullName(f));
        } else {
            prefs.remove(PREF_KEY_LAST_XSLT);
        }

        int numEntries = (templateFile.getItemCount() > MAX_HISTORY_ENTRIES ? MAX_HISTORY_ENTRIES
                : templateFile.getItemCount());

        for (int i = 0; i < numEntries; i++) {
            Object o = templateFile.getItemAt(i);
            String key = PREF_KEY_XSLT_HISTORY + "." + i;
            if (o instanceof File) {
                prefs.put(key, getFullName((File) o));
            } else if (o instanceof String) {
                prefs.put(key, (String) o);
            } else {
                prefs.remove(key);
            }
        }
    }

    private void restoreSettings() {
        Preferences prefs = Preferences.userNodeForPackage(getClass());
        final boolean useBuiltin = prefs.getBoolean(PREF_KEY_BUILTIN, true);
        builtin.setSelected(useBuiltin);
        external.setSelected(!useBuiltin);
        templateFile.setEnabled(!useBuiltin);

        // I'm actively setting the focus, because by default the focus is
        // set to the "Internal" radio button. I think that initial focus is
        // a bit confusing when the "external" one is selected.

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                if (useBuiltin) {
                    builtin.requestFocusInWindow();
                } else {
                    external.requestFocusInWindow();
                }
            }
        });

        // Restore the history
        for (int i = 0; i < 15; i++) {
            String fname = prefs.get(PREF_KEY_XSLT_HISTORY + "." + i, null);
            if (fname == null)
                break;
            ComboBoxFile f = new ComboBoxFile(fname);
            templateFile.addItem(f);
        }

        // The last used XSLT
        String file = prefs.get(PREF_KEY_LAST_XSLT, null);
        if (file != null) {
            ComboBoxFile f = new ComboBoxFile(file);
            templateFile.setSelectedItem(f);
        }
        outputFile.setText(prefs.get(PREF_KEY_OUTPUT, ""));
    }

    public static String getFullName(File fo) {
        if (fo == null)
            return null;
        try {
            // The canonical path is a bit more "user-friendly" especially
            // on Windows. But as it can throw an IOException (why?) I need
            // to wrap it here
            return fo.getCanonicalPath();
        } catch (IOException io) {
            return fo.getAbsolutePath();
        }
    }

    private void selectTemplate() {
        JFileChooser chooser = new JFileChooser(session.getProjectLoader().getFile());
        File tmpl = getTemplateFile();
        boolean xslt = true;
        String fname = "";
        if (tmpl != null) {
            fname = tmpl.getAbsolutePath().toLowerCase();
            xslt = fname.endsWith("xslt") || fname.endsWith("xsl");
        }

        chooser.addChoosableFileFilter(SPSUtils.XSLT_FILE_FILTER);
        chooser.addChoosableFileFilter(SPSUtils.VELOCITY_FILE_FILTER);
        if (xslt) {
            chooser.setFileFilter(SPSUtils.XSLT_FILE_FILTER);
        } else if (fname.endsWith("vm")) {
            chooser.setFileFilter(SPSUtils.VELOCITY_FILE_FILTER);
        } else {
            FileFilter[] filters = chooser.getChoosableFileFilters();
            chooser.setFileFilter(filters[0]);
        }
        chooser.setDialogTitle(Messages.getString("XSLTSelectionPanel.selectXsltTitle"));
        int response = chooser.showOpenDialog(dialog);
        if (response != JFileChooser.APPROVE_OPTION) {
            return;
        }

        File file = chooser.getSelectedFile();
        dialog.requestFocus();
        setTemplateFile(file);
    }

    private void closeDialog() {
        if (dialog != null) {
            saveSettings();
            dialog.setVisible(false);
            dialog.dispose();
        }
    }

    private void selectOutput() {
        JFileChooser chooser = new JFileChooser(session.getProjectLoader().getFile());
        chooser.addChoosableFileFilter(SPSUtils.HTML_FILE_FILTER);
        chooser.setDialogTitle(Messages.getString("XSLTSelectionPanel.saveAsTitle"));

        int response = chooser.showSaveDialog(session.getArchitectFrame());

        if (response != JFileChooser.APPROVE_OPTION) {
            return;
        }

        File file = chooser.getSelectedFile();
        // Add the .html extension only if the HTML file filter was selected
        if (chooser.getFileFilter() == SPSUtils.HTML_FILE_FILTER && !file.getPath().endsWith(".html")) { //$NON-NLS-1$

            file = new File(file.getPath() + ".html"); //$NON-NLS-1$
        }

        try {
            outputFile.setText(file.getCanonicalPath());
        } catch (IOException io) {
            outputFile.setText(file.getAbsolutePath());
        }
        dialog.requestFocus();
    }

    /**
     * Transforms the current playpen according to the selection that the user made.
     * <br/>
     * An xml OutputStream(using a {@link PipedOutputStream}) is generated, based on the
     * current playPen content and is read by a {@link PipedInputStream} which is used as the xml source.
     * <br/>
     * The stylesheet and the xml source are passed as parameters to the
     * {@link XsltTransformation} methods to generate an HTML report off the content
     * to a location specified by the user.
    */
    protected void transformFile() {

        File file = new File(outputFile.getText());

        if (file.exists()) {
            int response = JOptionPane.showConfirmDialog(session.getArchitectFrame(),
                    Messages.getString("XSLTSelectionPanel.fileAlreadyExists", file.getPath()), //$NON-NLS-1$
                    Messages.getString("XSLTSelectionPanel.fileAlreadyExistsDialogTitle"),
                    JOptionPane.YES_NO_OPTION); //$NON-NLS-1$
            if (response == JOptionPane.NO_OPTION) {
                return;
            }
        }

        setStatusBarText(Messages.getString("XSLTSelectionPanel.msgGenerating"));

        Thread t = new Thread(new Runnable() {
            public void run() {
                _transformFile();
            }
        });
        t.setName("HTML Generation Thread");
        t.setDaemon(true);
        t.start();
    }

    protected void _transformFile() {
        File file = new File(getOutputFilename());

        final ReportTransformer transformer;
        try {
            transformer = TransformerFactory.getTransformer(getTemplateFile());
        } catch (UnknowTemplateTypeException e) {
            SPSUtils.showExceptionDialogNoReport(panel, "Error", e);
            setStatusBarText("");
            return;
        }

        if (builtinOptionPanel != null) {
            builtinOptionPanel.applyChanges(transformer, getOutputFile(), session);
        }

        try {
            File xslt = getTemplateFile();
            if (xslt == null) {
                transformer.transform(builtinTransform, getOutputFile(), session);
            } else {
                transformer.transform(xslt, getOutputFile(), session);
            }

            //Opens up the html file in the default browser
            String fname = getOutputFilename().toLowerCase();
            if (fname.endsWith("html") || fname.endsWith("htm")) {
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        statusBar.setText(Messages.getString("XSLTSelectionPanel.msgStartingBrowser"));
                    }
                });
                BrowserUtil.launch(file.toURI().toString());
            }
        } catch (Exception e1) {
            SPSUtils.showExceptionDialogNoReport(session.getArchitectFrame(), "Transformation error", e1);
        }

        setStatusBarText("");
    }

    protected void setStatusBarText(final String text) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                statusBar.setText(text);
            }
        });
    }

    /**
     * A class used for the items in the combobox.
     * The only difference to the File class is, that the toString()
     * method returns only the file name, not the full path.
     * Otherwise the dropdown would be too wide.
     *
     * A better solution would be a dropdown that displays only the filename
     * for the selected file, but shows the full names when it is opened.
     * But unfortunately Swing cannot handle a dropdown where the popup is wider
     * than the actual component.
     */
    private static class ComboBoxFile extends File {

        public ComboBoxFile(File f) {
            super(f.getAbsolutePath());
        }

        public ComboBoxFile(String pathname) {
            super(pathname);
        }

        public String toString() {
            return getName();
        }
    }

    private static class ComboTooltipRenderer extends DefaultListCellRenderer {

        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {

            JComponent comp = (JComponent) super.getListCellRendererComponent(list, value, index, isSelected,
                    cellHasFocus);

            if (value instanceof File) {
                comp.setToolTipText(ExportHTMLPanel.getFullName((File) value));
            } else {
                comp.setToolTipText(null);
            }
            return comp;
        }
    }

    public static abstract class BuiltinOptionPanel extends JPanel {
        public abstract void applyChanges(ReportTransformer transformer, File outputFile, ArchitectSession session);
    }

    public static interface BuiltinOptionPanelFactory {
        public BuiltinOptionPanel createPanel(ArchitectSession session);
    }
}