// Patterns

// By Brian Prentice
// April 1997
   
import java.applet.*;
import java.awt.*;
import java.util.Vector;
import java.util.Hashtable;
import java.io.*;
import java.net.*;

public class Patterns extends Applet implements CommandConstants
{
	final static Color backgroundColor = new Color(0, 0x80, 0x80);
	final static int borderSize = 10;

	final static int NoThumbnailRows = 5;
	final static int NoThumbnailColumns = 5;

	private Panel buttonPanel1;
	private Panel buttonPanel2;
	private Panel makePanel;
	private Panel palettePanel;
	private Panel combinePanel;	
	private Panel cardControlPanel;
	private Panel workPadPanel;
	private Panel thumbnailPanel;
	private Panel cardDisplayPanel;
	private Panel panel1;
	private Panel panel2;
	private Panel panel3;
	private Panel panel4;
	private Panel panel5;

	private Button clearButton;
	private Button undoButton;
	private Button helpButton;
	private Button readButton;
	private Button writeButton;
	private Button makeButton;
	private Button paletteButton;
	private Button thumbnailButton;

	private CardLayout cardControlLayout;
	private CardLayout cardDisplayLayout;

	private Make make;
	private Palette palette;
	private Combine combine;
	private Ruler[] ruler;
	private WorkPad workPad;
	private boolean workPadDisplayed;
	private Thumbnail[] thumbnail = new Thumbnail[NoThumbnailRows * NoThumbnailColumns];
	private boolean thumbnailsDisplayed;
	private Display display;
	private boolean displayDisplayed;

	public void init()
	{
		setBackground(backgroundColor);

		clearButton = new Button("Clear");
		clearButton.setBackground(Color.lightGray);
		undoButton = new Button("Undo");
		undoButton.setBackground(Color.lightGray);
		helpButton = new Button("Help");
		helpButton.setBackground(Color.lightGray);
		readButton = new Button("Read");
		readButton.setBackground(Color.lightGray);
		readButton.disable();
		writeButton = new Button("Write");
		writeButton.setBackground(Color.lightGray);
		writeButton.disable();
		buttonPanel1 = new Panel();
		buttonPanel1.setLayout(new GridLayout(2, 3, 5, 5));
		buttonPanel1.add(clearButton);
		buttonPanel1.add(undoButton);
		buttonPanel1.add(helpButton);
		buttonPanel1.add(readButton);
		buttonPanel1.add(writeButton);


		makeButton = new Button("Make");
		makeButton.setBackground(Color.lightGray);
		paletteButton = new Button("Palette");
		paletteButton.setBackground(Color.lightGray);
		thumbnailButton = new Button("Thumbnail");
		thumbnailButton.setBackground(Color.lightGray);
		buttonPanel2 = new Panel();
		buttonPanel2.setLayout(new GridLayout(1, 3, 5, 5));
		buttonPanel2.add(makeButton);
		buttonPanel2.add(paletteButton);
		buttonPanel2.add(thumbnailButton);

		workPadPanel = new Panel();
		ruler = new  Ruler[4];
		ruler[0] = new Ruler(Ruler.left);
		ruler[1] = new Ruler(Ruler.right);
		ruler[2] = new Ruler(Ruler.top);
		ruler[3] = new Ruler(Ruler.bottom);
		workPad = new WorkPad(this, ruler);
		workPadPanel.setLayout(new BorderLayout());
		workPadPanel.add("West", ruler[0]);
		workPadPanel.add("East", ruler[1]);
		workPadPanel.add("North", ruler[2]);
		workPadPanel.add("South", ruler[3]);
		workPadPanel.add("Center", workPad);
		display = new Display();
		for (int i = 0; i < NoThumbnailRows * NoThumbnailColumns; i++)
			thumbnail[i] = new Thumbnail(this, workPad, display);
		thumbnailPanel = new Panel();
		thumbnailPanel.setLayout(new GridLayout(NoThumbnailRows, NoThumbnailColumns, 2, 2));
		for (int i = 0; i < NoThumbnailRows * NoThumbnailColumns; i++)
			thumbnailPanel.add(thumbnail[i]);
		

		makePanel = new Panel();
		make = new Make(workPad);
		makePanel.add(make);

		palettePanel = new Panel();
		palette = new Palette(workPad);
		palettePanel.add(palette);

		combinePanel = new Panel();
		combine = new Combine();
		combinePanel.add(combine);

		cardControlPanel = new Panel();
		cardControlLayout = new CardLayout();
		cardControlPanel.setLayout(cardControlLayout);
		cardControlPanel.add("Make", makePanel);
		cardControlPanel.add("Palette", palettePanel);
		cardControlPanel.add("Combine", combinePanel);

		panel1 = new Panel();
		panel1.add(buttonPanel1);
		panel2 = new Panel();
		panel2.add(buttonPanel2);
		panel3 = new Panel();
		panel3.add(cardControlPanel);
		panel4 = new Panel();
		panel4.setLayout(new BorderLayout());
		panel4.add("North", panel2);
		panel4.add("Center", panel3);
		panel5 = new Panel();
		panel5.setLayout(new BorderLayout());
		panel5.add("North", panel1);
		panel5.add("Center", panel4);

		cardDisplayPanel = new Panel();
		cardDisplayLayout = new CardLayout();
		cardDisplayPanel.setLayout(cardDisplayLayout);
		cardDisplayPanel.add("Work Pad", workPadPanel);
		workPadDisplayed = true;
		cardDisplayPanel.add("Thumbnails", thumbnailPanel);
		thumbnailsDisplayed = false;
		cardDisplayPanel.add("Display", display);
		displayDisplayed = false;

		setLayout(new BorderLayout());
		add("West", panel5);
		add("Center", cardDisplayPanel);
	}

	public Insets insets()
	{
		return new Insets(borderSize, borderSize, borderSize, borderSize);
	}

	public boolean action(Event e, Object arg)
	{
		if (e.target == clearButton)
		{
			if (workPadDisplayed)
				workPad.clear();
			else if (thumbnailsDisplayed)
				for (int i = 0; i < NoThumbnailRows * NoThumbnailColumns; i++)
					thumbnail[i].clear();
		}
		else if ((e.target == undoButton) && (workPadDisplayed))
			workPad.undo();
		else if (e.target == readButton)
		{
			if (thumbnailsDisplayed)
				Thumbnail.setCommand(readCommand, done);
			else if (displayDisplayed)
				getAppletContext().showStatus(display.read());
		}
		else if (e.target == writeButton)
		{
			if (thumbnailsDisplayed)
				Thumbnail.setCommand(writeCommand, done);
			else if (displayDisplayed)
				getAppletContext().showStatus(display.write());
		}
		else if (e.target == makeButton)
		{
			repaint();
			clearButton.enable();
			undoButton.enable();
			readButton.disable();
			writeButton.disable();
			cardControlLayout.show(cardControlPanel, "Make");
			cardDisplayLayout.show(cardDisplayPanel, "Work Pad");
			workPadDisplayed = true;
			thumbnailsDisplayed = false;
			displayDisplayed = false;
		}
		else if (e.target == paletteButton)
		{
			cardControlLayout.show(cardControlPanel, "Palette");
		}
		else if (e.target == thumbnailButton)
		{
			repaint();
			clearButton.enable();
			undoButton.disable();
			readButton.enable();
			writeButton.enable();
			cardControlLayout.show(cardControlPanel, "Combine");
			cardDisplayLayout.show(cardDisplayPanel, "Thumbnails");
			workPadDisplayed = false;
			thumbnailsDisplayed = true;
			displayDisplayed = false;
		}
		else if (e.target == helpButton)
		{
			try
			{				
				getAppletContext().showDocument(new URL(getCodeBase(), "PatternsHelp.html"));
			}
			catch(Exception error)
			{
				System.out.println("" + error);
			}
		}
		else
			return false;
		return true;
	}

	public void switchDisplay()
	{
		clearButton.disable();
		cardDisplayLayout.show(cardDisplayPanel, "Display");
		workPadDisplayed = false;
		thumbnailsDisplayed = false;
		displayDisplayed = true;
	}
}

class IntField extends TextField
{
	private int value;
	private int max;
	private int min;

	public IntField(String value, int size, int min, int max)
	{
		super(value, size);
		this.max = max;
		this.min = min;
	}

	private int stoi(String s)
	{
		return (new Integer(s)).intValue();
	}

	private String itos(int i)
	{
		return (new Integer(i)).toString();
	}

	public void setValue(int i)
	{
		setText(itos(i));
		validateValue();
	}

	public int getValue()
	{
		validateValue();
		return value;
	}

	public int incValue(int inc)
	{
		validateValue();
		setText(itos(stoi(getText()) + inc));
		validateValue();
		return value;
	}

	public void validateValue()
	{
		String s = getText();		
		if (s.equals(""))
		{
			value = min;
			setText(itos(min));
		}
		else
		{
			value = stoi(s);
			if (value < min)
			{
				value = min;
				setText(itos(min));
			}
			else if (value > max)
			{
				value = max;
				setText(itos(max));
			}
		}
	}

	public boolean keyDown(Event event, int key)
	{
		if ((key >= 0x30) && (key <= 0x39))
		{
			return false;
		}
		switch (key)
		{
			case 0x08:
			case 0x0A:
			case 0x7F:
			case Event.RIGHT:
			case Event.LEFT:
			case Event.HOME:
			case Event.END:
				return false;
			default:
				return true;
		}
	}
}

class DoubleField extends TextField
{
	private double defaultValue;
	private boolean decimalPointSeen = false;

	public DoubleField(String value, int size)
	{
		super(value, size);
		defaultValue = stod(value);
	}

	private double stod(String s)
	{
		return (new Double(s)).doubleValue();
	}

	private String dtos(double d)
	{
		return (new Double(d)).toString();
	}

	public void setValue(double d)
	{
		setText(dtos(d));
		getValue();
		decimalPointSeen = false;
	}

	public double getValue()
	{
		double d;		
		String s = getText();
		if (s.equals(""))
		{
			setText(dtos(defaultValue));
			return defaultValue;
		}
		else
			d = stod(s);
		if ((d < 0.0) || (d > 1.0))
		{
			setText(dtos(defaultValue));
			return defaultValue;
		}
		else
			return d;
	}

	public boolean keyDown(Event event, int key)
	{
		if ((key >= 0x30) && (key <= 0x39))
		{
			return false;
		}
		switch (key)
		{
			case 0x0A:
				decimalPointSeen = false;
			case 0x08:
			case 0x7F:
			case Event.RIGHT:
			case Event.LEFT:
			case Event.HOME:
			case Event.END:
				return false;
			case 0x2E:
				if (! decimalPointSeen)
				{
					decimalPointSeen = true;
					return false;
				}
			default:
				return true;
		}
	}
}

class Make extends Panel
{
	final static Color backgroundColor = new Color(220, 220, 220);

	final static String freehandName = "Freehand";
	final static String lineName = "Line";
	final static String rectangleName = "Rectangle";
	final static String ovalName = "Oval";
	final static String polygonName = "Polygon";
	final static String curveName = "Curve";
	final static String filledRectangleName = "Filled Rectangle";
	final static String filledOvalName = "Filled Oval";
	final static String filledPolygonName = "Filled Polygon";
	final static String filledCurveName = "Filled Curve";
	final static String modes[] =
	{
		freehandName, lineName, rectangleName, ovalName, polygonName, curveName,
		filledRectangleName, filledOvalName, filledPolygonName, filledCurveName
	};

	private Panel panel1;
	private Panel panel2;
	private Panel panel3;
	private Panel panel4;
	private Panel panel5;
	private Panel panel6;
	private Panel panel7;
	private Panel panel8;
	private Panel panel9;
	private Panel panel10;
	private Panel panel11;
	private Panel panel12;
	private Panel panel13;
	private Panel panel14;
	private Panel panel15;
	private Panel panel16;
	private Panel panel17;
	private Panel panel18;
	private Panel panel19;
	private Panel panel20;
	private Panel panel21;
	private Panel panel22;

	private Choice modeSelector;
	private Button downLineSizeButton;
	private IntField lineSizeInput;
	private Button upLineSizeButton;
	private IntField rulerDepthInput;
	private Checkbox gridSnapCheckbox;
	private Button downPointNoButton;
	private IntField pointNoInput;
	private Button upPointNoButton;
	private DoubleField xInput;
	private DoubleField yInput;
	private Button resetButton;
	private Button saveButton;
	private Button drawButton;

