/**
 * @(#)SliderView.java    2.6.0   29 August 2007
 *
 * Copyright 2007
 * 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 java.awt.geom.*;
import java.text.ParseException;

/**
 * <p>Class <code>SliderView</code> permits the construction of
 * a slider with pixel-level control of the size of the slider
 * track.  The caller can therefore control the relationship
 * between the minimum and maximum values represented by the
 * slider and the physical implementation in pixels.  This can
 * permit users to make slider selections with more accuracy.</p>
 * 
 * <p>The <code>SliderView</code> code was completely rewritten
 * for version 2.6.0.  A few older methods that are now obsolete
 * have been retained for backward compatibility as is noted in
 * the comments.  This class no longer depends on the Java class
 * <code>JSlider</code>.</p>
 * 
 * <p>In addition to pixel-level control of the size of the slider,
 * <code>SliderView</code> offers several advantages over the Java
 * class <code>JSlider</code>.</p>
 * 
 * <ul>
 *   <li>When the user clicks anywhere in the slider track, the
 *       thumb moves to that position immediately so the user
 *       can then begin to drag the thumb and fine tune the
 *       slider value selection.  This is far more useful than
 *       the Java default of moving the thumb by a small amount
 *       for each click.</li>
 *   <li>When the user releases the thumb, it snaps to the
 *       nearest valid integer position.  Although it is possible
 *       to request that behavior for a <code>JSlider</code>, it
 *       is not the default.</li>
 *   <li>The class <code>SliderView</code> provides what we
 *       believe is a better algorithm for automatically computing
 *       the location of tick marks and labels.  Moreover, the
 *       underlying methods for showing tick marks and labels are
 *       public so the caller can manually fine tune these objects
 *       to any degree desired.</li>
 *   <li>By default, a <code>SliderView</code> includes a text
 *       field that shows the current slider value and may be used
 *       to set that value if desired by the user.  The text field
 *       may, however, be omitted from the GUI by setting a certain
 *       constructor parameter appropriately.</li>
 *   <li>The caller can control the paints and colors used in the
 *       GUI.  In particular, a gradient paint may be used in the
 *       slider track if desired.</li>
 *   <li>The class <code>SliderView</code> provides many constructors
 *       which allow the caller to exercise whatever degree of control
 *       is needed in the slider construction.</li>
 *   <li>The caller may add actions to be performed when the mouse is
 *       pressed in the thumb or track, when it is dragged along the
 *       track, and when it is released.  This provides a very fine
 *       granularity of control if the slider is used to drive other
 *       elements of the GUI.</li>
 * </ul>
 *   
 * <p>The constructors for class <code>SliderView</code> allow
 * the following options:</p>
 * 
 * <ul>
 *   <li>Setting the slider orientation as either
 *       <code>HORIZONTAL</code> or <code>VERTICAL</code>.</li>
 *   <li>Setting the minimum and maximum slider values.</li>
 *   <li>Setting the initial slider value.</li>
 *   <li>Setting the size of the slider track in pixels.</li>
 *   <li>Setting the spacing of major ticks and labels to
 *       enable these to be drawn automatically.</li>
 *   <li>Setting the spacing of minor ticks to
 *       enable these to be drawn automatically.</li>
 *   <li>Permitting tick settings of zero to turn off the
 *       automatic features.</li>
 *   <li>Permitting choices of paints and colors for
 *       various components in the GUI.</li>
 *   <li>Providing a text field that shows the exact value
 *       of the slider and permits the slider value to
 *       be set directly by the user if desired.</li>
 *   <li>Permitting the caller to decide whether or not to
 *       show this text field.</li>
 * </ul>
 * 
 * <p>Remarks on the minimum and maximum settings and the
 * track pixels setting:</p>
 * 
 * <ul>
 *   <li>By giving the minimum and maximum values in the
 *       opposite order, the caller can reverse the direction
 *       of the slider.</li>
 *   <li>The slider may be used with the most precision if the
 *       difference (maximum-minimum) divides or equals
 *       (pixels-1).</li>
 *   <li>If (maximum-minimum) is larger than (pixels-1) then it
 *       is helpful if (pixels-1) divides (maximum-minimum).</li>
 *   <li>If (maximum-minimum) is very large, then a slider is a
 *       poor tool for selecting a value and a pure text field
 *       is a better choice.</li>
 * </ul>
 * 
 * <p>After construction, the caller may add one or more actions
 * that will be performed in any of the following 3 situations:</p>
 * 
 * <ul>
 *  <li>the slider thumb is pressed</li>
 *  <li>the slider thumb is sliding</li>
 *  <li>the slider thumb is released</li>
 * </ul>
 * 
 * <p>These actions should normally behave as listeners, that is,
 * they may query information about the slider from methods such
 * as <code>getValue</code> and <code>getTrackLocation</code> but
 * they should normally not call methods that change the slider
 * state.  These actions are intended to be used to control other
 * objects in the program, especially, in the GUI.</p>
 * 
 * <p>The actions are explicity forbidden to call four methods:
 * <code>setValue</code>, <code>setTrackLocation</code>,
 * <code>setViewState</code>, and <code>reset</code>.  The
 * reason for this explict ban is that the four methods invoke
 * the actions internally and therefore calling them from the
 * actions will set up a recursive infinite loop.</p>
 * 
 * <p>In unusual situations, the actions may change slider state
 * via calls to two helper methods <code>setValueHelper</code>
 * and <code>setTrackLocationHelper</code> which do not invoke
 * the actions.  This facility should be used with great caution
 * as what happens may be confusing to the interactive user.</p>
 * 
 * <p>Final comment: When the mouse is released after a click
 * or a slide, both the sliding action and the release action
 * will be performed.  This policy guarantees that the sliding
 * action is performed at least once between the mouse press
 * on the thumb or track and the subsequent mouse release.</p>
 * 
 * @author  Richard Rasala
 * @author  Jeff Raab
 * @version 2.6.0
 * @since   1.0
 */
