/**
 * ThisTime 2.0 (2008-03-30)
 * Copyright 2007 Zach Scrivena
 * zachscrivena@gmail.com
 * http://thistime.sourceforge.net/
 *
 * Simple clock and timer program.
 *
 * TERMS AND CONDITIONS:
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package thistime;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;


/**
 * Perform miscellaneous Swing-related operations.
 */
class SwingManipulator
{
    /**
    * Add a standard editing popup menu (Cut, Copy, Paste, Select All)
    * to the specified text fields.
    *
    * @param fields
    *      array of JTextField's for which to add the popup menu
    */
    static void addStandardEditingPopupMenu(
            final JTextField[] fields)
    {
        final JPopupMenu popupMenu = new JPopupMenu();

        /* text fields popup menu: "Cut" */
        final JMenuItem cutMenuItem = new JMenuItem("Cut", 't');
        cutMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                final Component c = popupMenu.getInvoker();

                if (c instanceof JTextField)
                {
                    ((JTextField) c).cut();
                }
            }
        });
        popupMenu.add(cutMenuItem);

        /* text fields popup menu: "Copy" */
        final JMenuItem copyMenuItem = new JMenuItem("Copy", 'C');
        copyMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                final Component c = popupMenu.getInvoker();

                if (c instanceof JTextField)
                {
                    ((JTextField) c).copy();
                }
            }
        });
        popupMenu.add(copyMenuItem);

        /* text fields popup menu: "Paste" */
        final JMenuItem pasteMenuItem = new JMenuItem("Paste", 'P');
        pasteMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                final Component c = popupMenu.getInvoker();

                if (c instanceof JTextField)
                {
                    ((JTextField) c).paste();
                }
            }
        });
        popupMenu.add(pasteMenuItem);
        popupMenu.addSeparator();

        /* text fields popup menu: "Select All" */
        final JMenuItem selectAllMenuItem = new JMenuItem("Select All", 'A');
        selectAllMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                final Component c = popupMenu.getInvoker();

                if (c instanceof JTextField)
                {
                    ((JTextField) c).selectAll();
                }
            }
        });
        popupMenu.add(selectAllMenuItem);

        /* add mouse listeners to the specified fields */
        for (final JTextField f : fields)
        {
            f.addMouseListener(new MouseAdapter()
            {
                @Override
                public void mousePressed(MouseEvent e)
                {
                    processMouseEvent(e);
                }

                @Override
                public void mouseReleased(MouseEvent e)
                {
                    processMouseEvent(e);
                }

                private void processMouseEvent(MouseEvent e)
                {
                    if (e.isPopupTrigger())
                    {
                        popupMenu.show(e.getComponent(), e.getX(), e.getY());
                        popupMenu.setInvoker(f);
                    }
                }
            });
        }
    }


    /**
    * Wrapper for the getText() method of a JTextField that always returns a String.
    *
    * @param f
    *      JTextField object on which to call getText()
    * @return
    *      String text in the JTextField
    */
    static String getTextJTextField(
            final JTextField f)
    {
        try
        {
            return f.getText();
        }
        catch (NullPointerException e)
        {
            return "";
        }
    }


    /**
    * Wrapper for the getPassword() method of a JPasswordField that always returns a char array.
    *
    * @param f
    *      JPasswordField object on which to call getPassword()
    * @return
    *      char array representing the text in the JPasswordField
    */
    static char[] getPasswordJPasswordField(
            final JPasswordField f)
    {
        try
        {
            return f.getPassword();
        }
        catch (NullPointerException e)
        {
            return new char[0];
        }
    }


    /**
    * Update progress bar.
    *
    * @param progressBar
    *     JProgressBar object to be updated
    * @param text
    *     Text string to be shown on the progress bar
    * @param percent
    *     Percentage of the task completed (if less than 0 or more than 100,
    *     then indeterminate mode is used)
    */
    static void updateProgressBar(
            final JProgressBar progressBar,
            final String text,
            final int percent)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                if ((percent < 0) || (percent > 100))
                {
                    /* indeterminate mode */
                    progressBar.setString(text);
                    progressBar.setIndeterminate(true);
                }
                else
                {
                    /* determinate mode */
                    progressBar.setValue(percent);
                    progressBar.setString(text);
                    progressBar.setIndeterminate(false);
                }
            }
        });
    }


    /**
    * Convenience method to display a JOptionPane option dialog with a label
    * and text area.
    *
    * @param parentComponent
    *     Frame in which the dialog is to be displayed; if null, or if the
    *     parentComponent has no Frame, then a default Frame is used
    * @param label
    *     Label String to be displayed
    * @param text
    *     Text String to be displayed in the text area
    * @param rows
    *     Height of the text area in number of rows
    * @param title
    *     Title String for the dialog
    * @param optionType
    *     Options available for the dialog: JOptionPane.DEFAULT_OPTION,
    *     YES_NO_OPTION, YES_NO_CANCEL_OPTION, or OK_CANCEL_OPTION
    * @param messageType
    *     Type of message; primarily used to determine the icon from the
    *     pluggable Look and Feel: JOptionPane.ERROR_MESSAGE,
    *     INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE,
    *     or PLAIN_MESSAGE
    * @param icon
    *     Icon to be displayed
    * @param Object[] options
    *     Array of objects indicating the possible choices the user can make;
    *     if the objects are components, they are rendered properly;
    *     non-String objects are rendered using their toString methods;
    *     if this parameter is null, the options are determined by the
    *     Look and Feel
    * @param initialValue
    *      Index of the default selection for the dialog; meaningful
    *      only if options is used; ignored if negative
    * @return
    *      Index of the option chosen by the user, or JOptionPane.CLOSED_OPTION
    *      if the user closed the dialog
    * @throw HeadlessException
    *     If GraphicsEnvironment.isHeadless returns true
    */
    static int showOptionTextDialog(
            final Component parentComponent,
            final String label,
            final String text,
            final int rows,
            final String title,
            final int optionType,
            final int messageType,
            final Icon icon,
            final Object[] options,
            final int initialValue) throws HeadlessException
    {
        class ChoiceDialog implements Runnable
        {
            /**
            * Index of the option chosen by the user, or CLOSED_OPTION if
            * the user closed the dialog.
            */
            public int choiceIndex;

            @Override
            public void run()
            {
                Object initialOption = null;

                if ((options != null) && (initialValue >= 0) && (initialValue < options.length))
                    initialOption = options[initialValue];

                final JPanel panel = new JPanel(new BorderLayout());

                panel.add(new JLabel(label + ":"), BorderLayout.NORTH);

                final JTextArea textArea = new JTextArea(text, rows, 50);
                textArea.setEditable(false);
                textArea.setWrapStyleWord(true);
                textArea.setLineWrap(true);
                textArea.setToolTipText(label);
                textArea.setFont(new Font(
                        Font.DIALOG,
                        Font.PLAIN,
                        textArea.getFont().getSize() - 2));

                panel.add(new JScrollPane(
                        textArea,
                        JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
                        BorderLayout.CENTER);

                choiceIndex = JOptionPane.showOptionDialog(
                        parentComponent,
                        panel,
                        title,
                        optionType,
                        messageType,
                        icon,
                        options,
                        initialOption);
            }
        }

        final ChoiceDialog r = new ChoiceDialog();

        if (SwingUtilities.isEventDispatchThread())
        {
            r.run();
        }
        else
        {
            try
            {
                SwingUtilities.invokeAndWait(r);
            }
            catch (Exception e)
            {
                /* ignore */
            }
        }

        return r.choiceIndex;
    }


    /**
    * Display a modal error dialog.
    *
    * @param parent
    *     Parent component of this dialog
    * @param title
    *     Title of dialog
    * @param message
    *     Error message to be displayed
    */
    static void showErrorDialog(
                final Component parent,
                final String title,
                final String message)
    {
        showOptionTextDialog(
                parent,
                "An error has occurred",
                message,
                5,
                title,
                JOptionPane.DEFAULT_OPTION,
                JOptionPane.ERROR_MESSAGE,
                null,
                null,
                0);
    }


    /**
    * Display a modal warning dialog.
    *
    * @param parent
    *     Parent component of this dialog
    * @param title
    *     Title of dialog
    * @param message
    *     Warning message to be displayed
    */
    static void showWarningDialog(
                final Component parent,
                final String title,
                final String message)
    {
        showOptionTextDialog(
                parent,
                "A warning has been issued",
                message,
                5,
                title,
                JOptionPane.DEFAULT_OPTION,
                JOptionPane.WARNING_MESSAGE,
                null,
                null,
                0);
    }


    /**
    * Display a modal information dialog.
    *
    * @param parent
    *     Parent component of this dialog
    * @param title
    *     Title of dialog
    * @param label
    *     Label string to be displayed
    * @param message
    *     Information message to be displayed
    * @param rows
    *     Height of the text area in number of rows
    */
    static void showInfoDialog(
                final Component parent,
                final String title,
                final String label,
                final String message,
                final int rows)
    {
        showOptionTextDialog(
                parent,
                label,
                message,
                rows,
                title,
                JOptionPane.DEFAULT_OPTION,
                JOptionPane.INFORMATION_MESSAGE,
                null,
                null,
                0);
    }
}