	private WorkPad workPad;
	private Vector points;

	public Make(WorkPad workPad)
	{		
		setBackground(backgroundColor);

		this.workPad = workPad;
		points = new Vector();

		panel1 = new Panel();
		panel1.add(new Label("Mode"));

		modeSelector = new Choice();
		for (int i = 0; i < modes.length; i++)
			modeSelector.addItem(modes[i]);
		panel2 = new Panel();
		panel2.add(modeSelector);

		panel3 = new Panel();
		panel3.add(new Label("Line Size"));

		downLineSizeButton = new Button("<");
		lineSizeInput = new IntField("1", 2, 1, 100);
		upLineSizeButton = new Button(">");

		panel4 = new Panel();
		panel4.add(downLineSizeButton);
		panel4.add(lineSizeInput);
		panel4.add(upLineSizeButton);

		panel5 = new Panel();
		panel5.add(new Label("Ruler Depth"));

		panel6 = new Panel();
		rulerDepthInput = new IntField("6", 2, 1, 7);
		panel6.add(rulerDepthInput);

		panel7 = new Panel();
		panel7.add(new Label("Grid Snap"));

		gridSnapCheckbox = new Checkbox();
		gridSnapCheckbox.setState(true);
		panel8 = new Panel();
		panel8.add(gridSnapCheckbox);

		panel9 = new Panel();
		panel9.add(new Label("Point No"));

		downPointNoButton = new Button("<");
		pointNoInput = new IntField("1", 2, 1, 100);
		upPointNoButton = new Button(">");

		panel10 = new Panel();
		panel10.add(downPointNoButton);
		panel10.add(pointNoInput);
		panel10.add(upPointNoButton);

		panel11 = new Panel();
		panel11.add(new Label("X"));

		xInput = new DoubleField("0.0", 5);
		panel12 = new Panel();
		panel12.add(xInput);

		panel13 = new Panel();
		panel13.add(new Label("Y"));

		yInput = new DoubleField("0.0", 5);
		panel14 = new Panel();
		panel14.add(yInput);

		resetButton = new Button("Reset");
		saveButton = new Button("Save");
		drawButton = new Button("Draw");
		panel15 = new Panel();
		panel15.setLayout(new GridLayout(1, 3, 10, 0));
		panel15.add(resetButton);
		panel15.add(saveButton);
		panel15.add(drawButton);

		panel16 = new Panel();
		panel16.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel16.add(panel1);
		panel16.add(panel2);

		panel17 = new Panel();
		panel17.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel17.add(panel3);
		panel17.add(panel4);

		panel18 = new Panel();
		panel18.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel18.add(panel5);
		panel18.add(panel6);

		panel19 = new Panel();
		panel19.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel19.add(panel7);
		panel19.add(panel8);

		panel20 = new Panel();
		panel20.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel20.add(panel9);
		panel20.add(panel10);

		panel21 = new Panel();
		panel21.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel21.add(panel11);
		panel21.add(panel12);
		panel21.add(panel13);
		panel21.add(panel14);

		panel22 = new Panel();
		panel22.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
		panel22.add(panel15);		

		setLayout(new GridLayout(7, 1, 2, 0));
		add(panel16);
		add(panel17);
		add(panel18);
		add(panel19);
		add(panel20);
		add(panel21);
		add(panel22);
	}

	public boolean keyUp(Event event, int key)
	{
		if (key == 10)
		{
			workPad.setLineSize(lineSizeInput.getValue());
			workPad.setRulerDepth(rulerDepthInput.getValue());
			pointNoInput.getValue();
			xInput.getValue();
			yInput.getValue();
		}
		return false;
	}

	public boolean action(Event event, Object arg)
	{
		if (event.target == modeSelector)
			workPad.setMode(modeSelector.getSelectedIndex());
		else if (event.target == downLineSizeButton)
			workPad.setLineSize(lineSizeInput.incValue(-1));
		else if (event.target == upLineSizeButton)
			workPad.setLineSize(lineSizeInput.incValue(1));
		else if (event.target == gridSnapCheckbox)
			workPad.setSnap(gridSnapCheckbox.getState());
		else if (event.target == downPointNoButton)
		{
			int n = pointNoInput.getValue();
			if (n > 1)
			{
				pointNoInput.setValue(n - 1);
				n = (n - 2) * 2;
				xInput.setValue(((Double) points.elementAt(n)).doubleValue());
				yInput.setValue(((Double) points.elementAt(n + 1)).doubleValue());
			}
		}
		else if (event.target == upPointNoButton)
		{
			int n = pointNoInput.getValue();
			int N = points.size() / 2;
			if (n <= N)
			{
				pointNoInput.setValue(n + 1);
				if (n == N)
				{
					xInput.setValue(0.0);
					yInput.setValue(0.0);
				}
				else
				{
					n *= 2;
					xInput.setValue(((Double) points.elementAt(n)).doubleValue());	
					yInput.setValue(((Double) points.elementAt(n + 1)).doubleValue());
				}
			}
		}
		else if (event.target == resetButton)
		{
			points = new Vector();
			pointNoInput.setValue(1);
			xInput.setValue(0.0);
			yInput.setValue(0.0);
		}
		else if (event.target == saveButton)
		{
			int n = (pointNoInput.getValue() - 1) * 2;
			if (n == points.size())
			{
				points.addElement(new Double(xInput.getValue()));				
				points.addElement(new Double(yInput.getValue()));				
			}
			else
			{
				points.setElementAt(new Double(xInput.getValue()), n);
				points.setElementAt(new Double(yInput.getValue()), n + 1);
			}
		}
		else if (event.target == drawButton)
			workPad.storePicture(points);
		else
			return false;
		return true;
	}
}

class Palette extends Panel
{
	final static Color backgroundColor = new Color(220, 220, 220);

	final static Color yellowGreen = Color.getColor("", 0x9ACD32);
	final static Color lightBlue = Color.getColor("", 0xADE8E6);
	final static Color teal = Color.getColor("", 0x008080);
	final static Color navyBlue = Color.getColor("", 0x23238E);
	final static Color darkGreen = Color.getColor("", 0x215E21);
	final static Color brown = Color.getColor("", 0xA62A2A);
	final static Color purple = Color.getColor("", 0x800080);
	final static Color defaultColor[] =
	{
		Color.black, Color.darkGray, Color.gray, Color.lightGray,
		Color.white, Color.red, Color.pink, Color.orange, Color.yellow,
		Color.cyan, Color.blue, Color.magenta, Color.green,
		yellowGreen, lightBlue, teal, navyBlue, darkGreen, brown, purple
	};

	private Panel panel1;
	private Panel panel2;
	private Panel panel3;
	private Panel panel4;
	private Panel panel5;
	private Panel panel6;
	private Panel panel7;
	private Panel panel8;
	private Panel panel9;
	private Panel panel10;
	private Panel panel11;
	private Panel panel12;
	private Panel panel13;

	private Label redLabel;
	private Button redFastDown;
	private Button redDown;
	private IntField redValue;
	private Button redUp;
	private Button redFastUp;

	private Label greenLabel;
	private Button greenFastDown;
	private Button greenDown;
	private IntField greenValue;
	private Button greenUp;
	private Button greenFastUp;

	private Label blueLabel;
	private Button blueFastDown;
	private Button blueDown;
	private IntField blueValue;
	private Button blueUp;
	private Button blueFastUp;

	private CustomColor customColor;

	private Button scrollButton;
	private Button addButton;
	private Button mixButton;

	private Vector colorSelector;

	private Button saveButton;
	private Button restoreButton;

	private Color color[];
	private Vector customPalette;
	private int palettePointer;

	private WorkPad workPad;

	public Palette(WorkPad workPad)
	{		
		setBackground(backgroundColor);

		color = new Color[defaultColor.length];		
		for (int i = 0; i < defaultColor.length; i++)
			color[i] = defaultColor[i];
		customPalette = new Vector();
		customPalette.addElement(color);
		palettePointer = 0;

		this.workPad = workPad;

		redFastDown = new Button("<<");
		redDown = new Button("<");
		greenFastDown = new Button("<<");
		greenDown = new Button("<");
		blueFastDown = new Button("<<");
		blueDown = new Button("<");

		panel1 = new Panel();		
		panel1.setLayout(new GridLayout(3, 2, 2, 2));
		panel1.add(redFastDown);
		panel1.add(redDown);
		panel1.add(greenFastDown);
		panel1.add(greenDown);
		panel1.add(blueFastDown);
		panel1.add(blueDown);

		redLabel = new Label("red");
		greenLabel = new Label("green");
		blueLabel = new Label("blue");

		panel2 = new Panel();		
		panel2.setLayout(new GridLayout(3, 1, 2, 2));
		panel2.add(redLabel);
		panel2.add(greenLabel);
		panel2.add(blueLabel);

		redValue = new IntField("255", 3, 0, 255);
		greenValue = new IntField("0", 3, 0, 255);
		blueValue = new IntField("0", 3, 0, 255);

		panel3 = new Panel();		
		panel3.setLayout(new GridLayout(3, 1, 2, 2));
		panel3.add(redValue);
		panel3.add(greenValue);
		panel3.add(blueValue);

		redUp = new Button(">");
		redFastUp = new Button(">>");
		greenUp = new Button(">");
		greenFastUp = new Button(">>");
		blueUp = new Button(">");
		blueFastUp = new Button(">>");

		panel4 = new Panel();		
		panel4.setLayout(new GridLayout(3, 2, 2, 2));
		panel4.add(redUp);
		panel4.add(redFastUp);
		panel4.add(greenUp);
		panel4.add(greenFastUp);
		panel4.add(blueUp);
		panel4.add(blueFastUp);
		
		panel5 = new Panel();
		panel5.add(panel1);		
		panel5.add(panel2);		
		panel5.add(panel3);		
		panel5.add(panel4);

		customColor = new CustomColor(Color.red);

		panel6 = new Panel();
		panel6.add(customColor);

		scrollButton = new Button("Scroll");
		addButton = new Button("Add");
		mixButton = new Button("Mix");

		panel7 = new Panel();
		panel7.setLayout(new GridLayout(1, 3, 10, 0));
		panel7.add(scrollButton);
		panel7.add(addButton);
		panel7.add(mixButton);

		panel8 = new Panel();
		panel8.add(panel7);

		saveButton = new Button("Save");
		restoreButton = new Button("Restore");

		panel9 = new Panel();
		panel9.setLayout(new GridLayout(1, 2, 10, 0));
		panel9.add(saveButton);
		panel9.add(restoreButton);

		panel10 = new Panel();
		panel10.add(panel9);

		colorSelector = new Vector();
		for (int i = 0; i < color.length; i++)
			colorSelector.addElement(new ColorSelector(color[i], workPad));

		panel11 = new Panel();
		panel11.setLayout(new GridLayout(2, 8));
		for (int i = 0; i < color.length; i++)
			panel11.add((ColorSelector) colorSelector.elementAt(i));

		panel12 = new Panel();
		panel12.add(panel11);

		panel13 = new Panel();
		panel13.setLayout(new GridLayout(3, 1));
		panel13.add(panel6);
		panel13.add(panel8);
		panel13.add(panel10);

		setLayout(new BorderLayout());
		add("North", panel5);
		add("South", panel12);
		add("Center", panel13);
	}

	private void setColor()
	{
		customColor.setColor(redValue.getValue(), greenValue.getValue(), blueValue.getValue());
	}

	private void repaintColorSelectors()
	{
		for (int i = 0; i < color.length; i++)
			((ColorSelector) colorSelector.elementAt(i)).repaint();
	}

