/*
 * @(#)InputDialog.java    1.0.1  29 May 2001
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.*;
import edu.neu.ccs.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

/**
 * <P>A modal dialog box for input of 
 * a <CODE>{@link Stringable Stringable}</CODE> object
 * that also provides static convenience methods
 * for input using a provided input component.</P>
 *
 * <P>The input properties for the input object 
 * determine the input model for the dialog box.
 *
 * In the mandatory model, OK and Reset buttons are provided.
 *
 * In the optional model, a Cancel button is provided
 * in addition to the buttons provided for the mandatory model
 *
 * If a suggested view state is present 
 * in the input properties for the given input component,
 * a Suggest button is also provided.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.0
 */
public class InputDialog 
    extends JPTDialog 
    implements JPTConstants {
    
    /** 
     * Object whose state is to be set 
     * through this dialog input operation. 
     */
    protected Stringable model = null;
    
    /** Whether or not this dialog was dismissed by cancellation. */
    protected boolean cancelled = false;
    
    /////////////////
    // Constructor //
    /////////////////

    /**
     * Constructor for a modal dialog box
     * for input using the given input component, 
     * with the input model and its associated controls 
     * based on the input properties of the input component.
     *
     * @param viewObject the graphical interface object 
     *      to use for input
     * @throws NullPointerException if the given input component
     *      is <CODE>null</CODE>
     */
     public InputDialog(TypedView viewObject) {
        super(getCompoundView(viewObject), null, (Frame)null, true);

        setResizable(false);

        // install gutter around dialog contents
        JPanel pane = (JPanel)getContentPane();
        pane.setBorder(new EmptyBorder(20, 20, 20, 20));

        // get properties for this input operation
        InputProperties properties = view.getInputProperties();        

        // set dialog title
        setTitle((String)properties.getProperty(
            InputProperties.DIALOG_TITLE));
        
        // establish local control of window events
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        createWindowListener(properties);
        
        // build the buttons for the dialog
        createActionsPanel(properties);

        // size appropriately and center on screen
        pack();
        center();
     }

    ////////////////
    // Public API //
    ////////////////
    
    /**
     * Returns the data model whose state was set 
     * through this dialog input operation.
     *
     * If this method is called before the dialog has been dismissed,
     * the model may not yet have had its state set.
     */
    public Stringable getModel() {
        return model;
    }
    
    /**
     * Returns whether or not 
     * this dialog was dismissed by cancellation.
     *
     * If this method is called before the dialog has been dismissed,
     * this method will return <CODE>false</CODE>.     
     */
    public boolean wasCancelled() {
        return cancelled;
    }
    
    ////////////////////
    // Static methods //
    ////////////////////
    
    /**
     * Shows a dialog for mandatory input using the given view.
     *
     * @param viewObject the input component to be used
     * @return the <CODE>Stringable</CODE> object 
     *      whose state is to be set
     *      through this dialog input operation
     * @throws NullPointerException if the given input component
     *      is <CODE>null</CODE>
     */
    public static Stringable showDemandDialog(
        TypedView viewObject) 
    {
        // perform the input operation
        InputDialog dialog = 
            showInputDialog(viewObject, JPTConstants.MANDATORY);

        // return the result of the input operation
        return dialog.getModel();
    }
    
    /**
     * Shows a dialog for optional input using the given view.
     *
     * @param viewObject the input component to be used
     * @return the <CODE>Stringable</CODE> object 
     *      whose state is to be set
     *      through this dialog input operation
     * @throws NullPointerException if the given input component
     *      is <CODE>null</CODE>
     * @throws CancelledException 
     *      if the user cancels the input operation
     */
    public static Stringable showRequestDialog(
        TypedView viewObject) 
        throws CancelledException 
    {
        // perform the input operation
        InputDialog dialog = 
            showInputDialog(viewObject, JPTConstants.OPTIONAL);

        // throw an exception if the input operation was cancelled
        if (dialog.wasCancelled())
            throw new CancelledException();

        // otherwise return the result of the input operation
        else
            return dialog.getModel();
    }
    
    ///////////////////////
    // Protected methods //
    ///////////////////////
    
    /**
     * Invokes the proper operation on the input component
     * to attempt to set the state of the model object.
     */
    protected void ok() {

        // get the input properties for the view
        InputProperties properties = view.getInputProperties();

        // get the input model for the view
        int inputModel = ((Integer)properties.getProperty(
            InputProperties.INPUT_MODEL)).intValue();
            
        // invoke the appropriate input operation
        if (inputModel == MANDATORY)
            demand();
        else
            request();    
    }

    /**
     * Performs a mandatory input operation on the input view.
     */
    protected void demand() {
        model = view.demandObject();

        // close the dialog once initialization is successful
        setVisible(false);
        dispose();
    }

    /**
     * Performs an optional input operation on the input view.
     */
    protected void request() {
        
        // try to initialize the model object 
        try {
            model = view.requestObject();
            
            // close the dialog if initialization is successful
            setVisible(false);
            dispose();
        }
        
        // catch cancel exception thrown by the input component
        // and keep the dialog open
        catch (CancelledException ex) {
            model = null;
        }
    }

    /**
     * Sets the view state of the input component
     * to its default view state.
     */
    protected void reset() {
        view.reset();
    }
    
    /**
     * Sets the view state of the input component
     * to its suggested view state.
     */
    protected void suggest() {
        InputProperties properties = view.getInputProperties();

        String suggestion = (String)properties.getProperty(
            InputProperties.SUGGESTION);

        if (suggestion == null)
            suggestion = "";

        view.setViewState(suggestion);
    }

    /**
     * Closes the dialog,
     * noting that the dialog was dismissed by cancellation.
     *
     * @see #wasCancelled()
     */
    protected void cancel() {
        cancelled = true;

        setVisible(false);
        dispose();
    }
    
    /**
     * Displays an error message notifying the user 
     * that the input operation is mandatory.
     */
    protected void refuse() {
        JOptionPane.showMessageDialog(
            this,
            "You must provide a valid value and press OK",
            "Input required",
            JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Installs an appropriate window listener for this dialog
     * given the provided input properties.
     *
     * @param properties the input properties 
     *      for the input operation to be performed
     */
    protected void createWindowListener(
        InputProperties properties) 
    {
        int inputModel = ((Integer)properties.getProperty(
            InputProperties.INPUT_MODEL)).intValue();
            
        WindowActionAdapter adapter = new WindowActionAdapter(this);

        // in mandatory model, refuse to close window
        if (inputModel == MANDATORY) {
            adapter.addWindowClosingAction(new SimpleAction() {
                public void perform() {
                    refuse();
                }
            });
        }
        
        // in optional model, window close equals cancel
        else {
            adapter.addWindowClosingAction(new SimpleAction() {
                public void perform() {
                    cancel();
                }
            });
        }
    }

    /**
     * Installs the appropriate controls for this dialog
     * given the provided input properties.
     *
     * @param properties the input properties 
     *      for the input operation to be performed
     */
    protected void createActionsPanel(
        InputProperties properties) 
    {
        int inputModel = ((Integer)properties.getProperty(
            InputProperties.INPUT_MODEL)).intValue();

        String suggestion = (String)properties.getProperty(
            InputProperties.SUGGESTION);

        // add OK button as the default button
        addDefaultAction(new SimpleAction("OK") {
            public void perform() {
                ok();
            }
        });

        // add Reset button
        addAction(new SimpleAction("Reset") {
            public void perform() {
                reset();
            }
        });
        
        // add Suggest button if appropriate
        if (suggestion != null) {
            addAction(new SimpleAction("Suggest") {
                public void perform() {
                    suggest();
                }
            });
        }
        
        // add Cancel button if appropriate
        if (inputModel == OPTIONAL) {
            addAction(new SimpleAction("Cancel") {
                public void perform() {
                    cancel();
                }
            });
        }
    }

    /**
     * Returns a typed view 
     * constructed from the given input component
     * and decorated based on the input properties of the view.
     *
     * @param viewObject the component to encapsulate and decorate
     */
    protected static TypedView getCompoundView(
        TypedView viewObject) 
    {
        return new CompoundView(viewObject);
    }

    /////////////////////
    // Private methods //
    /////////////////////
    
    /**
     * Shows a dialog containing the given input component
     * that performs an input operation using the given input model.
     *
     * @param viewObject the input component to be used
     * @param inputModel the model for the input operation
     * @return the dialog to be used for input
     * @throws NullPointerException if the given input component
     *      is <CODE>null</CODE>
     */
    private static InputDialog showInputDialog(
        TypedView viewObject,
        int inputModel) {

        // save the current input properties for the view
        // and modify the input properties to use the optional model
        InputProperties oldProperties = 
            setInputModel(viewObject, inputModel);
    
        // build the dialog box and perform the input operation
        InputDialog dialog = new InputDialog(viewObject);
        dialog.setVisible(true);
        
        // restore the previous input properties for the view object
        viewObject.setInputProperties(oldProperties);

        // return the created dialog
        return dialog;
    }

    /**
     * Sets the input model for the given view 
     * and returns the original properties for the view
     * so they may be saved by the caller and later be restored.
     *
     * @param viewObject the desired view
     * @param inputModel the desired input model
     * @throws NullPointerException if the given input component
     *      is <CODE>null</CODE>
     */
    private static InputProperties setInputModel(
        TypedView viewObject,
        int inputModel) 
    {
        // store original input properties for return
        InputProperties oldProperties = 
            viewObject.getInputProperties();

        // extend input properties to reflect 
        // the desired input model
        InputProperties newProperties = 
            new InputProperties(oldProperties);
        newProperties.setProperty(
            InputProperties.INPUT_MODEL,
            new Integer(inputModel));
    
        // install new input properties for the view        
        viewObject.setInputProperties(newProperties);
        
        // return the original input properties for the view
        return oldProperties;
    }

    ///////////////////
    // Inner classes //
    ///////////////////

    /**
     * <P>A typed view constructed from the provided input component
     * and decorated based on the input properties of the view.</P>
     *
     * @author  Jeff Raab
     * @author  Richard Rasala
     * @author  Viera K. Proulx
     * @version 2.1
     * @since   1.0
     */
    static class CompoundView 
        extends Display 
        implements TypedView 
    {
        /** The internal view object to be decorated. */
        private TypedView view = null;

        /**
         * Constructs a compound view from the provided view object
         * and decorates based on the input properties of the view.
         *
         * @param viewObject the view object to be decorated
         * @throws NullPointerException if the given input component
         *      is <CODE>null</CODE>
         */
        public CompoundView(TypedView viewObject) {
            super(
                viewObject, 
                (String)viewObject.getInputProperties().getProperty(
                    InputProperties.INPUT_PROMPT), 
                null, 
                Display.ABOVE, 
                Display.DEFAULT);
        
            view = viewObject;        
        }
        
        ///////////////
        // TypedView //
        ///////////////
        
        public Stringable demandObject() {
            return view.demandObject();
        }
        
        public Stringable requestObject() throws CancelledException {
            return view.requestObject();
        }
        
        public void setInputProperties(InputProperties properties) {
            view.setInputProperties(properties);
        }
        
        public InputProperties getInputProperties() {
            return view.getInputProperties();
        }

        public Class getDataType() {
            return view.getDataType();
        }   
    }
}
