/*
 * Copyright 1999 by Brown University Computer Science
 * 115 Waterman Street, 4th Floor, Providence, Rhode Island, 02906, U.S.A
 * All rights reserved.
 *
 * Permission is hereby granted for free use and modification of this
 * software.  However, this copyright must remain at all times.
 * Re-distribution is restricted to those with the express written consent
 * of the copyright holder.  Permission can be obtained by mailing
 * cs015headtas@cs.brown.edu.  
 */

package NGP.Components;

//an import statement means I can refer to a class as if I specified the
//whole package
import javax.swing.JSlider;
import javax.swing.JLabel;
import javax.swing.BorderFactory;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.util.Hashtable;
import java.util.Dictionary;

/**
 * A slider that is capable of displaying labels if
 * the programmer turns them on.  It is a subclass of the Swing JSlider.
 * Use this when you want the user to be able to dynamically change values
 * of whatever you want.  The slider has the capability to have multiple
 * label sets; good for when you want the slider to affect more than one
 * set of values.
 *
 * @see <a href="http://java.sun.com/products/jdk/1.2/docs/api/javax/swing/JSlider.html">javax.swing.JSlider</a>
 *
 * @author Matt Chotin (<a href="mailto:mhc@cs.brown.edu">mhc</a>) with initial
 * work done by Jon Moter.
 */

public class Slider extends JSlider implements NGP.Component {

  /** Used to create the slider in a horizontal position. */
  public static final int HORIZONTAL = JSlider.HORIZONTAL;
  /** Used to create the slider in a vertical position. */
  public static final int VERTICAL = JSlider.VERTICAL;

  private Hashtable _labels;

  /**
   * Constructs the Slider to appear in the specified container.
   *
   * @param container the container where the slider will show up
   */
  public Slider(NGP.Container container) {
    super();
    this.continueInitialization(container);
  }

  /**
   * Constructs the Slider to appear in the container using a specific
   * orientation.
   *
   * @param container the container where the slider will show up
   * @param orientation use either <code>Slider.VERTICAL</code> or
   * <code>Slider.HORIZONTAL</code>
   */
  public Slider(NGP.Container container, int orientation) {
    super(orientation);
    this.continueInitialization(container);
  }

  /**
   * Constructs the Slider to appear in the container with a minimum and
   * maximum value.
   *
   * @param container the container where the slider will show up
   * @param min minimum value of the slider
   * @param max maximum value of the slider
   */
  public Slider(NGP.Container container, int min, int max) {
    super(min, max);
    this.continueInitialization(container);
  }

  /**
   * Constructs the Slider to appear in the container with a minimum and
   * maximum value and a current value.
   *
   * @param container the container where the slider will show up
   * @param min minimum value of the slider
   * @param max maximum value of the slider
   * @param value initial value of the slider
   */
  public Slider(NGP.Container container, int min, int max, int value) {
    super(min, max, value);
    this.continueInitialization(container);
  }

  /**
   * Constructs the Slider to appear in the container oriented in a certain
   * manner with a minimum and maximum value and a current value.
   *
   * @param container the container where the slider will show up
   * @param orientation use either <code>Slider.VERTICAL</code> or
   * <code>Slider.HORIZONTAL</code>
   * @param min minimum value of the slider
   * @param max maximum value of the slider
   * @param value initial value of the slider
   */
  public Slider(NGP.Container container, int orientation, int min,
		int max, int value) {
    super(orientation, min, max, value);
    this.continueInitialization(container);
  }