	public boolean action(Event event, Object arg)
	{
		if (event.target == redFastDown)
		{
			redValue.incValue(-25);
			setColor();
		}
		else if (event.target == redDown)
		{
			redValue.incValue(-1);
			setColor();
		}
		else if (event.target == redFastUp)
		{
			redValue.incValue(25);
			setColor();
		}
		else if (event.target == redUp)
		{
			redValue.incValue(1);
			setColor();
		}
		else if (event.target == greenFastDown)
		{
			greenValue.incValue(-25);
			setColor();
		}
		else if (event.target == greenDown)
		{
			greenValue.incValue(-1);
			setColor();
		}
		else if (event.target == greenFastUp)
		{
			greenValue.incValue(25);
			setColor();
		}
		else if (event.target == greenUp)
		{
			greenValue.incValue(1);
			setColor();
		}
		else if (event.target == blueFastDown)
		{
			blueValue.incValue(-25);
			setColor();
		}
		else if (event.target == blueDown)
		{
			blueValue.incValue(-1);
			setColor();
		}
		else if (event.target == blueFastUp)
		{
			blueValue.incValue(25);
			setColor();
		}
		else if (event.target == blueUp)
		{
			blueValue.incValue(1);
			setColor();
		}
		else if (event.target == addButton)
		{
			((ColorSelector) colorSelector.elementAt(0)).setColor(customColor.getColor());
			ColorSelector.setSelectedColor(customColor.getColor());
			repaintColorSelectors();
		}
		else if (event.target == scrollButton)
		{
			int e = color.length - 1;
			ColorSelector.setSelectedColor(((ColorSelector) colorSelector.elementAt(1)).
																				getColor());
			Color c = ((ColorSelector) colorSelector.elementAt(e)).getColor();
			for (int i = e - 1; i >= 0; i--)
			{
				Color t = ((ColorSelector) colorSelector.elementAt(i)).getColor();
				((ColorSelector) colorSelector.elementAt(i)).setColor(c);
				c = t;
			}
			((ColorSelector) colorSelector.elementAt(e)).setColor(c);
		}
		else if (event.target == mixButton)
		{
			int nc = color.length;
			Color startColor = ((ColorSelector) colorSelector.elementAt(0)).getColor();
			Color endColor = ((ColorSelector) colorSelector.elementAt(nc - 1)).getColor();
			ColorSelector.setSelectedColor(startColor);
			int red = startColor.getRed();
			int green = startColor.getGreen();
			int blue = startColor.getBlue();
			int redInc = (endColor.getRed() - red) / nc;
			int greenInc = (endColor.getGreen() - green) / nc;
			int blueInc = (endColor.getBlue() - blue) / nc;
			for (int i = 1; i < nc - 1; i++)
			{
				red += redInc;
				green += greenInc;
				blue += blueInc;
				Color c = new Color(red, green, blue);
				((ColorSelector) colorSelector.elementAt(i)).setColor(c);
			}
			repaintColorSelectors();
		}
		else if (event.target == saveButton)
		{
			Color colors[] = new Color[color.length];
			for (int i = 0; i < color.length; i++)
				colors[i] = ((ColorSelector) colorSelector.elementAt(i)).getColor();
			customPalette.addElement(colors);
		}
		else if (event.target == restoreButton)
		{
			ColorSelector.setSelectedColor(((Color[]) customPalette.
															elementAt(palettePointer))[0]);
			for (int i = 0; i < color.length; i++)
				((ColorSelector) colorSelector.elementAt(i)).
							setColor(((Color[]) customPalette.elementAt(palettePointer))[i]);
			palettePointer = (palettePointer + 1) % customPalette.size();
		}
		else
			return false;
		return true;
	}

	public boolean keyUp(Event event, int key)
	{
		if (key == 10)
			setColor();
		return false;
	}

	public boolean mouseUp(Event event, int x, int y)
	{
		if (panel12.bounds().inside(x, y))
		{
			repaintColorSelectors();
			return true;
		}		
		return false;
	}
}

class CustomColor extends Canvas
{
	final static int width = 100;
	final static int height = 25;

	public CustomColor(Color color)
	{
		setBackground(color);
		resize(width, height);
	}

	public void setColor(int red, int green, int blue)
	{
		setBackground(new Color(red, green, blue));
		repaint();
	}

	public Color getColor()
	{
		return getBackground();
	}

	public void paint(Graphics g)
	{
		Rectangle r = bounds();
		g.setColor(getBackground());
		g.fillRect(0, 0, r.width, r.height);
	}
}

class ColorSelector extends Canvas
{
	final static int width = 20;
	final static int height = 20;

	private static Color selectedColor = Color.white;
	private Color color;
	private WorkPad workPad;

	public ColorSelector(Color buttonColor, WorkPad workPad)
	{
		color = buttonColor;
		this.workPad = workPad;
		resize(width, height);
	}

	public static void setSelectedColor(Color newColor)
	{
		selectedColor = newColor;		
	}

	public void setColor(Color newColor)
	{
		color = newColor;
		repaint();
	}

	public Color getColor()
	{
		return color;
	}

	public void paint(Graphics g)
	{
		g.setColor(color);
		g.fillRect(2, 2, width - 4, height - 4);
		if (color.equals(selectedColor))
		{
			workPad.setColor(selectedColor);
			g.setColor(Color.black);
		}
		else
			g.setColor(Palette.backgroundColor);
		g.drawRect(0, 0, width - 1, height - 1);
	}

	public boolean mouseUp(Event event, int x, int y)
	{
		selectedColor = color;
		workPad.setColor(color);
		return false;
	}
}

interface CommandConstants
{
	final static int save = 0;
	final static int display = 1;

	final static int noCommand = 2;	
	final static int together = 3;
	final static int beside = 4;
	final static int above = 5;
	final static int diagonal = 6;
	final static int pushLeft = 7;
	final static int pushUp = 8;
	final static int pushDiagonal = 9;
	final static int pushCorner = 10;
	final static int pushTriangle = 11;
	final static int edgeLimit = 12;
	final static int centerLimit = 13;

	final static int moveLeft = 14;
	final static int moveRight = 15;
	final static int moveUp = 16;
	final static int moveDown = 17;
	final static int moveCenter = 18;
	final static int rotate90 = 19;
	final static int rotate180 = 20;
	final static int rotate270 = 21;
	final static int reflectXAxis = 22;
	final static int reflectYAxis = 23;

	final static int readCommand = 24;
	final static int writeCommand = 25;

	final static int getFirstArgument = 0;
	final static int getSecondArgument = 1;
	final static int store = 2;	
	final static int done = 3;
}

class Combine extends Panel implements CommandConstants
{
	final static Color backgroundColor = new Color(220, 220, 220);

	final static String togetherName = "Together";
	final static String besideName = "Beside";
	final static String aboveName = "Above";
	final static String diagonalName = "Diagonal";
	final static String pushLeftName = "Push Left";
	final static String pushUpName = "Push Up";
	final static String pushDiagonalName = "Push Diagonal";
	final static String pushCornerName = "Corner Push";
	final static String pushTriangleName = "Triangle Push";
	final static String edgeLimitName = "Edge Limit";
	final static String centerLimitName = "Center Limit";
	final static String combinations[] =
	{
		togetherName,  besideName, aboveName, diagonalName, pushLeftName, pushUpName,
		pushDiagonalName, pushCornerName, pushTriangleName, edgeLimitName, centerLimitName
	};

	final static String leftName = "Move Left";
	final static String rightName = "Move Right";
	final static String upName = "Move Up";
	final static String downName = "Move Down";
	final static String centerName = "Move Center";
	final static String rotate90Name = "Rotate 90";
	final static String rotate180Name = "Rotate 180";
	final static String rotate270Name = "Rotate 270";
	final static String reflectXAxisName = "Reflect X Axis";
	final static String reflectYAxisName = "Reflect Y Axis";
	final static String modifications[] =
	{
		leftName, rightName, upName, downName, centerName, rotate90Name, rotate180Name,
		rotate270Name, reflectXAxisName, reflectYAxisName
	};

	private Panel panel1;
	private Panel panel2;
	private Panel panel3;
	private Panel panel4;
	private Panel panel5;
	private Panel panel6;
	private Panel panel7;
	private Panel panel8;
	private Panel panel9;

	private Choice combineSelector;
	private Button combineButton;
	private Choice modifySelector;
	private Button modifyButton;
	private Label ratioLabel;
	private Label pushRatioLabel;
	private Label pushNoLabel;
	private DoubleField ratioInput;
	private DoubleField pushRatioInput;
	private IntField pushNoInput;
	private Button saveButton;
	private Button displayButton;

	public Combine()
	{		
		setBackground(backgroundColor);

		combineSelector = new Choice();
		for (int i = 0; i < combinations.length; i++)
			combineSelector.addItem(combinations[i]);
		modifySelector = new Choice();
		for (int i = 0; i < modifications.length; i++)
			modifySelector.addItem(modifications[i]);
		panel1 = new Panel();
		panel1.setLayout(new GridLayout(2, 1, 5, 6));
		panel1.add(combineSelector);
		panel1.add(modifySelector);

		combineButton = new Button("Combine");
		modifyButton = new Button("Modify");
		panel2 = new Panel();
		panel2.setLayout(new GridLayout(2, 1, 5, 9));
		panel2.add(combineButton);
		panel2.add(modifyButton);

		panel3 = new Panel();
		panel3.add(panel1);
		panel3.add(panel2);

		ratioLabel = new Label("Ratio");
		pushRatioLabel = new Label("Push Ratio");
		pushNoLabel = new Label("Push No");
		panel4 = new Panel();
		panel4.setLayout(new GridLayout(3, 1, 5, 5));
		panel4.add(ratioLabel);
		panel4.add(pushRatioLabel);
		panel4.add(pushNoLabel);

		ratioInput = new DoubleField("0.5", 4);
		pushRatioInput = new DoubleField("0.4", 4);
		pushNoInput = new IntField("3", 4, 1, 25);
		panel5 = new Panel();
		panel5.setLayout(new GridLayout(3, 1, 5, 5));
		panel5.add(ratioInput);
		panel5.add(pushRatioInput);
		panel5.add(pushNoInput);

		panel6 = new Panel();
		panel6.add(panel4);
		panel6.add(panel5);

		saveButton = new Button("Save");
		displayButton = new Button("Display");
		panel7 = new Panel();
		panel7.setLayout(new GridLayout(1, 2, 10, 0));
		panel7.add(saveButton);
		panel7.add(displayButton);

		panel8 = new Panel();
		panel8.add(panel7);

		panel9 = new Panel();
		panel9.setLayout(new BorderLayout());
		panel9.add("North", panel3);
		panel9.add("Center", panel6);
		panel9.add("South", panel8);

		add(panel9);
	}

	public boolean keyUp(Event event, int key)
	{
		if (key == 10)
		{
			Thumbnail.setRatio(ratioInput.getValue());
			Thumbnail.setPushRatio(pushRatioInput.getValue());
			Thumbnail.setPushNo(pushNoInput.getValue());
		}
		return false;
	}

	public boolean action(Event event, Object arg)
	{
		if (event.target == combineButton)
		{
			String command = combineSelector.getSelectedItem();
			if (command == togetherName)
				Thumbnail.setCommand(together, getFirstArgument);
			else if (command == besideName)
				Thumbnail.setCommand(beside, getFirstArgument);
			else if (command == aboveName)
				Thumbnail.setCommand(above, getFirstArgument);
			else if (command == diagonalName)
				Thumbnail.setCommand(diagonal, getFirstArgument);
			else if (command == pushLeftName)
				Thumbnail.setCommand(pushLeft, getFirstArgument);
			else if (command == pushUpName)
				Thumbnail.setCommand(pushUp, getFirstArgument);
			else if (command == pushDiagonalName)
				Thumbnail.setCommand(pushDiagonal, getFirstArgument);
			else if (command == pushCornerName)
				Thumbnail.setCommand(pushCorner, getFirstArgument);
			else if (command == pushTriangleName)
				Thumbnail.setCommand(pushTriangle, getFirstArgument);
			else if (command == edgeLimitName)
				Thumbnail.setCommand(edgeLimit, getFirstArgument);
			else if (command == centerLimitName)
				Thumbnail.setCommand(centerLimit, getFirstArgument);
		}
		else if (event.target == modifyButton)
		{
			String command = modifySelector.getSelectedItem();
			if (command == leftName)
				Thumbnail.setCommand(moveLeft, getFirstArgument);
			else if (command == rightName)
				Thumbnail.setCommand(moveRight, getFirstArgument);
			else if (command == upName)
				Thumbnail.setCommand(moveUp, getFirstArgument);
			else if (command == downName)
				Thumbnail.setCommand(moveDown, getFirstArgument);
			else if (command == centerName)
				Thumbnail.setCommand(moveCenter, getFirstArgument);
			else if (command == rotate90Name)
				Thumbnail.setCommand(rotate90, getFirstArgument);
			else if (command == rotate180Name)
				Thumbnail.setCommand(rotate180, getFirstArgument);
			else if (command == rotate270Name)
				Thumbnail.setCommand(rotate270, getFirstArgument);
			else if (command == reflectXAxisName)
				Thumbnail.setCommand(reflectXAxis, getFirstArgument);
			else if (command == reflectYAxisName)
				Thumbnail.setCommand(reflectYAxis, getFirstArgument);
		}
		else if (event.target == saveButton)
			Thumbnail.setCommand(save, done);
		else if (event.target == displayButton)
		{
			Thumbnail.setCommand(display, done);
		}
		else
			return false;
		return true;
	}
}

