// $Id: JRangeSlider.java 271 2009-06-27 22:42:27Z revilo $
/*
 Copyright (c) 2006, 2007, The Cytoscape Consortium (www.cytoscape.org)

 The Cytoscape Consortium is:
 - Institute for Systems Biology
 - University of California San Diego
 - Memorial Sloan-Kettering Cancer Center
 - Institut Pasteur
 - Agilent Technologies

 This library is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 any later version.

 This library is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF
 MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  The software and
 documentation provided hereunder is on an "as is" basis, and the
 Institute for Systems Biology and the Whitehead Institute
 have no obligations to provide maintenance, support,
 updates, enhancements or modifications.  In no event shall the
 Institute for Systems Biology and the Whitehead Institute
 be liable to any party for direct, indirect, special,
 incidental or consequential damages, including lost profits, arising
 out of the use of this software and its documentation, even if the
 Institute for Systems Biology and the Whitehead Institute
 have been advised of the possibility of such damage.  See
 the GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation,
 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

package prefuse.util.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JComponent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


/**
 * <p>Implements a Swing-based Range slider, which allows the user to enter a
 * range (minimum and maximum) value.</p>
 *
 * @author Ben Bederson
 * @author Jesse Grosjean
 * @author Jon Meyer
 * @author Lance Good
 * @author jeffrey heer
 */
