// CbScrollbar.java
// A drop-in replacement for the AWT scrollbar class, with callbacks
// and a nicer look. This scrollbar is typically used to display some
// fraction of a list of items, with values ranging from min to max.
// The lvisible parameter determines how many of the list are lvisible
// at any one time. The value of the scrollbar ranges from min to
// max-lvisible+1 (the highest position in the list to start displaying)
import java.awt.*;

public class CbScrollbar extends Panel
{
	static final int VERTICAL = 0;
	static final int HORIZONTAL = 1;
	CbScrollbarCallback callback;		// who to call back to
	boolean inside, indent;
	int orient;			// horizontal or vertical?
	int value;			// position
	int lvisible;			// the number of lines lvisible
	int num;			// total number of lines
	int lineinc = 1;		// how much the arrow buttons move by
	Color lc1 = Color.white, lc2 = Color.lightGray, lc3 = Color.black;
	Color hc1 = Color.white, hc2 = new Color(210, 210, 210),
	      hc3 = Color.darkGray;
	Color bc = new Color(150, 150, 150);
	int y1, y2, x1, x2, drag;

	CbScrollbarArrow arrow1, arrow2;

	CbScrollbar(int o, CbScrollbarCallback cb)
	{
	this(o, 0, 1, 1, cb);
	}

	/**Create a new scrollbar
	 */
	CbScrollbar(int o, int v, int vis, int n, CbScrollbarCallback cb)
	{
	setValues(v, vis, n);
	orient = o;
	callback = cb;
	setLayout(null);
	if (orient == VERTICAL) {
		add(arrow1 = new CbScrollbarArrow(this, 0));
		add(arrow2 = new CbScrollbarArrow(this, 1));
		}
	else {
		add(arrow1 = new CbScrollbarArrow(this, 2));
		add(arrow2 = new CbScrollbarArrow(this, 3));
		}
	}

	/**Set the current scrollbar parameters
	 * @param v		Current position
	 * @param vis		Number of lines lvisible
	 * @param n		Total number of lines
	 */
	public void setValues(int v, int vis, int n)
	{
	value = v;
	lvisible = vis;
	num = n;
	if (lvisible > num) lvisible = num;
	checkValue();
	repaint();
	}

	public int getValue() { return value; }

	public void setValue(int v)
	{
	value = v;
	checkValue();
	repaint();
	}

	private void checkValue()
	{
	if (value < 0) value = 0;
	else if (value > num-lvisible) value = num-lvisible;
	}

	public void paint(Graphics g)
	{
	if (num == 0) return;
	int w = size().width, h = size().height;
	boolean ins = inside && !(arrow1.inside || arrow2.inside);
	Color c1 = ins ? hc1 : lc1, c2 = ins ? hc2 : lc2,
	      c3 = ins ? hc3 : lc3;
	g.setColor(bc);
	g.fillRect(0, 0, w, h);
	g.setColor(c3);
	g.drawLine(0, 0, w-1, 0); g.drawLine(0, 0, 0, h-1);
	g.setColor(c1);
	g.drawLine(w-1, h-1, w-1, 0); g.drawLine(w-1, h-1, 0, h-1);

	if (orient == VERTICAL) {
		int va = h-w*2;
		y1 = w+va*value/num;
		y2 = w+va*(value+lvisible)/num-1;
		g.setColor(c2);
		g.fillRect(1, y1, w-2, y2-y1);
		g.setColor(indent ? c3 : c1);
		g.drawLine(1, y1, w-2, y1);
		g.drawLine(1, y1, 1, y2-1);
		g.setColor(indent ? c1 : c3);
		g.drawLine(w-2, y2-1, w-2, y1);
		g.drawLine(w-2, y2-1, 1, y2-1);
		if (ins) {
			g.drawLine(w-3, y2-2, w-3, y1+1);
			g.drawLine(w-3, y2-2, 2, y2-2);
			}
		}
	else if (orient == HORIZONTAL) {
		int va = w-h*2;
		x1 = h+va*value/num;
		x2 = h+va*(value+lvisible)/num-1;
		g.setColor(c2);
		g.fillRect(x1, 1, x2-x1, h-2);
		g.setColor(indent ? c3 : c1);
		g.drawLine(x1, 1, x1, h-2);
		g.drawLine(x1, 1, x2-1, 1);
		g.setColor(indent ? c1 : c3);
		g.drawLine(x2-1, h-2, x1, h-2);
		g.drawLine(x2-1, h-2, x2-1, 1);
		if (ins) {
			g.drawLine(x2-2, h-3, x1+1, h-3);
			g.drawLine(x2-2, h-3, x2-2, 2);
			}
		}
	}

	/**Called by arrows to move the slider
	 */
	void arrowClick(int d)
	{
	int oldvalue = value;
	value += d;
	checkValue();
	if (value != oldvalue) {
		callback.moved(this, value);
		repaint();
		}
	}

	public void reshape(int nx, int ny, int nw, int nh)
	{
	super.reshape(nx, ny, nw, nh);
	if (orient == VERTICAL) {
		arrow1.reshape(1, 1, nw-2, nw-1);
		arrow2.reshape(1, nh-nw-1, nw-2, nw-1);
		}
	else {
		arrow1.reshape(1, 1, nh-1, nh-2);
		arrow2.reshape(nw-nh-1, 1, nh-1, nh-2);
		}
	repaint();
	}