class GraphicsExtension
{
    final static int DDA_SCALE = 8192;

    static void drawThickLine(Graphics g, int x1, int y1, int x2, int y2, int linewidth)
	{
		g.fillOval(x1 - linewidth / 2, y1 - linewidth / 2, linewidth, linewidth);

		if (x1 == x2 && y1 == y2)
		    return;

		if (Math.abs(x2 - x1) > Math.abs(y2 - y1))
    	{
		    int dy, srow;
		    int dx, col, row, prevrow;

		    if (x2 > x1)
				dx = 1;
		    else
				dx = -1;
		    dy = (y2 - y1) * DDA_SCALE / Math.abs(x2 - x1);
		    prevrow = row = y1;
		    srow = row * DDA_SCALE + DDA_SCALE / 2;
		    col = x1;
		    for (;;)
			{
				if (row != prevrow)
			    {
				    g.drawOval(col - linewidth / 2, prevrow - linewidth / 2,
							   linewidth, linewidth);
				    prevrow = row;
			    }
				g.drawOval(col - linewidth / 2, row - linewidth / 2,
						   linewidth, linewidth);
				if (col == x2)
				    break;
				srow += dy;
				row = srow / DDA_SCALE;
				col += dx;
			}
	    }
		else
	    {
		    int dx, scol;
		    int dy, col, row, prevcol;

		    if (y2 > y1)
				dy = 1;
		    else
				dy = -1;
		    dx = (x2 - x1) * DDA_SCALE / Math.abs(y2 - y1);
		    row = y1;
		    prevcol = col = x1;
		    scol = col * DDA_SCALE + DDA_SCALE / 2;
		    for (;;)
			{
				if (col != prevcol)
			    {
				    g.drawOval(prevcol - linewidth / 2, row - linewidth / 2,
							   linewidth, linewidth);
			    	prevcol = col;
			    }
				g.drawOval(col - linewidth / 2, row - linewidth / 2,
						   linewidth, linewidth);
				if (row == y2)
				    break;
				row += dy;
				scol += dx;
				col = scol / DDA_SCALE;
			}
	    }
	}

	static void drawThickRectangle(Graphics g, int x, int y, int width, int height,
								   int linewidth)
	{
		drawThickLine(g, x, y, x + width, y, linewidth);
		drawThickLine(g, x + width, y, x + width, y + height, linewidth);
		drawThickLine(g, x + width, y + height, x, y + height, linewidth);
		drawThickLine(g, x, y + height, x, y, linewidth);
	}

	static void drawThickOval(Graphics g, int x1, int y1, int width, int height, int linewidth)
	{
		int xd = x1 + (width - linewidth) / 2;
		int yd = y1 + (height - linewidth) / 2;
		double a = width / 2;
		double as = a * a;
		double b = height / 2;
		double bs = b * b;
		int x = 0;
		int y = (int) b;
		double d = bs - as * b + as / 4.0;
		drawThickOvalPoints(g, x, y, xd, yd, linewidth);
		while (as * (y - 0.5) > bs * (x + 1.0))
		{
			if (d < 0)
			{
				d = d + bs * (2.0 * x + 3.0);
				x++;
			}
			else
			{
				d = d + bs * (2.0 * x + 3.0) + as * (2.0 - 2.0 * y);
				x++;
				y--;
			}
			drawThickOvalPoints(g, x, y, xd, yd, linewidth);
		}
		d = bs * (x + 0.5) * (x + 0.5) + as * (y - 1) * (y - 1) - as * bs;
		while (y > 0)
		{
			if (d < 0)
			{
				d = d + bs * (2.0 * x + 2.0) + as * (3.0 - 2.0 * y);
				x++;
				y--;
			}
			else
			{
				d = d + as * (3.0 - 2.0 * y);
				y--;
			}
			drawThickOvalPoints(g, x, y, xd, yd, linewidth);
		}
	}

	static private void drawThickOvalPoints(Graphics g, int x, int y, int xd, int yd, int w)
	{
		g.fillOval(xd + x, yd + y, w, w);
		g.fillOval(xd + x, yd - y, w, w);
		g.fillOval(xd - x, yd - y, w, w);
		g.fillOval(xd - x, yd + y, w, w);
	}

	static void drawThickPolygon(Graphics g, Polygon p, int linewidth)
	{
		for (int i = 0; i < p.npoints; i++)
			drawThickLine(g, p.xpoints[i], p.ypoints[i],
				p.xpoints[(i + 1) % p.npoints], p.ypoints[(i + 1) % p.npoints], linewidth);
	}

	static void drawCurve(Graphics g, int cx0, int cy0, int cx1, int cy1,
									  int cx2, int cy2, int cx3, int cy3, int lineSize)
	{
		double cx = 3 * (cx1 - cx0);
		double bx = 3 * (cx2 - cx1) - cx;
		double ax = cx3 - cx0 - cx - bx;		
		double cy = 3 * (cy1 - cy0);
		double by = 3 * (cy2 - cy1) - cy;
		double ay = cy3 - cy0 - cy - by;
		int x1 = cx0;
		int y1 = cy0;
		for (double t = .01; t < (1.005); t += .01)
		{
			double t2 = t * t;
			double t3 = t2 * t;
			int x2 = (int) (ax * t3 + bx * t2 + cx * t + cx0);
			int y2 = (int) (ay * t3 + by * t2 + cy * t + cy0);
			if (lineSize == 1)
				g.drawLine(x1, y1, x2, y2);
			else
				drawThickLine(g, x1, y1, x2, y2, lineSize);
			x1 = x2;
			y1 = y2;
		}
	}

	static void drawFilledCurve(Graphics g, int cx0, int cy0, int cx1, int cy1,
											int cx2, int cy2, int cx3, int cy3, int lineSize)
	{
		Polygon p = new Polygon();
		double cx = 3 * (cx1 - cx0);
		double bx = 3 * (cx2 - cx1) - cx;
		double ax = cx3 - cx0 - cx - bx;		
		double cy = 3 * (cy1 - cy0);
		double by = 3 * (cy2 - cy1) - cy;
		double ay = cy3 - cy0 - cy - by;
		p.addPoint(cx0, cy0);
		for (double t = .01; t < (1.005); t += .01)
		{
			double t2 = t * t;
			double t3 = t2 * t;
			int x = (int) (ax * t3 + bx * t2 + cx * t + cx0);
			int y = (int) (ay * t3 + by * t2 + cy * t + cy0);
			p.addPoint(x, y);
		}
		p.addPoint(cx0, cy0);
		g.fillPolygon(p);
	}
}

interface MakeConstants
{
	final static int freehandMode = 0;
	final static int lineMode = 1;
	final static int rectangleMode = 2;
	final static int ovalMode = 3;
	final static int polygonMode = 4;
	final static int curveMode = 5;
	final static int filledRectangleMode = 6;
	final static int filledOvalMode = 7;
	final static int filledPolygonMode = 8;
	final static int filledCurveMode = 9;
}

class WorkPad extends Canvas implements MakeConstants
{
	final static Color XORColor = new Color(0xB0, 0x60, 0x90);

	private Patterns patterns;
	private Ruler[] ruler;
	private boolean snap;
	private Picture picture;
	private Color backgroundColor = Color.black;
	private Color color = Color.white;
	private int lineSize = 1;
	private int mode = freehandMode;
	private Polygon polygon;
	private int old_x, old_y, new_x, new_y;
	private int curveControl = 0;
	private int cx0, cy0, cx1, cy1, cx2, cy2, cx3, cy3;

	public WorkPad(Patterns patterns, Ruler[] ruler)
	{
		this.patterns = patterns;
		this.ruler = ruler;
		picture = new Picture(patterns);
		snap = true;
	}

	private void cleanup()
	{
		if ((mode == polygonMode) || (mode == filledPolygonMode))
		{
			if (polygon != null)
			{
				polygon = null;
				undo();
			}
		}
		if (curveControl != 0)
		{
			curveControl = 0;
			repaint();
		}
	}

	public void setColor(Color newColor)
	{
		cleanup();
		color = newColor;
	}

	public void setLineSize(int newLineSize)
	{
		cleanup();
		lineSize = newLineSize;
	}

	public void setMode(int newMode)
	{
		cleanup();
		mode = newMode;
	}

	public void setRulerDepth(int rulerDepth)
	{
		Ruler.setRulerDepth(rulerDepth);
		repaint();
	}

	public void setSnap(boolean snap)
	{
		this.snap = snap;	
	}

	public void clear()
	{
		picture = new Picture(patterns);
		cleanup();
		repaint();
	}

	public void paint(Graphics g)
	{
		g.setColor(backgroundColor);
		Rectangle r = bounds();
		g.fillRect(0, 0, r.width, r.height);
		picture.drawPicture(this, 0.0, 0.0, bounds().width - 1, 0.0, 0.0, bounds().height - 1);
		for (int i = 0; i < 4; i++)
			ruler[i].repaint();
	}

	public void undo()
	{
		picture.undo();
		cleanup();
		repaint();
	}

	public Picture getPicture()
	{
		return picture;
	}

	private double scaleX(int x)
	{
		return (double) (x) / (double) (bounds().width - 1);
	}

	private double scaleY(int y)
	{
		return (double) (y) / (double) (bounds().height - 1);
	}

	private void drawCrossHairs(int x, int y, boolean snap)
	{
		for (int i = 0; i < 4; i++)
			ruler[i].drawCrossHairs(x, y, snap);
	}

	private void removeCrossHairs()
	{    
		for (int i = 0; i < 4; i++)
			ruler[i].removeCrossHairs();
	}

    public boolean mouseEnter(Event e, int x, int y)
	{
		drawCrossHairs(x, y, snap);
		return true;
	}

    public boolean mouseMove(Event e, int x, int y)
    {
		drawCrossHairs(x, y, snap);
		return true;
	}

    public boolean mouseExit(Event e, int x, int y)
	{
		removeCrossHairs();
		return true;
	}

    public boolean mouseDown(Event e, int x, int y)
    {
		if (picture.full())
			return true;
		Graphics g;
		x = Ruler.getXVal();
		y = Ruler.getYVal();
		switch (mode)
		{
			case freehandMode:
				picture.initSegments(mode, 0, lineSize, color, scaleX(x), scaleY(y));
				break;
			case polygonMode:
			case filledPolygonMode:
				if (polygon != null)
				{
   					g = getGraphics();
        			g.setColor(color);
					g.setXORMode(XORColor);
					g.drawLine(old_x, old_y, new_x, new_y);
					g.drawLine(old_x, old_y, x, y);
					g.setPaintMode();
					g.dispose();
					new_x = x;
					new_y = y;
					return true;
				}
				else
				{
					polygon = new Polygon();
					polygon.addPoint(x, y);
					picture.initSegments(mode, 1, lineSize, color, scaleX(x), scaleY(y));
				}
			case lineMode:
				g = getGraphics();
        		g.setColor(color);
				g.setXORMode(XORColor);
				g.drawLine(x, y, x, y);
				g.setPaintMode();
				g.dispose();
			case rectangleMode:
			case ovalMode:
			case filledRectangleMode:
			case filledOvalMode:
				new_x = x;
				new_y = y;
				break;
		}
        old_x = x;
		old_y = y;
        return true;
    }
    
