/* class JOptionPane
 *
 * Copyright (C) 2001-2003  R M Pitman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package charvax.swing;

import charva.awt.*;
import charva.awt.event.ActionEvent;
import charva.awt.event.ActionListener;
import charva.awt.event.KeyEvent;
import charva.awt.event.KeyListener;
import charvax.swing.border.EmptyBorder;

/**
 * <p>JOptionPane makes it easy to pop up a standard dialog box that prompts
 * the user for information or displays some information.</p>
 * <p/>
 * <p>The labels of the option-buttons displayed within the popup dialog can be
 * customized by changing static variables in the JOptionPane class.
 * Similarly, "accelerator keys" can be set to have the same effect
 * as the option-buttons. For example, the following code would set
 * the label of the OK button to "OK (F1)", and set the F1 key to
 * have the same effect as the OK button:</p>
 * <pre>
 * 	JOptionPane.OK_LABEL = "OK (F1)";
 * 	JOptionPane.OK_ACCELERATOR = KeyEvent.VK_F1;
 * </pre>
 * <p>Note that after the buttons have been customized, they stay customized
 * for all future invocations of the JOptionPane "showXXXDialog()" methods.</p>
 * <p/>
 * <p>The parameters to these methods follow consistent patterns:</p>
 * <blockquote>
 * <dl>
 * <dt>parentComponent
 * <dd>Defines the Component that is to be the parent of this dialog
 * box. It is used in two ways: its screen coordinates are
 * used in the placement of the dialog box, and the dialog box inherits its
 * foreground and background colors from <code>parentComponent</code> (unless
 * the the JOptionPane's colors have been set explicitly). In general, the
 * dialog box centered on top of <code>parentComponent</code>. This parameter
 * may be null, in which case a default Frame is used as the parent, and
 * the dialog will be centered on the screen.<p>
 * <p/>
 * <dt>message
 * <dd>A descriptive message to be placed in the dialog box. In the
 * most common usage, message is just a String or String constant.
 * However, the type of this parameter is actually Object. Its
 * interpretation depends on its type:
 * <p/>
 * <blockquote>
 * <dl>
 * <dt>String
 * <dd>The string is displayed as a message on one line.
 * <dt>Object[]
 * <dd>An array of Objects is interpreted as a series of messages
 * arranged in a vertical stack. Each Object is converted to a String
 * using its toString() method. The result is wrapped in a JLabel and displayed.
 * </dl>
 * </blockquote>
 * <p/>
 * <dt>messageType
 * <dd>Defines the style of the message. The possible values are:
 * <ul>
 * <li><code>ERROR_MESSAGE</code>
 * <li><code>INFORMATION_MESSAGE</code>
 * <li><code>WARNING_MESSAGE</code>
 * <li><code>QUESTION_MESSAGE</code>
 * <li><code>PLAIN_MESSAGE </code>
 * </ul><p>
 * <dt>optionType
 * <dd>Defines the set of option buttons that appear at the bottom of the dialog box:
 * <ul>
 * <li><code>DEFAULT_OPTION</code>
 * <li><code>YES_NO_OPTION</code>
 * <li><code>YES_NO_CANCEL_OPTION</code>
 * <li><code>OK_CANCEL_OPTION</code>
 * </ul>
 * You aren't limited to this set of option buttons. You can
 * provide any buttons you want using the <code>options</code> parameter.<p>
 * <p/>
 * <dt>options
 * <dd>A more detailed description of the set of option buttons that
 * will appear at the bottom of the dialog box. The usual value for the
 * options parameter is an array of Strings. But the parameter type is
 * an array of Objects. A button is created for each object depending on
 * its type:<p>
 * <dl>
 * <dt>Component
 * <dd>The component is added to the button row directly.
 * <dt>other
 * <dd>The Object is converted to a string using its toString method
 * and the result is used to label a JButton.
 * </dl>
 * <p/>
 * <dt>icon
 * <dd>This parameter is used in javax.Swing to specify a decorative
 * icon to be placed in the dialog box. It is ignored by Charva.<p>
 * <dt>title
 * <dd>The title for the dialog box. <p>
 * <dt>initialValue
 * <dd>The default selection (input value). <p>
 * </dl>
 * </blockquote>
 */
public class JOptionPane {

