// Slider.java
// $Id: Slider.java,v 1.15 2000/08/16 21:37:57 ylafon Exp $
// Author: bmahe@sophia.inria.fr
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.tools.widgets;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;

/**
 * Slider : 
 * @author Benoit Mahe <bmahe@sophia.inria.fr>
 */

public class Slider extends Canvas {

    //inner classes

    class PointerClickListener extends MouseAdapter {

	Slider  slider  = null;

	public void mouseClicked(MouseEvent e) {
	    Object target = e.getComponent();
	    if (target == slider)
		slider.movePointerTo(e.getX(), e.getY());
	}

	public PointerClickListener(Slider s) {
	    this.slider  = s;
	}

    }

    class PointerMotionListener implements MouseMotionListener {

	Slider  slider  = null;

	public void mouseDragged(MouseEvent e) {
	    Object target = e.getComponent();
	    if (target == slider)
		slider.movePointerTo(e.getX(),e.getY());
	}

	public void mouseMoved(MouseEvent e) {
	}

	PointerMotionListener(Slider s) {
	    this.slider  = s;
	}

    }

    class Pointer {

	private int width       = 0;
	private int height      = 0;

	private int centerX = 0;

	Dimension size = null;

	int x = 0;
	int y = 0;

	public void fill3DPointer(Graphics g,
				  int x,
				  int y,
				  int width,
				  int height,
				  boolean raised)
	{
	    int xPoints[] = new int[3];
	    int yPoints[] = new int[3];
	    int nPoints   = 3;

	    Color c = g.getColor();
	    Color brighter = c.brighter();
	    Color darker = c.darker();

	    if (!raised) g.setColor(darker);

	    g.fillRect(x + 1, y + 1, width - 2, 2 * height / 3);
	    xPoints[0] = x;
	    yPoints[0] = y + 2 * height / 3;
	    xPoints[1] = x + width / 2;
	    yPoints[1] = y + height;
	    xPoints[2] = x + width;
	    yPoints[2] = y + 2 * height / 3;
	    g.fillPolygon(xPoints,yPoints,nPoints);
	    g.setColor(raised ? brighter : darker);
	    g.drawLine(x, y, x, y + (2 * height / 3) -1);
	    g.drawLine(x + 1, y, x + width - 2, y);
	    g.drawLine(xPoints[0],yPoints[0],xPoints[1],yPoints[1]);
	    g.setColor(raised ? darker : brighter);
	    g.drawLine(x + width - 1, y, x + width - 1, y + (2*height/3) - 2);
	    g.drawLine(xPoints[1],yPoints[1],xPoints[2] - 1,yPoints[2] - 1);
	}

	public void paint(Graphics g) {
	    fill3DPointer(g,
			  getLocation().x,
			  getLocation().y,
			  width,
			  height,
			  true);
	}

	public void setSize (int width, int height) {
	    this.width    = width;
	    this.height   = height;
	    size = new Dimension(width, height);
	}

	public int getCenterX() {
	    return centerX;
	}

	public void setLocation(int x, int y) {
	    pposition = new Point(x,y);
	    this.x = x-(width/2);
	    this.y = y-(height/2);
	    this.centerX = x;
	}

	public void setLocation(Point p) {
	    setLocation(p.x, p.y);
	}

	public Point getLocation() {
	    return new Point(x,y);
	}

	public Point getNewLocation(int x, int y) {
	    return new Point(getLocation().x + x,
			     getLocation().y + y);
	}

	Pointer(int width, int height, Point location) {
	    setSize(width,height);
	    setLocation(location);
	}

    }

    class Graduation {
	boolean isDouble;
	public int x1;
	public int y1;
	public int x2;
	public int y2;

	public double value;

	public void draw(Graphics g, boolean display) {
	    g.drawLine(x1,y1,x2,y2);
	    if (display) {
		String svalue = getStringValue();
		FontMetrics f = g.getFontMetrics();
		int height    = f.getHeight();
		int width     = f.stringWidth(svalue);
		g.drawString(svalue,x1-width/2,y2+height);
	    }
	}

	public String getStringValue() {
	    if (isDouble)
		return String.valueOf((float)value); //FIXME
	    else
		return String.valueOf((long)value);
	}

	public void showValue(Graphics g) {
	    String svalue = getStringValue();
	    FontMetrics f = g.getFontMetrics();
	    int height = f.getHeight();
	    int width  = f.stringWidth(svalue);
	    g.drawString(svalue,x1-width/2,y2+height);
	}

	public int dx(int x) {
	    int dx = x1-x;
	    return ((dx < 0) ? -dx : dx);
	}