    public boolean mouseDrag(Event e, int x, int y)
    {
		if ((x >= 0) && (y >= 0) && (x < bounds().width) && (y < bounds().height))
		{
			drawCrossHairs(x, y, snap);
			x = Ruler.getXVal();
			y = Ruler.getYVal();
		}
		else
			removeCrossHairs();
		if (picture.full())
			return true;
		Graphics g = getGraphics();
		switch (mode)
		{
			case freehandMode:
				picture.addSegment(scaleX(x), scaleY(y));
        		g.setColor(color);
				if (lineSize == 1)
					g.drawLine(old_x, old_y, x, y);
				else
			        GraphicsExtension.drawThickLine(g, old_x, old_y, x, y, lineSize);
	        	old_x = x;
		        old_y = y;
		        break;			
			case lineMode:
			case polygonMode:
			case filledPolygonMode:
				g.setColor(color);
				g.setXORMode(XORColor);
				g.drawLine(old_x, old_y, new_x, new_y);
				g.drawLine(old_x, old_y, x, y);
				g.setPaintMode();
				new_x = x;
				new_y = y;
				break;
			case curveMode:
			case filledCurveMode:
				if (curveControl < 2)
					break;
				g.setColor(color);
				g.setXORMode(XORColor);
				GraphicsExtension.drawCurve(g, cx0, cy0, cx1, cy1, cx2, cy2, cx3, cy3, 1);
				if ((e.modifiers & Event.SHIFT_MASK) != 0)			
				{
					cx1 = x;
					cy1 = y;
				}
				else
				{
					cx2 = x;
					cy2 = y;
				}
				GraphicsExtension.drawCurve(g, cx0, cy0, cx1, cy1, cx2, cy2, cx3, cy3, 1);
				g.setPaintMode();
				break;
			case rectangleMode:
			case filledRectangleMode:
				g.setColor(color);
				g.setXORMode(XORColor);
				g.drawRect(Math.min(new_x, old_x), Math.min(new_y, old_y),
						   Math.abs(new_x - old_x), Math.abs(new_y - old_y));
				g.drawRect(Math.min(x, old_x), Math.min(y, old_y),
						   Math.abs(x - old_x), Math.abs(y - old_y));
				g.setPaintMode();
				new_x = x;
				new_y = y;
				break;
			case ovalMode:
			case filledOvalMode:
				g.setColor(color);
				g.setXORMode(XORColor);
				g.drawOval(Math.min(new_x, old_x), Math.min(new_y, old_y),
						   Math.abs(new_x - old_x), Math.abs(new_y - old_y));
				g.drawOval(Math.min(x, old_x), Math.min(y, old_y),
						   Math.abs(x - old_x), Math.abs(y - old_y));
				g.setPaintMode();
				new_x = x;
				new_y = y;
				break;
		}
		g.dispose();
		return true;
	}

	public boolean mouseUp(Event e, int x, int y)
	{		
		if (picture.full())
			return true;
		Graphics g;
		if ((x >= 0) && (y >= 0) && (x < bounds().width) && (y < bounds().height))
		{
			x = Ruler.getXVal();
			y = Ruler.getYVal();
		}
		if (mode == freehandMode)
			return true;
		int x1, y1, w, h;
		if (mode == lineMode)
		{
        	g = getGraphics();
        	g.setColor(color);
			if (lineSize == 1)
				g.drawLine(old_x, old_y, new_x, new_y);
			else
		        GraphicsExtension.drawThickLine(g, old_x, old_y, new_x, new_y, lineSize);
			g.dispose();
			picture.addItem(mode, lineSize, color, scaleX(old_x), scaleY(old_y),
																scaleX(new_x), scaleY(new_y));
			return true;
		}
		if ((mode == polygonMode) || (mode == filledPolygonMode))
		{
        	g = getGraphics();
			if ((Math.abs(x - polygon.xpoints[0]) < 5) &&
				(Math.abs(y - polygon.ypoints[0]) < 5) && (polygon.npoints > 1))
			{
				g.setColor(color);
				g.setXORMode(XORColor);
				g.drawLine(old_x, old_y, new_x, new_y);
				g.setPaintMode();
				polygon.addPoint(polygon.xpoints[0], polygon.ypoints[0]);
				picture.addSegment(scaleX(polygon.xpoints[0]), scaleY(polygon.ypoints[0]));
				if (mode == polygonMode)
					if (lineSize == 1)
						g.drawPolygon(polygon);
					else
						GraphicsExtension.drawThickPolygon(g, polygon, lineSize);
				else
					g.fillPolygon(polygon);
				polygon = null;
			}
			else
			{
				g.setColor(color);
				g.drawLine(old_x, old_y, new_x, new_y);
				g.setXORMode(XORColor);
				g.drawLine(x, y, x, y);				
				g.setPaintMode();
				polygon.addPoint(x, y);
				picture.addSegment(scaleX(x), scaleY(y));
				old_x = x;
				old_y = y;
				new_x = x;
				new_y = y;
			}
			g.dispose();
			return true;
		}
		if ((mode == curveMode) || (mode == filledCurveMode))
		{
			switch (curveControl)
			{
				case 0:
					cx0 = x;
					cy0 = y;
					cx1 = x;
					cy1 = y;
					curveControl = 1;
					break;
				case 1:
					cx2 = x;
					cy2 = y;
					cx3 = x;
					cy3 = y;
		        	g = getGraphics();
		        	g.setColor(color);
					g.setXORMode(XORColor);
					GraphicsExtension.drawCurve(g, cx0, cy0, cx1, cy1, cx2, cy2, cx3, cy3, 1);
					g.setPaintMode();
					g.dispose();
					curveControl = 2;
					break;
				default:				
		        	g = getGraphics();
		        	g.setColor(color);
					GraphicsExtension.drawCurve(g, cx0, cy0, cx1, cy1,
															 cx2, cy2, cx3, cy3, lineSize);
					if (mode == filledCurveMode)
						GraphicsExtension.drawFilledCurve(g, cx0, cy0, cx1, cy1,
															 cx2, cy2, cx3, cy3, lineSize);
					g.dispose();
					picture.addCurve(mode, lineSize, color,
										scaleX(cx0), scaleY(cy0), scaleX(cx1), scaleY(cy1),
										scaleX(cx2), scaleY(cy2), scaleX(cx3), scaleY(cy3));
					curveControl = 0;
			}
			return true;
		}
		x1 = Math.min(x, old_x);
		y1 = Math.min(y, old_y);
		w = Math.abs(x - old_x);
		h = Math.abs(y - old_y);
        g = getGraphics();
        g.setColor(color);
		switch (mode)
		{
			case rectangleMode:
				if (lineSize == 1)
					g.drawRect(x1, y1, w, h);
				else
					GraphicsExtension.drawThickRectangle(g, x1, y1, w, h, lineSize);
				break;
			case ovalMode:
				if (lineSize == 1)
					g.drawOval(x1, y1, w, h);
				else
					GraphicsExtension.drawThickOval(g, x1, y1, w, h, lineSize);
				break;
			case filledRectangleMode:
				g.setXORMode(XORColor);
				g.drawRect(x1, y1, w, h);
				g.setPaintMode();
				g.fillRect(x1, y1, w, h);
				break;
			case filledOvalMode:
				g.setXORMode(XORColor);
				g.drawOval(x1, y1, w, h);
				g.setPaintMode();
				g.fillOval(x1, y1, w, h);
				break;
		}
		g.dispose();
		picture.addItem(mode, lineSize, color, scaleX(x), scaleY(y),
																scaleX(old_x), scaleY(old_y));
		return true;
	}

	public void storePicture(Vector points)
	{
		if (mode == freehandMode)
		{
			if (points.size() < 4)
				return;
			double x1 = ((Double) points.elementAt(0)).doubleValue();
			double y1 = ((Double) points.elementAt(1)).doubleValue();
			picture.initSegments(mode, 0, lineSize, color, x1, y1);
			double x = 0.0;
			double y = 0.0;
			for (int i = 2; i < points.size(); i += 2)
			{
				x = ((Double) points.elementAt(i)).doubleValue();
				y = ((Double) points.elementAt(i + 1)).doubleValue();
				picture.addSegment(x, y);
			}
			repaint();
			return;
		}
		if ((mode == polygonMode) || (mode == filledPolygonMode))
		{
			if (points.size() < 6)
				return;
			double x1 = ((Double) points.elementAt(0)).doubleValue();
			double y1 = ((Double) points.elementAt(1)).doubleValue();
			picture.initSegments(mode, 1, lineSize, color, x1, y1);
			double x = 0.0;
			double y = 0.0;
			for (int i = 2; i < points.size(); i += 2)
			{
				x = ((Double) points.elementAt(i)).doubleValue();
				y = ((Double) points.elementAt(i + 1)).doubleValue();
				picture.addSegment(x, y);
			}
			if ((x1 != x) || (y1 != y))
				picture.addSegment(x1, y1);
			repaint();
			return;
		}
		if ((mode == curveMode) || (mode == filledCurveMode))
		{
			if (points.size() != 8)
				return;
			picture.addCurve(mode, lineSize, color,
 							 ((Double) points.elementAt(0)).doubleValue(),
							 ((Double) points.elementAt(1)).doubleValue(),
							 ((Double) points.elementAt(4)).doubleValue(),
							 ((Double) points.elementAt(5)).doubleValue(),
							 ((Double) points.elementAt(6)).doubleValue(),
							 ((Double) points.elementAt(7)).doubleValue(),
							 ((Double) points.elementAt(2)).doubleValue(),
							 ((Double) points.elementAt(3)).doubleValue());
			repaint();
			return;
		}
		if (points.size() != 4)
			return;
		else
		{
			picture.addItem(mode, lineSize, color,
							((Double) points.elementAt(0)).doubleValue(),
							((Double) points.elementAt(1)).doubleValue(),
							((Double) points.elementAt(2)).doubleValue(),
							((Double) points.elementAt(3)).doubleValue());
			repaint();
		}
	}
}

class Ruler extends Canvas 
{
	final static int rulerSize = 15;
	final static int left = 0;
	final static int top = 1;
	final static int right = 2;
	final static int bottom = 3;
	final static Color XORColor = new Color(0x3F, 0xC0, 0xC0);

	private static int rulerDepth = 6;
	private static int X, Y;

	private int position;
	private int old;
	private boolean pointerOn;

	public Ruler(int position)
	{
		this.position = position;
	}

	public Dimension preferredSize()
	{
		return new Dimension(rulerSize, rulerSize);
	}

	public static void setRulerDepth(int depth)
	{
		rulerDepth = depth;
	}

	public static int getXVal()
	{
		return X;
	}

	public static int getYVal()
	{
		return Y;
	}

	public void paint(Graphics g)
	{
		g.setColor(Color.lightGray);
		g.fillRect(0, 0, bounds().width, bounds().height);
		g.setColor(Color.black);
		if ((position == left) || (position == right))
			makeMark(g, 0, bounds().height - 1, rulerDepth * 2);
		else
			makeMark(g, rulerSize, bounds().width - rulerSize - 1, rulerDepth * 2);
		pointerOn = false;
	}

	private void makeMark(Graphics g, int begin, int end, int n)
	{
		if (n > 1)
		{
			int center = (begin + end) / 2;
			switch (position)
			{
				case left:
					g.drawLine(bounds().width - n, center, bounds().width, center);
					break;
				case top:
					g.drawLine(center, bounds().height - n, center, bounds().height);
					break;
				case right:
					g.drawLine(0, center, bounds().width - rulerSize + n, center);
					break;
				case bottom:
					g.drawLine(center, 0, center, bounds().height - rulerSize + n);
					break;
			}
			makeMark(g, begin, center, n - 2);
			makeMark(g, center, end, n - 2); 
		}
	}

