/*
 * $Id: IzPanel.java 2692 2009-03-25 09:29:40Z fbuehlmann $
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 * 
 * http://izpack.org/
 * http://izpack.codehaus.org/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.izforge.izpack.installer;

import java.awt.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.metal.MetalLookAndFeel;

import com.izforge.izpack.Panel;
import com.izforge.izpack.gui.LabelFactory;
import com.izforge.izpack.gui.LayoutConstants;
import com.izforge.izpack.panels.HelpWindow;
import com.izforge.izpack.util.AbstractUIHandler;
import com.izforge.izpack.util.Debug;
import com.izforge.izpack.util.MultiLineLabel;
import com.izforge.izpack.util.VariableSubstitutor;
import com.izforge.izpack.adaptator.IXMLElement;

/**
 * Defines the base class for the IzPack panels. Any panel should be a subclass of it and should
 * belong to the <code>com.izforge.izpack.panels</code> package. Since IzPack version 3.9 the
 * layout handling will be delegated to the class LayoutHelper which can be accessed by
 * <code>getLayoutHelper</code>. There are some layout helper methods in this class which will be
 * exist some time longer, but they are deprecated. At a redesign or new panel use the layout
 * helper. There is a special layout manager for IzPanels. This layout manager will be supported by
 * the layout helper. There are some points which should be observed at layouting. One point e.g. is
 * the anchor. All IzPanels have to be able to use different anchors, as minimum CENTER and
 * NORTHWEST. To use a consistent appearance use this special layout manger and not others.
 * 
 * @author Julien Ponge
 * @author Klaus Bartz
 */
public class IzPanel extends JPanel implements AbstractUIHandler, LayoutConstants
{

    private static final long serialVersionUID = 3256442495255786038L;

    /**
     * The helper object which handles layout
     */
    protected LayoutHelper layoutHelper;

    /**
     * The component which should get the focus at activation
     */
    protected Component initialFocus = null;

    /**
     * The installer internal data (actually a melting-pot class with all-public fields.
     */
    protected InstallData idata;

    /**
     * The parent IzPack installer frame.
     */
    protected InstallerFrame parent;

    /**
     * i.e. "com.izforge.izpack.panels.HelloPanel"
     */
    protected String myFullClassname;

    /**
     * myClassname=i.e "FinishPanel"
     */
    protected String myClassname;

    /**
     * i.e. "FinishPanel." useFull for getString()
     */
    protected String myPrefix;

    /**
     * internal headline string
     */
    protected String headline;

    /**
     * internal headline Label
     */
    protected JLabel headLineLabel;

    /**
     * Is this panel general hidden or not
     */
    protected boolean hidden;

    /**
     * HEADLINE = "headline"
     */
    public final static String HEADLINE = "headline";

    private DataValidator validationService = null;

    private java.util.List<PanelAction> preActivateActions = null;
    
    private java.util.List<PanelAction> preValidateActions = null;
    
    private java.util.List<PanelAction> postValidateActions = null;
    
    /**
     * X_ORIGIN = 0
     */
    public final static int X_ORIGIN = 0;

    /**
     * Y_ORIGIN = 0
     */
    public final static int Y_ORIGIN = 0;

    /**
     * D = "." ( dot )
     */
    public final static String D = ".";

    /**
     * d = D
     */
    public final static String d = D;

    /**
     * COLS_1 = 1
     */
    public final static int COLS_1 = 1;

    /**
     * ROWS_1 = 1
     */
    public final static int ROWS_1 = 1;

    /**
     * Information about the panel
     */
    public Panel metadata;

    /**
     * The constructor.
     * 
     * @param parent The parent IzPack installer frame.
     * @param idata The installer internal data.
     */
    public IzPanel(InstallerFrame parent, InstallData idata)
    {
        this(parent, idata, (LayoutManager2) null);
    }

    /**
     * Creates a new IzPanel object with the given layout manager. Valid layout manager are the
     * IzPanelLayout and the GridBagLayout. New panels should be use the IzPanelLaout. If lm is
     * null, no layout manager will be created or initialized.
     * 
     * @param parent The parent IzPack installer frame.
     * @param idata The installer internal data.
     * @param lm layout manager to be used with this IzPanel
     */
    public IzPanel(InstallerFrame parent, InstallData idata, LayoutManager2 lm)
    {
        super();
        init(parent, idata);
        if (lm != null)
        {
            getLayoutHelper().startLayout(lm);
        }
    }

