org.webcat.plugintester.ui.MainFrameBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.webcat.plugintester.ui.MainFrameBuilder.java

Source

/*==========================================================================*\
 |  $Id: MainFrameBuilder.java,v 1.2 2011/03/18 11:31:32 aallowat Exp $
 |*-------------------------------------------------------------------------*|
 |  Copyright (C) 2006-2009 Virginia Tech
 |
 |  This file is part of Web-CAT.
 |
 |  Web-CAT is free software; you can redistribute it and/or modify
 |  it under the terms of the GNU Affero General Public License as published
 |  by the Free Software Foundation; either version 3 of the License, or
 |  (at your option) any later version.
 |
 |  Web-CAT 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 Affero General Public License
 |  along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/

package org.webcat.plugintester.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.Timer;
import javax.swing.TransferHandler;
import javax.swing.WindowConstants;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.plist.PropertyListConfiguration;
import org.webcat.plugintester.AppConstants;
import org.webcat.plugintester.PluginRunner;
import org.webcat.plugintester.util.PluginConfiguration;
import org.webcat.plugintester.util.WebCATConfiguration;

//-------------------------------------------------------------------------
/**
 * Manages the main frame window for the application.
 *
 * @author Tony Allevato
 * @version $Id: MainFrameBuilder.java,v 1.2 2011/03/18 11:31:32 aallowat Exp $
 */
public class MainFrameBuilder {
    //~ Constructors ..........................................................

    // ----------------------------------------------------------
    /**
     * Initializes a new instances of the MainFrameBuilder class.
     *
     * @param wcConfig the object that represents the Web-CAT application
     *     configuration
     * @param settings the Properties object that contains the settings that
     *     are specified in the user interface
     */
    public MainFrameBuilder(WebCATConfiguration wcConfig, Properties settings) {
        webcatConfig = wcConfig;
        currentSettings = settings;

        frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setTitle("Web-CAT Plug-in Tester");

        constructFrame(frame);
        initializeFields();

        frame.setLocationRelativeTo(null);
    }

    //~ Methods ...............................................................

    // ----------------------------------------------------------
    /**
     * Opens the main frame window.
     */
    public void openFrame() {
        frame.setVisible(true);
    }