	public void drawCrossHairs(int x, int y, boolean snap)
	{
		if (snap)
		{
			int min = 0;
			int max = bounds().width - 2 * rulerSize - 1;
			for (int i = 0; i < rulerDepth; i++)
			{
				int mid = (min + max) / 2;
				if (x < mid)
					max = mid;
				else
					min = mid;
			}
			x = ((x - min) < (max - x)) ? min : max;
			min = 0;
			max = bounds().height - 1;
			for (int i = 0; i < rulerDepth; i++)
			{
				int mid = (min + max) / 2;
				if (y < mid)
					max = mid;
				else
					min = mid;
			}
			y = ((y - min) < (max - y)) ? min : max;
		}
       	Graphics g = getGraphics();
		g.setXORMode(XORColor);
		if ((position == left) || (position == right))
		{
			if (pointerOn)
				g.drawLine(0, old, bounds().height, old);
			else
				pointerOn = true;
			g.drawLine(0, y, bounds().height, y);
			old = y;
		}
		else
		{
			if (pointerOn)
				g.drawLine(old + rulerSize, 0, old + rulerSize, bounds().height);
			else
				pointerOn = true;
			g.drawLine(x + rulerSize, 0, x + rulerSize, bounds().width);
			old = x;
		}
		g.setPaintMode();
		g.dispose();
		if (position == left)
			Y = y;
		if (position == top)
			X = x;
	}

	public void removeCrossHairs()
	{
       	Graphics g = getGraphics();
		g.setXORMode(XORColor);
		if (pointerOn)
		{
			if ((position == left) || (position == right))
			{
				g.drawLine(0, old, bounds().height, old);
				pointerOn = false;
			}
			else
			{
				g.drawLine(old + rulerSize, 0, old + rulerSize, bounds().height);
				pointerOn = false;
			}
		}
		g.setPaintMode();
		g.dispose();
	}
}

class Display extends Canvas
{
	private Color backgroundColor = Color.black;
	private Picture picture = null;

	public void setPicture(Picture picture)
	{
		this.picture = picture;
	}

	public void paint(Graphics g)
	{
		g.setColor(backgroundColor);
		Rectangle r = bounds();
		g.fillRect(0, 0, r.width, r.height);
		if (picture != null)
			picture.drawPicture(this, 0.0, 0.0, bounds().width - 1, 0.0, 0.0,
																		bounds().height - 1);
	}

	public String read()
	{
		Picture p = Picture.readPicture();
		String status = Picture.getIOStatus();
		if (status.startsWith("Error"))
			return status;
		picture = p;
		repaint();
		return status;
	}

	public String write()
	{
		picture.writePicture();
		return Picture.getIOStatus();
	}
}

class Thumbnail extends Canvas implements CommandConstants
{
	static int command = noCommand;
	static int state = done;
	static Picture savePicture = null;
	static double ratio = 0.5;
	static double pushRatio = 0.4;
	static int pushNo = 3;

	private Patterns patterns;
	private WorkPad workPad;
	private Display displayArea;
	private Color backgroundColor = Color.black;
	private Picture picture;

	public Thumbnail(Patterns patterns, WorkPad workPad, Display display)
	{
		this.patterns = patterns;
		this.workPad = workPad;
		displayArea = display;
		picture = null;
	}

	public static void setCommand(int newCommand, int newState)
	{
		command = newCommand;
		state = newState;
	}

	public static void setRatio(double newRatio)
	{
		ratio = newRatio;
	}

	public static void setPushRatio(double newPushRatio)
	{
		pushRatio = newPushRatio;
	}

	public static void setPushNo(int newPushNo)
	{
		pushNo = newPushNo;
	}

	public void clear()
	{
		picture = null;
		getParent().repaint();
		repaint();
	}

	public void paint(Graphics g)
	{
		g.setColor(backgroundColor);
		Rectangle r = bounds();
		g.fillRect(0, 0, r.width, r.height);
		if (picture != null)
			picture.drawPicture(this, 0.0, 0.0, bounds().width - 1, 0.0, 0.0,
																		bounds().height - 1);
	}

	public void read()
	{
		Picture p = Picture.readPicture();
		String status = Picture.getIOStatus();
		if (! status.startsWith("Error"))
		{
			picture = p;
			repaint();
		}
		patterns.getAppletContext().showStatus(status);
	}

	public void write()
	{
		picture.writePicture();
		patterns.getAppletContext().showStatus(Picture.getIOStatus());
	}

	public boolean mouseUp(Event e, int x, int y)
	{
		if (command == readCommand)
		{
			read();
			command = noCommand;
			return true;
		}
		if (state == store)
		{
			picture = savePicture;
			command = noCommand;
			state = done;
			repaint();
			return true;			
		}
		if ((picture == null) && (command != save))
		{
			command = noCommand;
			state = done;
			return true;			
		}
		switch (command)
		{
			case save:
				picture = workPad.getPicture().makeCopy();
				command = noCommand;
				state = done;
				repaint();
				break;
			case display:
				displayArea.setPicture(picture.makeCopy());
				command = noCommand;
				patterns.switchDisplay();
				break;
			case together:
				if (state == getFirstArgument)
				{
					savePicture = picture.makeCopy();
					state = getSecondArgument;
				}
				else if (state == getSecondArgument)
				{
					savePicture = Picture.together(savePicture, picture.makeCopy());
					state = store;
				}
				break;
			case beside:
				if (state == getFirstArgument)
				{
					savePicture = picture.makeCopy();
					state = getSecondArgument;
				}
				else if (state == getSecondArgument)
				{
					savePicture = Picture.beside(savePicture, picture.makeCopy(), ratio);
					state = store;
				}
				break;
			case above:
				if (state == getFirstArgument)
				{
					savePicture = picture.makeCopy();
					state = getSecondArgument;
				}
				else if (state == getSecondArgument)
				{
					savePicture = Picture.above(savePicture, picture.makeCopy(), ratio);
					state = store;
				}
				break;
			case diagonal:
				if (state == getFirstArgument)
				{
					savePicture = picture.makeCopy();
					state = getSecondArgument;
				}
				else if (state == getSecondArgument)
				{
					savePicture = Picture.diagonal(savePicture, picture.makeCopy(), ratio);
					state = store;
				}
				break;
			case pushLeft:
				savePicture = Picture.pushLeft(picture.makeCopy(), pushNo, pushRatio);
				state = store;
				break;
			case pushUp:
				savePicture = Picture.pushUp(picture.makeCopy(), pushNo, pushRatio);
				state = store;
				break;
			case pushDiagonal:
				savePicture = Picture.pushDiagonal(picture.makeCopy(), pushNo, pushRatio);
				state = store;
				break;
			case pushCorner:
				savePicture = Picture.pushCorner(picture.makeCopy(), pushNo, pushRatio);
				state = store;
				break;
			case pushTriangle:
				savePicture = Picture.pushTriangle(picture.makeCopy(), pushNo);
				state = store;
				break;
			case edgeLimit:
				savePicture = Picture.edgeLimit(picture.makeCopy(), pushNo, pushRatio);
				state = store;
				break;
			case centerLimit:
				savePicture = Picture.centerLimit(picture.makeCopy(), pushNo, pushRatio);
				state = store;
				break;
			case moveLeft:
				savePicture = Picture.moveLeft(picture.makeCopy(), ratio);
				state = store;
				break;
			case moveRight:
				savePicture = Picture.moveRight(picture.makeCopy(), ratio);
				state = store;
				break;
			case moveUp:
				savePicture = Picture.moveUp(picture.makeCopy(), ratio);
				state = store;
				break;
			case moveDown:
				savePicture = Picture.moveDown(picture.makeCopy(), ratio);
				state = store;
				break;
			case moveCenter:
				savePicture = Picture.moveCenter(picture.makeCopy(), ratio);
				state = store;
				break;
			case rotate90:
				savePicture = Picture.rotate90(picture.makeCopy());
				state = store;
				break;
			case rotate180:
				savePicture = Picture.rotate180(picture.makeCopy());
				state = store;
				break;
			case rotate270:
				savePicture = Picture.rotate270(picture.makeCopy());
				state = store;
				break;
			case reflectXAxis:
				savePicture = Picture.reflectXAxis(picture.makeCopy());
				state = store;
				break;
			case reflectYAxis:
				savePicture = Picture.reflectYAxis(picture.makeCopy());
				state = store;
				break;
			case writeCommand:
				write();
				command = noCommand;
				break;
		}
		return true;
	}
}

class Picture implements MakeConstants, Cloneable
{
	final static byte basicType = 0;
	final static byte connectType = 1;
	final static byte scaleType = 2;
	final static byte rotateType = 3;
	final static byte reflectType = 4;

	final static int pictureSize = 10000;
	final static int safetySize = 100;

	private static Patterns patterns;
	private static String filePosition = "0";
	private static byte pictureBuffer[];
	private static int bufferNext;
	private static int elementCount;
	private static Hashtable pictures;
	private	static String ioStatus;
	private byte picture[];
	private int noSegments;
	private int next;
	private int link;
	private int elementNo;

	public Picture()
	{
	}

	public Picture(Patterns patterns)
	{
		this.patterns = patterns;
		picture = new byte[pictureSize + safetySize];
		next = 0;
	}

	public Picture makeCopy()
	{
		try
		{
			return (Picture) this.clone();
		}
		catch (CloneNotSupportedException e)
		{
			return null;
		}
	}	

	public static String getIOStatus()
	{
		return ioStatus;
	}

	public void setElementNo(int n)
	{
		elementNo = n;
	}

	public int getElementNo()
	{
		return elementNo;
	}

	public static void clearElementCount()
	{
		elementCount = 0;
	}

	public static int incElementCount()
	{
		return ++elementCount;
	}

	public static void addBufferByte(byte b)
	{
		pictureBuffer[bufferNext++] = b;
	}

	public static byte getBufferByte()
	{
		return pictureBuffer[bufferNext++];
	}

	public static void addBufferInt(int i)
	{
		pictureBuffer[bufferNext++] = (byte) ((i >> 8) & 0xFF);
		pictureBuffer[bufferNext++] = (byte) (i & 0xFF);
	}

	public static int getBufferInt()
	{
		int h = pictureBuffer[bufferNext++];
		int l = pictureBuffer[bufferNext++];
		return (h << 8) | (l & 0xFF);
	}

	public static void addBufferDouble(double d)
	{
		long l = Double.doubleToLongBits(d);
		for (int i = 0; i < 8; i++)
		{
			pictureBuffer[bufferNext++] = (byte) ((l >>> ((7 - i) * 8)) & 0xFF);			
		}
	}

	private static double getBufferDouble()
	{
		long l = 0;
		for (int i = 0; i < 8 ; i++)
		{
			l <<= 8;
			l |= pictureBuffer[bufferNext++] & 0xFF;
		}
		return Double.longBitsToDouble(l);
	}

	private void addByte(byte b)
	{
		picture[next++] = b;
	}

	private byte getByte()
	{
		return picture[next++];
	}

	private void addInt(int i)
	{
		picture[next++] = (byte) ((i >> 8) & 0xFF);
		picture[next++] = (byte) (i & 0xFF);
	}

	private int getInt()
	{
		int h = picture[next++];
		int l = picture[next++];
		return (h << 8) | (l & 0xFF);
	}

	private void addDouble(double d)
	{
		long l = Double.doubleToLongBits(d);
		for (int i = 0; i < 8; i++)
		{
			picture[next++] = (byte) ((l >>> ((7 - i) * 8)) & 0xFF);			
		}
	}

	private double getDouble()
	{
		long l = 0;
		for (int i = 0; i < 8 ; i++)
		{
			l <<= 8;
			l |= picture[next++] & 0xFF;
		}
		return Double.longBitsToDouble(l);
	}

	private void addColor(Color color)
	{
		picture[next++] = (byte) (color.getRed() & 0xFF);
		picture[next++] = (byte) (color.getGreen() & 0xFF);
		picture[next++] = (byte) (color.getBlue() & 0xFF);
	}

	private Color getColor()
	{
		byte r = picture[next++];
		byte g = picture[next++];
		byte b = picture[next++];
		return new Color((r < 0) ? r + 256 : r, (g < 0) ? g + 256 : g, (b < 0) ? b + 256 : b);
	}

	private void addPosition(double x, double y)
	{
		addDouble(x);
		addDouble(y);
	}

	private void incNoSegments()
	{
		noSegments++;
		picture[link + 1] = (byte) ((noSegments >> 8) & 0xFF);
		picture[link + 2] = (byte) (noSegments & 0xFF);
	}

	private boolean decNoSegments()
	{
		if (noSegments == 0)
			return false;
		noSegments--;
		picture[link + 1] = (byte) ((noSegments >> 8) & 0xFF);
		picture[link + 2] = (byte) (noSegments & 0xFF);
		return true;
	}

