/* -------------------------------------------------------------------
 * GeoVISTA Center (Penn State, Dept. of Geography)
 *
 * Java source file for the class MultiSlider
 *
 * Copyright (c), 1999 - 2002, Masahiro Takatsuka and GeoVISTA Center
 * All Rights Researved.
 *
 * Original Author: Masahiro Takatsuka
 * $Author: eytanadar $
 *
 * $Date: 2005/10/05 20:19:52 $
 *
 *
 * Reference:		Document no:
 * ___				___
 *
 * To Do:
 * ___
 *
 ------------------------------------------------------------------- */

/* --------------------------- Package ---------------------------- */

import java.awt.*;
import java.io.*;
import javax.accessibility.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;

/*====================================================================
  Implementation of class MultiSlider
  ====================================================================*/
/***
 * A component that lets the user graphically select values by slding
 * multiple thumbs within a bounded interval. MultiSlider inherits all
 * fields and methods from javax.swing.JSlider.
 * <p>
 *
 * @version $Revision: 1.1 $
 * @author Masahiro Takatsuka (masa@jbeans.net)
 * @see JSlider
 */

public class MultiSlider extends JSlider {
    /***
     * @see #getUIClassID
     * @see #readObject
     */
    private static final String uiClassID = "MultiSliderUI";

    /***
     * An array of data models that handle the numeric maximum values,
     * minimum values, and current-position values for the multi slider.
     */
    private BoundedRangeModel[] sliderModels;

    /***
     * If it is true, a thumb is bounded by adjacent thumbs.
     */
    private boolean bounded = false;

    /***
     * This is a color to paint the current thumb
     */
    private Color currentThumbColor = Color.red;

    /***
     * this flag is used to create default anchors.
     */
    transient private boolean useEndPoints = false;

    transient private int valueBeforeStateChange;

    /***
     * Creates a slider with the specified orientation and the
     * specified mimimum, maximum, and initial values.
     *
     * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL
     *
     * @see #setOrientation
     * @see #setMinimum
     * @see #setMaximum
     * @see #setValue
     */
    public MultiSlider(int orientation, int min, int max, 
			 int val1, int val2) {
	checkOrientation(orientation);
	this.orientation = orientation;
	setNumberOfThumbs(min,max,new int[]{val1,val2});
    }

    /***
     * Creates a slider with the specified orientation and the
     * specified mimimum, maximum, and the number of thumbs.
     *
     * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL
     *
     * @see #setOrientation
     * @see #setMinimum
     * @see #setMaximum
     * @see #setValue
     */
    public MultiSlider(int orientation, int min, int max) {
	checkOrientation(orientation);
	this.orientation = orientation;
	setNumberOfThumbs(min, max, 2);
    }

    /***
     * Creates a horizontal slider with the range 0 to 100 and
     * an intitial value of 50.
     */
    public MultiSlider() {
	this(HORIZONTAL, 0, 100);
    }


    /***
     * Creates a slider using the specified orientation with the
     * range 0 to 100 and an intitial value of 50.
     */
    public MultiSlider(int orientation) {
	this(orientation, 0, 100);
    }


    /***
     * Creates a horizontal slider using the specified min and max
     * with an intitial value of 50.
     */
    public MultiSlider(int min, int max) {
	this(HORIZONTAL, min, max);
    }

    public void setCurrentThumbColor(Color c) {
	this.currentThumbColor = c;
    }

    public Color getCurrentThumbColor() {
	return this.currentThumbColor;
    }

    public int getTrackBuffer() {
	return ((MultiSliderUI) this.ui).getTrackBuffer();
    }

    /***
     * Validates the orientation parameter.
     */
    private void checkOrientation(int orientation) {
	switch (orientation) {
	case VERTICAL:
	case HORIZONTAL:
	    break;
	default:
	    throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL");
	}
    }

    /***
     * Notification from the UIFactory that the L&F has changed.
     * Called to replace the UI with the latest version from the
     * default UIFactory.
     *
     * @see JComponent#updateUI
     */
    public void updateUI() {
	updateLabelUIs();
	MultiSliderUI ui = new MultiSliderUI();
	if (this.sliderModels != null) {
	    ui.setThumbCount(this.sliderModels.length);
	}
	setUI((SliderUI) ui);
    }

    /***
     * Returns the number of thumbs in the slider.
     */
    public int getNumberOfThumbs() {
	return this.sliderModels.length;
    }

    /***
     * Sets the number of thumbs with the specified parameters.
     */
    private void setNumberOfThumbs(int min, int max, int num, boolean useEndPoints) {
	int [] values = createDefaultValues(min, max, num, useEndPoints);
	setNumberOfThumbs(min, max, values);
    }

    /***
     * Sets the number of thumbs with the specified parameters.
     */
    private void setNumberOfThumbs(int min, int max, int num) {
	setNumberOfThumbs(min, max, num, false);
    }