public class SliderView
    extends BasePane
    implements TypedView
{
    /** The minimum permitted pixels value: <code>20</code>. */
    public static final int MINIMUM_PIXELS = 20;
    
    /** The thumb size: <code>9</code>. */
    public static final int THUMB_SIZE = 9;
    
    /** The thumb size plus 1. */
    public static final int THUMB_PLUS = THUMB_SIZE + 1;
    
    /** The track thickness: <code>6</code>. */
    public static final int TRACK_THICK = 6;
    
    /** The track border thickness: <code>2</code>. */
    public static final int TRACK_BORDER_THICK = 2;
    
    /** The default major tick size: <code>8</code>. */
    public static final int MAJOR_TICK_SIZE = 8;
    
    /** The default minor tick size: <code>4</code>. */
    public static final int MINOR_TICK_SIZE = 4;
    
    /** Stroke of size 2: <code>BasicStroke(2)</code>. */
    public static final Stroke STROKE2 = new BasicStroke(2);
    
    /** The ID for a press action event. */
    public static final int PRESS_ACTION = 0;
    
    /** The ID for a sliding action event. */
    public static final int SLIDING_ACTION = 1;
    
    /** The ID for a release action event. */
    public static final int RELEASE_ACTION = 2;
    
    
    /**
     * <p>The slider thumb shape.</p>
     */
    protected Shape thumbShape = null;
    
    
    /**
     * <p>The slider thumb.</p>
     */
    protected ShapePaintable thumb = null;
    
    
    /**
     * <p>The ordinary fill paint for the thumb.</p>
     * 
     * <p>Default: Color.white.</p>
     */
    protected Paint ordinaryThumbPaint = Color.white;
    
    
    /**
     * <p>The selected fill paint for the thumb.</p>
     * 
     * <p>Default: Color.red.</p>
     */
    protected Paint selectedThumbPaint = Color.red;
    
    
    /**
     * <p>The slider track rect.</p>
     */
    protected XRect trackRect = null;
    
    
    /**
     * <p>The slider track.</p>
     */
    protected ShapePaintable track = null;
    
    
    /**
     * <p>The fill paint for the track.</p>
     * 
     * <p>Default: Color.yellow.</p>
     */
    protected Paint trackPaint = Color.yellow;
    
    
    /**
     * <p>The slider track border rect.</p>
     */
    protected XRect trackBorderRect = null;
    
    
    /**
     * <p>The slider track border.</p>
     */
    protected ShapePaintable trackBorder = null;
    
    
    /**
     * <p>The common color for lines, borders, and text labels.</p>
     * 
     * <p>Default: Color.black.</p>
     */
    protected Color commonColor = Color.black;
    
    
    /**
     * <p>The paintable sequence for slider tick marks.</p>
     * 
     * <p>An empty sequence is constructed in this class.</p>
     * 
     * <p>If majorTicks and/or minorTicks is positive then
     * the corresponding default methods below will
     * populate this paintable sequence with ticks.</p>
     * 
     * <p>If both majorTicks and minorTicks are zero, then
     * the caller has complete control of what ticks,
     * if any, will be installed.</p>
     */
    protected final PaintableSequence tickSequence =
        new PaintableSequence();
    
    
    /**
     * <p>The paintable sequence for slider labels.</p>
     * 
     * <p>An empty sequence is constructed in this class.</p>
     * 
     * <p>If majorTicks is positive, then the default labels
     * will be installed at the corresponding ticks.</p>
     * 
     * <p>If majorTicks is zero, then the caller has complete
     * control of what labels, if any, will be installed.</p>
     */
    protected final PaintableSequence labelSequence =
        new PaintableSequence();
    
    
    /**
     * <p>The paintable sequence for more general
     * <code>Paintable</code> objects.</p>
     * 
     * <p>The caller has complete control of what objects,
     * if any, will be installed.</p>
     */
    protected final PaintableSequence objectSequence =
        new PaintableSequence();
    
    
    /**
     * <p>The paintable sequence for all of the paintables
     * in the slider GUI including the thumb, the track,
     * the major and minor tick marks, and the labels.</p>
     * 
     * <p>Populated by <code>initializeSequence()</code>.
     * This sequence contains other sequences as items.
     * These other sequences are populated by their own
     * methods.</p>
     */
    protected final PaintableSequence mainSequence =
        new PaintableSequence();
    
    
    /**
     * <p>Once initialized by <code>freezeBounds()</code>,
     * this rectangle contains the bounds of the sequence
     * <code>mainSequence</code>.</p>
     */
    protected XRect mainBounds = null;
    
    
    /**
     * The x-coordinate of the top-left corner of the
     * main bounds (as an integer).
     */
    protected int cornerX = 0;
    
    
    /**
     * The y-coordinate of the top-left corner of the
     * main bounds (as an integer).
     */
    protected int cornerY = 0;
    
    
    /** The paintable component to hold the main sequence. */
    protected PaintableComponent mainComponent =
        new PaintableComponent(mainSequence);
    
    
    /** The mouse adapter of the main component. */
    protected MouseActionAdapter adapter =
        mainComponent.getMouseActionAdapter();
    
    
    /**
     * The x-coordinate of the current mouse position
     * as adjusted by cornerX to be in the coordinate
     * system of the main sequence. 
     */
    protected int mouseX = 0;
    
    
    /**
     * The y-coordinate of the current mouse position
     * as adjusted by cornerY to be in the coordinate
     * system of the main sequence. 
     */
    protected int mouseY = 0;
    
    
    /** The mouse press action. */
    protected MouseAction mousePressAction =
        new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                mousePress(evt);
            }
    };
    
    
    /** The mouse sliding action. */
    protected MouseAction mouseSlidingAction =
        new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                mouseSliding(evt);
            }
    };
    
    
    /** The mouse release action. */
    protected MouseAction mouseReleaseAction =
        new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                mouseRelease(evt);
            }
    };
    
    
    /** 
     * The caller-defined actions performed 
     * when the slider thumb is pressed. 
     */
    protected ActionSequence pressActions = new ActionSequence();
    
    
    /** 
     * The caller-defined actions performed 
     * when the slider thumb is sliding. 
     */
    protected ActionSequence slidingActions = new ActionSequence();
    
    
    /** 
     * The caller-defined actions performed 
     * when the slider thumb is released. 
     */
    protected ActionSequence releaseActions = new ActionSequence();
    
    
    /**
     * <p>The standard event that will be passed to the sequence
     * <code>pressActions</code> when the mouse is pressed in
     * the active area of the slider (thumb or track).</p>
     * 
     * <p>The source is <code>this</code>.</p>
     * 
     * <p>The ID is <code>PRESS_ACTION</code>.</p>
     * 
     * <p>The command string is empty.</p>
     */
    public final ActionEvent pressEvent =
        new ActionEvent(this, PRESS_ACTION, "");
    
    
    /**
     * <p>The standard event that will be passed to the sequence
     * <code>slidingActions</code> when the mouse is sliding
     * after being pressed in the active area of the slider
     * (thumb or track).</p>
     * 
     * <p>The source is <code>this</code>.</p>
     * 
     * <p>The ID is <code>SLIDING_ACTION</code>.</p>
     * 
     * <p>The command string is empty.</p>
     */
    public final ActionEvent slidingEvent =
        new ActionEvent(this, SLIDING_ACTION, "");
    
    
    /**
     * <p>The standard event that will be passed to the sequence
     * <code>releaseActions</code> when the mouse is released
     * after being pressed in the active area of the slider
     * (thumb or track).</p>
     * 
     * <p>The source is <code>this</code>.</p>
     * 
     * <p>The ID is <code>RELEASE_ACTION</code>.</p>
     * 
     * <p>The command string is empty.</p>
     */
    public final ActionEvent releaseEvent =
        new ActionEvent(this, RELEASE_ACTION, "");
    
    
    /**
     * The text field view that will echo the current
     * conceptual value of the slider.  This view may
     * also be used for input of the slider value.
     */
    protected TextFieldView valueTFV = new TextFieldView();
    
    
    /**
     * The action to set the slider value from the
     * value text field view.
     */
    protected SimpleAction setValueFromValueTFV =
        new SimpleAction() {
            public void perform() {
                setValueFromValueTFV();
            }
    };
    
    
    /**
     * The slider orientation which should be either
     * <code>HORIZONTAL</code> or <code>VERTICAL</code>.
     */
    protected int orientation = HORIZONTAL;
    
    
    /**
     * <p>The track minimum value.</p>
     * 
     * <p>If the caller enters a minimum larger than the maximum
     * then that will reverse the order on the slider.</p>
     * 
     * <p>It is required that minimum and maximum be unequal.
     */
    protected int minimum;
    
    
    /**
     * <p>The track maximum value.</p>
     * 
     * <p>If the caller enters a minimum larger than the maximum
     * then that will reverse the order on the slider.</p>
     * 
     * <p>It is required that minimum and maximum be unequal.
     */
    protected int maximum;
    
    
    /**
     * The difference (maximum - minimum).
     */
    protected int delta;
    
    
    /**
     * <p>The value of the slider, that is, what will be returned
     * by the call <code>getValue()</code>.</p>
     * 
     * <p>The value should be between minimum and maximum.</p>
     */
    protected int value;
    
    
    /** The default value for this slider. */
    protected int defaultValue;
    
    
    /** 
     * <p>The track pixels, that is, the size of the track
     * along its major direction.</p>
     * 
     * <p>It is required that pixels be at least
     * <code>MINIMUM_PIXELS</code>.</p>
     */
    protected int pixels;
    
    
    /** 
     * <p>The track limit, that is, the last valid location
     * on the track along its major direction.</p>
     * 
     * <p>Equivalent to (pixels - 1).</p>
     */
    protected int limit;
    
    
    /**
     * <p>The thumb location along the track.</p>
     * 
     * <p>The location should be between 0 and limit.</p>
     */
    protected int location;
    
    
    /** The scale factor for <code>locationToValue</code>. */
    protected double f_loc_to_val;
    
    
    /** The translation for <code>locationToValue</code>. */
    protected double t_loc_to_val;
    
    
    /** The scale factor for <code>valueToLocation</code>. */
    protected double f_val_to_loc;
    
    
    /** The translation for <code>valueToLocation</code>. */
    protected double t_val_to_loc;
    
    
    /**
     * <p>The major ticks setting.</p>
     * 
     * <p>If zero, then major ticks and labels will not be shown
     * automatically but may be added by the caller.</p>
     */
    protected int majorTicks;
    
    
    /**
     * <p>The minor ticks setting.</p>
     * 
     * <p>If zero, then minor ticks will not be shown
     * automatically but may be added by the caller.</p>
     */
    protected int minorTicks;
    
    
    /**
     * <p>The dragging parameter will be set to true if and
     * only if the user is currently dragging the thumb.
     * This is only used internally.</p>
     */
    protected boolean dragging = false;
    
    
    /** The property list for this view object. */
    protected InputProperties properties = new InputProperties();
    
    
    /**
     * <p>Constructs a default <code>SliderView</code>.</p>
     * 
     * <p>The orientation will be horizontal.</p>
     * 
     * <p>The minimum will be 0.</p>
     * 
     * <p>The maximum will be 100.</p>
     * 
     * <p>The initial value will be 50.</p>
     * 
     * <p>The size of the track will be 101 pixels.  To control
     * the track size you must use one of the constructors that
     * include the <code>pixels</code> parameter.</p>
     * 
     * <p>If this constructor is used, there will be no automatic
     * major ticks, minor ticks, or labels.  These may be added
     * by the caller if desired by using the methods of this
     * class.</p>
     * 
     * <p>This constructor will show the text field in which the
     * value of the slider is automatically displayed.</p>
     * 
     * <p>This constructor is included for backward compatibility
     * and to permit experiments since no parameters are required.</p>
     */
    public SliderView() {
        this(HORIZONTAL, 0, 100, 50, 101, 0, 0,
                null, null, null, null, true);
    }
    
    
    /**
     * <p>Constructs a <code>SliderView</code> with the given
     * orientation which must be <code>HORIZONTAL</code> or
     * <code>VERTICAL</code>.</p>
     * 
     * <p>The minimum will be 0.</p>
     * 
     * <p>The maximum will be 100.</p>
     * 
     * <p>The initial value will be 50.</p>
     * 
     * <p>The size of the track will be 101 pixels.  To control
     * the track size you must use one of the constructors that
     * include the <code>pixels</code> parameter.</p>
     * 
     * <p>If this constructor is used, there will be no automatic
     * major ticks, minor ticks, or labels.  These may be added
     * by the caller if desired by using the methods of this
     * class.</p>
     * 
     * <p>This constructor will show the text field in which the
     * value of the slider is automatically displayed.</p>
     * 
     * <p>This constructor is included for backward compatibility.</p>
     */
    public SliderView(int orientation) {
        this(orientation, 0, 100, 50, 101, 0, 0,
                null, null, null, null, true);
    }
    
    
    /**
     * <p>Constructs a <code>SliderView</code> with the given
     * parameters.</p>
     * 
     * <p>The <code>orientation</code> controls the orientation
     * of the slider in the GUI and should be either
     * <code>HORIZONTAL</code> or <code>VERTICAL</code>.</p>
     * 
     * <p>The <code>minimum</code> and <code>maximum</code>
     * parameters determine the minimum and maximum values that
     * the slider can return via the <code>getValue()</code>
     * method.  It is permissible for these values to be in the
     * opposite order and in that case the slider will work in
     * the opposite order.</p>
     * 
     * <p>The <code>value</code> paramter is the initial slider
     * value, except that, if it is out of range, then it will
     * be forced into range.</p>
     * 
     * <p>The size of the track will be 101 pixels.  To control
     * the track size you must use one of the constructors that
     * include the <code>pixels</code> parameter.</p>
     * 
     * <p>If this constructor is used, there will be no automatic
     * major ticks, minor ticks, or labels.  These may be added
     * by the caller if desired by using the methods of this
     * class.</p>
     * 
     * <p>This constructor will show the text field in which the
     * value of the slider is automatically displayed.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if minimum equals maximum
     * or if pixels is less than <code>MINIMUM_PIXELS</code>.
     * 
     * <p>This constructor is included for backward compatibility.</p>
     */
    public SliderView(int orientation,
            int minimum,
            int maximum,
            int value)
    {
        this(orientation, minimum, maximum, value, 101, 0, 0,
                null, null, null, null, true);
    }
    
    
    /**
     * <p>Constructs a <code>SliderView</code> with the given
     * parameters.</p>
     * 
     * <p>The <code>orientation</code> controls the orientation
     * of the slider in the GUI and should be either
     * <code>HORIZONTAL</code> or <code>VERTICAL</code>.</p>
     * 
     * <p>The <code>minimum</code> and <code>maximum</code>
     * parameters determine the minimum and maximum values that
     * the slider can return via the <code>getValue()</code>
     * method.  It is permissible for these values to be in the
     * opposite order and in that case the slider will work in
     * the opposite order.</p>
     * 
     * <p>The <code>value</code> paramter is the initial slider
     * value, except that, if it is out of range, then it will
     * be forced into range.</p>
     * 
     * <p>The <code>pixels</code>parameter is the physical size
     * of the slider track in the direction of slider motion.</p>
     * 
     * <p>If this constructor is used, there will be no automatic
     * major ticks, minor ticks, or labels.  These may be added
     * by the caller if desired by using the methods of this
     * class.</p>
     * 
     * <p>This constructor will show the text field in which the
     * value of the slider is automatically displayed.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if minimum equals maximum
     * or if pixels is less than <code>MINIMUM_PIXELS</code>.
     */
    public SliderView
        (int orientation,
         int minimum,
         int maximum,
         int value,
         int pixels)
    {
        this(orientation, minimum, maximum, value, pixels, 0, 0,
                null, null, null, null, true);
    }
    
    
    /**
     * <p>Constructs a <code>SliderView</code> with the given
     * parameters.</p>
     * 
     * <p>The <code>orientation</code> controls the orientation
     * of the slider in the GUI and should be either
     * <code>HORIZONTAL</code> or <code>VERTICAL</code>.</p>
     * 
     * <p>The <code>minimum</code> and <code>maximum</code>
     * parameters determine the minimum and maximum values that
     * the slider can return via the <code>getValue()</code>
     * method.  It is permissible for these values to be in the
     * opposite order and in that case the slider will work in
     * the opposite order.</p>
     * 
     * <p>The <code>value</code> paramter is the initial slider
     * value, except that, if it is out of range, then it will
     * be forced into range.</p>
     * 
     * <p>The <code>pixels</code>parameter is the physical size
     * of the slider track in the direction of slider motion.</p>
     * 
     * <p>The <code>majorTicks</code>parameter controls the
     * position of the major ticks along the slider and the
     * associated automatic labels.  If this parameter is 0,
     * then major ticks and labels will not be automatically
     * drawn.  These decorations may be supplied manually by
     * the caller using methods in the class.</p>
     * 
     * <p>The <code>minorTicks</code>parameter controls the
     * position of the minor ticks along the slider.  If
     * this parameter is 0, then minor ticks will not be
     * automatically drawn.</p>
     * 
     * <p>This constructor will show the text field in which the
     * value of the slider is automatically displayed.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if minimum equals maximum
     * or if pixels is less than <code>MINIMUM_PIXELS</code>.
     */
    public SliderView
        (int orientation,
         int minimum,
         int maximum,
         int value,
         int pixels,
         int majorTicks,
         int minorTicks)
    {
        this(orientation, minimum, maximum, value, pixels, majorTicks, minorTicks,
                null, null, null, null, true);
    }
    
    
    /**
     * <p>Constructs a <code>SliderView</code> with the given
     * parameters.</p>
     * 
     * <p>The <code>orientation</code> controls the orientation
     * of the slider in the GUI and should be either
     * <code>HORIZONTAL</code> or <code>VERTICAL</code>.</p>
     * 
     * <p>The <code>minimum</code> and <code>maximum</code>
     * parameters determine the minimum and maximum values that
     * the slider can return via the <code>getValue()</code>
     * method.  It is permissible for these values to be in the
     * opposite order and in that case the slider will work in
     * the opposite order.</p>
     * 
     * <p>The <code>value</code> paramter is the initial slider
     * value, except that, if it is out of range, then it will
     * be forced into range.</p>
     * 
     * <p>The <code>pixels</code>parameter is the physical size
     * of the slider track in the direction of slider motion.</p>
     * 
     * <p>The <code>majorTicks</code>parameter controls the
     * position of the major ticks along the slider and the
     * associated automatic labels.  If this parameter is 0,
     * then major ticks and labels will not be automatically
     * drawn.  These decorations may be supplied manually by
     * the caller using methods in the class.</p>
     * 
     * <p>The <code>minorTicks</code>parameter controls the
     * position of the minor ticks along the slider.  If
     * this parameter is 0, then minor ticks will not be
     * automatically drawn.</p>
     * 
     * <p>This constructor allows the caller to set the various
     * paints and colors used in the GUI.  If a parameter is
     * <code>null</code> then the default will be used.</p>
     * 
     * <p>The paint and color defaults are:</p>
     * 
     * <ul>
     *   <li><code>ordinaryThumbPaint</code>: <code>Color.white</code></li>
     *   <li><code>selectedThumbPaint</code>: <code>Color.red</code></li>
     *   <li><code>trackPaint</code>: <code>Color.yellow</code></li>
     *   <li><code>commonColor</code>: <code>Color.black</code></li>
     * </ul>
     * 
     * <p>The common color is used for tick marks, borders, and text.</p>
     * 
     * <p>This constructor will show the text field in which the
     * value of the slider is automatically displayed <i>if</i> the
     * <code>showValueTFV</code> parameter is set to <code>true</code>.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if minimum equals maximum
     * or if pixels is less than <code>MINIMUM_PIXELS</code>.
     */
    public SliderView
        (int orientation,
         int minimum,
         int maximum,
         int value,
         int pixels,
         int majorTicks,
         int minorTicks,
         Paint ordinaryThumbPaint,
         Paint selectedThumbPaint,
         Paint trackPaint,
         Color commonColor,
         boolean showValueTFV)
    {
        initializeNumerics
            (orientation, minimum, maximum, value, pixels, majorTicks, minorTicks);
        
        initializePaints
            (ordinaryThumbPaint, selectedThumbPaint, trackPaint, commonColor);
        
        initializeValueTFV();
        
        initializeThumb();
        initializeTrack();
        
        installDefaultMajorTicks();
        installDefaultMinorTicks();
        installDefaultLabels();
        
        initializeMainSequence();
        
        initializeMouse();
        
        installMainPanel(showValueTFV);
    }


    /**
     * <p>Initializes important numerical parameters.</p>
     * 
     * <p>Initializes the orientation so that it must be
     * either <code>HORIZONTAL</code>
     * or <code>VERTICAL</code>.</p>
     * 
     * <p>Initializes the numeric relationships between
     * the conceptual slider data (minimum, maximum, value)
     * and the pixel data (pixels, limit, location).</p>
     * 
     * <p>Initializes the tick settings.  Furthermore, if
     * both tick settings are positive, then minorTicks
     * is forced to be a divisor of majorTicks.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if minimum equals maximum
     * or if pixels is less than <code>MINIMUM_PIXELS</code>.
     */
    protected final void initializeNumerics
        (int orientation,
         int minimum,
         int maximum,
         int value,
         int pixels,
         int majorTicks,
         int minorTicks)
    {
        String message = "";
        
        if (minimum == maximum)
            message = "Slider minimum cannot equal slider maximum\n";
        
        if (pixels < MINIMUM_PIXELS)
            message += "Slider pixels must be at least " + MINIMUM_PIXELS + "\n";
        
        if (message.length() > 0)
            throw new IllegalArgumentException(message);
        
        if (orientation != VERTICAL)
            orientation = HORIZONTAL;
        
        this.orientation = orientation;
        
        this.minimum = minimum;
        this.maximum = maximum;
        
        delta = maximum - minimum;
        
        value = getValidValue(value);
        this.value = value;
        
        defaultValue = value;
        
        this.pixels = pixels;
        
        limit = pixels - 1;
        
        f_loc_to_val = delta;
        f_loc_to_val /= limit;
        
        t_loc_to_val = minimum;
        
        f_val_to_loc = limit;
        f_val_to_loc /= delta;
        
        t_val_to_loc = - f_val_to_loc * minimum;
        
        location = valueToLocation(value);
        
        majorTicks = (majorTicks > 0) ? majorTicks : 0;
        minorTicks = (minorTicks > 0) ? minorTicks : 0;
        
        if ((majorTicks > 0) && (minorTicks > 0))
            minorTicks = (int) MathUtilities.GCD(majorTicks, minorTicks);
        
        this.majorTicks = majorTicks;
        this.minorTicks = minorTicks;
    }
    
    
    /**
     * <p>Performs the paint and color initializations.</p>
     *
     * <p>This method which is called by the most elaborate constructor
     * allows the caller to set the various paints and colors used in
     * the GUI.  If a parameter is <code>null</code> then the default
     * will be used.</p>
     * 
     * <p>The paint and color defaults are:</p>
     * 
     * <ul>
     *   <li><code>ordinaryThumbPaint</code>: <code>Color.white</code></li>
     *   <li><code>selectedThumbPaint</code>: <code>Color.red</code></li>
     *   <li><code>trackPaint</code>: <code>Color.yellow</code></li>
     *   <li><code>commonColor</code>: <code>Color.black</code></li>
     * </ul>
     * 
     * <p>The common color is used by tick marks, borders, and text.</p>
     * 
     * <p>There are individual public methods to reset each particular
     * paint or color after a constructor has been called.</p>
     */
    protected final void initializePaints
        (Paint ordinaryThumbPaint,
         Paint selectedThumbPaint,
         Paint trackPaint,
         Color commonColor
        )
    {
        if (ordinaryThumbPaint != null)
            this.ordinaryThumbPaint = ordinaryThumbPaint;
        
        if (selectedThumbPaint != null)
            this.selectedThumbPaint = selectedThumbPaint;
        
        if (trackPaint != null)
            this.trackPaint = trackPaint;
        
        if (commonColor != null)
            this.commonColor = commonColor;
    }
    
    
    /** Performs the initializations for the value text field. */
    protected final void initializeValueTFV() {
        valueTFV.addActionListener(setValueFromValueTFV);
        
        valueTFV.setFont(labelFont);
        valueTFV.setForeground(commonColor);
        valueTFV.setSelectedTextColor(commonColor);
        
        String minString = "" + minimum;
        String maxString = "" + maximum;
        
        int n1 = minString.length();
        int n2 = maxString.length();
        
        String sample = (n1 > n2) ? minString : maxString;
        
        int width = TextFieldView.getSampleWidth(labelFont, sample);
        
        valueTFV.setPreferredWidth(width);
    }
    
    
    /** Performs the initializations for the thumb. */
    protected final void initializeThumb() {
        int s = THUMB_SIZE;
        int p = THUMB_PLUS;
        
        PlotMarkAlgorithm alg;
        
        if (orientation == HORIZONTAL) {
            alg = PlotMarkAlgorithm.ThumbS;
            thumbShape = alg.makeShape(0, s, s);
        }
        else {
            alg = PlotMarkAlgorithm.ThumbE;
            thumbShape = alg.makeShape(s, 0, s);
        }
        
        thumb = new ShapePaintable
            (thumbShape, PaintMode.FILL_DRAW, ordinaryThumbPaint, commonColor, STROKE2);
        
        thumb.setDefaultOriginalBounds2D(new XRect(-p, -p, 2*p, 2*p));
        thumb.setDefaultOriginalCenter(new XPoint2D(0, 0));
    }
    
    
    /**
     * <p>Performs the initializations for the track and track border.</p>
     * 
     * <p>Sets the original default bounds to allow for the thumb.</p>
     */
    protected final void initializeTrack() {
        int t = TRACK_THICK;
        int h = t/2;
        
        int b = TRACK_BORDER_THICK;
        
        int p = THUMB_PLUS;
        
        XRect bounds = null;
        
        if (orientation == HORIZONTAL) {
            trackRect = new XRect(0, -h, pixels, t);
            trackBorderRect = new XRect(-b, -(h + b), pixels + 2*b, t + 2*b);
            bounds = new XRect(-p, -p, pixels + 2*p, 2*p);
        }
        else {
            trackRect = new XRect(-h, 0, t, pixels);
            trackBorderRect = new XRect(-(h + b), -b, t + 2*b, pixels + 2*b);
            bounds = new XRect(-p, -p, 2*p, pixels + 2*p);
        }
        
        track = new ShapePaintable
            (trackRect, PaintMode.FILL, trackPaint);
        
        trackBorder = new ShapePaintable
            (trackBorderRect, PaintMode.FILL, commonColor);
        
        track.setDefaultOriginalBounds2D(bounds);
        
        trackBorder.setDefaultOriginalBounds2D(bounds);
    }
    
    
    /**
     * <p>Appends all items to the main paintable sequence. </p>
     * 
     * <p>These are in order:</p>
     * 
     * <ul>
     *   <li>Index 0: <code>thumb</code></li>
     *   <li>Index 1: <code>track</code></li>
     *   <li>Index 2: <code>trackBorder</code></li>
     *   <li>Index 3: <code>tickSequence</code> for ticks</li>
     *   <li>Index 4: <code>labelSequence</code> for labels</li>
     *   <li>Index 5: <code>objectSequence</code> for general decorations</li>
     * </ul>
     * 
     * <p>This order is the top-to-bottom order of painting.
     * In particular, any general decorations will be below
     * all other objects in the GUI.</p>
     */
    protected final void initializeMainSequence() {
        mainSequence.appendPaintable(thumb);
        mainSequence.appendPaintable(track);
        mainSequence.appendPaintable(trackBorder);
        mainSequence.appendPaintable(tickSequence);
        mainSequence.appendPaintable(labelSequence);
        mainSequence.appendPaintable(objectSequence);
        
        resetThumb();
    }
    
    
    /** Initializes the mouse behavior. */
    protected final void initializeMouse() {
        adapter.addMousePressedAction(mousePressAction);
        adapter.addMouseDraggedAction(mouseSlidingAction);
        adapter.addMouseReleasedAction(mouseReleaseAction);
    }
    
    
    /**
     * <p>Installs the main panel with the slider and optionally
     * the value text field.</p>
     * 
     * <p>If <code>showValueTFV</code> is true then the text field
     * in which the value of the slider is automatically displayed
     * will be installed in the GUI.</p>
     */
    protected final void installMainPanel(boolean showValueTFV) {
        TablePanel mainPanel = null;
        
        Object[] stuff = null;
        
        if (showValueTFV)
            stuff = new Object[] { mainComponent, valueTFV };
        else
            stuff = new Object[] { mainComponent };
        
        if (orientation == HORIZONTAL)
            mainPanel = new HTable(stuff, gap, gap, NORTH);
        else
            mainPanel = new VTable(stuff, gap, gap, WEST);
        
        addObject(mainPanel);
        
        updateValueTFV();
    }
    
    
    /**
     * <p>Creates the paintable for one tick.</p>
     *
     * @param val  the tick position in value coordinates
     * @param size the tick size
     */
    public final ShapePaintable makeTick(int val, int size) {
        val = getValidValue(val);
        
        int loc = valueToLocation(val);
        
        int p = THUMB_PLUS + gap;
        int q = p + size;
        
        XLine2D line = new XLine2D();
        
        if (orientation == HORIZONTAL) {
            line.setLine(loc, p, loc, q);
        }
        else {
            line.setLine(p, loc, q, loc);
        }
        
        ShapePaintable paintable =
            new ShapePaintable(line, PaintMode.DRAW, null, commonColor, STROKE2);
        
        return paintable;
    }
    
    
    /**
     * <p>Returns the array of slider values that will be
     * used to generate the tick value positions based on
     * the given tick spacing and the internal minimum
     * and maximum.<p>
     * 
     * <p>For example, if minimum = 1, maximum = 99, and
     * spacing = 10, the following array of values will
     * be returned:</p>
     * 
     * <pre>    1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 99</pre>
     * 
     * <p>Thus, the tick value positions strictly between
     * minimum and maximum are multiples of the spacing.</p>
     * 
     * <p>This algorithm in NOT the one used in the Java
     * <code>JSlider</code> class.</p>
     * 
     * <p>This method may be overridden in a derived class
     * if a different algorithm is desired.  Alternatively,
     * several methods below accept an explicit array of
     * values and a caller may supply whatever values are
     * desired.</p>
     * 
     * <p>Returns an empty array if spacing &lt;= 0;
     * 
     * @param spacing the tick spacing in value coordinates
     */
    public int[] getTickValues(int spacing) {
        if (spacing <= 0)
            return new int[0];
        
        int[] values = null;
        
        int a = minimum % spacing;
        
        if (a < 0)
            a += spacing;
        
        int b = maximum % spacing;
        
        if (b < 0)
            b += spacing;
        
        int count = 0;
        int s = minimum;
        int t = maximum;
        
        if (delta > 0) {
            if (a > 0) {
                count++;
                s += (spacing - a);
            }
            
            if (b > 0) {
                count++;
                t -= b;
            }
            
            if (s <= t) {
                count += ((t - s) / spacing) + 1;
            }
            
            values = new int[count];
            
            int k = 0;
            
            if (a > 0) {
                values[k] = minimum;
                k++;
            }
            
            while (s <= t) {
                values[k] = s;
                k++;
                s += spacing;
            }
            
            if (b > 0) {
                values[k] = maximum;
                k++;
            }
        }
        else {
            if (a > 0) {
                count++;
                s -= a;
            }
            
            if (b > 0) {
                count++;
                t += (spacing - b);
            }
            
            if (s >= t) {
                count += ((s - t) / spacing) + 1;
            }
            
            values = new int[count];
            
            int k = 0;
            
            if (a > 0) {
                values[k] = minimum;
                k++;
            }
            
            while (s >= t) {
                values[k] = s;
                k++;
                s -= spacing;
            }
            
            if (b > 0) {
                values[k] = maximum;
                k++;
            }
        }
        
        return values;
    }
    
    
    /**
     * <p>Installs ticks of the given size at the given value
     * positions.</p>
     *
     * @param values the tick positions in value coordinates
     * @param size   the tick size
     */
    public final void installTicks(int[] values, int size) {
        if ((values == null) || (size <= 0))
            return;
        
        int n = values.length;
        
        for (int i = 0; i < n; i++)
            tickSequence.appendPaintable(makeTick(values[i], size));
    }
    
    
    /**
     * <p>Installs the default major ticks provided that the
     * majorTicks value is positive.  Does nothing if
     * majorTicks is zero.</p>
     * 
     * <p>Calls <code>getTickValues</code> to get the
     * tick positions.</p>
     * 
     * <p>Uses <code>MAJOR_TICK_SIZE</code> for the tick size.</p>
     */
    public final void installDefaultMajorTicks() {
        if (majorTicks <= 0)
            return;
        
        int[] values = getTickValues(majorTicks);
        installTicks(values, MAJOR_TICK_SIZE);
    }
    
    
    /**
     * <p>Installs the default minor ticks provided that the
     * minorTicks value is positive.  Does nothing if
     * minorTicks is zero.</p>
     * 
     * <p>Calls <code>getTickValues</code> to get the
     * tick positions.</p>
     * 
     * <p>Uses <code>MINOR_TICK_SIZE</code> for the tick size.</p>
     */
    public final void installDefaultMinorTicks() {
        if (minorTicks <= 0)
            return;
        
        int[] values = getTickValues(minorTicks);
        installTicks(values, MINOR_TICK_SIZE);
    }
    
    
    /**
     * <p>Installs standard major tick marks following the
     * very simple algorithm available in this class prior
     * to version 2.6.0.</p>
     *
     * <p>Since the installation of tick marks is now made
     * automatic in several constructors, this method will
     * do nothing if either <code>majorTicks</code> or
     * <code>minorTicks</code> is positive.  If both of
     * these settings are zero, then this method will make
     * major ticks using a spacing of (1/10) of the
     * difference between <code>maximum</code> and
     * <code>minimum</code>.  It will not make minor ticks.</p>
     * 
     * <p>This method is retained for backward compatibility
     * but is not recommended for new code.</p>
     */
    public final void installStandardTicks() {
        if ((majorTicks > 0) || (minorTicks > 0))
            return;
        
        majorTicks = Math.abs(delta/10);
        installDefaultMajorTicks();
        majorTicks = 0;
    }
    
    
    /**
     * <p>Creates the paintable for one label.</p>
     * 
     * <p>This method allows space for a tick whose size is
     * given by the size parameter.  If this parameter is
     * zero, then no allowance is made for a tick.</p>
     *
     * @param val   the label position in value coordinates
     * @param label the label as a String
     * @param size  the tick size
     */
    public final TextPaintable makeLabel(int val, String label, int size) {
        val = getValidValue(val);
        
        int loc = valueToLocation(val);
        
        int p = THUMB_PLUS + gap;
        
        if (size > 0)
            p += size + gap;
        
        TextPaintable paintable = null;
        
        if (orientation == HORIZONTAL) {
            paintable =
                new TextPaintable
                    (label, labelFont, commonColor,
                     TextAnchor.CENTER_ASCENTLINE, loc, p);
        }
        else {
            paintable =
                new TextPaintable
                    (label, labelFont, commonColor,
                     TextAnchor.LEFT_ASCENTLINE, p, loc - fontSize/2 + 1);
        }
        
        return paintable;
    }
    
    
    /**
     * <p>Returns the array of numeric Strings corresponding
     * to the array of integer values.</p>
     *  
     * <p>Returns an empty array if the parameter array is
     * <code>null</code>.</p>
     * 
     * @param values the integer values to convert to String
     */
    public final String[] makeLabelStrings(int[] values) {
        if (values == null)
            return new String[0];
        
        int length = values.length;
        
        String[] labels = new String[length];
        
        for (int i = 0; i < length; i++)
            labels[i] = "" + values[i];
        
        return labels;
    }
    
    
    /**
     * <p>Returns the array of <code>TextPaintable</code> labels
     * positioned at the given value positions and using the
     * given String labels.</p>
     * 
     * <p>Returns an empty array if either parameter array is
     * <code>null</code>.</p>
     * 
     * <p>If the parameter arrays have unequal length, then
     * the minimum of the lengths is used in the construction.</p>
     * 
     * <p>This method allows space for a tick whose size is
     * given by the size parameter.  If this parameter is
     * zero, then no allowance is made for a tick.</p>
     *
     * @param values the value positions for the labels
     * @param labels the label data as String
     * @param size   the tick size
     */
    public final TextPaintable[] makeLabels
        (int[] values, String[] labels, int size)
    {
        if ((values == null) || (labels == null))
            return new TextPaintable[0];
        
        int a = values.length;
        int b = labels.length;
        int n = Math.min(a, b);
        
        TextPaintable[] tp = new TextPaintable[n];
        
        for (int i = 0; i < n; i++)
            tp[i] = makeLabel(values[i], labels[i], size);
        
        return tp;
    }
    
    
    /**
     * <p>Makes an array of <code>TextPaintable</code> labels
     * using the given value positions and String labels and
     * installs the created labels in the internal paintable
     * sequence that is associated with labels.</p>
     * 
     * <p>This method allows space for a tick whose size is
     * given by the size parameter.  If this parameter is
     * zero, then no allowance is made for a tick.</p>
     *
     * @param values the value positions for the labels
     * @param labels the label data as String
     * @param size   the tick size
     */
    public final void installLabels
        (int[] values, String[] labels, int size)
    {
        labelSequence.appendSequence(makeLabels(values, labels, size));
    }
    
    
    /**
     * <p>Installs the default labels provided that the
     * majorTicks value is positive.  Does nothing if
     * majorTicks is zero.</p>
     * 
     * <p>Calls <code>getTickValues</code> to get the
     * tick positions.</p>
     * 
     * <p>Then calls <code>makeLabelStrings</code> to
     * get the String labels.</p>
     * 
     * <p>Assumes <code>MAJOR_TICK_SIZE</code> as the
     * tick size.</p>
     * 
     * <p>Then calls <code>installLabels</code>.</p>
     */
    public final void installDefaultLabels() {
        if (majorTicks <= 0)
            return;
        
        int[] values = getTickValues(majorTicks);
        String[] labels = makeLabelStrings(values);
        installLabels(values, labels, MAJOR_TICK_SIZE);
    }
    
    
    /**
     * <p>Installs standard labels following the
     * very simple algorithm available in this class prior
     * to version 2.6.0.</p>
     *
     * <p>Since the installation of labels is now made
     * automatic in several constructors, this method will
     * do nothing if either <code>majorTicks</code> or
     * <code>minorTicks</code> is positive.  If both of
     * these settings are zero, then this method will make
     * labels using a spacing of (1/10) of the
     * difference between <code>maximum</code> and
     * <code>minimum</code>.</p>
     * 
     * <p>This method is retained for backward compatibility
     * but is not recommended for new code.</p>
     */
    public void installStandardLabels() {
        if ((majorTicks > 0) || (minorTicks > 0))
            return;
        
        majorTicks = Math.abs(delta/10);
        installDefaultLabels();
        majorTicks = 0;
    }
    
    
    /**
     * <p>Installs the given <code>Paintable</code> objects as
     * decorations in the internal paintable sequence that is
     * associated with objects.</p>
     * 
     * <p>This method gives the caller complete control of what
     * <code>Paintable</code> objects are placed as decorations
     * in the slider GUI and where.</p>
     * 
     * <p>These decorations will be below all other objects in
     * the GUI.  It is recommended that the caller experiment
     * carefully with the placement of decorations to avoid
     * visual conflicts.</p>
     * 
     * <p>This method supports rather esoteric needs and will
     * likely not be used by most callers of this class.</p>
     * 
     * @param objects the paintable objects to place in the GUI
     */
    public final void installObjects(Paintable[] objects) {
        objectSequence.appendSequence(objects);
    }
    
    
    /**
     * <p>If the given value is between minimum and maximum, then
     * return that value; otherwise return the closest valid
     * value to the given value.</p>
     * 
     * <p>This method is a helper method that does not change the
     * slider state.</p>
     * 
     * @param val the value to test
     */
    public final int getValidValue(int val) {
        if (delta > 0) {
            if (val < minimum)
                return minimum;
            
            if (val > maximum)
                return maximum;
            
            return val;
        }
        else {
            if (val > minimum)
                return minimum;
            
            if (val < maximum)
                return maximum;
            
            return val;
        }
    }
    
    
    /**
     * <p>If the given location is between 0 and limit, then
     * return that location; otherwise return the closest
     * valid location to the given location.</p>
     * 
     * <p>This method is a helper method that does not change the
     * slider state.</p>
     * 
     * @param loc the location to test
     */
    public final int getValidLocation(int loc) {
        if (loc < 0)
            return 0;
        
        if (loc > limit)
            return limit;
        
        return loc;
    }
    
    
    /**
     * <p>The affine transform that converts the given value
     * to its best approximation as a location on the track.</p>
     * 
     * <p>This method is a helper method that does not change the
     * slider state.</p>
     * 
     * @param val the value to convert
     */
    public final int valueToLocation(int val) {
        return (int) Math.round(f_val_to_loc * val + t_val_to_loc);
    }
    
    
    /**
     * <p>The affine transform that converts the given location
     * on the track to its best approximation as a value.</p>
     * 
     * <p>This method is a helper method that does not change the
     * slider state.</p>
     * 
     * @param loc the location to convert
     */
    public final int locationToValue(int loc) {
        return (int) Math.round(f_loc_to_val * loc + t_loc_to_val);
    }
    
    
    /** Returns the current conceptual value of the slider. */
    public final int getValue() { return value; }
    
    
    /**
     * <p>Sets the current conceptual value of the slider
     * in a manner that simulates what happens when the
     * slider is set interactively via the mouse. </p>
     * 
     * <p>Uses the following steps:</p>
     * 
     * <ul>
     *   <li>Invokes <code>pressActions</code>.</li>
     *   <li>Sets the slider value using <code>setValueHelper</code>.</li>
     *   <li>Invokes <code>slidingActions</code>.</li>
     *   <li>Invokes <code>releaseActions</code>.</li>
     * </ul>
     * 
     * @param val the value to set
     */
    public final void setValue(int val) {
        pressActions.actionPerformed(pressEvent);
        
        setValueHelper(val);
        
        slidingActions.actionPerformed(slidingEvent);
        releaseActions.actionPerformed(releaseEvent);
    }
    
    
    /**
     * <p>Sets the current conceptual value of the slider
     * but does not invoke the press, sliding, or release
     * actions.</p>
     * 
     * <p>Ensures that the value used is valid.</p>
     * 
     * <p>Updates the corresponding track location
     * to the best approximation for this value.</p>
     * 
     * <p>Calls <code>resetThumb()</code> and
     * <code>updateValueTFV()</code> to update the GUI.</p>
     * 
     * <p>This method is normally not called directly since
     * it does not invoke the user defined actions.  However,
     * to support unusual circumstances, it is available as
     * a public method.</p>
     * 
     * @param val the value to set
    */
    public final void setValueHelper(int val) {
        value = getValidValue(val);
        location = valueToLocation(value);
        
        resetThumb();
        updateValueTFV();
    }
    
    
    /** Returns the current track location of the slider. */
    public final int getTrackLocation() { return location; }
    
    
    /**
     * <p>Sets the current track location of the slider
     * in a manner that simulates what happens when the
     * slider is set interactively via the mouse. </p>
     * 
     * <p>Uses the following steps:</p>
     * 
     * <ul>
     *   <li>Invokes <code>pressActions</code>.</li>
     *   <li>Sets the slider location using <code>setLocationHelper</code>.</li>
     *   <li>Invokes <code>slidingActions</code>.</li>
     *   <li>Invokes <code>releaseActions</code>.</li>
     * </ul>
     * 
     * @param loc the location to set
    */
    public final void setTrackLocation(int loc) {
        pressActions.actionPerformed(pressEvent);
        
        setTrackLocationHelper(loc);
        
        slidingActions.actionPerformed(slidingEvent);
        releaseActions.actionPerformed(releaseEvent);
    }
    
    
    /**
     * <p>Sets the current track location of the slider
     * but does not invoke the press, sliding, or release
     * actions.</p>
     * 
     * <p>Ensures that the location used is valid.</p>
     * 
     * <p>Updates the corresponding conceptual value
     * to the best approximation for this location.</p>
     * 
     * <p>Calls <code>resetThumb()</code> and
     * <code>updateValueTFV()</code> to update the GUI.</p>
     * 
     * <p>This method is normally not called directly since
     * it does not invoke the user defined actions.  However,
     * to support unusual circumstances, it is available as
     * a public method.</p>
     * 
     * @param loc the location to set
    */
    public final void setTrackLocationHelper(int loc) {
        location = getValidLocation(loc);
        value = locationToValue(location);
        
        resetThumb();
        updateValueTFV();
    }
    
    
    /**
     * <p>The method to reset the location of the
     * slider thumb on the track.</p>
     */
    public final void resetThumb() {
        if (orientation == HORIZONTAL) {
            thumb.moveCenterTo(location, 0);
        }
        else {
            thumb.moveCenterTo(0, location);
        }
    }
    
    
    /**
     * Set the ordinary paint for the thumb.
     * 
     * @param paint the paint to set
     */
    public final void setOrdinaryThumbPaint(Paint paint) {
        if (paint == null)
            return;
        
        ordinaryThumbPaint = paint;
        
        if (!dragging)
            thumb.setFillPaint(paint);
    }
    
    
    /**
     * Set the selected paint for the thumb.
     * 
     * @param paint the paint to set
     */
    public final void setSelectedThumbPaint(Paint paint) {
        if (paint == null)
            return;
        
        selectedThumbPaint = paint;
        
        if (dragging)
            thumb.setFillPaint(paint);
    }
    
    
    /**
     * Set the paint for the track.
     * 
     * @param paint the paint to set
     */
    public final void setTrackPaint(Paint paint) {
        if (paint == null)
            return;
        
        trackPaint = paint;
        
        track.setFillPaint(paint);
    }
    
    
    /**
     * Set the common color of ticks, borders, and text labels.
     */
    public final void setCommonColor(Color color) {
        if (color == null)
            return;
        
        commonColor = color;
        
        trackBorder.setFillPaint(color);
        
        int n = tickSequence.length();
        
        for (int i = 0; i < n; i++) {
            Paintable paintable = tickSequence.getPaintable(i);
            
            if (paintable instanceof ShapePaintable) {
                ShapePaintable sp = (ShapePaintable) paintable;
                
                sp.setDrawPaint(color);
            }
        }
        
        n = labelSequence.length();
        
        for (int i = 0; i < n; i++) {
            Paintable paintable = labelSequence.getPaintable(i);
            
            if (paintable instanceof TextPaintable) {
                TextPaintable tp = (TextPaintable) paintable;
                
                tp.setFillPaint(color);
            }
        }
        
        valueTFV.setForeground(commonColor);
        valueTFV.setSelectedTextColor(commonColor);
    }
    
    
    /**
     * <p>Adds the given action listener to the sequence of actions
     * that is performed when slider is pressed in its active area
     * (thumb or track).</p>
     * 
     * <p>This action will be invoked with the standard event
     * <code>pressEvent</code>.</p>
     * 
     * <p>This action may query this slider using the methods
     * <code>getValue()</code> or <code>getTrackLocation()</code>
     * to decide what to do.</p>
     * 
     * @param a the press action listener to add
     */
    public void addPressAction(ActionListener a) {
        pressActions.add(a);
    }
    
    
    /**
     * <p>Adds the given action listener to the sequence of actions
     * that is performed when slider is sliding after being pressed
     * in its active area (thumb or track).</p>
     *
     * <p>This action will be invoked with the standard event
     * <code>slidingEvent</code>.</p>
     * 
     * <p>This action may query this slider using the methods
     * <code>getValue()</code> or <code>getTrackLocation()</code>
     * to decide what to do.</p>
     * 
     * @param a the sliding action listener to add
     */
    public void addSlidingAction(ActionListener a) {
        slidingActions.add(a);
    }
    
    
    /**
     * <p>Adds the given action listener to the sequence of actions
     * that is performed when slider is released after being pressed
     * in its active area (thumb or track).</p>
     *
     * <p>This action will be invoked with the standard event
     * <code>releaseEvent</code>.</p>
     * 
     * <p>This action may query this slider using the methods
     * <code>getValue()</code> or <code>getTrackLocation()</code>
     * to decide what to do.</p>
     * 
     * @param a the release action listener to add
     */
    public void addReleaseAction(ActionListener a) {
        releaseActions.add(a);
    }
    
    
    /**
     * Removes the given action listener from the press actions.
     *
     * @param a the press action listener to remove
     */
    public void removePressAction(ActionListener a) {
        pressActions.remove(a);
    }
    
    
    /**
     * Removes the given action listener from the sliding actions.
     *
     * @param a the sliding action listener to remove
     */
    public void removeSlidingAction(ActionListener a) {
        slidingActions.remove(a);
    }
    
    
    /**
     * Removes the given action listener from the release actions.
     *
     * @param a the release action listener to remove
     */
    public void removeReleaseAction(ActionListener a) {
        releaseActions.remove(a);
    }
    
    
    /**
     * <p>Sets the sequence of press actions.</p>
     * 
     * <p>Clears the press actions if the parameter is
     * <code>null</code>.</p>
     *
     * @param sequence the new press actions
     */
    public void setPressActions(ActionSequence sequence) {
        if (sequence == null)
            pressActions.clear();
        else
            pressActions = sequence;
    }    
    
    
    /**
     * <p>Sets the sequence of sliding actions.</p>
     * 
     * <p>Clears the sliding actions if the parameter is
     * <code>null</code>.</p>
     *
     * @param sequence the new sliding actions
     */
    public void setSlidingActions(ActionSequence sequence) {
        if (sequence == null)
            slidingActions.clear();
        else
            slidingActions = sequence;
    }    
    
    
    /**
     * <p>Sets the sequence of release actions.</p>
     * 
     * <p>Clears the release actions if the parameter is
     * <code>null</code>.</p>
     *
     * @param sequence the new release actions
     */
    public void setReleaseActions(ActionSequence sequence) {
        if (sequence == null)
            releaseActions.clear();
        else
            releaseActions = sequence;
    }    
    
    
    /** Returns the sequence of press actions. */
    public ActionSequence getPressActions() {
        return pressActions;
    }    
    
    
    /** Returns the sequence of sliding actions. */
    public ActionSequence getSlidingActions() {
        return slidingActions;
    }    
    
    
    /** Returns the sequence of release actions. */
    public ActionSequence getReleaseActions() {
        return releaseActions;
    }    
    
    
    /**
     * <p>This method computes the bounds of the sequence
     * <code>mainSequence</code>, stores those bounds in
     * <code>mainBounds</code>, and sets the corner data
     * <code>cornerX</code> and <code>cornerY</code> for
     * use in computation of mouse coordinates.</p>
     * 
     * <p>This method also sets the default bounds of
     * <code>mainSequence</code> to the newly computed
     * <code>mainBounds</code> so that any further change
     * to the main sequence cannot affect its bounds.</p>
     * 
     * <p>The purpose of this method is make computations
     * with the mouse efficient.</p>
     * 
     * <p>This method will be called when the first mouse
     * interaction with the main component occurs.  If it
     * is called again, it will do nothing.</p>
     */
    protected final void freezeBounds() {
        if (mainBounds != null)
            return;
        
        mainBounds = mainSequence.getBounds2D();
        
        mainSequence.setDefaultBounds2D(mainBounds);
        
        cornerX = (int) Math.round(mainBounds.x);
        cornerY = (int) Math.round(mainBounds.y);
    }
    
    
    /**
     * <p>Sets <code>mouseX</code> and <code>mouseY</code>
     * by adjusting the mouse position in the given mouse
     * event by the corner coordinates.</p>
     * 
     * @param evt the mouse event
     */
    protected final void setMouseCoordinates(MouseEvent evt) {
        if (evt == null)
            return;
        
        mouseX = cornerX + evt.getX();
        mouseY = cornerY + evt.getY();
    }
    
    
    /**
     * <p>The method to update the value text field view
     * using the current value settings.</p>
     */
    protected final void updateValueTFV() {
        valueTFV.setViewState("" + value);
    }
    
    
    /**
     * <p>The method to update the value
     * using the contents of the value text field view.</p>
     * 
     * <p>If the contents of the value text field view
     * causes a parse exception, the user will be given
     * a chance to correct the error or cancel.  If the
     * contents is parsable as an integer, then it will
     * be used in a call to <code>setValue</code>.  If
     * that call determines that the value is invalid
     * then the value set will be forced into range.</p>
     * 
     * <p>This method implements the action of the same name
     * and the corresponding action is set as a listener for
     * the value text field view.</p>
     * 
     * <p>If successful, this method will reset the thumb
     * and perform a press, sliding, release sequence
     * for the user-defined actions.</p>
     */
    protected final void setValueFromValueTFV() {
        try {
            int val = valueTFV.requestInt();

            setValue(val);
        }
        catch (CancelledException ex) {
            updateValueTFV();
        }
    }
    
    
    /**
     * <p>The mouse press behavior.</p>
     * 
     * <p>If the mouse was pressed in the active area of the slider
     * (thumb or track), performs the standard startup behavior for
     * the mouse press:</p>
     * 
     * <ul>
     *   <li>Calls the user-defined <code>pressActions</code>
     *       passing the standard event <code>pressEvent</code>.
     *   <li>Sets the fill paint of the thumb to the selected
     *       fill paint.</li>
     *   <li>Uses the mouse position to set the location of the
     *       thumb along the track.</li>
     *   <li>Updates the slider value accordingly.</li>
     * </ul>
     * 
     * <p>Before processing, calls <code>freezeBounds</code>
     * to ensure that the relation between
     * the event coordinates for the main component and
     * the mouse coordinates relative to the main sequence
     * is fixed once and for all.</p>
     */
    protected final void mousePress(MouseEvent evt) {
        freezeBounds();
        
        setMouseCoordinates(evt);
        
        int index = mainSequence.hitsItemAtIndex(mouseX, mouseY);
        
        dragging = (index >= 0) && (index <= 2);
        
        if (dragging) {
            pressActions.actionPerformed(pressEvent);
            
            thumb.setFillPaint(selectedThumbPaint);
            
            if (orientation == HORIZONTAL)
                setTrackLocationHelper(mouseX);
            else
                setTrackLocationHelper(mouseY);
        }
    }
    
    
    /**
     * <p>The mouse sliding behavior.</p>
     * 
     * <p>If the mouse was pressed in the active area of the slider
     * (thumb or track), performs the standard sliding behavior for
     * the subsequent mouse movement:</p>
     * 
     * <ul>
     *   <li>Uses the mouse position to set the location of the
     *       thumb along the track.</li>
     *   <li>Updates the slider value accordingly.</li>
     *   <li>Calls the user-defined <code>slidingActions</code>
     *       passing the standard event <code>slidingEvent</code>.</li>
     * </ul>
     */
    protected final void mouseSliding(MouseEvent evt) {
        if (dragging) {
            setMouseCoordinates(evt);
            
            if (orientation == HORIZONTAL)
                setTrackLocationHelper(mouseX);
            else
                setTrackLocationHelper(mouseY);
            
            slidingActions.actionPerformed(slidingEvent);
        }
    }
    
    
    /**
     * <p>The mouse release behavior.</p>
     * 
     * <p>If the mouse was pressed in the active area of the slider
     * (thumb or track), performs the standard release behavior for
     * the subsequent mouse release:</p>
     * 
     * <ul>
     *   <li>Sets the fill paint of the thumb to the ordinary
     *       fill paint.</li>
     *   <li>Uses the mouse position to set the location of the
     *       thumb along the track.</li>
     *   <li>Updates the slider value accordingly.</li>
     *   <li>Uses the new slider value to reset the location of
     *       the thumb along the track to the position that most
     *       accurately reflects the new slider value.  In other
     *       words, the thumb will jump to the most accurate
     *       position once the mouse is released.</li>
     *   <li>Calls the user-defined <code>slidingActions</code>
     *       passing the standard event <code>slidingEvent</code>.</li>
     *   <li>Calls the user-defined <code>releaseActions</code>
     *       passing the standard event <code>releaseEvent</code>.
     * </ul>
     */
    protected final void mouseRelease(MouseEvent evt) {
        if (dragging) {
            thumb.setFillPaint(ordinaryThumbPaint);
            
            setMouseCoordinates(evt);
            
            if (orientation == HORIZONTAL)
                setTrackLocationHelper(mouseX);
            else
                setTrackLocationHelper(mouseY);
            
            setValueHelper(value);
            
            dragging = false;
            
            slidingActions.actionPerformed(slidingEvent);
            releaseActions.actionPerformed(releaseEvent);
        }
    }
    
    
    ///////////////
    // TypedView //
    ///////////////

    /**
     * Returns an <code>XInt</code> object 
     * whose state is set to the value of this slider.
     */
    public Stringable demandObject() {
        return new XInt(getValue());
    }
    
    
    /**
     * Returns an <code>XInt</code> object 
     * whose state is set to the value of this slider.
     */
    public Stringable requestObject() {
        return demandObject();
    }
    
    
    public void setInputProperties(InputProperties p) {
        if (p == null)
            p = InputProperties.BASE_PROPERTIES;
    
        properties = p;
    }
    
    
    public InputProperties getInputProperties() {
        return properties;
    }
    
    
    /** Returns the <code>XInt</code> class object. */ 
    public Class getDataType() {
        return XInt.class;
    }
    
    
    /////////////////
    // Displayable //
    /////////////////
                                                                    
    /**
     * <p>Sets the value for this slider
     * to the <code>int</code> value represented
     * by the given <code>String</code> data.</p>
     *
     * <p>If the <code>String</code> is not parsable to
     * an <code>int</code>, then this method does
     * nothing.</p>
     * 
     * <p>If the <code>String</code> is parsable to an
     * <code>int</code> but that <code>int</code> is not
     * in range then the value will be forced into range.</p>
     * 
     * @param data the new view state
     */
    public void setViewState(String data) {
        try {
            XInt x = new XInt(data);
            setValue(x.getValue());
        }
        catch (ParseException ex) { }
    }
    
    
    /**
     * Returns a <code>String</code> representation
     * of the current integer value of this slider.
     */
    public String getViewState() {
        return "" + value;
    }
    
    
    /**
     * <p>Sets the default value for this slider
     * to the <code>int</code> value represented
     * by the given <code>String</code> data.</p>
     *
     * <p>If the <code>String</code> is not parsable to
     * an <code>int</code>, then this method does
     * nothing.</p>
     * 
     * <p>If the <code>String</code> is parsable to an
     * <code>int</code> but that <code>int</code> is not
     * in range then the value will be forced into range.</p>
     * 
     * @param data the new default view state
     */
    public void setDefaultViewState(String data) {
        try {
            XInt x = new XInt(data);
            defaultValue = getValidValue(x.getValue());
        }
        catch (ParseException ex) { }
    }
    
    
    /**
     * Returns a <code>String</code> representation
     * of the default integer value of this slider.
     */
    public String getDefaultViewState() {
        return "" + defaultValue;
    }
    
    
    /** Resets the slider to its default value. */
    public void reset() {
        setValue(defaultValue);
    }
    
    
    /**
     * Returns a <code>String</code> representation
     * of the current integer value of this slider.
     */
    public String toString() {
        return "" + value;
    }
    
}

