/*
 * @(#)SliderView.java    2.3.3   4 January 2005
 *
 * Copyright 2005
 * 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.event.*;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Vector;
import javax.swing.*;
import javax.swing.event.*;

/**
 * <P>A <CODE>{@link TypedView TypedView}</CODE> 
 * for input of <CODE>int</CODE> values in a bounded range
 * using direct manipulation.</P>
 *
 * @author  Jeff Raab
 * @author  Richard Rasala
 * @version 2.3.3
 * @since   1.0
 */
public class SliderView 
    extends JSlider 
    implements TypedView, JPTConstants
{
    /** The default value for this slider. */
    protected int defaultValue = 0;
    
    /** The property list for this view object. */
    protected InputProperties properties = new InputProperties();
    
    /** The action triggered by sliding movement of this slider. */
    protected Action slideAction = new AbstractAction() {
        public void actionPerformed(ActionEvent evt) {
            if (getValueIsAdjusting())
                slidingActions.actionPerformed(evt);
        }
    };
    
    /** 
     * The action sequence performed 
     * each time the slider knob is moved. 
     */
    protected ActionSequence slidingActions = new ActionSequence();
    
    /** The action triggered by sliding movement of this slider. */
    protected Action releaseAction = new AbstractAction() {
        public void actionPerformed(ActionEvent evt) {
            if (!getValueIsAdjusting())
                releaseActions.actionPerformed(evt);
        }
    };
    
    /** 
     * The action sequence performed 
     * each time the slider knob is released. 
     */
    protected ActionSequence releaseActions = new ActionSequence();
    
    
    //////////////////
    // Constructors //
    //////////////////
    
    /**
     * Constructs a horizontal slider 
     * with the range [0, 100] and a default value of 50.
     */
    public SliderView() {
        this(HORIZONTAL, 0, 100, 50);
    }
    
    
    /**
     * Constructs a slider with the given orientation,
     * the range [0, 100] and a default value of 50.
     *
     * @param orientation the orientation for this slider
     */
    public SliderView(int orientation) {
        this(orientation, 0, 100, 50);
    }
    
    
    /**
     * Constructs a slider with the given orientation,
     * a range from the provided minimum to the provided maximum, 
     * and the given default value.
     *
     * @param orientation the orientation for this slider
     * @param min the minimum value for this slider
     * @param max the maximum value for this slider
     * @param value the default value for this slider
     */
    public SliderView(
        int orientation, 
        int min, 
        int max, 
        int value) 
    {
        super(orientation, min, max, value);

        // allow super call to enforce range checking
        defaultValue = getValue();

        // establish listeners for change actions
        ChangeActionAdapter adapter = new ChangeActionAdapter();
        adapter.addChangeAction(slideAction);
        adapter.addChangeAction(releaseAction);
        addChangeListener(adapter);
    }
    
    
    ///////////////
    // TypedView //
    ///////////////

    /**
     * Returns an <CODE>XInt</CODE> object 
     * whose state is set to the value of this slider.
     *
     * @see #requestObject()
     * @see TypedView
     */
    public Stringable demandObject() {
        return new XInt(getValue());
    }
    
    
    /**
     * Returns an <CODE>XInt</CODE> object 
     * whose state is set to the value of this slider.
     *
     * @see #demandObject()
     * @see TypedView
     */
    public Stringable requestObject() {
        return demandObject();
    }
    
    
    public void setInputProperties(InputProperties p) {
        if (p == null)
            p = InputProperties.BASE_PROPERTIES;
    
        InputProperties oldProperties = getInputProperties();
    
        properties = p;
        
        // if the input properties has changed
        if ((getInputProperties() != null) &&
            !getInputProperties().equals(oldProperties)) {
        
            // notify listeners of property change
            firePropertyChange(
                INPUT_PROPERTIES,
                oldProperties,
                getInputProperties());
        }
    }
    
    
    public InputProperties getInputProperties() {
        return properties;
    }
    
    
    /**
     * Returns the <CODE>XInt</CODE> class object.
     *
     * @see TypedView
     */ 
    public Class getDataType() {
        return XInt.class;
    }
    
    
    /////////////////
    // Displayable //
    /////////////////
                                                                    
    /**
     * Sets the value for this slider
     * to the <CODE>int</CODE> value represented
     * by the given <CODE>String</CODE> data.
     *
     * If the value to be set is out of the bounded range
     * for this slider, the current value is not changed.
     *
     * @param data the new view state
     * @see #getViewState()
     * @see Displayable
     */
    public void setViewState(String data) {
        setValue(SliderView.parseInt(
            data, "setViewState"));
        
        // notify listeners of property change
        firePropertyChange(VIEW_STATE, null, data);
    }
    
    
    /**
     * Returns a <CODE>String</CODE> representation
     * of the current value for this slider.
     *
     * @see #setViewState(String)
     * @see Displayable
     */
    public String getViewState() {
        return getValue() + "";
    }
    
    
    /**
     * Sets the default value for this slider
     * to the <CODE>int</CODE> value represented
     * by the given <CODE>String</CODE> data.
     *
     * If the new default value is out of the bounded range
     * for this slider, the current default value is not changed.
     *
     * @param data the new default view state
     * @see #reset()
     * @see Displayable
     */
    public void setDefaultViewState(String data) {
        defaultValue = SliderView.parseInt(
            data, "setDefaultViewState");

        // notify listeners of property change
        firePropertyChange(DEFAULT_VIEW_STATE, null, data);
    }
    
    
    /**
     * Returns a <CODE>String</CODE> representation
     * of the default value for this slider.
     *
     * @see #setDefaultViewState(String)
     * @see #reset()
     */
    public String getDefaultViewState() {
        return "" + defaultValue;
    }

    public void reset() {
        setValue(defaultValue);
    }
    
    
    ////////////////
    // Public API //
    ////////////////
    
    /**
     * Adds the given action listener to the sequence of actions
     * that is performed when the value for this slider is changed
     * through direct manipulation of the slider knob.
     *
     * @param a the action listener to be added
     */
    public void addSlidingAction(ActionListener a) {
        slidingActions.add(a);
    }
    
    
    /**
     * Adds the given action listener to the sequence of actions
     * that is performed when the value for this slider is changed
     * through method calls, 
     * or when the value for this slider has finished changing 
     * through direct manipulation of the slider knob.
     *
     * @param a the action listener to be added
     */
    public void addReleaseAction(ActionListener a) {
        releaseActions.add(a);
    }
    
    
    /**
     * Removes the given action listener from the sequence of actions
     * that is performed when the value for this slider is changed
     * through direct manipulation of the slider knob.
     *
     * @param a the action listener to be removed
     */
    public void removeSlidingAction(ActionListener a) {
        slidingActions.remove(a);
    }
    
    
    /**
     * Removes the given action listener from the sequence of actions
     * that is performed when the value for this slider is changed
     * through method calls, 
     * or when the value for this slider has finished changing 
     * through direct manipulation of the slider knob.
     *
     * @param a the action listener to be removed
     */
    public void removeReleaseAction(ActionListener a) {
        releaseActions.remove(a);
    }
    
    
    /**
     * Sets the sequence of action listeners
     * that is performed when the value for this slider is changed
     * through direct manipulation of the slider knob.
     *
     * @param sequence the new action sequence to be performed
     * @see #addSlidingAction(ActionListener)
     * @see #removeSlidingAction(ActionListener)
     * @see #getSlidingActions()
     */
    public void setSlidingActions(ActionSequence sequence) {
        if (sequence == null)
            slidingActions.clear();
        else
            slidingActions = sequence;
    }    
    
    
    /**
     * Sets the sequence of action listeners
     * that is performed when the value for this slider is changed
     * through method calls, 
     * or when the value for this slider has finished changing 
     * through direct manipulation of the slider knob.
     *
     * @param sequence the new action sequence to be performed
     * @see #addReleaseAction(ActionListener)
     * @see #removeReleaseAction(ActionListener)
     * @see #getReleaseActions()
     */
    public void setReleaseActions(ActionSequence sequence) {
        if (sequence == null)
            releaseActions.clear();
        else
            releaseActions = sequence;
    }    
    
    
    /**
     * Returns the sequence of action listeners
     * that is performed when the value for this slider is changed
     * through direct manipulation of the slider knob,
     * or when the user has first selected the slider knob
     * to perform direct manipulation of the value for this slider.
     *
     * @see #setSlidingActions(ActionSequence)
     * @see #addSlidingAction(ActionListener)
     * @see #removeSlidingAction(ActionListener)
     */
    public ActionSequence getSlidingActions() {
        return slidingActions;
    }    
    
    
    /**
     * Returns the sequence of action listeners
     * that is performed when the value for this slider is changed
     * through method calls, 
     * or when the value for this slider has finished changing 
     * through direct manipulation of the slider knob.
     *
     * @see #setReleaseActions(ActionSequence)
     * @see #addReleaseAction(ActionListener)
     * @see #removeReleaseAction(ActionListener)
     */
    public ActionSequence getReleaseActions() {
        return releaseActions;
    }    
    
    
    /**
     * Installs ten major tick marks for this slider,
     * which includes tick marks at the maximum and minimum values.
     */
    public void installStandardTicks() {
        setPaintTicks(true);

        setMajorTickSpacing(
            (int)(getMaximum() - getMinimum()) / 9);
    }
    
    
    /**
     * Installs labels at the major tick marks for this slider,
     * and labels at the maximum and minimum value for this slider,
     * each of which displays the value at that location.
     */
    public void installStandardLabels() {
        setPaintLabels(true);
        
        setLabelTable(
            createStandardLabels(
                getMajorTickSpacing(), 
                getMinimum()));
    }
    
    
    ///////////////////////
    // Protected methods //
    ///////////////////////

    /**
     * Returns the <CODE>int</CODE> value
     * represented by the given <CODE>String</CODE> data.
     *
     * @param data the data <CODE>String</CODE> to be parsed
     * @param caller the name of the method that requires parsing
     * @throws JPTError if there is an error parsing the data
     */
    protected static int parseInt(
        String data, 
        String caller) 
    {
        try {
            return (new XInt(data)).getValue();
        }
        catch (ParseException ex) {
            throw new JPTError(
                "Malformed data in SliderView." + 
                caller + ": " + data);
        }
    }
    
}