    /**
     * Creates a new IzPanel object.
     * 
     * @param parent the Parent Frame
     * @param idata Installers Runtime Data Set
     * @param iconName The Headline IconName
     */
    public IzPanel(InstallerFrame parent, InstallData idata, String iconName)
    {
        this(parent, idata, iconName, -1);
    }

    /**
     * The constructor with Icon.
     * 
     * @param parent The parent IzPack installer frame.
     * @param idata The installer internal data.
     * @param iconName A iconname to show as left oriented headline-leading Icon.
     * @param instance An instance counter
     */
    public IzPanel(InstallerFrame parent, InstallData idata, String iconName, int instance)
    {
        this(parent, idata);
        buildHeadline(iconName, instance);
    }

    /**
     * Build the IzPanel internal Headline. If an external headline# is used, this method returns
     * immediately with false. Allows also to display a leading Icon for the PanelHeadline. This
     * Icon can also be different if the panel has more than one Instances. The UserInputPanel is
     * one of these Candidates. <p/> by marc.eppelmann&#064;gmx.de
     * 
     * @param imageIconName an Iconname
     * @param instanceNumber an panel instance
     * @return true if successful build
     */
    protected boolean buildHeadline(String imageIconName, int instanceNumber)
    {
        boolean result = false;
        if (parent.isHeading(this)) { return (false); }

        // TODO: proteced instancenumber
        // TODO: is to be validated
        // TODO:
        // TODO: first Test if a Resource for your protected Instance exists.
        String headline;
        String headlineSearchBaseKey = myClassname + d + "headline"; // Results for example in
        // "ShortcutPanel.headline"
        // :

        if (instanceNumber > -1) // Search for Results for example in "ShortcutPanel.headline.1,
        // 2, 3 etc." :
        {
            String instanceSearchKey = headlineSearchBaseKey + d + Integer.toString(instanceNumber);

            String instanceHeadline = getString(instanceSearchKey);

            if (Debug.isLOG())
            {
                System.out.println("found headline: " + instanceHeadline + d + " for instance # "
                        + instanceNumber);
            }
            if (!instanceSearchKey.equals(instanceHeadline))
            {
                headline = instanceHeadline;
            }
            else
            {
                headline = getString(headlineSearchBaseKey);
            }
        }
        else
        {
            headline = getString(headlineSearchBaseKey);
        }

        if (headline != null)
        {
            if ((imageIconName != null) && !"".equals(imageIconName))
            {
                headLineLabel = new JLabel(headline, getImageIcon(imageIconName),
                        SwingConstants.LEADING);
            }
            else
            {
                headLineLabel = new JLabel(headline);
            }

            Font font = headLineLabel.getFont();
            float size = font.getSize();
            int style = 0;
            font = font.deriveFont(style, (size * 1.5f));
            headLineLabel.setFont(font);

            GridBagConstraints gbc = new GridBagConstraints();

            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.gridwidth = 1;
            gbc.gridheight = 1;

            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.anchor = GridBagConstraints.WEST;
            gbc.insets = new Insets(0, 0, 0, 0);
            headLineLabel.setName(HEADLINE);
            ((GridBagLayout) getLayout()).addLayoutComponent(headLineLabel, gbc);

            add(headLineLabel);
        }

        return result;
    }

    /**
     * Gets a language Resource String from the parent, which holds these global resource.
     * 
     * @param key The Search key
     * @return The Languageresource or the key if not found.
     */
    public String getString(String key)
    {
        return parent.langpack.getString(key);
    }

    /**
     * Gets a named image icon
     * 
     * @param iconName a valid image icon
     * @return the icon
     */
    public ImageIcon getImageIcon(String iconName)
    {
        return parent.icons.getImageIcon(iconName);
    }

    /**
     * Inits and sets the internal layout helper object.
     */
    protected void initLayoutHelper()
    {
        layoutHelper = new LayoutHelper(this);
    }

    /**
     * Gets and fills the classname fields
     */
    protected void getClassName()
    {
        myFullClassname = getClass().getName();
        myClassname = myFullClassname.substring(myFullClassname.lastIndexOf(".") + 1);
        myPrefix = myClassname + ".";
    }