    /**
     * Creates a JOptionPane with a test message
     */
    public JOptionPane() {
        this("This is a test message", PLAIN_MESSAGE, DEFAULT_OPTION, null, null, null);
    }

    /**
     * Creates an instance of JOptionPane to display the specified message.
     *
     * @param message_ the message to display. It can be a String
     *                 or an array of Strings. If it is an array of Strings, they are
     *                 stacked vertically.
     */
    public JOptionPane(Object message_) {
        this(message_, PLAIN_MESSAGE, DEFAULT_OPTION, null, null, null);
    }

    /**
     * Creates an instance of JOptionPane to display the specified message.
     *
     * @param message_     the message to display. It can be a String
     *                     or an array of Strings.
     * @param messagetype_ is ignored. It is used in javax.swing to
     *                     determine which icon to display.
     */
    public JOptionPane(Object message_, int messagetype_) {
        this(message_, messagetype_, DEFAULT_OPTION, null, null, null);
    }

    /**
     * Creates an instance of JOptionPane to display the specified message.
     *
     * @param message_     the message to display. It can be a String
     *                     or an array of Strings.
     * @param messagetype_ is ignored. It is used in javax.swing to
     *                     determine which icon to display.
     * @param optiontype_  determines which option-buttons to display. Allowed
     *                     values are: DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION, and
     *                     OK_CANCEL_OPTION.
     */
    public JOptionPane(Object message_, int messagetype_, int optiontype_) {
        this(message_, messagetype_, optiontype_, null, null, null);
    }

    /**
     * Creates an instance of JOptionPane to display the specified message.
     *
     * @param message_      the message to display. It can be a String
     *                      or an array of Strings.
     * @param messageType_  is ignored. It is used in javax.swing to
     *                      determine which icon to display.
     * @param optionType_   determines which option-buttons to display. Allowed
     *                      values are: DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION, and
     *                      OK_CANCEL_OPTION.
     * @param icon_         the Icon image to display (not used by Charva; it is here
     *                      for compatibility with Swing).
     * @param options_      the choices the user can select
     * @param initialValue_ the choice that is initially selected; if null,
     *                      then nothing will be initially selected; only meaningful if "options"
     *                      is used
     */
    public JOptionPane(Object message_, int messageType_, int optionType_,
                       Object icon_, Object[] options_, Object initialValue_) {
        _message = message_;
        _messagetype = messageType_;
        _optiontype = optionType_;
        _options = options_;
        _initialValue = initialValue_;
    }

    /**
     * If newvalue_ is true, a JTextField will be displayed for
     * the user to provide text input.
     */
    public void setWantsInput(boolean newvalue_) {
        _wantsInput = newvalue_;
    }

    /**
     * Returns the value of _wantsInput.
     */
    public boolean getWantsInput() {
        return _wantsInput;
    }

    /**
     * Sets the default input value that is displayed to the user.
     * Only used if <code>_wantsInput</code> is true.
     */
    public void setInitialSelectionValue(Object value_) {
        _inputValue = (String) value_;
    }

    /**
     * Sets the initial value of the text field for the user to modify.
     */
    public void setInputValue(Object value_) {
        _inputValue = (String) value_;
    }

    /**
     * Returns the value the user has input, (relevant only if
     * _wantsInput is true).
     */
    public Object getInputValue() {
        return _inputValue;
    }

    /**
     * Sets the options this pane displays in the button-panel at
     * the bottom. If an element in
     * newOptions is an instance of a subclass of AbstractButton (for
     * example, a JButton), it is added directly to the pane,
     * otherwise a button is created for the element. The advantage
     * of adding a button rather than a string is that a mnemonic
     * can be set for the button.
     */
    public void setOptions(Object[] newOptions_) {
        _options = newOptions_;
    }

    /**
     * Returns the choices the user can make.
     */
    public Object[] getOptions() {
        return _options;
    }

    /**
     * Sets the initial value that is to be enabled -- the Component
     * that has the focus when the pane is initially displayed.
     * (NOT IMPLEMENTED YET).
     */
    public void setInitialValue(Object initialValue_) {
    }

    /**
     * Returns the initial value that is to be enabled -- the Component
     * that has the focus when the pane is initially displayed.
     * (NOT IMPLEMENTED YET).
     */
    public Object getInitialValue() {
        return null;
    }