	public Graduation(int x1, int y1, int x2, int y2, double value) {
	    this.x1    = x1;
	    this.y1    = y1;
	    this.x2    = x2;
	    this.y2    = y2;
	    this.value = value;
	    isDouble = true;
	}

	public Graduation(int x1, int y1, int x2, int y2, long value) {
	    this.x1    = x1;
	    this.y1    = y1;
	    this.x2    = x2;
	    this.y2    = y2;
	    this.value = value;
	    isDouble = false;
	}

    }

    //SLIDER itself

    int marginx       = 10;
    int marginy       = 5;
    int defaultWidth  = 180;
    int defaultHeight = 53;
    int width         = 0;
    int height        = 0;

    int minheight     = 53;
    int minwidth      = 150;

    int rect_margin_x = 20;
    int rect_margin_y = 10;

    int pointerWidth  = 12;
    int pointerHeight = 20;

    int pointerX = 0;
    int pointerY = 0;

    int guideHeight = 2;
    int graduationHeight = 5;

    double min  = 0;
    double max  = 0;
    double step = 0;

    int minpixelstep = 2;

    boolean border     = false;
    boolean manageLong = false;

    Color color = Color.gray;

    Dimension size = null;

    Point pposition = null;

    Graduation graduations [] = null;

    Graduation currentGraduation = null;

    protected Pointer pointer = null;

    protected void updateCurrentGraduation() {
	if (pposition.x > (width - marginx - rect_margin_x)) {
	    currentGraduation = graduations[graduations.length - 1];
	} else if (pposition.x < (marginx + rect_margin_x)) {
	    currentGraduation = graduations[0];
	} else {
	    int dx = -1;
	    int mindx = getGraduationLength()+50;
	    int i = 0;
	    while (i < graduations.length) {
		dx = (graduations[i].dx(pposition.x));
		if (dx < mindx) {
		    mindx = dx;
		    i++;
		} else break;
	    }
	    currentGraduation = graduations[i-1];
	}
    }

    protected int getGoodX(int x) {
	if (x > (width - marginx - rect_margin_x)) {
	    if (graduations[graduations.length - 1].x1 != 
		pointer.getCenterX()) {
		currentGraduation = graduations[graduations.length - 1];
		return graduations[graduations.length - 1].x1;
	    }
	    else return -1;
	} else  if (x < (marginx + rect_margin_x)) {
	    if (graduations[0].x1 != pointer.getCenterX()) {
		currentGraduation = graduations[0];
		return graduations[0].x1;      
	    }
	    else return -1;
	} else {
	    // find the nearest graduation.
	    int dx = -1;
	    int mindx = getGraduationLength()+50;
	    int i = 0;
	    while (i < graduations.length) {
		dx = (graduations[i].dx(x));
		if (dx < mindx) {
		    mindx = dx;
		    i++;
		} else break;
	    }
	    if (graduations[i-1].x1 != pointer.getCenterX()) {
		currentGraduation = graduations[i-1];
		return graduations[i-1].x1;
	    }
	    return -1; //FIXME
	}
    }

    /**
     * Get the current value pointed by the slider.
     * @return the value.
     */
    public double getValue() {
	if (currentGraduation != null) {
	    return currentGraduation.value;
	}
	else return min;
    }

    protected void updatePointerPosition(double value) {
	if (value <= graduations[0].value){
	    pointer.setLocation(graduations[0].x1, pposition.y);
	    return;
	}
	int maxidx = graduations.length -1;
	if (value == graduations[maxidx].value) {
	    pointer.setLocation(graduations[maxidx].x1,	pposition.y);
	    return;
	}
	if (value > graduations[graduations.length -1].value)
	    setMax(value + 10*step);
	for (int i = 1; i < graduations.length -2; i++) {
	    if (value - graduations[i].value < step) {
		pointer.setLocation(graduations[i].x1, pposition.y);
		return ;
	    }
	}
    }

    /**
     * Set the value pointed by the slider.
     * if the value is too high, resize the slider.
     * @param The value to point.
     */
    public void setValue(double value) {
	updatePointerPosition(value);
	updateCurrentGraduation();
	if (getGraphics() != null)
	    paint(getGraphics());
    }

    public void setValue(long value) {
	setValue((double)value);
    }

    protected void movePointerTo(int x, int y) {
	int gx = getGoodX(x);
	if (gx != -1) {
	    pointer.setLocation(gx,pposition.y);
	    if (getGraphics() != null)
		paint(getGraphics());
	}
    }

    protected void movePointerTo(Point p) {
	int gx = getGoodX(p.x);
	if (gx != -1) {
	    pointer.setLocation(gx,pposition.y);
	    if (getGraphics() != null)
		paint(getGraphics());
	}
    }