    /***
     * Sets the number of thumbs with the specified parameters.
     */
    private void setNumberOfThumbs(int min, int max, int[] values) {
	if (values == null || values.length < 1) {
	    values = new int[] {50};
	}
	int num = values.length;
	this.sliderModels = new BoundedRangeModel[num];
	for (int i = 0; i < num; i++) {
	    this.sliderModels[i] = new DefaultBoundedRangeModel(values[i], 0, min, max);
	    this.sliderModels[i].addChangeListener(changeListener);
	}
	updateUI();
    }

    /***
     * Sets the number of thumbs.
     */
    private void setNumberOfThumbs(int num) {
	setNumberOfThumbs(num, false);
    }

    /***
     * Sets the number of thumbs.
     */
    private void setNumberOfThumbs(int num, boolean useEndPoints) {
	if (getNumberOfThumbs() != num) {
	    setNumberOfThumbs(getMinimum(), getMaximum(), num, useEndPoints);
	}
    }

    /***
     * Sets the number of thumbs by specifying the initial values.
     */
    private void setNumberOfThumbs(int[] values) {
	setNumberOfThumbs(getMinimum(), getMaximum(), values);
    }

    /***
     * creates evenly spaced values for thumbs.
     */
    private int[] createDefaultValues(int min, int max, int num_of_values, boolean useEndPoints) {
	int[] values = new int[num_of_values];
	int range = max - min;

	if (!useEndPoints) {
	    int step = range / (num_of_values + 1);
	    for (int i = 0; i < num_of_values; i++) {
		values[i] = min + (i + 1) * step;
	    }
	} else {
	    if (num_of_values < 1) {
		return new int[0];
	    }
	    values[0] = getMinimum();
	    values[num_of_values - 1] = getMaximum();
	    int[] def = createDefaultValues(getMinimum(), getMaximum(), num_of_values - 2, false);
	    for (int i = 0; i < def.length; i++) {
		values[i + 1] = def[i];
	    }
	}
	return values;
    }

    /***
     * Returns the index number of currently operated thumb.
     */
    public int getCurrentThumbIndex() {
	return ((MultiSliderUI)ui).getCurrentIndex();
    }

    /***
     * Returns data model that handles the sliders three
     * fundamental properties: minimum, maximum, value.
     *
     * @see #setModel
     */
    public BoundedRangeModel getModel() {
	return getModelAt(getCurrentThumbIndex());
    }

    /***
     * Returns data model that handles the sliders three
     * fundamental properties: minimum, maximum, value.
     *
     * @see #setModel
     */
    public BoundedRangeModel getModelAt(int index) {
	if (this.sliderModels == null || index >= this.sliderModels.length) {
	    return null;
	}
	return this.sliderModels[index];
    }

    /***
     * Returns data model that handles the sliders three
     * fundamental properties: minimum, maximum, value.
     *
     * @see #setModel
     */
    public BoundedRangeModel[] getModels() {
	return this.sliderModels;
    }

    /***
     * Sets the model that handles the sliders three
     * fundamental properties: minimum, maximum, value.
     *
     * @see #getModel
     * @beaninfo
     *       bound: true
     * description: The sliders BoundedRangeModel.
     */
    public void setModel(BoundedRangeModel newModel) {
	setModelAt(getCurrentThumbIndex(), newModel);
    }

    /***
     * Sets the model that handles the sliders three
     * fundamental properties: minimum, maximum, value.
     *
     * @see #getModel
     * @beaninfo
     *       bound: true
     * description: The sliders BoundedRangeModel.
     */
    public void setModelAt(int index, BoundedRangeModel newModel) {
	BoundedRangeModel oldModel = getModelAt(index);

	if (oldModel != null) {
	    oldModel.removeChangeListener(changeListener);
	}

	this.sliderModels[index] = newModel;

	if (newModel != null) {
	    newModel.addChangeListener(changeListener);

	    if (accessibleContext != null) {
		accessibleContext.firePropertyChange(
						     AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
						     (oldModel == null
						      ? null : new Integer(oldModel.getValue())),
						     (newModel == null
						      ? null : new Integer(newModel.getValue())));
	    }
	}

	firePropertyChange("model", oldModel, this.sliderModels[index]);
    }

    /***
     * Sets the models minimum property.
     *
     * @see #getMinimum
     * @see BoundedRangeModel#setMinimum
     * @beaninfo
     *       bound: true
     *   preferred: true
     * description: The sliders minimum value.
     */
    public void setMinimum(int minimum) {
	int count = getNumberOfThumbs();
	int oldMin = getModel().getMinimum();
	for (int i = 0; i < count; i++) {
	    getModelAt(i).setMinimum(minimum);
	}
	firePropertyChange( "minimum", new Integer( oldMin ), new Integer( minimum ) );
    }