public class JRangeSlider extends JComponent implements MouseListener, MouseMotionListener,
                                                        KeyListener {
	/*
	 * NOTE: This is a modified version of the original class distributed by
	 * Ben Bederson, Jesse Grosjean, and Jon Meyer as part of an HCIL Tech
	 * Report.  It is modified to allow both vertical and horitonal modes.
	 * It also fixes a bug with offset on the buttons. Also fixed a bug with
	 * rendering using (x,y) instead of (0,0) as origin.  Also modified to
	 * render arrows as a series of lines rather than as a GeneralPath.
	 * Also modified to fix rounding errors on toLocal and toScreen.
	 *
	 * With inclusion in prefuse, this class has been further modified to use a
	 * bounded range model, support keyboard commands and more extensize
	 * parameterization of rendering/appearance options. Furthermore, a stub
	 * method has been introduced to allow subclasses to perform custom
	 * rendering within the slider through.
	 */
	/**
	 * 
	 */
	final public static int VERTICAL = 0;

	/**
	 * 
	 */
	final public static int HORIZONTAL = 1;

	/**
	 * 
	 */
	final public static int LEFTRIGHT_TOPBOTTOM = 0;

	/**
	 * 
	 */
	final public static int RIGHTLEFT_BOTTOMTOP = 1;

	/**
	 * 
	 */
	final public static int PREFERRED_BREADTH = 16;

	/**
	 * 
	 */
	final public static int PREFERRED_LENGTH = 300;
	final protected static int ARROW_SZ = 16;
	final protected static int ARROW_WIDTH = 8;
	final protected static int ARROW_HEIGHT = 4;
	protected BoundedRangeModel model;
	protected int orientation;
	protected int direction;
	protected boolean empty;
	protected int increment = 1;
	protected int minExtent = 0; // min extent, in pixels
	protected ArrayList listeners = new ArrayList();
	protected ChangeEvent changeEvent = null;
	protected ChangeListener lstnr;
	protected Color thumbColor = new Color(150, 180, 220);

	// ------------------------------------------------------------------------

	/**
	 * Create a new range slider.
	 *
	 * @param minimum - the minimum value of the range.
	 * @param maximum - the maximum value of the range.
	 * @param lowValue - the current low value shown by the range slider's bar.
	 * @param highValue - the current high value shown by the range slider's bar.
	 * @param orientation - construct a horizontal or vertical slider?
	 */
	public JRangeSlider(int minimum, int maximum, int lowValue, int highValue, int orientation) {
		this(new DefaultBoundedRangeModel(lowValue, highValue, minimum, maximum), orientation,
		     LEFTRIGHT_TOPBOTTOM);
	}

	/**
	 * Create a new range slider.
	 *
	 * @param minimum - the minimum value of the range.
	 * @param maximum - the maximum value of the range.
	 * @param lowValue - the current low value shown by the range slider's bar.
	 * @param highValue - the current high value shown by the range slider's bar.
	 * @param orientation - construct a horizontal or vertical slider?
	 * @param direction - Is the slider left-to-right/top-to-bottom or right-to-left/bottom-to-top
	 */
	public JRangeSlider(int minimum, int maximum, int lowValue, int highValue, int orientation,
	                    int direction) {
		this(new DefaultBoundedRangeModel(lowValue, highValue, minimum, maximum), orientation,
		     direction);
	}

	/**
	 * Create a new range slider.
	 *
	 * @param model - a BoundedRangeModel specifying the slider's range
	 * @param orientation - construct a horizontal or vertical slider?
	 * @param direction - Is the slider left-to-right/top-to-bottom or right-to-left/bottom-to-top
	 */
	public JRangeSlider(BoundedRangeModel model, int orientation, int direction) {
		super.setFocusable(true);
		this.model = model;
		this.orientation = orientation;
		this.direction = direction;

		setForeground(Color.LIGHT_GRAY);

		this.lstnr = createListener();
		model.addChangeListener(lstnr);

		addMouseListener(this);
		addMouseMotionListener(this);
		addKeyListener(this);
	}

	/**
	 * Create a listener to relay change events from the bounded range model.
	 * @return a ChangeListener to relay events from the range model
	 */
	protected ChangeListener createListener() {
		return new RangeSliderChangeListener();
	}

	/**
	 * Listener that fires a change event when it receives  change event from
	 * the slider list model.
	 */
	protected class RangeSliderChangeListener implements ChangeListener {
		public void stateChanged(ChangeEvent e) {
			fireChangeEvent();
		}
	}

	/**
	 * Returns the current "low" value shown by the range slider's bar. The low
	 * value meets the constraint minimum <= lowValue <= highValue <= maximum.
	 */
	public int getLowValue() {
		return model.getValue();
	}

	/**
	 * Sets the low value shown by this range slider. This causes the range slider to be
	 * repainted and a ChangeEvent to be fired.
	 * @param lowValue the low value to use
	 */
	public void setLowValue(int lowValue) {
		int e = (model.getValue() - lowValue) + model.getExtent();
		model.setRangeProperties(lowValue, e, model.getMinimum(), model.getMaximum(), false);
		model.setValue(lowValue);
	}

	/**
	 * Returns the current "high" value shown by the range slider's bar. The high
	 * value meets the constraint minimum <= lowValue <= highValue <= maximum.
	 */
	public int getHighValue() {
		return model.getValue() + model.getExtent();
	}

	/**
	 * Sets the high value shown by this range slider. This causes the range slider to be
	 * repainted and a ChangeEvent to be fired.
	 * @param highValue the high value to use
	 */
	public void setHighValue(int highValue) {
		model.setExtent(highValue - model.getValue());
	}

	/**
	 * Set the slider range span.
	 * @param lowValue the low value of the slider range
	 * @param highValue the high value of the slider range
	 */
	public void setRange(int lowValue, int highValue) {
		model.setRangeProperties(lowValue, highValue - lowValue, model.getMinimum(),
		                         model.getMaximum(), false);
	}

	/**
	 * Gets the minimum possible value for either the low value or the high value.
	 * @return the minimum possible range value
	 */
	public int getMinimum() {
		return model.getMinimum();
	}

	/**
	 * Sets the minimum possible value for either the low value or the high value.
	 * @param minimum the minimum possible range value
	 */
	public void setMinimum(int minimum) {
		model.setMinimum(minimum);
	}

	/**
	 * Gets the maximum possible value for either the low value or the high value.
	 * @return the maximum possible range value
	 */
	public int getMaximum() {
		return model.getMaximum();
	}

	/**
	 * Sets the maximum possible value for either the low value or the high value.
	 * @param maximum the maximum possible range value
	 */
	public void setMaximum(int maximum) {
		model.setMaximum(maximum);
	}

	/**
	 * Sets the minimum extent (difference between low and high values).
	 * This method <strong>does not</strong> change the current state of the
	 * model, but can affect all subsequent interaction.
	 * @param minExtent the minimum extent allowed in subsequent interaction
	 */
	public void setMinExtent(int minExtent) {
		this.minExtent = minExtent;
	}

	/**
	 * Sets whether this slider is empty.
	 * @param empty true if set to empty, false otherwise
	 */
	public void setEmpty(boolean empty) {
		this.empty = empty;
		repaint();
	}

	/**
	 * Get the slider thumb color. This is the part of the slider between
	 * the range resize buttons.
	 * @return the slider thumb color
	 */
	public Color getThumbColor() {
		return thumbColor;
	}

	/**
	 * Set the slider thumb color. This is the part of the slider between
	 * the range resize buttons.
	 * @param thumbColor the slider thumb color
	 */
	public void setThumbColor(Color thumbColor) {
		this.thumbColor = thumbColor;
	}

	/**
	 * Get the BoundedRangeModel backing this slider.
	 * @return the slider's range model
	 */
	public BoundedRangeModel getModel() {
		return model;
	}

	/**
	 * Set the BoundedRangeModel backing this slider.
	 * @param brm the slider range model to use
	 */
	public void setModel(BoundedRangeModel brm) {
		model.removeChangeListener(lstnr);
		model = brm;
		model.addChangeListener(lstnr);
		repaint();
	}

	/**
	 * Registers a listener for ChangeEvents.
	 * @param cl the ChangeListener to add
	 */
	public void addChangeListener(ChangeListener cl) {
		if (!listeners.contains(cl))
			listeners.add(cl);
	}

	/**
	 * Removes a listener for ChangeEvents.
	 * @param cl the ChangeListener to remove
	 */
	public void removeChangeListener(ChangeListener cl) {
		listeners.remove(cl);
	}

	/**
	 * Fire a change event to all listeners.
	 */
	protected void fireChangeEvent() {
		repaint();

		if (changeEvent == null)
			changeEvent = new ChangeEvent(this);

		Iterator iter = listeners.iterator();

		while (iter.hasNext())
			((ChangeListener) iter.next()).stateChanged(changeEvent);
	}

	/**
	 * @see java.awt.Component#getPreferredSize()
	 */
	public Dimension getPreferredSize() {
		if (orientation == VERTICAL) {
			return new Dimension(PREFERRED_BREADTH, PREFERRED_LENGTH);
		} else {
			return new Dimension(PREFERRED_LENGTH, PREFERRED_BREADTH);
		}
	}

	// ------------------------------------------------------------------------
	// Rendering

	/**
	 * Override this method to perform custom painting of the slider trough.
	 * @param g a Graphics2D context for rendering
	 * @param width the width of the slider trough
	 * @param height the height of the slider trough
	 */
	protected void customPaint(Graphics2D g, int width, int height) {
		// does nothing in this class
		// subclasses can override to perform custom painting
	}

	/**
	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
	 */
	public void paintComponent(Graphics g) {
		Rectangle bounds = getBounds();
		int width = (int) bounds.getWidth() - 1;
		int height = (int) bounds.getHeight() - 1;

		int min = toScreen(getLowValue());
		int max = toScreen(getHighValue());

		// Paint the full slider if the slider is marked as empty
		if (empty) {
			if (direction == LEFTRIGHT_TOPBOTTOM) {
				min = ARROW_SZ;
				max = (orientation == VERTICAL) ? (height - ARROW_SZ) : (width - ARROW_SZ);
			} else {
				min = (orientation == VERTICAL) ? (height - ARROW_SZ) : (width - ARROW_SZ);
				max = ARROW_SZ;
			}
		}

		Graphics2D g2 = (Graphics2D) g;
		g2.setColor(getBackground());
		g2.fillRect(0, 0, width, height);
		g2.setColor(getForeground());
		g2.drawRect(0, 0, width, height);

		customPaint(g2, width, height);

		// Draw arrow and thumb backgrounds
		g2.setStroke(new BasicStroke(1));

		if (orientation == VERTICAL) {
			if (direction == LEFTRIGHT_TOPBOTTOM) {
				g2.setColor(getForeground());
				g2.fillRect(0, min - ARROW_SZ, width, ARROW_SZ - 1);
				paint3DRectLighting(g2, 0, min - ARROW_SZ, width, ARROW_SZ - 1);

				if (thumbColor != null) {
					g2.setColor(thumbColor);
					g2.fillRect(0, min, width, max - min - 1);
					paint3DRectLighting(g2, 0, min, width, max - min - 1);
				}

				g2.setColor(getForeground());
				g2.fillRect(0, max, width, ARROW_SZ - 1);
				paint3DRectLighting(g2, 0, max, width, ARROW_SZ - 1);

				// Draw arrows          
				g2.setColor(Color.black);
				paintArrow(g2, (width - ARROW_WIDTH) / 2.0,
				           min - ARROW_SZ + ((ARROW_SZ - ARROW_HEIGHT) / 2.0), ARROW_WIDTH,
				           ARROW_HEIGHT, true);
				paintArrow(g2, (width - ARROW_WIDTH) / 2.0,
				           max + ((ARROW_SZ - ARROW_HEIGHT) / 2.0), ARROW_WIDTH, ARROW_HEIGHT, false);
			} else {
				g2.setColor(getForeground());
				g2.fillRect(0, min, width, ARROW_SZ - 1);
				paint3DRectLighting(g2, 0, min, width, ARROW_SZ - 1);

				if (thumbColor != null) {
					g2.setColor(thumbColor);
					g2.fillRect(0, max, width, min - max - 1);
					paint3DRectLighting(g2, 0, max, width, min - max - 1);
				}

				g2.setColor(getForeground());
				g2.fillRect(0, max - ARROW_SZ, width, ARROW_SZ - 1);
				paint3DRectLighting(g2, 0, max - ARROW_SZ, width, ARROW_SZ - 1);

				// Draw arrows          
				g2.setColor(Color.black);
				paintArrow(g2, (width - ARROW_WIDTH) / 2.0,
				           min + ((ARROW_SZ - ARROW_HEIGHT) / 2.0), ARROW_WIDTH, ARROW_HEIGHT, false);
				paintArrow(g2, (width - ARROW_WIDTH) / 2.0,
				           max - ARROW_SZ + ((ARROW_SZ - ARROW_HEIGHT) / 2.0), ARROW_WIDTH,
				           ARROW_HEIGHT, true);
			}
		} else {
			if (direction == LEFTRIGHT_TOPBOTTOM) {
				g2.setColor(getForeground());
				g2.fillRect(min - ARROW_SZ, 0, ARROW_SZ - 1, height);
				paint3DRectLighting(g2, min - ARROW_SZ, 0, ARROW_SZ - 1, height);

				if (thumbColor != null) {
					g2.setColor(thumbColor);
					g2.fillRect(min, 0, max - min - 1, height);
					paint3DRectLighting(g2, min, 0, max - min - 1, height);
				}

				g2.setColor(getForeground());
				g2.fillRect(max, 0, ARROW_SZ - 1, height);
				paint3DRectLighting(g2, max, 0, ARROW_SZ - 1, height);

				// Draw arrows          
				g2.setColor(Color.black);
				paintArrow(g2, min - ARROW_SZ + ((ARROW_SZ - ARROW_HEIGHT) / 2.0),
				           (height - ARROW_WIDTH) / 2.0, ARROW_HEIGHT, ARROW_WIDTH, true);
				paintArrow(g2, max + ((ARROW_SZ - ARROW_HEIGHT) / 2.0),
				           (height - ARROW_WIDTH) / 2.0, ARROW_HEIGHT, ARROW_WIDTH, false);
			} else {
				g2.setColor(getForeground());
				g2.fillRect(min, 0, ARROW_SZ - 1, height);
				paint3DRectLighting(g2, min, 0, ARROW_SZ - 1, height);

				if (thumbColor != null) {
					g2.setColor(thumbColor);
					g2.fillRect(max, 0, min - max - 1, height);
					paint3DRectLighting(g2, max, 0, min - max - 1, height);
				}

				g2.setColor(getForeground());
				g2.fillRect(max - ARROW_SZ, 0, ARROW_SZ - 1, height);
				paint3DRectLighting(g2, max - ARROW_SZ, 0, ARROW_SZ - 1, height);

				// Draw arrows          
				g2.setColor(Color.black);
				paintArrow(g2, min + ((ARROW_SZ - ARROW_HEIGHT) / 2.0),
				           (height - ARROW_WIDTH) / 2.0, ARROW_HEIGHT, ARROW_WIDTH, true);
				paintArrow(g2, max - ARROW_SZ + ((ARROW_SZ - ARROW_HEIGHT) / 2.0),
				           (height - ARROW_WIDTH) / 2.0, ARROW_HEIGHT, ARROW_WIDTH, false);
			}
		}
	}

	/**
	 * This draws an arrow as a series of lines within the specified box.
	 * The last boolean specifies whether the point should be at the
	 * right/bottom or left/top.
	 */
	protected void paintArrow(Graphics2D g2, double x, double y, int w, int h, boolean topDown) {
		int intX = (int) (x + 0.5);
		int intY = (int) (y + 0.5);

		if (orientation == VERTICAL) {
			if ((w % 2) == 0) {
				w = w - 1;
			}

			if (topDown) {
				for (int i = 0; i < ((w / 2) + 1); i++) {
					g2.drawLine(intX + i, intY + i, (intX + w) - i - 1, intY + i);
				}
			} else {
				for (int i = 0; i < ((w / 2) + 1); i++) {
					g2.drawLine((intX + (w / 2)) - i, intY + i, ((intX + w) - (w / 2) + i) - 1,
					            intY + i);
				}
			}
		} else {
			if ((h % 2) == 0) {
				h = h - 1;
			}

			if (topDown) {
				for (int i = 0; i < ((h / 2) + 1); i++) {
					g2.drawLine(intX + i, intY + i, intX + i, (intY + h) - i - 1);
				}
			} else {
				for (int i = 0; i < ((h / 2) + 1); i++) {
					g2.drawLine(intX + i, (intY + (h / 2)) - i, intX + i,
					            ((intY + h) - (h / 2) + i) - 1);
				}
			}
		}
	}

	/**
	 * Adds Windows2K type 3D lighting effects
	 */
	protected void paint3DRectLighting(Graphics2D g2, int x, int y, int width, int height) {
		g2.setColor(Color.white);
		g2.drawLine(x + 1, y + 1, x + 1, (y + height) - 1);
		g2.drawLine(x + 1, y + 1, (x + width) - 1, y + 1);
		g2.setColor(Color.gray);
		g2.drawLine(x + 1, (y + height) - 1, (x + width) - 1, (y + height) - 1);
		g2.drawLine((x + width) - 1, y + 1, (x + width) - 1, (y + height) - 1);
		g2.setColor(Color.darkGray);
		g2.drawLine(x, y + height, x + width, y + height);
		g2.drawLine(x + width, y, x + width, y + height);
	}

	/**
	 * Converts from screen coordinates to a range value.
	 */
	protected int toLocal(int xOrY) {
		Dimension sz = getSize();
		int min = getMinimum();
		double scale;

		if (orientation == VERTICAL) {
			scale = (sz.height - (2 * ARROW_SZ)) / (double) (getMaximum() - min);
		} else {
			scale = (sz.width - (2 * ARROW_SZ)) / (double) (getMaximum() - min);
		}

		if (direction == LEFTRIGHT_TOPBOTTOM) {
			return (int) (((xOrY - ARROW_SZ) / scale) + min + 0.5);
		} else {
			if (orientation == VERTICAL) {
				return (int) (((sz.height - xOrY - ARROW_SZ) / scale) + min + 0.5);
			} else {
				return (int) (((sz.width - xOrY - ARROW_SZ) / scale) + min + 0.5);
			}
		}
	}

	/**
	 * Converts from a range value to screen coordinates.
	 */
	protected int toScreen(int xOrY) {
		Dimension sz = getSize();
		int min = getMinimum();
		double scale;

		if (orientation == VERTICAL) {
			scale = (sz.height - (2 * ARROW_SZ)) / (double) (getMaximum() - min);
		} else {
			scale = (sz.width - (2 * ARROW_SZ)) / (double) (getMaximum() - min);
		}

		// If the direction is left/right_top/bottom then we subtract the min and multiply times scale
		// Otherwise, we have to invert the number by subtracting the value from the height
		if (direction == LEFTRIGHT_TOPBOTTOM) {
			return (int) (ARROW_SZ + ((xOrY - min) * scale) + 0.5);
		} else {
			if (orientation == VERTICAL) {
				return (int) (sz.height - ((xOrY - min) * scale) - ARROW_SZ + 0.5);
			} else {
				return (int) (sz.width - ((xOrY - min) * scale) - ARROW_SZ + 0.5);
			}
		}
	}

	/**
	 * Converts from a range value to screen coordinates.
	 */
	protected double toScreenDouble(int xOrY) {
		Dimension sz = getSize();
		int min = getMinimum();
		double scale;

		if (orientation == VERTICAL) {
			scale = (sz.height - (2 * ARROW_SZ)) / (double) ((getMaximum() + 1) - min);
		} else {
			scale = (sz.width - (2 * ARROW_SZ)) / (double) ((getMaximum() + 1) - min);
		}

		// If the direction is left/right_top/bottom then we subtract the min and multiply times scale
		// Otherwise, we have to invert the number by subtracting the value from the height
		if (direction == LEFTRIGHT_TOPBOTTOM) {
			return ARROW_SZ + ((xOrY - min) * scale);
		} else {
			if (orientation == VERTICAL) {
				return sz.height - ((xOrY - min) * scale) - ARROW_SZ;
			} else {
				return sz.width - ((xOrY - min) * scale) - ARROW_SZ;
			}
		}
	}

	// ------------------------------------------------------------------------
	// Event Handling
	static final int PICK_NONE = 0;
	static final int PICK_LEFT_OR_TOP = 1;
	static final int PICK_THUMB = 2;
	static final int PICK_RIGHT_OR_BOTTOM = 3;
	int pick;
	int pickOffsetLow;
	int pickOffsetHigh;
	int mouse;

	private int pickHandle(int xOrY) {
		int min = toScreen(getLowValue());
		int max = toScreen(getHighValue());
		int pick = PICK_NONE;

		if (direction == LEFTRIGHT_TOPBOTTOM) {
			if ((xOrY > (min - ARROW_SZ)) && (xOrY < min)) {
				pick = PICK_LEFT_OR_TOP;
			} else if ((xOrY >= min) && (xOrY <= max)) {
				pick = PICK_THUMB;
			} else if ((xOrY > max) && (xOrY < (max + ARROW_SZ))) {
				pick = PICK_RIGHT_OR_BOTTOM;
			}
		} else {
			if ((xOrY > min) && (xOrY < (min + ARROW_SZ))) {
				pick = PICK_LEFT_OR_TOP;
			} else if ((xOrY <= min) && (xOrY >= max)) {
				pick = PICK_THUMB;
			} else if (((xOrY > (max - ARROW_SZ)) && (xOrY < max))) {
				pick = PICK_RIGHT_OR_BOTTOM;
			}
		}

		return pick;
	}

	private void offset(int dxOrDy) {
		model.setValue(model.getValue() + dxOrDy);
	}

	/**
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	public void mousePressed(MouseEvent e) {
		if (orientation == VERTICAL) {
			pick = pickHandle(e.getY());
			pickOffsetLow = e.getY() - toScreen(getLowValue());
			pickOffsetHigh = e.getY() - toScreen(getHighValue());
			mouse = e.getY();
		} else {
			pick = pickHandle(e.getX());
			pickOffsetLow = e.getX() - toScreen(getLowValue());
			pickOffsetHigh = e.getX() - toScreen(getHighValue());
			mouse = e.getX();
		}

		repaint();
	}

	/**
	 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
	 */
	public void mouseDragged(MouseEvent e) {
		requestFocus();

		int value = (orientation == VERTICAL) ? e.getY() : e.getX();

		int minimum = getMinimum();
		int maximum = getMaximum();
		int lowValue = getLowValue();
		int highValue = getHighValue();

		switch (pick) {
			case PICK_LEFT_OR_TOP:

				int low = toLocal(value - pickOffsetLow);

				if (low < minimum) {
					low = minimum;
				}

				if (low > maximum) {
					low = maximum;
				}

				if (low > (highValue - minExtent)) {
					low = highValue - minExtent;
				}

				setLowValue(low);

				break;

			case PICK_RIGHT_OR_BOTTOM:

				int high = toLocal(value - pickOffsetHigh);

				if (high < minimum) {
					high = minimum;
				}

				if (high > maximum) {
					high = maximum;
				}

				if (high < (lowValue + minExtent)) {
					high = lowValue + minExtent;
				}

				setHighValue(high);

				break;

			case PICK_THUMB:

				int dxOrDy = toLocal(value - pickOffsetLow) - lowValue;

				if ((dxOrDy < 0) && ((lowValue + dxOrDy) < minimum)) {
					dxOrDy = minimum - lowValue;
				}

				if ((dxOrDy > 0) && ((highValue + dxOrDy) > maximum)) {
					dxOrDy = maximum - highValue;
				}

				if (dxOrDy != 0) {
					offset(dxOrDy);
				}

				break;
		}
	}

	/**
	 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(MouseEvent e) {
		pick = PICK_NONE;
		repaint();
	}

	/**
	 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	public void mouseMoved(MouseEvent e) {
		if (orientation == VERTICAL) {
			switch (pickHandle(e.getY())) {
				case PICK_LEFT_OR_TOP:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;

				case PICK_RIGHT_OR_BOTTOM:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;

				case PICK_THUMB:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;

				case PICK_NONE:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;
			}
		} else {
			switch (pickHandle(e.getX())) {
				case PICK_LEFT_OR_TOP:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;

				case PICK_RIGHT_OR_BOTTOM:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;

				case PICK_THUMB:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;

				case PICK_NONE:
					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

					break;
			}
		}
	}

	/**
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	public void mouseClicked(MouseEvent e) {
	}

	/**
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	public void mouseEntered(MouseEvent e) {
	}

	/**
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	public void mouseExited(MouseEvent e) {
	}

	private void grow(int increment) {
		model.setRangeProperties(model.getValue() - increment, model.getExtent() + (2 * increment),
		                         model.getMinimum(), model.getMaximum(), false);
	}

	/**
	 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
	 */
	public void keyPressed(KeyEvent e) {
		int kc = e.getKeyCode();
		boolean v = (orientation == VERTICAL);
		boolean d = (kc == KeyEvent.VK_DOWN);
		boolean u = (kc == KeyEvent.VK_UP);
		boolean l = (kc == KeyEvent.VK_LEFT);
		boolean r = (kc == KeyEvent.VK_RIGHT);

		int minimum = getMinimum();
		int maximum = getMaximum();
		int lowValue = getLowValue();
		int highValue = getHighValue();

		if ((v && r) || (!v && u)) {
			if (((lowValue - increment) >= minimum) && ((highValue + increment) <= maximum)) {
				grow(increment);
			}
		} else if ((v && l) || (!v && d)) {
			if ((highValue - lowValue) >= (2 * increment)) {
				grow(-1 * increment);
			}
		} else if ((v && d) || (!v && l)) {
			if ((lowValue - increment) >= minimum) {
				offset(-increment);
			}
		} else if ((v && u) || (!v && r)) {
			if ((highValue + increment) <= maximum) {
				offset(increment);
			}
		}
	}

	/**
	 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
	 */
	public void keyReleased(KeyEvent e) {
	}

	/**
	 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
	 */
	public void keyTyped(KeyEvent e) {
	}
} // end of class JRangeSlider