	public Dimension preferredSize()
	{
	return orient==VERTICAL ? new Dimension(16, 100)
				: new Dimension(100, 16);
	}

	public Dimension minimumSize()
	{
	return preferredSize();
	}

	public boolean mouseDown(Event e, int mx, int my)
	{
	if (orient == VERTICAL) {
		// move up/down one page, or start dragging
		if (my < y1) arrowClick(-lvisible);
		else if (my > y2) arrowClick(lvisible);
		else {
			indent = true;
			drag = my-y1;
			repaint();
			}
		}
	else {
		// move left/right one page, or start dragging
		if (mx < x1) arrowClick(-lvisible);
		else if (mx > x2) arrowClick(lvisible);
		else {
			indent = true;
			drag = mx-x1;
			repaint();
			}
		}
	return true;
	}

	public boolean mouseDrag(Event e, int mx, int my)
	{
	if (indent) {
		int w = size().width, h = size().height;
		int oldvalue = value;
		if (orient == VERTICAL) {
			int va = h-w*2, ny = my-drag-w;
			value = ny*num/va;
			}
		else {
			int va = w-h*2, nx = mx-drag-h;
			value = nx*num/va;
			}
		checkValue();
		if (value != oldvalue) {
			callback.moving(this, value);
			repaint();
			}
		}
	return indent;
	}

	public boolean mouseUp(Event e, int mx, int my)
	{
	if (indent) {
		indent = false;
		repaint();
		callback.moved(this, value);
		return true;
		}
	return false;
	}

/*
	public boolean mouseEnter(Event e, int mx, int my)
	{
	inside = true;
	repaint();
	return true;
	}

	public boolean mouseExit(Event e, int mx, int my)
	{
	inside = false;
	repaint();
	return true;
	}
*/
}

class CbScrollbarArrow extends Canvas implements Runnable
{
	int mode;
	CbScrollbar scrollbar;
	boolean inside, indent;
	Thread th;

	CbScrollbarArrow(CbScrollbar p, int m)
	{
	scrollbar = p;
	mode = m;
	}

	public void paint(Graphics g)
	{
	int w = size().width, h = size().height;
	Color c1 = inside ? scrollbar.hc1 : scrollbar.lc1,
	      c2 = inside ? scrollbar.hc2 : scrollbar.lc2,
	      c3 = inside ? scrollbar.hc3 : scrollbar.lc3;
	g.setColor(scrollbar.bc);
	g.fillRect(0, 0, w, h);
	int xp[] = new int[3], yp[] = new int[3];
	// blank, dark, light
	if (mode == 0) {
		// up arrow
		xp[0] = w/2; xp[1] = w-1; xp[2] = 0;
		yp[0] = 0;   yp[1] = h-1; yp[2] = h-1;
		}
	else if (mode == 1) {
		// down arrow
		xp[0] = 0;   xp[1] = w/2; xp[2] = w-1;
		yp[0] = 0;   yp[1] = h-1; yp[2] = 0;
		}
	else if (mode == 2) {
		// left arrow
		xp[0] = 0;   xp[1] = w-1; xp[2] = w-1; 
		yp[0] = h/2; yp[1] = h-1; yp[2] = 0;
		}
	else if (mode == 3) {
		// right arrow
		xp[0] = 0;   xp[1] = w-1; xp[2] = 0;
		yp[0] = 0;   yp[1] = h/2; yp[2] = h-1;
		}
	g.setColor(c2);
	g.fillPolygon(xp, yp, 3);
	g.setColor(indent ? c1 : c3);
	g.drawLine(xp[1], yp[1], xp[2], yp[2]);
	g.setColor(indent ? c3 : c1);
	g.drawLine(xp[0], yp[0], xp[2], yp[2]);
	}

	public boolean mouseDown(Event e, int mx, int my)
	{
	indent = true;
	repaint();
	(th = new Thread(this)).start();
	return true;
	}

	public boolean mouseUp(Event e, int mx, int my)
	{
	indent = false;
	repaint();
	if (th != null) th.stop();
	return true;
	}

	/**Thread for doing repeated scrolling
	 */
	public void run()
	{
	int stime = 500;
	while(true) {
		scrollbar.arrowClick(mode%2 == 0 ? -1 : 1);
		try { Thread.sleep(stime); } catch(Exception e) { }
		stime = 100;
		}
	}
}


// CbScrollbarCallback
// Methods for reporting the movement of the scrollbar to another object
interface CbScrollbarCallback
{
	/**Called when the scrollbar stops moving. This happens when an
	 * arrow is clicked, the scrollbar is moved by a page, or the user
	 * lets go of the scrollbar after dragging it.
	 * @param sb	The scrollar that has been moved
	 * @param v	The new value
	 */
	void moved(CbScrollbar sb, int v);

	/**Called upon every pixel movement of the scrollbar when it is
	 * being dragged, but NOT when moved() is called.
	 * @param sb	The scrollar that has been moved
	 * @param v	The new value
	 */
	void moving(CbScrollbar sb, int v);
}


