/*
 * org.openmicroscopy.shoola.agents.metadata.util.ScriptingDialog 
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2014 University of Dundee. All rights reserved.
 *
 *
 *  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 org.openmicroscopy.shoola.agents.util.ui;

//Java imports
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

//Third-party libraries
import info.clearthought.layout.TableLayout;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

//Application-internal dependencies
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.agents.util.ViewerSorter;
import org.openmicroscopy.shoola.env.data.model.ParamData;
import org.openmicroscopy.shoola.env.data.model.ScriptObject;
import org.openmicroscopy.shoola.util.ui.IconManager;
import org.openmicroscopy.shoola.util.ui.NumericalTextField;
import org.openmicroscopy.shoola.util.ui.TitlePanel;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.omeeditpane.OMEWikiComponent;
import org.openmicroscopy.shoola.util.ui.tdialog.TinyDialog;

import pojos.DataObject;

/** 
 * Dialog to run the selected script. The UI is created on the fly.
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since 3.0-Beta4
 */
public class ScriptingDialog
    extends JDialog
    implements ActionListener, DocumentListener, PropertyChangeListener
{

    /** Message indicating that script can only run on objects.*/
    public static final String WARNING = 
       "This script can only run on data from the same group.\n" +
       "Please perform a separate run of the script for each group,\n" +
       "selecting only data from a single group to run the script on each time.";

    /** Bound property indicating to run the script. */
    public static final String RUN_SELECTED_SCRIPT_PROPERTY =
            "runSelectedScript";

    /** Bound property indicating to download the script. */
    public static final String DOWNLOAD_SELECTED_SCRIPT_PROPERTY =
            "downloadSelectedScript";

    /** Bound property indicating to view the script. */
    public static final String VIEW_SELECTED_SCRIPT_PROPERTY =
            "viewSelectedScript";

    /** Bound property indicating to close the dialog. */
    public static final String CLOSE_SCRIPT_PROPERTY = "closeScript";

    /** The background of the description. */
    static final Color BG_COLOR = Color.LIGHT_GRAY;

    /**
     * The size of the invisible components used to separate buttons
     * horizontally.
     */
    private static final Dimension H_SPACER_SIZE = new Dimension(5, 10);

    /** Title of the dialog. */
    private static final String TITLE = "Run Script";

    /** The text displayed in the header. */
    private static final String TEXT = "Set the parameters of the " +
            "selected script.";

    /** The text displayed in the header. */
    private static final String TEXT_END = EditorUtil.MANDATORY_SYMBOL +
            " indicates the required parameter.";

    /** Indicates to close the dialog. */
    private static final int CANCEL = 0;

    /** Indicates to run the script. */
    private static final int APPLY = 1;

    /** Indicates to download the script. */
    private static final int DOWNLOAD = 2;

    /** Indicates to view the script. */
    private static final int VIEW = 3;

    /** Close the dialog. */
    private JButton cancelButton;

    /** Run the script. */
    private JButton applyButton;

    /** Menu offering the ability to download or view the script. */
    private JButton menuButton;

    /** The object to handle. */
    private ScriptObject script;

    /** The components to display. */
    private Map<String, ScriptComponent> components;

    /** The components to display. */
    private Map<String, ScriptComponent> componentsAll;

    /** Used to sort collections. */
    private ViewerSorter sorter;

    /** The menu offering various options to manipulate the script. */
    private JPopupMenu optionMenu;

    /** The objects of reference.*/
    private List<DataObject> refObjects;

    /** The component displaying the data types.*/
    private JComboBox dataTypes;

    /** The component related to the identifier.*/
    private IdentifierParamPane identifier;

    /** Flag indicating if the binary data are available.*/
    private boolean binaryAvailable;

    /** Populates the changes of data types.*/
    private void handleDataTypeChanges()
    {
        if (identifier == null || dataTypes == null) return;
        Object o = dataTypes.getSelectedItem();
        Class<?> c = script.convertDataType(o.toString());
        if (c != null && CollectionUtils.isNotEmpty(refObjects)) {
            DataObject object = refObjects.get(0);
            if (object.getClass().equals(c))
                identifier.setValues(refObjects);
            else identifier.setValues(null);
        }
    }

    /** Sets the type according to the selected objects.*/
    private void setSelectedDataType()
    {
        if (identifier == null || dataTypes == null ||
                CollectionUtils.isEmpty(refObjects))
            return;
        DataObject object = refObjects.get(0);
        Class<?> klass = object.getClass();
        Class<?> c;
        int selected = dataTypes.getSelectedIndex();
        for (int i = 0; i < dataTypes.getItemCount(); i++) {
            c = script.convertDataType(dataTypes.getItemAt(i).toString());
            if (klass.equals(c)) {
                selected = i;
                break;
            }
        }
        dataTypes.setSelectedIndex(selected);
    }

    /** 
     * Creates the option menu.
     * 
     * @return See above.
     */
    private JPopupMenu createOptionMenu()
    {
        if (optionMenu != null) return optionMenu;
        optionMenu = new JPopupMenu();
        optionMenu.add(createButton("Download", DOWNLOAD));
        optionMenu.add(createButton("View", VIEW));
        return optionMenu;
    }

    /**
     * Creates a button.
     *
     * @param text The text of the button.
     * @param actionID The action command id.
     * @return See above.
     */
    private JButton createButton(String text, int actionID)
    {
        JButton b = new JButton(text);
        b.setActionCommand(""+actionID);
        b.addActionListener(this);
        b.setOpaque(false);
        UIUtilities.unifiedButtonLookAndFeel(b);
        return b;
    }

    /** Closes the dialog. */
    private void close()
    {
        setVisible(false);
        firePropertyChange(CLOSE_SCRIPT_PROPERTY, Boolean.valueOf(false),
                Boolean.valueOf(true));
    }

    /**
     * Displays information of the identifier.
     *
     * @param location Indicates where to display the component.
     */
    private void displayIdentifierInformation(Point location)
    {
        JLabel l = new JLabel(IdentifierParamPane.INFO_TEXT);
        TinyDialog d = new TinyDialog((Frame) getOwner(), l,
                TinyDialog.CLOSE_ONLY);
        d.pack();
        d.setLocation(location);
        d.setVisible(true);
    }

    /**
     * Sets the enabled flag of the {@link #applyButton} to <code>true</code>
     * if the script can run i.e. all required fields are filled, to 
     * <code>false</code> otherwise.
     */
    private void canRunScript()
    {
        Iterator<Entry<String, ScriptComponent>>
        i = components.entrySet().iterator();
        Entry<String, ScriptComponent> entry;
        ScriptComponent c;
        int required = 0;
        int valueSet = 0;
        Object value;
        while (i.hasNext()) {
            entry = i.next();
            c = entry.getValue();
            if (c.isRequired()) {
                required++;
                value = c.getValue();
                if (value != null) {
                    if (value instanceof String) {
                        if (StringUtils.isNotBlank((String) value)) valueSet++;
                    } else if (value instanceof List) {
                        List<Object> l = (List<Object>) value;
                        if (l.size() > 0) valueSet++;
                    } else if (value instanceof Map) {
                        Map<Object, Object> m = (Map<Object, Object>) value;
                        if (m.size() > 0) valueSet++;
                    } else valueSet++;
                }
            }
        }
        applyButton.setEnabled(required == valueSet);
        if (!binaryAvailable) applyButton.setEnabled(false);
    }

    /** Collects the data and fires a property.*/
    private void runScript()
    {
        Entry<String, ScriptComponent> entry;
        ScriptComponent c;
        Iterator<Entry<String, ScriptComponent>> i =
                componentsAll.entrySet().iterator();
        Map<String, ParamData> inputs = script.getInputs();
        ParamData param;
        while (i.hasNext()) {
            entry = i.next();
            c = entry.getValue();
            param = inputs.get(entry.getKey());
            param.setValueToPass(c.getValue());
        }
        firePropertyChange(RUN_SELECTED_SCRIPT_PROPERTY, null, script);
        close();
    }

    /**
     * Creates a component displaying the various options.
     *
     * @param values The values to display.
     * @param defValue The default value.
     * @return See above.
     */
    private JComboBox createValuesBox(List<Object> values, Object defValue)
    {
        if (values == null) return null;
        Object[] v = new Object[values.size()];
        Iterator<Object> i = values.iterator();
        int j = 0;
        int index = 0;
        while (i.hasNext()) {
            v[j] = i.next();
            if (v[j].equals(defValue)) {
                index = j;
            }
            j++;
        }
        JComboBox box = new JComboBox(v);
        box.setSelectedIndex(index);
        return box;
    }

    /** Initializes the components. */
    private void initComponents()
    {
        sorter = new ViewerSorter();
        cancelButton = new JButton("Cancel");
        cancelButton.setToolTipText("Close the dialog.");
        cancelButton.setActionCommand(""+CANCEL);
        cancelButton.addActionListener(this);
        applyButton = new JButton("Run Script");
        applyButton.setToolTipText("Run the selected script.");
        applyButton.setActionCommand(""+APPLY);
        applyButton.addActionListener(this);
        IconManager icons = IconManager.getInstance();
        menuButton = new JButton(icons.getIcon(IconManager.FILTER_MENU));
        menuButton.setText("Script");
        menuButton.setHorizontalTextPosition(JButton.LEFT);
        menuButton.addMouseListener(new MouseAdapter() {

            public void mouseReleased(MouseEvent e) {
                Object src = e.getSource();
                if (src instanceof Component) {
                    Point p = e.getPoint();
                    createOptionMenu().show((Component) src, p.x, p.y);
                }
            }
        });
        components = new LinkedHashMap<String, ScriptComponent>();
        componentsAll = new LinkedHashMap<String, ScriptComponent>();
        Map<String, ParamData> types = script.getInputs();
        if (types == null) return;
        List <ScriptComponent> results = new ArrayList<ScriptComponent>();
        Entry<String, ParamData> entry;
        ParamData param;
        JComponent comp;
        ScriptComponent c;
        String name;
        Class<?> type;
        Object defValue ;
        Iterator<Entry<String, ParamData>> i = types.entrySet().iterator();
        List<Object> values;
        Number n;
        String details = "";
        String text = "";
        String grouping;
        String parent;
        Map<String, List<ScriptComponent>> childrenMap =
                new HashMap<String, List<ScriptComponent>>();
        List<ScriptComponent> l;
        int length;
        boolean columnsSet;
        while (i.hasNext()) {
            text = "";
            columnsSet = false;
            comp = null;
            entry = i.next();
            param = entry.getValue();
            name = entry.getKey();
            type = param.getPrototype();
            values = param.getValues();
            defValue = param.getDefaultValue();
            if (CollectionUtils.isNotEmpty(values)) {
                comp = createValuesBox(values, defValue);
            }
            if (Long.class.equals(type) || Integer.class.equals(type) ||
                    Float.class.equals(type) || Double.class.equals(type)) {
                if (comp == null) {
                    if (script.isIdentifier(name)) {
                        comp = new NumericalTextField();
                        ((NumericalTextField) comp).setNumberType(type);
                        ((NumericalTextField) comp).setNegativeAccepted(false);
                        if (CollectionUtils.isNotEmpty(refObjects)) {
                            //support of image type
                            DataObject object = refObjects.get(0);
                            if (script.isSupportedType(object, name)){
                                defValue = object.getId();
                            }
                        }
                    } else {
                        comp = new NumericalTextField();
                        ((NumericalTextField) comp).setNumberType(type);
                        n = param.getMinValue();
                        if (n != null) {
                            if (Long.class.equals(type)) {
                                text += "Min: "+n.longValue()+" ";
                                ((NumericalTextField) comp).setMinimum(
                                        n.longValue());
                            } else if (Integer.class.equals(type)) {
                                text += "Min: "+n.intValue()+" ";
                                ((NumericalTextField) comp).setMinimum(
                                        n.intValue());
                            } else if (Double.class.equals(type)) {
                                text += "Min: "+n.doubleValue()+" ";
                                ((NumericalTextField) comp).setMinimum(
                                        n.doubleValue());
                            } else if (Float.class.equals(type)) {
                                text += "Min: "+n.floatValue()+" ";
                                ((NumericalTextField) comp).setMinimum(
                                        n.floatValue());
                            }
                        } else {
                            ((NumericalTextField) comp).setNegativeAccepted(true);
                        }
                        n = param.getMaxValue();
                        if (n != null) {
                            if (Long.class.equals(type)) {
                                text += "Max: "+n.longValue()+" ";
                                ((NumericalTextField) comp).setMaximum(
                                        n.longValue());
                            } else if (Integer.class.equals(type)) {
                                text += "Max: "+n.intValue()+" ";
                                ((NumericalTextField) comp).setMaximum(
                                        n.intValue());
                            } else if (Double.class.equals(type)) {
                                text += "Max: "+n.doubleValue()+" ";
                                ((NumericalTextField) comp).setMaximum(
                                        n.doubleValue());
                            } else if (Float.class.equals(type)) {
                                text += "Max: "+n.floatValue()+" ";
                                ((NumericalTextField) comp).setMaximum(
                                        n.floatValue());
                            } 
                        }
                    }
                    if (defValue != null)
                        ((NumericalTextField) comp).setText(""+defValue);
                }
            } else if (String.class.equals(type)) {
                if (comp == null) {
                    comp = new JTextField();
                    if (defValue != null) {
                        length = defValue.toString().length();
                        String s = defValue.toString().trim();
                        ((JTextField) comp).setColumns(length);
                        ((JTextField) comp).setText(s);
                        columnsSet = s.length() > 0;
                    }
                }
            } else if (Boolean.class.equals(type)) {
                if (comp == null) {
                    comp = new JCheckBox();
                    if (defValue != null)
                        ((JCheckBox) comp).setSelected((Boolean) defValue);
                }
            } else if (Map.class.equals(type)) {
                if (comp == null)
                    comp = new ComplexParamPane(param.getKeyType(),
                            param.getValueType());
                else 
                    comp = new ComplexParamPane(param.getKeyType(),
                            (JComboBox) comp);
            } else if (List.class.equals(type)) {
                if (script.isIdentifier(name)) {
                    identifier = new IdentifierParamPane(Long.class);
                    identifier.setValues(refObjects);
                    identifier.addDocumentListener(this);
                    comp = identifier;
                } else {
                    if (comp == null)
                        comp = new ComplexParamPane(param.getKeyType());
                    else 
                        comp = new ComplexParamPane((JComboBox) comp);
                }
            }
            if (comp != null) {
                if (comp instanceof JTextField) {
                    if (!columnsSet)
                        ((JTextField) comp).setColumns(ScriptComponent.COLUMNS);
                    ((JTextField) comp).getDocument().addDocumentListener(this);
                }
                if (comp instanceof ComplexParamPane)
                    comp.addPropertyChangeListener(this);
                comp.setToolTipText(param.getDescription());
                c = new ScriptComponent(comp, name);
                if (text.trim().length() > 0) c.setUnit(text);
                if (!(comp instanceof JComboBox || comp instanceof JCheckBox))
                    c.setRequired(!param.isOptional());
                if (details != null && details.trim().length() > 0)
                    c.setInfo(details);
                if (comp instanceof JComboBox && script.isDataType(name)) {
                    dataTypes = (JComboBox) comp;
                    dataTypes.addActionListener(new ActionListener() {

                        public void actionPerformed(ActionEvent e) {
                            handleDataTypeChanges();
                        }
                    });
                }
                grouping = param.getGrouping();
                parent = param.getParent();
                c.setParentIndex(parent);
                if (parent.length() > 0) {
                    l = childrenMap.get(parent);
                    if (l == null) {
                        l = new ArrayList<ScriptComponent>();
                        childrenMap.put(parent, l);
                    }
                    l.add(c);
                }

                if (grouping.length() > 0) {
                    c.setGrouping(grouping);
                    c.setNameLabel(grouping);
                } else {
                    c.setNameLabel(name);
                }
                if (c.hasChildren() || parent.length() == 0) {
                    results.add(c);
                } 
                componentsAll.put(c.getParameterName(), c);
            }
        }
        ScriptComponent key;
        Iterator<ScriptComponent> k = results.iterator();
        while (k.hasNext()) {
            key = k.next();
            grouping = key.getGrouping();
            l = childrenMap.get(grouping);
            childrenMap.remove(grouping);
            if (l != null)
                key.setChildren(sorter.sort(l));
        }
        if (childrenMap != null && childrenMap.size() > 0) {
            Iterator<String> j = childrenMap.keySet().iterator();
            ScriptComponent sc;
            while (j.hasNext()) {
                parent = j.next();
                sc = new ScriptComponent();
                sc.setGrouping(parent);
                sc.setNameLabel(parent);
                sc.setChildren(sorter.sort(childrenMap.get(parent)));
                results.add(sc);
            }
        }
        List<ScriptComponent> sortedKeys = sorter.sort(results);
        k = sortedKeys.iterator();

        while (k.hasNext()) {
            key = k.next();
            components.put(key.getParameterName(), key);
        }
        setSelectedDataType();
        if (identifier != null) 
            identifier.addPropertyChangeListener(this);
        canRunScript();
    }

    /**
     * Builds the panel hosting the components.
     *
     * @return See above.
     */
    private JPanel buildControlPanel()
    {
        JPanel controlPanel = new JPanel();
        controlPanel.setBorder(null);
        controlPanel.add(cancelButton);
        controlPanel.add(Box.createRigidArea(H_SPACER_SIZE));
        controlPanel.add(applyButton);
        controlPanel.add(Box.createRigidArea(H_SPACER_SIZE));
        JPanel bar = new JPanel();
        bar.setLayout(new BoxLayout(bar, BoxLayout.Y_AXIS));
        bar.add(controlPanel);
        bar.add(Box.createVerticalStrut(10));

        JPanel all = new JPanel();
        all.setLayout(new BoxLayout(all, BoxLayout.X_AXIS));
        all.add(UIUtilities.buildComponentPanel(menuButton));
        all.add(UIUtilities.buildComponentPanelRight(bar));
        return all;
    }

    /**
     * Returns the component displaying the description of the script.
     *
     * @return See above.
     */
    private JComponent buildDescriptionPane()
    {
        String description = script.getDescription();
        if (StringUtils.isBlank(description))
            return  null;
        OMEWikiComponent area = new OMEWikiComponent(false);
        area.setEnabled(false);
        area.setText(description);
        JPanel content = new JPanel();
        content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
        JLabel label = UIUtilities.setTextFont(script.getName());
        Font f = label.getFont();
        label.setFont(f.deriveFont(f.getStyle(), f.getSize()+2));
        content.add(UIUtilities.buildComponentPanel(label));
        content.add(Box.createVerticalStrut(5));
        JPanel p = UIUtilities.buildComponentPanel(area);
        p.setBackground(BG_COLOR);
        area.setBackground(BG_COLOR);
        content.add(p);
        return content;
    }

    /** 
     * Builds and lays out the details of the script e.g.
     * authors, contact, version.
     * 
     * @return See above.
     */
    private JPanel buildScriptDetails()
    {
        String[] authors = script.getAuthors();
        String contact = script.getContact();
        String version = script.getVersion();
        if (authors == null && contact == null && version == null)
            return null;
        JPanel p = new JPanel();
        p.setBackground(BG_COLOR);
        double[] columns = {TableLayout.PREFERRED, 5, TableLayout.FILL};
        TableLayout layout = new TableLayout();
        layout.setColumn(columns);
        p.setLayout(layout);
        int row = 0;
        JLabel l;
        if (authors != null && authors.length > 0) {
            l = UIUtilities.setTextFont("Authors:");
            StringBuffer buffer = new StringBuffer();
            int n = authors.length-1;
            for (int i = 0; i < authors.length; i++) {
                buffer.append(authors[i]);
                if (i < n) buffer.append(", ");
            }
            layout.insertRow(row, TableLayout.PREFERRED);
            p.add(l, "0,"+row);
            l = new JLabel();
            l.setText(buffer.toString());
            p.add(l, "2,"+row);
            row++;
        }
        if (StringUtils.isNotBlank(contact)) {
            l = UIUtilities.setTextFont("Contact:");
            layout.insertRow(row, TableLayout.PREFERRED);
            p.add(l, "0,"+row);
            l = new JLabel();
            l.setText(contact);
            p.add(l, "2,"+row);
            row++;
        }
        if (StringUtils.isNotBlank(version)) {
            l = UIUtilities.setTextFont("Version:");
            layout.insertRow(row, TableLayout.PREFERRED);
            p.add(l, "0,"+row);
            l = new JLabel();
            l.setText(version);
            p.add(l, "2,"+row);
        }
        if (p.getComponentCount() == 0) return null;
        return p;
    }

    /**
     * Builds the component displaying the parameters.
     *
     * @return See above.
     */
    private JPanel buildBody()
    {
        JPanel p = new JPanel();
        p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        double[] columns = {TableLayout.PREFERRED, 5, TableLayout.FILL};
        TableLayout layout = new TableLayout();
        layout.setColumn(columns);
        p.setLayout(layout);
        int row = 0;
        JComponent area = buildDescriptionPane();
        JComponent authorsPane = buildScriptDetails();
        if (area != null) {
            layout.insertRow(row, TableLayout.PREFERRED);
            p.add(area, "0,"+row+", 2, "+row);
            row++;
        }

        if (authorsPane != null) {
            layout.insertRow(row, TableLayout.PREFERRED);
            p.add(authorsPane, "0,"+row+", 2, "+row);
            row++;
        }
        layout.insertRow(row, 5);

        row++;
        Entry<String, ScriptComponent> entry;
        Iterator<Entry<String, ScriptComponent>>
        i = components.entrySet().iterator();
        ScriptComponent comp;
        int required = 0;
        while (i.hasNext()) {
            entry = i.next();
            comp = entry.getValue();
            layout.insertRow(row, TableLayout.PREFERRED);
            comp.buildUI();
            if (comp.isRequired()) required++;
            p.add(comp, "0,"+row+", 2, "+row);
            row++;
            layout.insertRow(row, 2);
            row++;
        }
        if (required > 0) {
            JLabel label = new JLabel(TEXT_END);
            Font font = label.getFont();
            label.setFont(font.deriveFont(font.getStyle(), font.getSize()-2));
            label.setForeground(UIUtilities.REQUIRED_FIELDS_COLOR);
            layout.insertRow(row, TableLayout.PREFERRED);
            p.add(UIUtilities.buildComponentPanel(label), "0,"+row+",2, "+row);
        }

        JPanel controls = new JPanel();
        controls.setLayout(new BorderLayout(0, 0));
        controls.add(new JScrollPane(p), BorderLayout.CENTER);
        controls.add(buildControlPanel(), BorderLayout.SOUTH);
        return controls;
    }

    /** Builds and lays out the UI. */
    private void buildGUI()
    {
        String text = TEXT;
        TitlePanel tp = new TitlePanel(TITLE, text, script.getIconLarge());
        Container c = getContentPane();
        c.removeAll();
        c.setLayout(new BorderLayout(0, 0));
        c.add(tp, BorderLayout.NORTH);
        c.add(buildBody(), BorderLayout.CENTER);
    }

    /**
     * Creates a new instance.
     *
     * @param parent The parent of the frame.
     * @param script The script to run. Mustn't be <code>null</code>.
     * @param refObjects The objects of reference.
     * @param binaryAvailable Flag indicating if binary data are available.
     */
    public ScriptingDialog(JFrame parent, ScriptObject script,
            List<DataObject> refObjects, boolean binaryAvailable)
    {
        super(parent);
        this.binaryAvailable = binaryAvailable;
        reset(script, refObjects);
    }

    /**
     * Resets the value.
     *
     * @param script The script to run. Mustn't be <code>null</code>.
     * @param refObjects The objects of reference.
     */
    public void reset(ScriptObject script, List<DataObject> refObjects)
    {
        if (script == null)
            throw new IllegalArgumentException("No script specified");
        this.script = script;
        this.refObjects = refObjects;
        initComponents();
        buildGUI();
        pack();
    }

    /**
     * Closes or runs the scripts.
     * @see ActionListener#actionPerformed(ActionEvent)
     */
    public void actionPerformed(ActionEvent e)
    {
        int index = Integer.parseInt(e.getActionCommand());
        switch (index) {
        case CANCEL:
            close();
            break;
        case APPLY:
            runScript();
            break;
        case DOWNLOAD:
            firePropertyChange(DOWNLOAD_SELECTED_SCRIPT_PROPERTY, null, script);
            break;
        case VIEW:
            firePropertyChange(VIEW_SELECTED_SCRIPT_PROPERTY, null, script);
        }
    }

    /**
     * Checks if the user can run the script.
     * @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
        String name = evt.getPropertyName();
        if (RowPane.MODIFIED_CONTENT_PROPERTY.equals(name))
            canRunScript();
        else if (IdentifierParamPane.DISPLAY_INFO_PROPERTY.equals(name)) {
            displayIdentifierInformation((Point) evt.getNewValue());
        }
    }

    /**
     * Allows the user to run or not the script.
     * @see DocumentListener#insertUpdate(DocumentEvent)
     */
    public void insertUpdate(DocumentEvent e) { canRunScript(); }

    /**
     * Allows the user to run or not the script.
     * @see DocumentListener#removeUpdate(DocumentEvent)
     */
    public void removeUpdate(DocumentEvent e) { canRunScript(); }

    /**
     * Required by the {@link DocumentListener} I/F but no-operation 
     * implementation in our case.
     * @see DocumentListener#changedUpdate(DocumentEvent)
     */
    public void changedUpdate(DocumentEvent e) {}

}