    /***
     * Sets the models maximum property.
     *
     * @see #getMaximum
     * @see BoundedRangeModel#setMaximum
     * @beaninfo
     *       bound: true
     *   preferred: true
     * description: The sliders maximum value.
     */
    public void setMaximum(int maximum) {
	int count = getNumberOfThumbs();
	int oldMax = getModel().getMaximum();
	for (int i = 0; i < count; i++) {
	    getModelAt(i).setMaximum(maximum);
	}
	firePropertyChange( "maximum", new Integer( oldMax ), new Integer( maximum ) );
    }

    /***
     * Returns the sliders value.
     * @return the models value property
     * @see #setValue
     */
    public int getValue() {
	return getValueAt(getCurrentThumbIndex());
    }

    /***
     * Returns the sliders value.
     * @return the models value property
     * @see #setValue
     */
    public int getValueAt(int index) {
	return getModelAt(index).getValue();
    }

    /***
     * Sets the sliders current value.  This method just forwards
     * the value to the model.
     *
     * @see #getValue
     * @beaninfo
     *   preferred: true
     * description: The sliders current value.
     */
    public void setValue(int n) {
	setValueAt(getCurrentThumbIndex(), n);
    }

    /***
     * Sets the sliders current value.  This method just forwards
     * the value to the model.
     *
     * @see #getValue
     * @beaninfo
     *   preferred: true
     * description: The sliders current value.
     */
    public void setValueAt(int index, int n) {
	BoundedRangeModel m = getModelAt(index);
	int oldValue = m.getValue();
	m.setValue(n);

	if (accessibleContext != null) {
	    accessibleContext.firePropertyChange(
						 AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
						 new Integer(oldValue),
						 new Integer(m.getValue()));
	}
    }

    /***
     * True if the slider knob is being dragged.
     *
     * @return the value of the models valueIsAdjusting property
     * @see #setValueIsAdjusting
     */
    public boolean getValueIsAdjusting() {
	boolean result = false;
	int count = getNumberOfThumbs();
	for (int i = 0; i < count; i++) {
	    result = (result || getValueIsAdjustingAt(i));
	}
	return result;
    }

    /***
     * True if the slider knob is being dragged.
     */
    public boolean getValueIsAdjustingAt(int index) {
	return getModelAt(index).getValueIsAdjusting();
    }

    /***
     * Sets the models valueIsAdjusting property.  Slider look and
     * feel implementations should set this property to true when
     * a knob drag begins, and to false when the drag ends.  The
     * slider model will not generate ChangeEvents while
     * valueIsAdjusting is true.
     *
     * @see #getValueIsAdjusting
     * @see BoundedRangeModel#setValueIsAdjusting
     * @beaninfo
     *      expert: true
     * description: True if the slider knob is being dragged.
     */
    public void setValueIsAdjusting(boolean b) {
	setValueIsAdjustingAt(getCurrentThumbIndex(), b);
    }

    /***
     * Sets the models valueIsAdjusting property.  Slider look and
     * feel implementations should set this property to true when
     * a knob drag begins, and to false when the drag ends.  The
     * slider model will not generate ChangeEvents while
     * valueIsAdjusting is true.
     */
    public void setValueIsAdjustingAt(int index, boolean b) {
	BoundedRangeModel m = getModelAt(index);
	boolean oldValue = m.getValueIsAdjusting();
	m.setValueIsAdjusting(b);

	if ((oldValue != b) && (accessibleContext != null)) {
	    accessibleContext.firePropertyChange(
						 AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
						 ((oldValue) ? AccessibleState.BUSY : null),
						 ((b) ? AccessibleState.BUSY : null));
	}
    }

    /***
     * Sets the size of the range "covered" by the knob.  Most look
     * and feel implementations will change the value by this amount
     * if the user clicks on either side of the knob.
     *
     * @see #getExtent
     * @see BoundedRangeModel#setExtent
     * @beaninfo
     *      expert: true
     * description: Size of the range covered by the knob.
     */
    public void setExtent(int extent) {
	int count = getNumberOfThumbs();
	for (int i = 0; i < count; i++) {
	    getModelAt(i).setExtent(extent);
	}
    }


    /***
     * Sets a bounded attribute of a slider thumb.
     * <PRE>
     * </PRE>
     *
     * @param b
     * @return void
     */
    public void setBounded(boolean b) {
	this.bounded = b;
    }

    /***
     * Returns a bounded attribute of a slider thumb.
     * <PRE>
     * </PRE>
     *
     * @return boolean
     */
    public boolean isBounded() {
	return this.bounded;
    }

    public int getValueBeforeStateChange() {
	return this.valueBeforeStateChange;
    }

    void setValueBeforeStateChange(int v) {
	this.valueBeforeStateChange = v;
    }
}