    /**
     * Internal init method
     * 
     * @param parent the parent frame
     * @param idata installers runtime dataset
     */
    protected void init(InstallerFrame parent, InstallData idata)
    {
        getClassName();

        this.idata = idata;
        this.parent = parent;
        // To get the Panel object via idata is a hack because InstallData will
        // be hold global data, not panel specific data. But the Panel object will
        // be needed in the constructor of some derived classes. And to expand the
        // constructor is also not a good way because all derived classes have to
        // change then the signature. Also the custem IzPanels elswhere. Therefore
        // this hack...
        // Problems with this hack will be exist if more than one threads calls the
        // constructors of derived clases. This is not the case.
        this.metadata = idata.currentPanel;
        idata.currentPanel = null;
        initLayoutHelper();

    }

    /**
     * Indicates wether the panel has been validated or not. The installer won't let the user go
     * further through the installation process until the panel is validated. Default behaviour is
     * to return <code>true</code>.
     * 
     * @return A boolean stating whether the panel has been validated or not.
     */
    protected boolean isValidated()
    {
        return true;
    }

    public boolean panelValidated()
    {
        return isValidated() && validatePanel();
    }

    /**
     * This method is called when the panel becomes active. Default is to do nothing : feel free to
     * implement what you need in your subclasses. A panel becomes active when the user reaches it
     * during the installation process.
     */
    public void panelActivate()
    {
    }

    /**
     * This method is called when the panel gets desactivated, when the user switches to the next
     * panel. By default it doesn't do anything.
     */
    public void panelDeactivate()
    {
    }

    /**
     * Asks the panel to set its own XML data that can be brought back for an automated installation
     * process. Use it as a blackbox if your panel needs to do something even in automated mode.
     * 
     * @param panelRoot The XML root element of the panels blackbox tree.
     */
    public void makeXMLData(IXMLElement panelRoot)
    {
    }

    /**
     * Ask the user a question.
     * 
     * @param title Message title.
     * @param question The question.
     * @param choices The set of choices to present.
     * @return The user's choice.
     * @see AbstractUIHandler#askQuestion(String, String, int)
     */
    public int askQuestion(String title, String question, int choices)
    {
        return askQuestion(title, question, choices, -1);
    }

    /**
     * Ask the user a question.
     * 
     * @param title Message title.
     * @param question The question.
     * @param choices The set of choices to present.
     * @param default_choice The default choice. (-1 = no default choice)
     * @return The user's choice.
     * @see AbstractUIHandler#askQuestion(String, String, int, int)
     */
    public int askQuestion(String title, String question, int choices, int default_choice)
    {
        int jo_choices = 0;

        if (choices == AbstractUIHandler.CHOICES_YES_NO)
        {
            jo_choices = JOptionPane.YES_NO_OPTION;
        }
        else if (choices == AbstractUIHandler.CHOICES_YES_NO_CANCEL)
        {
            jo_choices = JOptionPane.YES_NO_CANCEL_OPTION;
        }

        int user_choice = JOptionPane.showConfirmDialog(this, question, title, jo_choices,
                JOptionPane.QUESTION_MESSAGE);

        if (user_choice == JOptionPane.CANCEL_OPTION) { return AbstractUIHandler.ANSWER_CANCEL; }

        if (user_choice == JOptionPane.YES_OPTION) { return AbstractUIHandler.ANSWER_YES; }

        if (user_choice == JOptionPane.CLOSED_OPTION) { return AbstractUIHandler.ANSWER_NO; }

        if (user_choice == JOptionPane.NO_OPTION) { return AbstractUIHandler.ANSWER_NO; }

        return default_choice;
    }

