// Copyright 2004 Bjorn Danielsson <java@dax.nu>
// $Id: DoodleCanvas.java 33 2005-09-25 09:29:56Z bd $

import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.Vector;
   
public class DoodleCanvas extends Canvas {

    public boolean antialiasing = false;
    public boolean debugGraphics = false;
    public boolean metaKeyIsPressed = false;

    public Color[] palette;
    public int paletteIndex;

    public int[] brush;
    public int brushIndex;

    protected int bufwidth;
    protected int bufheight;
    protected Image bufimage;
    protected volatile String busyString;

    private boolean penDown = false;
    private int penX = 0;
    private int penY = 0;
    private Vector drawEvents = null;
    private Object liftPenEvent;
    private int radius;

    public DoodleCanvas() {
	palette = new Color[15];
	palette[0] = new Color(0xffffff);
	palette[1] = new Color(0x000000);
	palette[2] = new Color(0x999999);
	palette[3] = new Color(0x663300);
	palette[4] = new Color(0x993333);
	palette[5] = new Color(0xff66cc);
	palette[6] = new Color(0x990099);
	palette[7] = new Color(0xff0000);
	palette[8] = new Color(0xff9933);
	palette[9] = new Color(0xffff00);
	palette[10] = new Color(0x339933);
	palette[11] = new Color(0x00ff00);
	palette[12] = new Color(0x0000ff);
	palette[13] = new Color(0x33ccff);
	palette[14] = new Color(0xccffff);
	paletteIndex = 1;

	brush = new int[3];
	brush[0] = 1;
	brush[1] = 3;
	brush[2] = 5;
	brushIndex = 1;

	liftPenEvent = new Object();
	reset();
    }

    public Image getImage() {
	return bufimage;
    }

    protected void initBuffer() {
	Image oldimage = bufimage;
	bufwidth = getWidth();
	bufheight = getHeight();
	bufimage = createImage(bufwidth, bufheight);
	Graphics g = bufimage.getGraphics();
	clear(g);
	if (oldimage != null) {
	    g.drawImage(oldimage, 0, 0, null);
	}
    }

    protected boolean invalidbuffer() {
	return (bufimage == null || getWidth() != bufwidth || getHeight() != bufheight);
    }

    public void reset() {
	bufimage = null;
	//antialiasing = true;
	//debugGraphics = true;
	metaKeyIsPressed = false;
	drawEvents = new Vector();
	drawEvents.addElement(liftPenEvent);
	repaint();
    }

    private void clear(Graphics g) {
	int w = getWidth();
	int h = getHeight();
	g.setColor(Color.white);
	g.fillRect(0, 0, w, h);
    }

    public void setBusy(String s) {
	busyString = s;
	bufwidth = -1;		// Kludge: force buffer refresh
	repaint();
    }

    private void setantialiasing(Graphics g, boolean flag) {
	if (antialiasing) {
	    Graphics2D g2 = (Graphics2D) g;
	    Object value =  RenderingHints.VALUE_ANTIALIAS_OFF;
	    if (flag) value = RenderingHints.VALUE_ANTIALIAS_ON;
	    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, value);
	}
    }

    private class KeyThing extends KeyAdapter {
	public void keyPressed(KeyEvent e) {
	    int code = e.getKeyCode();
	    if (code == KeyEvent.VK_META) {
		metaKeyIsPressed = true;
		repaint();
	    } else if (code == KeyEvent.VK_D) {
		System.out.println("DoodleCanvas detected VK_D event");
	    }
	}

	public void keyReleased(KeyEvent e) {
	    if (e.getKeyCode() == KeyEvent.VK_META) {
		metaKeyIsPressed = false;
		repaint();
	    }
	}
    }

    private class MouseThing extends MouseAdapter
	    implements MouseMotionListener {

	public void mouseMoved(MouseEvent e) {
	    // don't do nothing...
	}

	public void mouseDragged(MouseEvent e) {
	    if (busyString == null) {
		drawEvents.addElement(e);
		repaint();
	    }
	}

	public void mousePressed(MouseEvent e) {
	    if (busyString == null) {
		drawEvents.addElement(e);
		repaint();
	    }
	}

	public void mouseReleased(MouseEvent e) {
	    if (busyString == null) {
		drawEvents.addElement(e);
		drawEvents.addElement(liftPenEvent);
		repaint();
	    }
	}

    }

    public void addEventListeners() {
	MouseThing m = new MouseThing();
	addMouseListener(m);
	addMouseMotionListener(m);
	addKeyListener(new KeyThing());
    }

    private void doodleBetween(Graphics g, int x0, int y0, int x1, int y1) {
	if (Math.abs(x1-x0) < 2 && Math.abs(y1-y0) < 2) {
	    g.fillRect(x1 - radius, y1 - radius, 2*radius, 2*radius);
	} else {
	    int x = Math.round((x0+x1)/2);
	    int y = Math.round((y0+y1)/2);
	    doodleBetween(g, x0, y0, x, y);
	    doodleBetween(g, x, y, x1, y1);
	}
    }

    public void update(Graphics g) {
	g.setClip(0, 0, getWidth(), getHeight());	// Workaround for Apple Java 1.3 bug
	if (busyString != null) {
	    g.setColor(new Color(0x000080));
	    g.fillRect(0, 0, getWidth(), getHeight());
	    Font f = g.getFont();
	    g.setFont(new Font(f.getName(), f.getStyle(), 3*f.getSize()));
	    g.setColor(Color.white);
	    g.drawString(busyString, 120, 160);
	} else {
	    if (invalidbuffer()) {
		initBuffer();
		g.drawImage(bufimage, 0, 0, null);
	    }

	    Graphics gbuf = bufimage.getGraphics();

	    setantialiasing(g, true);
	    setantialiasing(gbuf, true);

	    while (!drawEvents.isEmpty()) {
		Object obj = drawEvents.elementAt(0);
		drawEvents.removeElementAt(0);
		if (obj instanceof MouseEvent) {
		    MouseEvent e = (MouseEvent) obj;
		    int x1 = e.getX();
		    int y1 = e.getY();
		    if (!penDown) {
			penX = x1;
			penY = y1;
			penDown = true;
		    }
		    radius = (int) Math.round(0.886*brush[brushIndex]);	// 0.886 = sqrt(pi)/2
		    g.setColor(palette[paletteIndex]);
		    gbuf.setColor(palette[paletteIndex]);
		    doodleBetween(g, penX, penY, x1, y1);
		    doodleBetween(gbuf, penX, penY, x1, y1);
		    penX = x1;
		    penY = y1;
		} else {
		    penDown = false;
		}
	    }

	    setantialiasing(g, false);
	    setantialiasing(gbuf, false);
	}
    }

    public void paint(Graphics g) {
	if (invalidbuffer()) update(g);
	else g.drawImage(bufimage, 0, 0, null);
    }

    // For pre-1.2 java plugins
    public int getWidth() { return getSize().width; }
    public int getHeight() { return getSize().height; }

}