    /**
     * Creates and returns a new JDialog for displaying the required
     * message. The dialog inherits the foreground and background colors
     * of the <code>owner_</code> component and is centered on it.
     * If <code>owner_</code> is null, default (black/white) colors are used.
     */
    public JDialog createDialog(Component owner_, String title_) {

        /* This is a non-static inner class used for a popup dialog.
         */
        JOptionPane.Popup dlg = null;

        dlg = this.new Popup(owner_, _message, title_);
        if (owner_ != null)
            dlg.setLocationRelativeTo(owner_);

        return dlg;
    }

    /**
     * Brings up a dialog where the number of choices is dependent on the
     * value of the optiontype_ parameter.
     *
     * @param parent_     Determines the frame in which the dialog is displayed.
     * @param message_    the String to display.
     * @param title_      the title of the dialog.
     * @param optiontype_ must be YES_NO_OPTION or YES_NO_CANCEL_OPTION.
     */
    public static int showConfirmDialog(Component parent_,
                                        Object message_, String title_, int optiontype_) {

        JOptionPane pane = new JOptionPane(message_, PLAIN_MESSAGE, optiontype_);
        Popup dialog = (Popup) pane.createDialog(parent_, title_);
        dialog.show();
        return ((Integer) pane.getValue()).intValue();
    }

    /**
     * Brings up a dialog that allows the user to input a value.
     *
     * @param parent_      Determines the frame in which the dialog is displayed.
     * @param message_     the String to display.
     * @param title_       the title of the dialog.
     * @param messagetype_ is ignored (it is used in javax.swing to determine
     *                     which icon to display).
     */
    public static String showInputDialog(Component parent_,
                                         Object message_, String title_, int messagetype_) {

        JOptionPane pane = new JOptionPane(message_,
                messagetype_, OK_CANCEL_OPTION);
        pane._wantsInput = true;
        Popup dialog = (Popup) pane.createDialog(parent_, title_);
        dialog.show();
        int option = ((Integer) pane.getValue()).intValue();
        if (option == CANCEL_OPTION)
            return null;
        else
            return (String) pane.getInputValue();
    }

    /**
     * Brings up a confirmation dialog titled "Confirm"
     */
    public static void showMessageDialog(Component parent_, Object message_) {
        showMessageDialog(parent_, message_, "Confirm", DEFAULT_OPTION);
    }

    /**
     * Brings up a confirmation dialog with the specified title.  The
     * msgtype parameter is ignored (it is used in the javax.swing package to
     * specify an icon to display).
     */
    public static void showMessageDialog(Component parent_,
                                         Object message_, String title_, int msgtype_) {

        JOptionPane pane = new JOptionPane(message_, msgtype_, DEFAULT_OPTION);
        JDialog dialog = pane.createDialog(parent_, title_);
        dialog.show();
    }

    /**
     * Returns the option the user has selected.
     */
    public Object getValue() {
        return _value;
    }

    // INSTANCE VARIABLES
    protected Object _message;
    protected int _messagetype;

    /**
     * Determines which option buttons to display (unless an array
     * of options is explicitly specified with <code>setOptions()</code>).
     */
    protected int _optiontype;

    /**
     * If true, an TextField will be displayed for the user to provide
     * input.
     */
    protected boolean _wantsInput = false;

    protected String _inputValue = "";

    /**
     * Array of options to display to the user in the bottom button-panel.
     * The objects in this array can be any combination of Strings or
     * components which are subclasses of AbstractButton.
     * Buttons are just added to the bottom button-panel; Strings are
     * wrapped in a JButton which is then added to the button-panel.
     */
    protected Object[] _options;

    /**
     * Option that should be initially selected in <code>_options</code>.
     */
    protected Object _initialValue;

    /**
     * The currently selected option.
     */
    protected Object _value;

    // Message types
    public static final int ERROR_MESSAGE = 100;
    public static final int INFORMATION_MESSAGE = 101;
    public static final int WARNING_MESSAGE = 102;
    public static final int QUESTION_MESSAGE = 103;
    public static final int PLAIN_MESSAGE = 104;

    // Option types
    public static final int DEFAULT_OPTION = 200;
    public static final int YES_NO_OPTION = 201;
    public static final int YES_NO_CANCEL_OPTION = 202;
    public static final int OK_CANCEL_OPTION = 203;