	private void cleanup()
	{
		if (next == 0)
			return;
		int save = next;
		next -= 2;
		next = getInt();
		int mode = getByte();
		if ((mode == freehandMode) || (mode == polygonMode) || (mode == filledPolygonMode))
		{
			if (getInt() == 0)
			{
				next -= 3;
				return;
			}
		}
		next = save;
	}

	public boolean full()
	{
		if (next > pictureSize)
			return true;
		else
			return false;
	}

	public void initSegments(int mode, int noSegs, int arg, Color color, double x, double y)
	{
		cleanup();
		link = next;
		addByte((byte) mode);
		noSegments = noSegs;
		addInt(noSegments);
		addInt(arg);
		addColor(color);
		addPosition(x, y);
		addInt(link);
	}

	public void addSegment(double x, double y)
	{
		incNoSegments();
		next -= 2;
		addPosition(x, y);
		addInt(link);
	}

	public void addItem(int mode, int lineSize, Color color, double x, double y,
																			double w, double h)
	{
		cleanup();
		link = next;
		addByte((byte) mode);
		addInt(lineSize);
		addColor(color);
		addPosition(x, y);
		addPosition(w, h);
		addInt(link);
	}

	public void addCurve(int mode, int lineSize, Color color,
												double x0, double y0, double x1, double y1,
												double x2, double y2, double x3, double y3)
	{
		cleanup();
		link = next;
		addByte((byte) mode);
		addInt(lineSize);		
		addColor(color);
		addPosition(x0, y0);
		addPosition(x1, y1);
		addPosition(x2, y2);
		addPosition(x3, y3);
		addInt(link);
	}

	public void undo()
	{
		if (next > 0)
		{
			byte p[] = new byte[pictureSize + safetySize];
			for (int i = 0; i <next; i++)
				p[i] = picture[i];
			picture = p;
			next -= 2;
			next = getInt();
		}		
	}

	private int scaleLineSize(double xh, double yh, double xv, double yv)
	{
		int lineSize = getInt();
		if (lineSize == 1)
			return 1;
		double xs = Math.abs(xh + xv);
		double ys = Math.abs(yh + yv);
		double scale = (xs < ys) ? xs : ys;
		lineSize = (int) Math.round((double) (lineSize) * scale / 384.0);
		return (lineSize < 1) ? 1 : lineSize;
	}

	private void setClipRectangle(Graphics g, double x0, double y0, double xh, double yh,
																		double xv, double yv)
	{
		int x1 = (int) Math.round(x0);
		int y1 = (int) Math.round(y0);
		int x2 = (int) Math.round(xh + xv + x0);
		int y2 = (int) Math.round(yh + yv + y0);
		g.clipRect(Math.min(x1, x2), Math.min(y1, y2),
											Math.abs(x1 - x2) + 1, Math.abs(y1 - y2) + 1);
	}

	public void drawPicture(Canvas c, double x0, double y0, double xh, double yh,
																		double xv, double yv)
	{
		Graphics g = c.getGraphics();
		setClipRectangle(g, x0, y0, xh, yh, xv, yv);
		int mode, noSegments, lineSize, link;
		int x, y, x1, y1, x2, y2, w, h;
		int cx0, cy0, cx1, cy1, cx2, cy2, cx3, cy3;
		double dx, dy;
		int end = next;
		next = 0;
		while (next < end)
		{
			mode = getByte();
			if (mode == freehandMode)
			{
				noSegments = getInt();
				lineSize = scaleLineSize(xh, yh, xv, yv);
				Color color = getColor();
				g.setColor(color);
				dx = getDouble();
				dy = getDouble();
				x1 = (int) Math.round(dx * xh + dy * xv + x0);
				y1 = (int) Math.round(dx * yh + dy * yv + y0);
				while (noSegments > 0)
				{
					dx = getDouble();
					dy = getDouble();
					x2 = (int) Math.round(dx * xh + dy * xv + x0);
					y2 = (int) Math.round(dx * yh + dy * yv + y0);
					if (lineSize == 1)
						g.drawLine(x1, y1, x2, y2);
					else
				        GraphicsExtension.drawThickLine(g, x1, y1, x2, y2, lineSize);
					x1 = x2;
					y1 = y2;
					noSegments--;
				}
				link = getInt();
				continue;
			}
			if ((mode == polygonMode) || (mode == filledPolygonMode))
			{
				noSegments = getInt();
				lineSize = scaleLineSize(xh, yh, xv, yv);
				Color color = getColor();
				g.setColor(color);
				Polygon polygon = new Polygon();
				do
				{
					dx = getDouble();
					dy = getDouble();
					x = (int) Math.round(dx * xh + dy * xv + x0);
					y = (int) Math.round(dx * yh + dy * yv + y0);
					polygon.addPoint(x, y);
					noSegments--;
				}
				while (noSegments > 0);
				if (mode == polygonMode)
					if (lineSize == 1)
						g.drawPolygon(polygon);
					else
						GraphicsExtension.drawThickPolygon(g, polygon, lineSize);
				else
					g.fillPolygon(polygon);
				link = getInt();
				continue;
			}
			if ((mode == curveMode) || (mode == filledCurveMode))
			{
				lineSize = scaleLineSize(xh, yh, xv, yv);
				Color color = getColor();
				g.setColor(color);
				dx = getDouble();
				dy = getDouble();
				cx0 = (int) Math.round(dx * xh + dy * xv + x0);
				cy0 = (int) Math.round(dx * yh + dy * yv + y0);
				dx = getDouble();
				dy = getDouble();
				cx1 = (int) Math.round(dx * xh + dy * xv + x0);
				cy1 = (int) Math.round(dx * yh + dy * yv + y0);
				dx = getDouble();
				dy = getDouble();
				cx2 = (int) Math.round(dx * xh + dy * xv + x0);
				cy2 = (int) Math.round(dx * yh + dy * yv + y0);
				dx = getDouble();
				dy = getDouble();
				cx3 = (int) Math.round(dx * xh + dy * xv + x0);
				cy3 = (int) Math.round(dx * yh + dy * yv + y0);
				if (mode == curveMode)
					GraphicsExtension.drawCurve(g, cx0, cy0, cx1, cy1, cx2, cy2,
																		cx3, cy3, lineSize);
				else
					GraphicsExtension.drawFilledCurve(g, cx0, cy0, cx1, cy1, cx2, cy2,
																		cx3, cy3, lineSize);
				link = getInt();
				continue;
			}
			lineSize = scaleLineSize(xh, yh, xv, yv);
			Color color = getColor();
			g.setColor(color);
			dx = getDouble();
			dy = getDouble();
			x1 = (int) Math.round(dx * xh + dy * xv + x0);
			y1 = (int) Math.round(dx * yh + dy * yv + y0);
			dx = getDouble();
			dy = getDouble();
			x2 = (int) Math.round(dx * xh + dy * xv + x0);
			y2 = (int) Math.round(dx * yh + dy * yv + y0);
			x = Math.min(x1, x2);
			y = Math.min(y1, y2);
			w = Math.abs(x2 - x1);
			h = Math.abs(y2 - y1);
			link = getInt();
			g.setColor(color);
			switch (mode)
			{
				case lineMode:
					if (lineSize == 1)
						g.drawLine(x1, y1, x2, y2);
					else
			    	    GraphicsExtension.drawThickLine(g, x1, y1, x2, y2, lineSize);
					break;
				case rectangleMode:
					if (lineSize == 1)
						g.drawRect(x, y, w, h);
					else
						GraphicsExtension.drawThickRectangle(g, x, y, w, h, lineSize);
					break;
				case ovalMode:
					if (lineSize == 1)
						g.drawOval(x, y, w, h);
					else
						GraphicsExtension.drawThickOval(g, x, y, w, h, lineSize);
					break;
				case filledRectangleMode:
					g.fillRect(x, y, w, h);
					break;
				case filledOvalMode:
					g.fillOval(x, y, w, h);
			}
		}
		next = end;
		g.dispose();
	}

	public void prepareToWrite()
	{
		addBufferByte(basicType);
		setElementNo(incElementCount());
		addBufferInt(getElementNo());
		addBufferInt(next);
		for (int i = 0; i < next; i++)
			addBufferByte(picture[i]);
	}

	public void clearElementNo()
	{
		setElementNo(0);
	}

	public void writePicture()
	{
		cleanup();
		clearElementNo();
		clearElementCount();
		pictureBuffer = new byte[pictureSize + safetySize];
		bufferNext = 0;
		prepareToWrite();
		String params = patterns.getParameter("UserName") + ";" +
						patterns.getParameter("Directory") + ";" +
						patterns.getParameter("WriteFile") + ";" +
						patterns.getParameter("Mail") + ";" +
						patterns.getParameter("Message") + "\n";
		Socket s = null;
		try
		{
			s = new Socket(patterns.getParameter("Server"), 80);
			DataInputStream in = new DataInputStream(s.getInputStream());
			DataOutputStream out = new DataOutputStream(s.getOutputStream());
			out.writeBytes("POST /cgi-bin/bprentice/WritePicture HTTP/1.0\n");
			out.writeBytes("Content-type: text/plain\n");
			out.writeBytes("Content-length: " + (params.length() + bufferNext) + "\n\n");
			out.writeBytes(params);
			for (int i = 0; i < bufferNext; i++)
				out.writeByte(pictureBuffer[i]);
			String line;
			do
				line = in.readLine();
			while (! line.equals(""));
			line = in.readLine();
			if (line.startsWith("Error:"))
				ioStatus = line;
			else
				ioStatus = "picture written (" + bufferNext + " bytes)";
		}
		catch (Exception e1)
		{
			ioStatus = "Error writing picture: " + e1;
		}
		finally
		{
			try
			{
				pictureBuffer = null;
				if (s != null)
					s.close();
			}
			catch (Exception e2)
			{
			}
		}
	}

	public static Picture assemblePicture()
	{
		Picture picture = null;
		byte pictureType = getBufferByte();
		int elementNo = getBufferInt();
		switch (pictureType)
		{
			case basicType:
				picture = new Picture(patterns);
				int n = getBufferInt();
				for (int i = 0; i < n; i++)
					picture.addByte(getBufferByte());
				break;
			case connectType:
				int leftElementNo = getBufferInt();
				int rightElementNo = getBufferInt();
				Picture leftPicture, rightPicture;
				if (leftElementNo == 0)
					leftPicture = assemblePicture();
				else
					leftPicture = (Picture) pictures.get(new Integer(leftElementNo));				
				if (rightElementNo == 0)
					rightPicture = assemblePicture();
				else
					rightPicture = (Picture) pictures.get(new Integer(rightElementNo));
				picture = new ConnectPicture(leftPicture, rightPicture);
				break;
			case scaleType:
				double x0 = getBufferDouble();
				double y0 = getBufferDouble();
				double xh = getBufferDouble();
				double yh = getBufferDouble();
				double xv = getBufferDouble();
				double yv = getBufferDouble();
				int scaleElementNo = getBufferInt();
				if (scaleElementNo == 0)
					picture = new ScalePicture(x0, y0, xh, yh, xv, yv, assemblePicture());
				else
					picture = new ScalePicture(x0, y0, xh, yh, xv, yv,
										(Picture) pictures.get(new Integer(scaleElementNo)));
				break;
			case rotateType:
				int degrees = getBufferInt();
				int rotateElementNo = getBufferInt();
				if (rotateElementNo == 0)
					picture = new RotatePicture(degrees, assemblePicture());
				else
					picture = new RotatePicture(degrees,
										(Picture) pictures.get(new Integer(rotateElementNo)));
				break;
			case reflectType:
				byte axis = getBufferByte();
				int reflectElementNo =	getBufferInt();
				if (reflectElementNo == 0)
					picture = new ReflectPicture(axis, assemblePicture());
				else
					picture = new ReflectPicture(axis,
										(Picture) pictures.get(new Integer(reflectElementNo)));
				break;
		}
		pictures.put(new Integer(elementNo), picture);
		return picture;
	}