    /**
     * paint the slider.
     */
    public void paint (Graphics g) {
	Dimension d = getSize();
	updateSize(d);
	Shape s = g.getClip();
	Image dbi    = null;
	Graphics dbg = null;
	dbi = ImageCache.getImage(this, d.width, d.height);
	dbg = dbi.getGraphics();
	dbg.setClip(s);
	dbg.setColor(color);
	dbg.clearRect(0,0, d.width, d.height);
	dbg.fillRect(marginx,
		     marginy,
		     width-(2*marginx),
		     height-(2*marginy));
	if (border) {
	    dbg.setColor(color.darker());
	    dbg.drawRect(marginx,
			 marginy,
			 width-(2*marginx),
			 height-(2*marginy));
	}
	dbg.setColor(Color.white);
	dbg.fill3DRect(marginx + rect_margin_x,
		       marginy + rect_margin_y - guideHeight/2,
		       getGraduationLength(),
		       guideHeight,
		       false);
	paintGraduation(dbg);
	dbg.setColor(color.darker());
	pointer.paint(dbg);
	g.drawImage(dbi, 0, 0, this);
    }

    protected int getGraduationLength() {
	// use to recover arround error.
	int length = width - 2 * (marginx + rect_margin_x);
	int nbpart = (int)((max - min) / step);
	int pixelstep = length / nbpart;
	return pixelstep * nbpart;
    }

    protected void updateGraduationPosition() {
	int length = width - 2 * (marginx + rect_margin_x);
	int nbpart = (int) ((max - min) / step);
	int pixelstep = length / nbpart;
	int y1 = marginy  + 5 * rect_margin_y / 3;
	int y2 = y1+ graduationHeight;
	int minpixel = marginx + rect_margin_x;
	int maxpixel = length + minpixel;
	int gradx = minpixel;
	graduations = new Graduation[nbpart+1];
	int i=0;
	while (i < graduations.length){
	    if (manageLong) {
		graduations[i] = 
		    new Graduation(gradx,y1,gradx,y2,(long)(min+i*step));
	    } else {
		graduations[i] = new Graduation(gradx,y1,gradx,y2,min+i*step);
	    }
	    gradx += pixelstep;
	    i++;
	} 
    }

    protected void paintGraduation(Graphics g) {
	g.setColor(Color.black);
	Font font = new Font(g.getFont().getName(),
			     g.getFont().getStyle(),
			     g.getFont().getSize() - 1);
	g.setFont(font);
	for (int i=0 ; i < graduations.length; i++)
	    graduations[i].draw(g, (i == 0) || (i == graduations.length-1));
	g.setColor(Color.white);
	currentGraduation.showValue(g);
    }

    /**
     * update the slider.
     */
    public void update (Graphics g){
	paint(g);
    }

    protected void setDefaultSize(double min, double max, double step) {
	int length = minwidth - 2 * (marginx + rect_margin_x);
	int nbpart = (int)((max - min) / step);
	int pixelstep = length / nbpart;
 	if (pixelstep < minpixelstep) {
 	    minwidth = (minpixelstep * nbpart) + 2 * (marginx + rect_margin_x);
 	    defaultWidth = minwidth;
 	}
	updateSize(defaultWidth, defaultHeight);
	pposition = new Point(pposition.x,  marginy + rect_margin_y);
	pointer.setLocation(pposition);
	currentGraduation = graduations[0];
    }

    /**
     * Resizes this component so that it has width "width" and height "height".
     * @param d - The dimension specifying the new size of this slider. 
     */
    public void updateSize(Dimension d) {
	updateSize(d.width, d.height);
    }

    private Dimension oldsize = new Dimension(0,0);

    /**
     * Resizes this component so that it has width "width" 
     * and height "height". 
     * @param width - The new width of this slider
     * @param height - The new height of this slider
     */
    public void updateSize (int width, int height) {
	if ((oldsize.width != width) || (oldsize.height != height)) {
	    double value = getValue();
	    if (height < minheight)
		height = minheight;
	    if (width < minwidth)
		width = minwidth;
	    super.setSize(width, height);
	    oldsize = getSize();
	    this.width    = width;
	    this.height   = height;
	    rect_margin_y = (height - (2* marginy)) / 3;
	    updateGraduationPosition();
	    pposition.y = marginy + rect_margin_y;
	    updatePointerPosition(value);
	    updateCurrentGraduation();
	}
    }

    /**
     * Gets the mininimum size of this component.
     * @return A dimension object indicating this slider's minimum size.
     */
    public Dimension getMinimumSize() {
	return new Dimension(minwidth,minheight);
    }