    // ----------------------------------------------------------
    /**
     * Creates and lays out the Swing components for the window.
     *
     * @param frame the JFrame instance that will contain the components
     */
    private void constructFrame(JFrame frame) {
        GridBagConstraints gridBagConstraints;

        fileChooser = new JFileChooser();
        bottomPanel = new JPanel();
        runButton = new JButton();
        mainPanel = new JPanel();
        JLabel webCatHomeLabel = new JLabel();
        webCatHomeField = new JTextField();
        webCatHomeBrowseButton = new JButton();
        JSeparator jSeparator0 = new JSeparator();
        JLabel submissionLabel = new JLabel();
        submissionField = new JTextField();
        submissionBrowseButton = new JButton();
        JSeparator jSeparator1 = new JSeparator();
        JLabel pluginsLabel = new JLabel();
        JPanel pluginsPanel = new JPanel();
        JScrollPane pluginsScrollPane = new JScrollPane();
        pluginsTable = new JTable();
        pluginAddButton = new JButton();
        pluginRemoveButton = new JButton();
        JSeparator jSeparator2 = new JSeparator();
        JTabbedPane tabPane = new JTabbedPane();
        propertiesPanel = new JPanel();
        JScrollPane propertiesScrollPane = new JScrollPane();
        propertiesEditor = new JEditorPane();
        documentationPanel = new JPanel();
        JScrollPane documentationScrollPane = new JScrollPane();
        documentationEditor = new JEditorPane();

        // File chooser
        fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

        // Bottom panel
        bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 4, 4));

        // Run button
        runButton.setText("Run Plug-ins");
        runButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                runButtonActionPerformed(evt);
            }
        });
        bottomPanel.add(runButton);

        frame.getContentPane().add(bottomPanel, BorderLayout.PAGE_END);

        // Main panel
        mainPanel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
        mainPanel.setLayout(new GridBagLayout());

        // Web-CAT Home field
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
        gridBagConstraints.anchor = GridBagConstraints.LINE_START;
        webCatHomeLabel.setText("Web-CAT Home:");
        mainPanel.add(webCatHomeLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        webCatHomeField.setTransferHandler(new WebCATHomeTransferHandler());
        mainPanel.add(webCatHomeField, gridBagConstraints);

        // Web-CAT Home browse button
        webCatHomeBrowseButton.setText("Browse...");
        webCatHomeBrowseButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                webCatHomeBrowseButtonActionPerformed(evt);
            }
        });
        mainPanel.add(webCatHomeBrowseButton, new GridBagConstraints());

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(jSeparator0, gridBagConstraints);

        // Submission field
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
        gridBagConstraints.anchor = GridBagConstraints.LINE_START;
        submissionLabel.setText("Submission:");
        mainPanel.add(submissionLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        submissionField.setTransferHandler(new SubmissionTransferHandler());
        mainPanel.add(submissionField, gridBagConstraints);

        // Submission browse button
        submissionBrowseButton.setText("Browse...");
        submissionBrowseButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                submissionBrowseButtonActionPerformed(evt);
            }
        });
        mainPanel.add(submissionBrowseButton, new GridBagConstraints());

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(jSeparator1, gridBagConstraints);

        // Plug-ins area
        pluginsLabel.setText("Plug-ins to run:");
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(pluginsLabel, gridBagConstraints);

        pluginsPanel.setPreferredSize(new Dimension(400, 80));
        pluginsPanel.setLayout(new GridBagLayout());

        pluginsModel = new PluginsTableModel();
        pluginsTable.setModel(pluginsModel);
        pluginsTable.setColumnSelectionAllowed(true);
        pluginsTable.getTableHeader().setReorderingAllowed(false);
        pluginsScrollPane.setViewportView(pluginsTable);
        pluginsTable.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        pluginsTable.getColumnModel().getColumn(0).setResizable(false);
        pluginsScrollPane.setTransferHandler(new PluginsTransferHandler());

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        pluginsPanel.add(pluginsScrollPane, gridBagConstraints);

        pluginAddButton.setText("Add...");
        pluginAddButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                pluginAddButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.PAGE_START;
        pluginsPanel.add(pluginAddButton, gridBagConstraints);

        pluginRemoveButton.setText("Remove");

        pluginRemoveButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                pluginRemoveButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.PAGE_START;
        pluginsPanel.add(pluginRemoveButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 0.25;
        mainPanel.add(pluginsPanel, gridBagConstraints);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(jSeparator2, gridBagConstraints);

        tabPane.setPreferredSize(new Dimension(466, 150));

        propertiesPanel.setLayout(new GridBagLayout());

        propertiesEditor.addKeyListener(new KeyAdapter() {
            public void keyTyped(KeyEvent e) {
                propertiesTimer.restart();
            }
        });

        propertiesTimer = new Timer(1000, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                updateGradingProperties();
            }
        });

        propertiesScrollPane.setViewportView(propertiesEditor);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        propertiesPanel.add(propertiesScrollPane, gridBagConstraints);

        tabPane.addTab("Properties", propertiesPanel);

        documentationPanel.setLayout(new GridBagLayout());

        documentationEditor.setContentType("text/html");
        documentationEditor.setEditable(false);
        documentationScrollPane.setViewportView(documentationEditor);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        documentationPanel.add(documentationScrollPane, gridBagConstraints);

        tabPane.addTab("Documentation", documentationPanel);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBagConstraints.gridheight = GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weighty = 1.0;
        mainPanel.add(tabPane, gridBagConstraints);

        frame.getContentPane().add(mainPanel, BorderLayout.CENTER);

        frame.pack();
    }

    // ----------------------------------------------------------
    /**
     * Initializes the frame components with values obtained from the current
     * settings.
     */
    private void initializeFields() {
        String homePath = currentSettings.getProperty(AppConstants.PROP_WEBCAT_HOME);

        if (homePath != null) {
            webCatHomeField.setText(homePath);
        }

        String subPath = currentSettings.getProperty(AppConstants.PROP_LAST_SUBMISSION_PATH);

        if (subPath != null) {
            submissionField.setText(subPath);
        }

        pluginsModel.updateModelFromProperties(currentSettings);

        String props = currentSettings.getProperty(AppConstants.PROP_USER_GRADING_PROPERTIES);

        if (props != null) {
            propertiesEditor.setText(props);
        }

        updateDocumentationPane();
    }

    // ----------------------------------------------------------
    /**
     * Sets the Web-CAT home location to the specified directory.
     *
     * @param file a File object that represents the directory containing the
     *     Web-CAT server
     */
    private void setWebCATHome(File file) {
        String path;

        if (file.isDirectory()) {
            path = file.getAbsolutePath();
        } else {
            path = null;
            JOptionPane.showMessageDialog(frame,
                    "The selection you have made does not appear to be\n"
                            + "a valid submission. You should select the directory\n"
                            + "that represents the expanded submission contents.\n",
                    "Not a valid submission", JOptionPane.ERROR_MESSAGE);
        }

        webCatHomeField.setText(path);

        if (path == null) {
            currentSettings.remove(AppConstants.PROP_WEBCAT_HOME);
        } else {
            currentSettings.setProperty(AppConstants.PROP_WEBCAT_HOME, path);
            webcatConfig = new WebCATConfiguration(path);
        }
    }

    // ----------------------------------------------------------
    /**
     * Sets the current submission to the specified directory.
     *
     * @param file a File object that represents the directory containing the
     *     submission to use for testing
     */
    private void setSubmission(File file) {
        String path;

        if (file.isDirectory()) {
            path = file.getAbsolutePath();
        } else {
            path = null;
            JOptionPane.showMessageDialog(frame,
                    "The selection you have made does not appear to be\n"
                            + "a valid submission. You should select the directory\n"
                            + "that represents the expanded submission contents.\n",
                    "Not a valid submission", JOptionPane.ERROR_MESSAGE);
        }

        submissionField.setText(path);

        if (path == null) {
            currentSettings.remove(AppConstants.PROP_LAST_SUBMISSION_PATH);
        } else {
            currentSettings.setProperty(AppConstants.PROP_LAST_SUBMISSION_PATH, path);
        }
    }

    // ----------------------------------------------------------
    /**
     * Adds the plugin represented by the specified file or directory to the
     * testing chain.
     *
     * @param file a File object that represents either the directory
     *     containing the grading plugin or the config.plist file inside the
     *     plugin's directory
     */
    private void addPlugin(File file) {
        String path;

        if (file.isDirectory()) {
            File configFile = new File(file, "config.plist");

            if (configFile.exists()) {
                path = file.getAbsolutePath();
            } else {
                path = null;
                JOptionPane.showMessageDialog(frame,
                        "The selection you have made does not appear to be\n"
                                + "a valid grading plug-in. You should either select the\n"
                                + "directory that represents the plug-in contents, or\n"
                                + "the config.plist file inside that directory.",
                        "Not a valid plug-in", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            if (file.getName().equalsIgnoreCase("config.plist")) {
                path = file.getParentFile().getAbsolutePath();
            } else {
                path = null;
                JOptionPane.showMessageDialog(frame,
                        "The selection you have made does not appear to be\n"
                                + "a valid grading plug-in. You should either select the\n"
                                + "directory that represents the plug-in contents, or\n"
                                + "the config.plist file inside that directory.",
                        "Not a valid plug-in", JOptionPane.ERROR_MESSAGE);
            }
        }

        if (path != null) {
            try {
                new PluginConfiguration(new File(path));
                pluginsModel.addPlugin(path);
                pluginsModel.updatePropertiesFromModel(currentSettings);
                updateDocumentationPane();
            } catch (ConfigurationException e) {
                JOptionPane.showMessageDialog(frame,
                        "An error occurred while parsing the plug-in's " + "config.plist:\n\n" + e.getMessage(),
                        "Not a valid plug-in", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    // ----------------------------------------------------------
    /**
     * Update the user's grading properties in the current settings based on
     * the text entered in the property editor pane.
     */
    private void updateGradingProperties() {
        String props = propertiesEditor.getText();

        if (props == null) {
            props = "";
        }

        currentSettings.setProperty(AppConstants.PROP_USER_GRADING_PROPERTIES, props);
    }

    // ----------------------------------------------------------
    /**
     * Called when the "Browse..." button is pressed to let the user choose the
     * Web-CAT home location.
     *
     * @param evt the event
     */
    private void webCatHomeBrowseButtonActionPerformed(ActionEvent evt) {
        if (fileChooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
            setWebCATHome(fileChooser.getSelectedFile());
        }
    }

    // ----------------------------------------------------------
    /**
     * Called when the "Browse..." button is pressed to let the user choose the
     * submission to be used for testing.
     *
     * @param evt the event
     */
    private void submissionBrowseButtonActionPerformed(ActionEvent evt) {
        if (fileChooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
            setSubmission(fileChooser.getSelectedFile());
        }
    }

    // ----------------------------------------------------------
    /**
     * Called when the "Add..." button is pressed to let the user choose a
     * plugin to be added to the testing chain.
     *
     * @param evt the event
     */
    private void pluginAddButtonActionPerformed(ActionEvent evt) {
        if (fileChooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
            addPlugin(fileChooser.getSelectedFile());
        }
    }

    // ----------------------------------------------------------
    /**
     * Called when the "Remove" button is pressed to let the user remove the
     * currently selected plugin from the testing chain.
     *
     * @param evt the event
     */
    private void pluginRemoveButtonActionPerformed(ActionEvent evt) {
        int row = pluginsTable.getSelectedRow();

        if (row != -1) {
            pluginsModel.removePluginAtIndex(row);
            pluginsModel.updatePropertiesFromModel(currentSettings);
            updateDocumentationPane();
        }
    }

    // ----------------------------------------------------------
    /**
     * Called when the "Run Plug-ins" button is pressed to begin the grading
     * process.
     *
     * @param evt the event
     */
    private void runButtonActionPerformed(ActionEvent evt) {
        PluginRunner runner = new PluginRunner(webcatConfig, currentSettings);

        runner.run();
    }

    // ----------------------------------------------------------
    /**
     * Updates the documentation pane with property descriptions when the
     * current plugin chain is modified.
     */
    private void updateDocumentationPane() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><body>");

        String[] plugins = pluginsModel.getPlugins();

        for (String pluginDir : plugins) {
            PluginConfiguration pluginConfig = null;

            try {
                pluginConfig = new PluginConfiguration(new File(pluginDir));
            } catch (ConfigurationException e) {
                // Do nothing.
            }

            Map<String, PropertyListConfiguration> option = pluginConfig.getOptions();

            for (String optionName : option.keySet()) {
                PropertyListConfiguration plist = option.get(optionName);

                appendPropertyInfoToBuffer(plist, buffer);
            }
        }

        buffer.append("</body></html>");

        documentationEditor.setText(buffer.toString());
    }

    // ----------------------------------------------------------
    /**
     * Appends a description of the specified property as HTML to a string
     * buffer.
     *
     * @param plist the property list containing information about the property
     * @param buffer the buffer to which the description will be appended
     */
    private void appendPropertyInfoToBuffer(PropertyListConfiguration plist, StringBuffer buffer) {
        buffer.append("<font face=\"Arial\">");
        buffer.append("<b>");
        buffer.append(plist.getString("property"));
        buffer.append("</b> <i>(type: ");
        buffer.append(plist.getString("type"));
        buffer.append(")</i>");
        buffer.append("</font>");
        buffer.append("<br>");
        buffer.append("<font face=\"Arial\" size=\"-1\">");
        buffer.append(plist.getString("description"));
        buffer.append("</font>");
        buffer.append("<hr>");
    }

    //~ Private classes .......................................................

    // ----------------------------------------------------------
    /**
     * Handles drop operations of a file onto the current submission text
     * field.
     */
    private class SubmissionTransferHandler extends TransferHandler {
        //~ Methods ...........................................................

        // ----------------------------------------------------------
        /**
         * Returns true if the dropped content is a file; otherwise, false.
         *
         * @param comp the component receiving the drop
         * @param transferFlavors the data flavors of the content being dropped
         * @return true if the dropped content is a file; otherwise, false.
         */
        public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
            for (DataFlavor flavor : transferFlavors) {
                if (flavor.isFlavorJavaFileListType()) {
                    return true;
                }
            }

            return false;
        }

        // ----------------------------------------------------------
        /**
         * Sets the current submission for testing to the dropped file, if
         * possible.
         *
         * @param comp the component receiving the drop
         * @param t the data being dropped
         * @return true if the content was successfully dropped; otherwise,
         *     false.
         */
        public boolean importData(JComponent comp, Transferable t) {
            try {
                @SuppressWarnings("unchecked")
                List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);

                if (files.size() == 1) {
                    setSubmission(files.get(0));
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return false;
        }

        //~ Instance/static variables .........................................

        private static final long serialVersionUID = -6870865104793930201L;
    }

    // ----------------------------------------------------------
    /**
     * Handles drop operations of a file onto the current submission text
     * field.
     */
    private class WebCATHomeTransferHandler extends TransferHandler {
        //~ Methods ...........................................................

        // ----------------------------------------------------------
        /**
         * Returns true if the dropped content is a file; otherwise, false.
         *
         * @param comp the component receiving the drop
         * @param transferFlavors the data flavors of the content being dropped
         * @return true if the dropped content is a file; otherwise, false.
         */
        public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
            for (DataFlavor flavor : transferFlavors) {
                if (flavor.isFlavorJavaFileListType()) {
                    return true;
                }
            }

            return false;
        }

        // ----------------------------------------------------------
        /**
         * Sets the current submission for testing to the dropped file, if
         * possible.
         *
         * @param comp the component receiving the drop
         * @param t the data being dropped
         * @return true if the content was successfully dropped; otherwise,
         *     false.
         */
        public boolean importData(JComponent comp, Transferable t) {
            try {
                @SuppressWarnings("unchecked")
                List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);

                if (files.size() == 1) {
                    setWebCATHome(files.get(0));
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return false;
        }

        //~ Instance/static variables .........................................

        private static final long serialVersionUID = 771815718413890191L;
    }

    // ----------------------------------------------------------
    private class PluginsTransferHandler extends TransferHandler {
        //~ Methods ...........................................................

        // ----------------------------------------------------------
        /**
         * Returns true if the dropped content is a file; otherwise, false.
         *
         * @param comp the component receiving the drop
         * @param transferFlavors the data flavors of the content being dropped
         * @return true if the dropped content is a file; otherwise, false.
         */
        public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
            for (DataFlavor flavor : transferFlavors) {
                if (flavor.isFlavorJavaFileListType()) {
                    return true;
                }
            }

            return false;
        }

        // ----------------------------------------------------------
        /**
         * Adds the dropped file to the plugin chain, if possible.
         *
         * @param comp the component receiving the drop
         * @param t the data being dropped
         * @return true if the content was successfully dropped; otherwise,
         *     false.
         */
        public boolean importData(JComponent comp, Transferable t) {
            try {
                @SuppressWarnings("unchecked")
                List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);

                if (files.size() == 1) {
                    addPlugin(files.get(0));
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return false;
        }

        //~ Instance/static variables .........................................

        private static final long serialVersionUID = -99073136818056014L;
    }

    //~ Instance/static variables .............................................

    private JFrame frame;
    private JFileChooser fileChooser;
    private JPanel mainPanel;
    private JTextField webCatHomeField;
    private JButton webCatHomeBrowseButton;
    private JTextField submissionField;
    private JButton submissionBrowseButton;
    private JPanel propertiesPanel;
    private JEditorPane propertiesEditor;
    private JPanel documentationPanel;
    private JEditorPane documentationEditor;
    private JTable pluginsTable;
    private JButton pluginAddButton;
    private JButton pluginRemoveButton;
    private JPanel bottomPanel;
    private JButton runButton;

    private PluginsTableModel pluginsModel;
    private Timer propertiesTimer;

    private WebCATConfiguration webcatConfig;
    private Properties currentSettings;
}