	public static Picture readPicture()
	{
		Picture picture = null;
		pictureBuffer = new byte[pictureSize + safetySize];
		Socket s = null;
		String params = patterns.getParameter("UserName") + ";" +
						patterns.getParameter("Directory") + ";" +
						patterns.getParameter("ReadFile") + "\n";
		try
		{
			s = new Socket(patterns.getParameter("Server"), 80);
			DataInputStream in = new DataInputStream(s.getInputStream());
			DataOutputStream out = new DataOutputStream(s.getOutputStream());
			out.writeBytes("POST /cgi-bin/bprentice/ReadPicture HTTP/1.0\n");
			out.writeBytes("Content-type: text/plain\n");
			out.writeBytes("Content-length: " +
										(params.length() + filePosition.length()) + "\n\n");
			out.writeBytes(params);
			out.writeBytes(filePosition);
			String line;
			do
			{
				line = in.readLine();
				if (line.startsWith("Content-length:"))
					bufferNext =
							(new Integer(line.substring(16, line.length())).intValue()) - 8;
			}
			while (! line.equals(""));
			StringBuffer sb = new StringBuffer(8);
			for (int i = 0; i < 8; i++)
					sb.append((char) in.readByte());
			String m = new String(sb);
			if (m.startsWith("Error:"))
				ioStatus = m + in.readLine();				
			else
			{
				filePosition = m;
				for (int i = 0; i < bufferNext; i++)
					pictureBuffer[i] = (byte) in.readByte();
				ioStatus = "picture read (" + bufferNext + " bytes)";
				pictures = new Hashtable();
				bufferNext = 0;
				picture = assemblePicture();
				pictures = null;
				pictureBuffer = null;				
			}
		}
		catch (Exception e1)
		{
			ioStatus = "Error reading picture: " + e1;
		}
		finally
		{
			try
			{
				if (s != null)
					s.close();
			}
			catch (Exception e2)
			{
			}
		}
		return picture;
	}

	// An Implementation of the Henderson Picture Language
	// Reference
	// Lisp: A Language for Stratified Design
	// By Harold Abelson and Gerald Jay Sussman
	// Byte February 1988

	public static Picture together(Picture picture1, Picture picture2)
	{
		return new ConnectPicture(picture1, picture2);
	}

	public static Picture beside(Picture picture1, Picture picture2, double ratio)
	{
		return new ConnectPicture(
			new ScalePicture(0.0, 0.0, ratio, ratio, 1.0, 1.0, picture1),
			new ScalePicture(ratio / (1.0 - ratio), 0.0, 1.0 - ratio, 1.0 - ratio,
							 1.0, 1.0, picture2));
	}

	public static Picture above(Picture picture1, Picture picture2, double ratio)
	{
		return new ConnectPicture(
			new ScalePicture(0.0, 0.0, 1.0, 1.0, ratio, ratio, picture1),
			new ScalePicture(0.0, ratio / (1.0 - ratio), 1.0, 1.0,
							 1.0 - ratio, 1.0 - ratio, picture2));
	}

	public static Picture diagonal(Picture picture1, Picture picture2, double ratio)
	{
		return new ConnectPicture(
			moveLeft(moveUp(picture1, ratio), ratio),
			moveRight(moveDown(picture2, ratio), ratio));
	}

	public static Picture moveLeft(Picture picture, double ratio)
	{
		return new ScalePicture(0.0, 0.0, ratio, ratio, 1.0, 1.0, picture);
	}

	public static Picture moveRight(Picture picture, double ratio)
	{
		return new ScalePicture(ratio / (1.0 - ratio), 0.0, 1.0 - ratio, 1.0 - ratio,
								1.0, 1.0, picture);
	}

	public static Picture moveUp(Picture picture, double ratio)
	{
		return new ScalePicture(0.0, 0.0, 1.0, 1.0, ratio, ratio, picture);
	}

	public static Picture moveDown(Picture picture, double ratio)
	{
		return new ScalePicture(0.0, ratio / (1.0 - ratio), 1.0, 1.0,
								1.0 - ratio, 1.0 - ratio, picture);
	}

	public static Picture moveCenter(Picture picture, double ratio)
	{
		return moveUp(moveDown(moveLeft(moveRight(picture, ratio), 1.0 / (1.0 + ratio)),
																ratio), 1.0 / (1.0 + ratio));
	}

	public static Picture rotate90(Picture picture)
	{
		return new RotatePicture(90, picture);
	}

	public static Picture rotate180(Picture picture)
	{
		return new RotatePicture(180, picture);
	}

	public static Picture rotate270(Picture picture)
	{
		return new RotatePicture(270, picture);
	}

	public static Picture reflectXAxis(Picture picture)
	{
		return new ReflectPicture((byte) 0, picture);
	}

	public static Picture reflectYAxis(Picture picture)
	{
		return new ReflectPicture((byte) 1, picture);
	}

	public static Picture pushLeft(Picture picture, int n, double ratio)
	{
		Picture newPicture = picture;
		double a = ratio;
		double b = 1.0 - ratio;
		double r = a / b;
		while (n-- > 0)
		{
			newPicture = beside(picture, newPicture, ratio);
			b = a + b;
			a = a * r;
			ratio = a / (a + b);
		}
		return newPicture;
	}

	public static Picture pushUp(Picture picture, int n, double ratio)
	{
		Picture newPicture = picture;
		double a = ratio;
		double b = 1.0 - ratio;
		double r = a / b;
		while (n-- > 0)
		{
			newPicture = above(picture, newPicture, ratio);
			b = a + b;
			a = a * r;
			ratio = a / (a + b);
		}
		return newPicture;
	}

	public static Picture pushDiagonal(Picture picture, int n, double ratio)
	{
		Picture newPicture = picture;
		double a = ratio;
		double b = 1.0 - ratio;
		double r = a / b;
		while (n-- > 0)
		{
			newPicture = diagonal(picture, newPicture, ratio);
			b = a + b;
			a = a * r;
			ratio = a / (a + b);
		}
		return newPicture;
	}

	public static Picture pushCorner(Picture picture, int n, double ratio)
	{
		return pushUp(pushLeft(picture, n, ratio), n, ratio);
	}

	public static Picture triangle(Picture picture1, Picture picture2)
	{
		return above(picture1, beside(picture2, picture2, 0.5), 0.5);
	}

	public static Picture pushTriangle(Picture picture, int n)
	{
		if (n > 0)
			return triangle(picture, pushTriangle(picture, n - 1));
		else
			return picture;
	}

	public static Picture fourPictures(Picture picture1, int rotation1,
									   Picture picture2, int rotation2,
									   Picture picture3, int rotation3,
									   Picture picture4, int rotation4)
	{
		return beside(above(new RotatePicture(rotation1, picture1),
							new RotatePicture(rotation2, picture2), 0.5),
					  above(new RotatePicture(rotation3, picture3),
							new RotatePicture(rotation4, picture4), 0.5), 0.5);
	}

	public static Picture fourSame(Picture picture,
								   int rotation1, int rotation2, int rotation3, int rotation4)
	{
		return fourPictures(picture, rotation1, picture, rotation2,
							picture, rotation3, picture, rotation4);
	}

	public static Picture edgeLimit(Picture picture, int n, double ratio)
	{
		return fourSame(pushCorner(picture, n, ratio), 0, 270, 90, 180);
	}

	public static Picture centerLimit(Picture picture, int n, double ratio)
	{
		return fourSame(pushCorner(picture, n, ratio), 180, 90, 270, 0);
	}
}

class ConnectPicture extends Picture
{
	private Picture leftPointer, rightPointer;

	ConnectPicture(Picture leftPointer, Picture rightPointer)
	{
		this.leftPointer = leftPointer;
		this.rightPointer = rightPointer;
	}

	public void prepareToWrite()
	{
		addBufferByte(connectType);
		setElementNo(incElementCount());
		addBufferInt(getElementNo());
		int leftElementNo = leftPointer.getElementNo();
		addBufferInt(leftElementNo);
		int rightElementNo = rightPointer.getElementNo();
		addBufferInt(rightElementNo);
		if (leftElementNo == 0)
			leftPointer.prepareToWrite();		
		if (rightElementNo == 0)
			rightPointer.prepareToWrite();		
	}

	public void clearElementNo()
	{
		setElementNo(0);
		leftPointer.clearElementNo();
		rightPointer.clearElementNo();
	}

	public void drawPicture(Canvas c, double x0, double y0, double xh, double yh,
																		double xv, double yv)
	{
		leftPointer.drawPicture(c, x0, y0, xh, yh, xv, yv);
		rightPointer.drawPicture(c, x0, y0, xh, yh, xv, yv);
	}
}

class ScalePicture extends Picture
{
	private double x0, y0, xh, yh, xv, yv;
	private Picture scalePointer;

	ScalePicture(double x0, double y0, double xh, double yh, double xv, double yv,
				 Picture scalePointer)
	{
		this.x0 = x0;
		this.y0 = y0;
		this.xh = xh;
		this.yh = yh;
		this.xv = xv;
		this.yv = yv;
		this.scalePointer = scalePointer;
	}

	public void prepareToWrite()
	{
		addBufferByte(scaleType);
		setElementNo(incElementCount());
		addBufferInt(getElementNo());
		addBufferDouble(x0);
		addBufferDouble(y0);
		addBufferDouble(xh);
		addBufferDouble(yh);
		addBufferDouble(xv);
		addBufferDouble(yv);
		int elementNo = scalePointer.getElementNo();
		addBufferInt(elementNo);
		if (elementNo == 0)
			scalePointer.prepareToWrite();		
	}

	public void clearElementNo()
	{
		setElementNo(0);
		scalePointer.clearElementNo();
	}

	public void drawPicture(Canvas c, double x0, double y0, double xh, double yh,
																		double xv, double yv)
	{
		scalePointer.drawPicture(c, x0 + this.x0 * this.xh * xh + this.y0 * this.xv * xv,
									y0 + this.x0 * this.yh * yh + this.y0 * this.yv * yv,
									this.xh * xh, this.yh * yh, this.xv * xv, this.yv * yv);
	}
}

class RotatePicture extends Picture
{
	private int degrees;
	private Picture rotatePointer;

	RotatePicture(int degrees, Picture rotatePointer)
	{
		this.degrees = degrees;
		this.rotatePointer = rotatePointer;
	}

	public void prepareToWrite()
	{
		addBufferByte(rotateType);
		setElementNo(incElementCount());
		addBufferInt(getElementNo());
		addBufferInt(degrees);
		int elementNo = rotatePointer.getElementNo();
		addBufferInt(elementNo);
		if (elementNo == 0)
			rotatePointer.prepareToWrite();		
	}

	public void clearElementNo()
	{
		setElementNo(0);
		rotatePointer.clearElementNo();
	}

	public void drawPicture(Canvas c, double x0, double y0, double xh, double yh,
																		double xv, double yv)
	{
		switch (degrees)
		{
			case 0:
				rotatePointer.drawPicture(c, x0, y0, xh, yh, xv, yv);
				break;
			case 90:
				rotatePointer.drawPicture(c, x0 + xh, y0 + yh, xv, yv, -xh, -yh);
				break;
			case 180:
				rotatePointer.drawPicture(c, x0 + xh + xv, y0 + yh + yv, -xh, -yh, -xv, -yv);
				break;
			case 270:
				rotatePointer.drawPicture(c, x0 + xv, y0 + yv, -xv, -yv, xh, yh);
				break;
		}
	}
}

class ReflectPicture extends Picture
{
	private byte axis;
	private Picture reflectPointer;

	ReflectPicture(byte axis, Picture reflectPointer)
	{
		this.axis = axis;
		this.reflectPointer = reflectPointer;
	}

	public void prepareToWrite()
	{
		addBufferByte(reflectType);
		setElementNo(incElementCount());
		addBufferInt(getElementNo());
		addBufferByte(axis);
		int elementNo = reflectPointer.getElementNo();
		addBufferInt(elementNo);
		if (elementNo == 0)
			reflectPointer.prepareToWrite();		
	}

	public void clearElementNo()
	{
		setElementNo(0);
		reflectPointer.clearElementNo();
	}

	public void drawPicture(Canvas c, double x0, double y0, double xh, double yh,
																		double xv, double yv)
	{
		if (axis == 0)
			reflectPointer.drawPicture(c, x0 + xv, y0 + yv, xh, yh, -xv, -yv);
		else
			reflectPointer.drawPicture(c, x0 + xh, y0 + yh, -xh, -yh, xv, yv);
	}
}