    /**
     * Set the slider's color.
     * @param color - the slider's color.
     */
    public void setColor(Color color) {
	this.color = color;
    }

    /**
     * Set the minimum bound of the slider.
     * Use initialize or SetBounds if you want to set more than one value.
     * @param min - the min bound
     * @see #setMax 
     * @see #setBounds 
     * @see #setStep 
     * @see #initialize
     */
    public void setMin(double min) {
	this.min = min;
	update();
    }

    /**
     * Set the maximum bound of the slider.
     * Use initialize or SetBounds if you want to set more than one value.
     * @param max - the max bound
     * @see #setMin 
     * @see #setBounds 
     * @see #setStep 
     * @see #initialize
     */
    public void setMax(double max) {
	this.max = max;
	update();
    }

    /**
     * Set the bounds of the slider.
     * @param min - the min bound
     * @param max - the max bound
     * @see #setMin 
     * @see #setMax
     * @see #setStep 
     * @see #initialize
     */
    public void setBounds(double min, double max) {
	this.min = min;
	this.max = max;
	update();    
    }

    /**
     * Set the step of the slider.
     * Use initialize or SetBounds if you want to set more than one value.
     * @param step - the step between two position
     * @see #setMin 
     * @see #setMax
     * @see #setBounds 
     * @see #initialize
     */
    public void setStep(double step) {
	this.step = step;
	update();
    }

    /**
     * Initialize the slider's bounds and Step
     * @param min - the min bound
     * @param max - the max bound
     * @param step - the step between two position
     * @see #setMin 
     * @see #setMax
     * @see #setBounds 
     * @see #initialize
     */
    public void initialize(double min, double max, double step) {
	this.manageLong = false;
	this.min = min;
	this.max = max;
	this.step = step;
	update();
    }

    public void initialize(double min, double max, 
			   double step, boolean border) 
    {
	initialize(min,max,step);
	this.border = border;
    }

    public void initialize(long min, long max, long step) {
	initialize((double)min, (double) max, (double) step);
	this.manageLong = true;
	update();
    }

    public void initialize(long min, long max, long step, boolean border) {
	initialize(min,max,step);
	this.border = border;
    }

    protected void update() {
	setDefaultSize(min,max,step);
	updateGraduationPosition();
	if (graduations != null)
	    currentGraduation = graduations[0];
	setVisible(true);
	Component parent = getParent();
	if (parent != null)
	    parent.validate();
    }

    /**
     * Constructs an invisible Slider.
     * use initialize to define it.
     * @param minPixelStep - the min step (in pixels) between two positions.
     * @param border - if true draw a border arround the slider.
     */
    public Slider(int minPixelStep, boolean border) {
	this();
	this.minpixelstep = minPixelStep;
	this.border = border;
    }

    /**
     * Constructs an invisible Slider.
     * use initialize to define it.
     */
    public Slider() {
	pposition = new Point(marginx + rect_margin_x, 
			      marginy + rect_margin_y);
	pointer = new Pointer(pointerWidth,pointerHeight, pposition);
	PointerClickListener clickListener = 
	    new PointerClickListener(this);
	PointerMotionListener motionListener = 
	    new PointerMotionListener(this);
	addMouseListener( clickListener );
	addMouseMotionListener( motionListener );
	setVisible(false);
    }

    // TEST
    public static void main(String argv[]) {
	Frame f = new Frame ("Slider");
	f.setLayout( new GridLayout(6,1));
	Slider slider = null;

	slider = new Slider ();
	slider.initialize(0.0, 1.0, 0.1);
	slider.setColor(Color.lightGray);
	f.add (slider);

	slider = new Slider ();
	slider.initialize(0.0, 0.1, 0.01);
	slider.setColor(Color.lightGray);
	f.add (slider);

	slider = new Slider ();
	slider.initialize(0.0, 0.01, 0.0005);
	slider.setColor(Color.lightGray);
	f.add (slider);

	slider = new Slider ();
	slider.initialize(0, 100, 10);
	slider.setColor(Color.lightGray);
	f.add (slider);

	slider = new Slider ();
	slider.initialize(0, 30000, 5000);
	slider.setColor(Color.lightGray);
	f.add (slider);

	slider = new Slider ();
	slider.initialize(0, 30000, 1000);
	slider.setColor(Color.lightGray);
	f.add (slider);

	if (argv.length > 1) {
	    int width = Integer.parseInt(argv[0]);
	    int height = Integer.parseInt(argv[1]);
	    slider.setSize(width, height);
	}
	f.pack();
	f.show ();
    }

}