    // Return values
    public static final int YES_OPTION = 300;
    public static final int NO_OPTION = 301;
    public static final int CANCEL_OPTION = 302;
    public static final int OK_OPTION = 303;
    public static final int CLOSED_OPTION = 304;

    // Label values - can be changed to customize appearance
    public static String YES_LABEL = "Yes";
    public static String NO_LABEL = "No";
    public static String CANCEL_LABEL = "Cancel";
    public static String OK_LABEL = "OK";

    // Accelerator keystrokes - can be customized.
    public static int YES_ACCELERATOR = -1;
    public static int NO_ACCELERATOR = -1;
    public static int CANCEL_ACCELERATOR = -1;
    public static int OK_ACCELERATOR = -1;

    //====================================================================

    /**
     * This is a non-static inner class used for the popup dialog that
     * JOptionPane creates.
     */
    private class Popup
            extends JDialog
            implements ActionListener, KeyListener {
        /**
         * Constructor
         */
        Popup(Component owner_, Object message_, String title_) {

            super();

            // The window inherits the colors of its parent if there is one,
            // otherwise the default colors are used.
            if (owner_ != null) {
                super.setForeground(owner_.getForeground());
                super.setBackground(owner_.getBackground());
            } else {
                super.setForeground(Toolkit.getDefaultForeground());
                super.setBackground(Toolkit.getDefaultBackground());
            }
            setTitle(title_);

            _ownerComponent = owner_;
            setLayout(new BorderLayout());

            JPanel northpan = new JPanel();
            northpan.setBorder(new EmptyBorder(2, 2, 1, 2));

            JPanel messagepanel = new JPanel();
            if (message_ instanceof String) {
                messagepanel.add(new JLabel((String) message_));
            } else if (message_ instanceof Object[]) {
                messagepanel.setLayout(new BoxLayout(getOwner(), BoxLayout.Y_AXIS));

                Object[] objects = (Object[]) message_;
                for (int i = 0; i < objects.length; i++) {
                    messagepanel.add(new JLabel(objects[i].toString()));
                }
            } else {
                throw new IllegalArgumentException("illegal message type " +
                        message_.getClass().getName());
            }

            northpan.add(messagepanel);
            add(northpan, BorderLayout.NORTH);

            if (_wantsInput) {
                JPanel centerpan = new JPanel();
                centerpan.setBorder(new EmptyBorder(1, 1, 1, 1));
                _inputField = new JTextField(_inputValue, 20);
                _inputField.addActionListener(this);
                centerpan.add(_inputField);
                add(centerpan, BorderLayout.CENTER);
            }

            JPanel southpan = new JPanel();
            southpan.setLayout(new FlowLayout(FlowLayout.CENTER, 1, 1));

            if (_options != null) {
                // Option buttons were explicitly specified.
                for (int i = 0; i < _options.length; i++) {
                    AbstractButton button = null;
                    if (_options[i] instanceof String)
                        button = new JButton((String) _options[i]);
                    else if (_options[i] instanceof AbstractButton)
                        button = (AbstractButton) _options[i];
                    button.addActionListener(this);
                    southpan.add(button);
                }
            } else {
                // Decide which option buttons to display, based on the
                // value of _optiontype.
                if (_optiontype == DEFAULT_OPTION ||
                        _optiontype == OK_CANCEL_OPTION) {

                    _okButton = new JButton("OK");
                    _okButton.setText(OK_LABEL);
                    _okButton.addActionListener(this);
                    southpan.add(_okButton);
                }
                if (_optiontype == YES_NO_OPTION ||
                        _optiontype == YES_NO_CANCEL_OPTION) {

                    _yesButton = new JButton("Yes");
                    _yesButton.setText(YES_LABEL);
                    _yesButton.addActionListener(this);
                    southpan.add(_yesButton);
                }
                if (_optiontype == YES_NO_OPTION ||
                        _optiontype == YES_NO_CANCEL_OPTION) {

                    _noButton = new JButton("No");
                    _noButton.setText(NO_LABEL);
                    _noButton.addActionListener(this);
                    southpan.add(_noButton);
                }
                if (_optiontype == YES_NO_CANCEL_OPTION ||
                        _optiontype == OK_CANCEL_OPTION) {

                    _cancelButton = new JButton("Cancel");
                    _cancelButton.setText(CANCEL_LABEL);
                    _cancelButton.addActionListener(this);
                    southpan.add(_cancelButton);
                }
            }
            add(southpan, BorderLayout.SOUTH);

            setSize(Toolkit.getDefaultToolkit().getScreenSize());
            pack();
            Dimension msgsize = messagepanel.getSize();
            setSize(msgsize.width + 8, msgsize.height + 9);
            pack();

            /* Center the dialog over its parent component.
             */
            Dimension ourSize = getSize();
            if (_ownerComponent != null) {
                Point ownerOrigin = _ownerComponent.getLocationOnScreen();
                Dimension ownerSize = _ownerComponent.getSize();
                Point ownerCenter = ownerOrigin.addOffset(ownerSize.width / 2, ownerSize.height / 2);
                setLocation(ownerCenter.addOffset(-ourSize.width / 2, -ourSize.height / 2));
            } else {
                /* The parent component was not specified. Center this
                 * dialog box in the middle of the screen.
                 */
                Dimension screensize =
                        Toolkit.getDefaultToolkit().getScreenSize();
                Point screenCenter = new Point(screensize.width / 2,
                        screensize.height / 2);
                setLocation(screenCenter.addOffset(-ourSize.width / 2, -ourSize.height / 2));
            }

            // Add a KeyListener in case one or more accelerators were set.
            addKeyListener(this);
        }

        /**
         * Gets called when the user presses an option-button (or
         * if ENTER is pressed while focus is in the TextField).
         */
        public void actionPerformed(ActionEvent e_) {
            if (_wantsInput)
                _inputValue = _inputField.getText();

            if (_options != null) {
                // Options were specified explicitly.
                // So ignore ENTER if pressed while focus is in textfield.
                if (e_.getSource() == _inputField)
                    return;

                AbstractButton source = (AbstractButton) e_.getSource();
                for (int i = 0; i < _options.length; i++) {
                    if (_options[i] instanceof String &&
                            source.getText().equals(_options[i])) {

                        _value = _options[i];
                        break;
                    } else if (source == _options[i]) {
                        _value = source;
                        break;
                    }
                }
            } else {
                // Options were not specified explicitly.
                Object source = e_.getSource();
                if (source == _okButton || source == _inputField) {
                    _value = new Integer(JOptionPane.OK_OPTION);
                } else if (source == _yesButton) {
                    _value = new Integer(JOptionPane.YES_OPTION);
                } else if (source == _noButton) {
                    _value = new Integer(JOptionPane.NO_OPTION);
                } else if (source == _cancelButton) {
                    _value = new Integer(JOptionPane.CANCEL_OPTION);
                }
            }

            hide();
        }

        public void keyPressed(KeyEvent e_) {
            int key = e_.getKeyCode();
            if (key == OK_ACCELERATOR &&
                    (_optiontype == DEFAULT_OPTION ||
                    _optiontype == OK_CANCEL_OPTION)) {

                _value = new Integer(JOptionPane.OK_OPTION);
                _inputValue = _inputField.getText();
                hide();
            } else if (key == YES_ACCELERATOR &&
                    (_optiontype == YES_NO_OPTION ||
                    _optiontype == YES_NO_CANCEL_OPTION)) {
                _value = new Integer(JOptionPane.YES_OPTION);
                _inputValue = _inputField.getText();
                hide();
            } else if (key == NO_ACCELERATOR &&
                    (_optiontype == YES_NO_OPTION ||
                    _optiontype == YES_NO_CANCEL_OPTION)) {
                _value = new Integer(JOptionPane.NO_OPTION);
                hide();
            } else if (key == CANCEL_ACCELERATOR &&
                    (_optiontype == YES_NO_CANCEL_OPTION ||
                    _optiontype == OK_CANCEL_OPTION)) {
                _value = new Integer(JOptionPane.CANCEL_OPTION);
                hide();
            }
        }

        public void keyTyped(KeyEvent e_) {
        }

        public void keyReleased(KeyEvent e_) {
        }

        private Component _ownerComponent;
        private JButton _okButton;
        private JButton _yesButton;
        private JButton _noButton;
        private JButton _cancelButton;
        private JTextField _inputField = new JTextField(20);
    }
}