  /**
   * Do the stuff that makes it work.
   */
  private void continueInitialization(NGP.Container container) {
    SliderListener listener = new SliderListener();
    this.addChangeListener(listener);
    this.addMouseListener(listener);
    this.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));

    _labels = new Hashtable();
    _labels.put("default", new Hashtable());

    container.add(this);
  }

  /**
   * Called when the slider is first pressed, must override if you want
   * something to happen.  Most times you will only use {@link #drag() drag}
   * and {@link #release() release}.
   */
  public void press() { }

  /**
   * Called repeatedly as the user is dragging the slider, must override
   * if you want something to happen.
   */
  public void drag() { }

  /**
   * Called when the user releases the slider, must override if you want
   * something to happen.
   */
  public void release() { }

  /**
   * Create labels to show the values of the slider separted by a given
   * incrememnt.
   *
   * @param increment integer to indicate how often you want a label
   */
  public void makeDefaultLabels(int increment) {
    _labels.remove("default");
    _labels.put("default", this.createStandardLabels(increment));
    this.setLabelTable((Hashtable)_labels.get("default"));
    this.setPaintLabels(true);
  }

  /**
   * Create a label for the slider at a given value (goes in the default set).
   *
   * @param value integer value where you want the label
   * @param label String that should be displayed at that value
   */
  public void addLabel(int value, String label) {
    ((Hashtable)_labels.get("default")).put(new Integer(value),
					    new JLabel(label));
    this.setLabelTable((Hashtable)_labels.get("default"));
  }

  /**
   * Add another set of labels to the slider.
   *
   * @param labelSet String representing the name of the label set you want to
   * add.
   * @return true if the set was added, false if the set already existed
   */
  public boolean addLabelSet(String labelSet) {
    if (!_labels.containsKey(labelSet)) {
      _labels.put(labelSet, new Hashtable());
      return true;
    }
    return false;
  }

  /**
   * Create a label for the slider within a specific set at a given value.
   *
   * @param labelSet the String representing the set to put labels in
   * @param value integer value for where you want the label
   * @param label String that should be displayed at that value
   */
  public void addLabel(String labelSet, int value, String label) {
    ((Hashtable)_labels.get(labelSet)).put(new Integer(value),
					   new JLabel(label));
  }

  /**
   * Tell the slider which label set it should be displaying
   *
   * @param labelSet String string representing which label set to display
   */
  public void setLabelSet(String labelSet) {
    this.setLabelTable((Hashtable)_labels.get(labelSet));
    this.setPaintLabels(true);
  }

  /**
   * Show the labels (labels are not shown by default).
   */
  public void turnOnLabels() {
    this.setPaintLabels(true);
  }

  /**
   * Do not show the labels.
   */
  public void turnOffLabels() {
    this.setPaintLabels(false);
  }

  /**
   * Turn on the tick marks.  Tick mark increments should be set with
   * {@link JSlider#setMajorTickSpacing(int) setMajorTickSpacing} and
   * {@link JSlider#setMinorTickSpacing(int) setMinorTickSpacking}.
   */
  public void turnOnTicks() {
    this.setPaintTicks(true);
  }

  /**
   * Do not show the tick marks.
   */
  public void turnOffTicks() {
    this.setPaintTicks(false);
  }

  /**
   * Convert the value of the slider to a string.  Useful for sending this
   * information to a text field or label.
   *
   * @return String value of the slider.
   */
  public final String valueToString() {
    return String.valueOf(this.getValue());
  }

  /**
   * Set the preferred dimensions of this Component.
   *
   * @param d the <code>Dimension</code> for this <code>Component</code>.
   * @see #getDimension() getDimension
   */
  public void setDimension(java.awt.Dimension d) {
    this.setPreferredSize(d);
  }

  /**
   * Get the preferred dimensions of this Component.
   *
   * @return the preferred <code>Dimension</code> of this
   * <code>Component</code>.
   * @see #setDimension(java.awt.Dimension) setDimension
   */
  public java.awt.Dimension getDimension() {
    return this.getPreferredSize();
  }


  /**
   * This inner-class is a listener for the slider.  It will detect when the
   * user is manipulating the slider and call the appropriate slider
   * method.  Those methods must be overridden by the programmer to do
   * do something useful.
   */
  private class SliderListener implements ChangeListener, MouseListener {
    private boolean _first = true;
    private boolean _moused = false;

    public void stateChanged(ChangeEvent e) {
      Slider source = (Slider)e.getSource();

      // check if this is the first time it's pressed
      if (_first && _moused) {
	source.press();
	_first = false;
      }

      // check if it is being dragged
      else if (source.getValueIsAdjusting() && _moused) {
	source.drag();
      }

      // otherwise, user is releasing slider
      else {
	if (_moused) {
	  source.release();
	  _first = true;
        }
      }
    }
    
    public void mouseClicked(MouseEvent e) { }
    public void mouseEntered(MouseEvent e) { }
    public void mouseExited(MouseEvent e) { }
    public void mousePressed(MouseEvent e) { _moused = true; }
    //If it's being released and it was pressed before, have it call release()
    public void mouseReleased(MouseEvent e) {
      if(!_first) {
	Slider source = (Slider)e.getSource();
	source.release();
	_first = true;
      }
      _moused = false;
    }

  }
}