    public boolean emitNotificationFeedback(String message)
    {
        return (JOptionPane.showConfirmDialog(this, message, idata.langpack
                .getString("installer.Message"), JOptionPane.WARNING_MESSAGE,
                JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION);
    }

    /**
     * Notify the user about something.
     * 
     * @param message The notification.
     */
    public void emitNotification(String message)
    {
        JOptionPane.showMessageDialog(this, message);
    }

    /**
     * Warn the user about something.
     * 
     * @param message The warning message.
     */
    public boolean emitWarning(String title, String message)
    {
        return (JOptionPane.showConfirmDialog(this, message, title, JOptionPane.WARNING_MESSAGE,
                JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION);

    }

    /**
     * Notify the user of some error.
     * 
     * @param message The error message.
     */
    public void emitError(String title, String message)
    {
        JOptionPane.showMessageDialog(this, message, title, JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Notify the user of some error and block the next button.
     * 
     * @param message The error message.
     */
    public void emitErrorAndBlockNext(String title, String message)
    {
        emitError(title, message);
        parent.lockNextButton();
    }
    
    /**
     * Returns the component which should be get the focus at activation of this panel.
     * 
     * @return the component which should be get the focus at activation of this panel
     */
    public Component getInitialFocus()
    {
        return initialFocus;
    }

    /**
     * Sets the component which should be get the focus at activation of this panel.
     * 
     * @param component which should be get the focus at activation of this panel
     */
    public void setInitialFocus(Component component)
    {
        initialFocus = component;
    }

    /**
     * Calls the langpack of parent InstallerFrame for the String <tt>RuntimeClassName.subkey</tt>.
     * Do not add a point infront of subkey, it is always added in this method. If
     * <tt>RuntimeClassName.subkey</tt> is not found, the super class name will be used until it
     * is <tt>IzPanel</tt>. If no key will be found, null returns.
     * 
     * @param subkey the subkey for the string which should be returned
     * @return the founded string
     */
    public String getI18nStringForClass(String subkey)
    {
        String retval = null;
        Class clazz = this.getClass();
        while (retval == null && !clazz.getName().endsWith(".IzPanel"))
        {
            retval = getI18nStringForClass(clazz.getName(), subkey, null);
            clazz = clazz.getSuperclass();
        }
        return (retval);
    }

    /**
     * Calls the langpack of parent InstallerFrame for the String <tt>RuntimeClassName.subkey</tt>.
     * Do not add a point infront of subkey, it is always added in this method. If no key will be
     * found the key or - if alternate class is null - null returns.
     * 
     * @param subkey the subkey for the string which should be returned
     * @param alternateClass the short name of the class which should be used if no string is
     * present with the runtime class name
     * @return the founded string
     */
    public String getI18nStringForClass(String subkey, String alternateClass)
    {
        return (getI18nStringForClass(getClass().getName(), subkey, alternateClass));

    }

    private String getI18nStringForClass(String curClassName, String subkey, String alternateClass)
    {

        int nameStart = curClassName.lastIndexOf('.') + 1;
        curClassName = curClassName.substring(nameStart, curClassName.length());
        StringBuffer buf = new StringBuffer();
        buf.append(curClassName).append(".").append(subkey);
        String fullkey = buf.toString();
        String panelid = null;
        if (getMetadata() != null)
        {
            panelid = getMetadata().getPanelid();
        }
        String retval = null;
        if (panelid != null)
        {
            buf.append(".");
            buf.append(panelid);
            retval = parent.langpack.getString(buf.toString());
        }
        if (retval == null || retval.startsWith(fullkey))
        {
            retval = parent.langpack.getString(fullkey);
        }
        if (retval == null || retval.startsWith(fullkey))
        {
            if (alternateClass == null) { return (null); }
            buf.delete(0, buf.length());
            buf.append(alternateClass).append(".").append(subkey);
            retval = parent.langpack.getString(buf.toString());
        }
        if (retval != null && retval.indexOf('$') > -1)
        {
            VariableSubstitutor substitutor = new VariableSubstitutor(idata.getVariables());
            retval = substitutor.substitute(retval, null);
        }
        return (retval);
    }

    /**
     * Returns the parent of this IzPanel (which is a InstallerFrame).
     * 
     * @return the parent of this IzPanel
     */
    public InstallerFrame getInstallerFrame()
    {
        return (parent);
    }

    // ------------- Helper for common used components ----- START ---

    /**
     * Creates a label via LabelFactory using iconId, pos and method getI18nStringForClass for
     * resolving the text to be used. If the icon id is null, the label will be created also.
     * 
     * @param subkey the subkey which should be used for resolving the text
     * @param alternateClass the short name of the class which should be used if no string is
     * present with the runtime class name
     * @param iconId id string for the icon
     * @param pos horizontal alignment
     * @return the newly created label
     */
    public JLabel createLabel(String subkey, String alternateClass, String iconId, int pos)
    {
        ImageIcon ii = (iconId != null) ? parent.icons.getImageIcon(iconId) : null;
        String msg = getI18nStringForClass(subkey, alternateClass);
        JLabel label = LabelFactory.create(msg, ii, pos);
        if (label != null)
        {
            label.setFont(getControlTextFont());
        }
        return (label);

    }

    /**
     * Creates a label via LabelFactory using iconId, pos and method getI18nStringForClass for
     * resolving the text to be used. If the icon id is null, the label will be created also. If
     * isFullLine true a LabelFactory.FullLineLabel will be created instead of a JLabel. The
     * difference between both classes are a different layout handling.
     * 
     * @param subkey the subkey which should be used for resolving the text
     * @param alternateClass the short name of the class which should be used if no string is
     * present with the runtime class name
     * @param iconId id string for the icon
     * @param pos horizontal alignment
     * @param isFullLine determines whether a FullLineLabel or a JLabel should be created
     * @return the newly created label
     */
    public JLabel createLabel(String subkey, String alternateClass, String iconId, int pos,
            boolean isFullLine)
    {
        ImageIcon ii = (iconId != null) ? parent.icons.getImageIcon(iconId) : null;
        String msg = getI18nStringForClass(subkey, alternateClass);
        JLabel label = LabelFactory.create(msg, ii, pos, isFullLine);
        if (label != null)
        {
            label.setFont(getControlTextFont());
        }
        return (label);

    }

    /**
     * Creates a label via LabelFactory with the given ids and the given horizontal alignment. If
     * the icon id is null, the label will be created also. The strings are the ids for the text in
     * langpack and the icon in icons of the installer frame.
     * 
     * @param textId id string for the text
     * @param iconId id string for the icon
     * @param pos horizontal alignment
     * @return the newly created label
     */
    public JLabel createLabel(String textId, String iconId, int pos)
    {
        return (createLabel(textId, iconId, pos, false));
    }

    /**
     * Creates a label via LabelFactory with the given ids and the given horizontal alignment. If
     * the icon id is null, the label will be created also. The strings are the ids for the text in
     * langpack and the icon in icons of the installer frame. If isFullLine true a
     * LabelFactory.FullLineLabel will be created instead of a JLabel. The difference between both
     * classes are a different layout handling.
     * 
     * @param textId id string for the text
     * @param iconId id string for the icon
     * @param pos horizontal alignment
     * @param isFullLine determines whether a FullLineLabel or a JLabel should be created
     * @return the newly created label
     */
    public JLabel createLabel(String textId, String iconId, int pos, boolean isFullLine)
    {
        ImageIcon ii = (iconId != null) ? parent.icons.getImageIcon(iconId) : null;
        JLabel label = LabelFactory.create(parent.langpack.getString(textId), ii, pos, isFullLine);
        if (label != null)
        {
            label.setFont(getControlTextFont());
        }
        return (label);

    }

    /**
     * Creates a multi line label with the language dependent text given by the text id. The strings
     * is the id for the text in langpack of the installer frame. The horizontal alignment will be
     * LEFT.
     * 
     * @param textId id string for the text
     * @return the newly created multi line label
     */
    public MultiLineLabel createMultiLineLabelLang(String textId)
    {
        return (createMultiLineLabel(parent.langpack.getString(textId)));
    }

    /**
     * Creates a multi line label with the given text. The horizontal alignment will be LEFT.
     * 
     * @param text text to be used in the label
     * @return the newly created multi line label
     */
    public MultiLineLabel createMultiLineLabel(String text)
    {
        return (createMultiLineLabel(text, null, SwingConstants.LEFT));
    }

    /**
     * Creates a label via LabelFactory with the given text, the given icon id and the given
     * horizontal alignment. If the icon id is null, the label will be created also. The strings are
     * the ids for the text in langpack and the icon in icons of the installer frame.
     * 
     * @param text text to be used in the label
     * @param iconId id string for the icon
     * @param pos horizontal alignment
     * @return the created multi line label
     */
    public MultiLineLabel createMultiLineLabel(String text, String iconId, int pos)
    {
        MultiLineLabel mll = null;
        mll = new MultiLineLabel(text, 0, 0);
        if (mll != null)
        {
            mll.setFont(getControlTextFont());
        }
        return (mll);
    }

    /**
     * The Font of Labels in many cases
     */
    public Font getControlTextFont()
    {
        Font fontObj = (getLAF() != null) ?
                          MetalLookAndFeel.getControlTextFont() : getFont();
              //if guiprefs 'labelFontSize' multiplier value
              // has been setup then apply it to the font:
        final float val;
        if ((val=LabelFactory.getLabelFontSize()) != 1.0f)
            fontObj = fontObj.deriveFont(fontObj.getSize2D()*val);
        return fontObj;
    }

    protected static MetalLookAndFeel getLAF()
    {
        LookAndFeel laf = UIManager.getLookAndFeel();
        if (laf instanceof MetalLookAndFeel) { return ((MetalLookAndFeel) laf); }
        return (null);
    }

    // ------------- Helper for common used components ----- END ---
    // ------------------- Layout stuff -------------------- START ---
    /**
     * Returns the default GridBagConstraints of this panel.
     * 
     * @return the default GridBagConstraints of this panel
     * @deprecated use <code>getLayoutHelper().getDefaulConstraints</code> instead
     */
    public GridBagConstraints getDefaultGridBagConstraints()
    {
        return (GridBagConstraints) (layoutHelper.getDefaultConstraints());
    }

    /**
     * Sets the default GridBagConstraints of this panel to the given object.
     * 
     * @param constraints which should be set as default for this object
     * @deprecated use <code>getLayoutHelper().setDefaultConstraints</code> instead
     */
    public void setDefaultGridBagConstraints(GridBagConstraints constraints)
    {
        layoutHelper.setDefaultConstraints(constraints);
    }

    /**
     * Resets the grid counters which are used at getNextXGridBagConstraints and
     * getNextYGridBagConstraints.
     * 
     * @deprecated use <code>getLayoutHelper().resetGridCounter</code> instead
     */
    public void resetGridCounter()
    {
        layoutHelper.resetGridCounter();
    }

    /**
     * Returns a newly created GridBagConstraints with the given values and the values from the
     * defaultGridBagConstraints for the other parameters.
     * 
     * @param gridx value to be used for the new constraint
     * @param gridy value to be used for the new constraint
     * @return newly created GridBagConstraints with the given values and the values from the
     * defaultGridBagConstraints for the other parameters
     * @deprecated use <code>getLayoutHelper().getNewConstraints</code> instead
     */
    public GridBagConstraints getNewGridBagConstraints(int gridx, int gridy)
    {
        return (GridBagConstraints) (layoutHelper.getNewConstraints(gridx, gridy));
    }

    /**
     * Returns a newly created GridBagConstraints with the given values and the values from the
     * defaultGridBagConstraints for the other parameters.
     * 
     * @param gridx value to be used for the new constraint
     * @param gridy value to be used for the new constraint
     * @param gridwidth value to be used for the new constraint
     * @param gridheight value to be used for the new constraint
     * @return newly created GridBagConstraints with the given values and the values from the
     * defaultGridBagConstraints for the other parameters
     * @deprecated use <code>getLayoutHelper().getNewConstraints</code> instead
     */
    public GridBagConstraints getNewGridBagConstraints(int gridx, int gridy, int gridwidth,
            int gridheight)
    {
        return (GridBagConstraints) (layoutHelper.getNewConstraints(gridx, gridy, gridwidth,
                gridheight));
    }

    /**
     * Returns a newly created GridBagConstraints for the next column of the current layout row.
     * 
     * @return a newly created GridBagConstraints for the next column of the current layout row
     * @deprecated use <code>getLayoutHelper().getNextXConstraints</code> instead
     */
    public GridBagConstraints getNextXGridBagConstraints()
    {
        return (GridBagConstraints) (layoutHelper.getNextXConstraints());
    }

    /**
     * Returns a newly created GridBagConstraints with column 0 for the next row.
     * 
     * @return a newly created GridBagConstraints with column 0 for the next row
     * @deprecated use <code>getLayoutHelper().getNextYConstraints</code> instead
     */
    public GridBagConstraints getNextYGridBagConstraints()
    {
        return (GridBagConstraints) (layoutHelper.getNextYConstraints());
    }

    /**
     * Returns a newly created GridBagConstraints with column 0 for the next row using the given
     * parameters.
     * 
     * @param gridwidth width for this constraint
     * @param gridheight height for this constraint
     * @return a newly created GridBagConstraints with column 0 for the next row using the given
     * parameters
     * @deprecated use <code>getLayoutHelper().getNextYConstraints</code> instead
     */
    public GridBagConstraints getNextYGridBagConstraints(int gridwidth, int gridheight)
    {
        return (GridBagConstraints) (layoutHelper.getNextYConstraints(gridwidth, gridheight));
    }

    /**
     * Start layout determining. If it is needed, a dummy component will be created as first row.
     * This will be done, if the IzPack guiprefs modifier with the key "layoutAnchor" has the value
     * "SOUTH" or "SOUTHWEST". The earlier used value "BOTTOM" and the declaration via the IzPack
     * variable <code>IzPanel.LayoutType</code> are also supported.
     * 
     * @deprecated use <code>getLayoutHelper().startLayout</code> instead
     */
    public void startGridBagLayout()
    {
        layoutHelper.startLayout(new GridBagLayout());
    }

    /**
     * Complete layout determining. If it is needed, a dummy component will be created as last row.
     * This will be done, if the IzPack guiprefs modifier with the key "layoutAnchor" has the value
     * "NORTH" or "NORTHWEST". The earlier used value "TOP" and the declaration via the IzPack
     * variable <code>IzPanel.LayoutType</code> are also supported.
     * 
     * @deprecated use <code>getLayoutHelper().completeLayout</code> instead
     */
    public void completeGridBagLayout()
    {
        layoutHelper.completeLayout();
    }

    // ------------------- Layout stuff -------------------- END ---

    // ------------------- Summary stuff -------------------- START ---

    /**
     * This method will be called from the SummaryPanel to get the summary of this class which
     * should be placed in the SummaryPanel. The returned text should not contain a caption of this
     * item. The caption will be requested from the method getCaption. If <code>null</code>
     * returns, no summary for this panel will be generated. Default behaviour is to return
     * <code>null</code>.
     * 
     * @return the summary for this class
     */
    public String getSummaryBody()
    {
        return null;
    }

    /**
     * This method will be called from the SummaryPanel to get the caption for this class which
     * should be placed in the SummaryPanel. If <code>null</code> returns, no summary for this
     * panel will be generated. Default behaviour is to return the string given by langpack for the
     * key <code>&lt;current class name>.summaryCaption&gt;</code> if exist, else the string
     * &quot;summaryCaption.&lt;ClassName&gt;&quot;.
     * 
     * @return the caption for this class
     */
    public String getSummaryCaption()
    {
        String caption;
        if (parent.isHeading(this) && idata.guiPrefs.modifier.containsKey("useHeadingForSummary")
                && (idata.guiPrefs.modifier.get("useHeadingForSummary")).equalsIgnoreCase("yes"))
        {
            caption = getI18nStringForClass("headline", this.getClass().getName());
        }
        else
        {
            caption = getI18nStringForClass("summaryCaption", this.getClass().getName());
        }

        return (caption);
    }

    // ------------------- Summary stuff -------------------- END ---

    // ------------------- Inner classes ------------------- START ---

    public static class Filler extends JComponent
    {

        private static final long serialVersionUID = 3258416144414095153L;

    }

    // ------------------- Inner classes ------------------- END ---

    /**
     * Returns whether this panel will be hidden general or not. A hidden panel will be not counted
     * in the step counter and for panel icons.
     * 
     * @return whether this panel will be hidden general or not
     */
    public boolean isHidden()
    {
        return hidden;
    }

    /**
     * Set whether this panel should be hidden or not. A hidden panel will be not counted in the
     * step counter and for panel icons.
     * 
     * @param hidden flag to be set
     */
    public void setHidden(boolean hidden)
    {
        this.hidden = hidden;
    }

    /**
     * Returns the used layout helper. Can be used in a derived class to create custom layout.
     * 
     * @return the used layout helper
     */
    public LayoutHelper getLayoutHelper()
    {
        return layoutHelper;
    }

    /**
     * @return the metadata
     */
    public Panel getMetadata()
    {
        return this.metadata;
    }

    /**
     * @param p the metadata to set
     */
    public void setMetadata(Panel p)
    {
        this.metadata = p;
    }

    public DataValidator getValidationService()
    {
        return validationService;
    }

    public void setValidationService(DataValidator validationService)
    {
        this.validationService = validationService;
    }

    /*--------------------------------------------------------------------------*/
    /**
     * This method validates the field content. Validating is performed through a user supplied
     * service class that provides the validation rules.
     * 
     * @return <code>true</code> if the validation passes or no implementation of a validation
     * rule exists. Otherwise <code>false</code> is returned.
     */
    /*--------------------------------------------------------------------------*/
    private final boolean validatePanel()
    {
        boolean returnValue = false;
        if (this.validationService != null)
        {
            Component guiComponent = getTopLevelAncestor();
            Cursor originalCursor = guiComponent.getCursor();
            Cursor newCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
            try
            {
                guiComponent.setCursor(newCursor);
                // validating the data
                DataValidator.Status returnStatus = this.validationService.validateData(this.idata);
                if (returnStatus == DataValidator.Status.OK)
                {
                    returnValue = true;
                }
                else
                {
                    Debug.trace("Validation did not pass!");
                    // try to parse the text, and substitute any variable it finds
                    VariableSubstitutor vs = new VariableSubstitutor(idata.getVariables());
                    if (this.validationService.getWarningMessageId() != null
                            && returnStatus == DataValidator.Status.WARNING)
                    {

                        String warningMessage = parent.langpack.getString(this.validationService
                                .getWarningMessageId());
                        if (this.emitWarning(getString("data.validation.warning.title"), vs
                                .substitute(warningMessage, null)))
                        {
                            returnValue = true;
                            Debug.trace("... but user decided to go on!");
                        }
                    }
                    else
                    {
                        String errorMessage = parent.langpack.getString(this.validationService
                                .getErrorMessageId());
                        this.emitError(getString("data.validation.error.title"), vs.substitute(
                                errorMessage, null));

                    }
                }
            }
            finally
            {
                guiComponent.setCursor(originalCursor);
            }
        }
        else
        {
            returnValue = true;
        }
        return returnValue;
    }

    /**
     * Parses the text for special variables.
     */
    protected String parseText(String string_to_parse)
    {
        try
        {
            // Initialize the variable substitutor
            VariableSubstitutor vs = new VariableSubstitutor(idata.getVariables());

            // Parses the info text
            string_to_parse = vs.substitute(string_to_parse, null);
        }
        catch (Exception err)
        {
            err.printStackTrace();
        }
        return string_to_parse;
    }

    private HashMap<String, String> helps = null;

    public void setHelps(HashMap helps)
    {
        this.helps = helps;
    }

    public String getHelpUrl(String isoCode)
    {
        String url = null;
        if (this.helps != null)
        {
            url = helps.get(isoCode);
        }
        return url;
    }

    /**
     * Indicates wether the panel can display help. The installer will hide Help button if current
     * panel does not support help functions. Default behaviour is to return <code>false</code>.
     * 
     * @return A boolean stating wether the panel supports Help function.
     */
    public boolean canShowHelp()
    {
        return getHelpUrl(this.idata.localeISO3) != null;
    }

    /**
     * This method is called when Help button has been clicked. By default it doesn't do anything.
     */
    public void showHelp()
    {
        String helpName = getHelpUrl(this.idata.localeISO3);
        // System.out.println("Help function called, helpName: " + helpName);
        if (helpName != null)
        {
            URL helpUrl = getClass().getResource("/res/" + helpName);
            getHelpWindow().showHelp(getString("installer.help"), helpUrl);
        }
    }

    private HelpWindow helpWindow = null;

    private HelpWindow getHelpWindow()
    {
        if (this.helpWindow != null) { return this.helpWindow; }

        this.helpWindow = new HelpWindow(parent, getString("installer.prev"));
        return this.helpWindow;
    }

    public void addPreActivationAction(PanelAction preActivateAction)
    {
        if (preActivateActions == null)
        {
            preActivateActions = new ArrayList<PanelAction>();
        }
        this.preActivateActions.add(preActivateAction);
    }

    public void addPreValidationAction(PanelAction preValidateAction)
    {
        if (preValidateActions == null)
        {
            preValidateActions = new ArrayList<PanelAction>();
        }
        this.preValidateActions.add(preValidateAction);
    }

    public void addPostValidationAction(PanelAction postValidateAction)
    {
        if (postValidateActions == null)
        {
            postValidateActions = new ArrayList<PanelAction>();
        }
        this.postValidateActions.add(postValidateAction);
    }
    
    protected final void executePreActivationActions()
    {
        if (preActivateActions != null)
        {
            for (int actionIndex = 0; actionIndex < preActivateActions.size(); actionIndex++)
            {
                preActivateActions.get(actionIndex).executeAction(idata, this);
            }
        }
    }

    protected final void executePreValidationActions()
    {
        if (preValidateActions != null)
        {
            for (int actionIndex = 0; actionIndex < preValidateActions.size(); actionIndex++)
            {
                preValidateActions.get(actionIndex).executeAction(idata, this);
            }
        }
    }

    protected final void executePostValidationActions()
    {
        if (postValidateActions != null)
        {
            for (int actionIndex = 0; actionIndex < postValidateActions.size(); actionIndex++)
            {
                postValidateActions.get(actionIndex).executeAction(idata, this);
            }
        }
    }

}
