/**
 * Draw2D.java
 * Copyright (c) 1999 Yoon Kyung Koo. All rights reserved.
 *
 * First release date 1999/04/12
 *
 * @version 1.0 1999/04/12
 * @author <A HREF="mailto:yoonforh@moon.daewoo.co.kr">Yoon Kyung Koo</A>
 * @since JDK 1.2
 */

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;

/**
 * Draw2D application
 */
class Draw2D extends JFrame {
	// member fields
	ExitController exiter = new ExitController();
	DrawCanvas canvas = new DrawCanvas();

	// constructor
	Draw2D(String title) {
		super(title); // call Frame's constructor
		// create menu bar
		JMenuBar menuBar = new JMenuBar();
		setJMenuBar(menuBar);

		// shape menu
		JMenu shapeMenu = menuBar.add(new JMenu("Shape"));
		shapeMenu.setMnemonic('S');
		ButtonGroup shapeBG = new ButtonGroup();
		JRadioButtonMenuItem lineRB, rectRB, roundrectRB, ellipseRB, arcRB;
		lineRB = (JRadioButtonMenuItem) shapeMenu.add(new JRadioButtonMenuItem("Line", true));
		lineRB.addItemListener(canvas.controller);
		shapeBG.add(lineRB);
		rectRB = (JRadioButtonMenuItem) shapeMenu.add(new JRadioButtonMenuItem("Rect", false));
		rectRB.addItemListener(canvas.controller);
		shapeBG.add(rectRB);
		roundrectRB = (JRadioButtonMenuItem) shapeMenu.add(new JRadioButtonMenuItem("RoundRect", false));
		roundrectRB.addItemListener(canvas.controller);
		shapeBG.add(roundrectRB);
		ellipseRB = (JRadioButtonMenuItem) shapeMenu.add(new JRadioButtonMenuItem("Ellipse", false));
		ellipseRB.addItemListener(canvas.controller);
		shapeBG.add(ellipseRB);
		arcRB = (JRadioButtonMenuItem) shapeMenu.add(new JRadioButtonMenuItem("Arc", false));
		arcRB.addItemListener(canvas.controller);
		shapeBG.add(arcRB);
		shapeMenu.addSeparator();
		shapeMenu.add(new JMenuItem("Exit", 'X')).addActionListener(exiter);
		
		// stroke menu
		JMenu strokeMenu = menuBar.add(new JMenu("Stroke"));
		strokeMenu.setMnemonic('T');
		ButtonGroup strokeBG = new ButtonGroup();
		JRadioButtonMenuItem normalRB, thinRB, thickRB;
		normalRB = (JRadioButtonMenuItem) strokeMenu.add(new JRadioButtonMenuItem("Normal", true));
		normalRB.addItemListener(canvas.controller);
		strokeBG.add(normalRB);
		thinRB = (JRadioButtonMenuItem) strokeMenu.add(new JRadioButtonMenuItem("Thin", false));
		thinRB.addItemListener(canvas.controller);
		strokeBG.add(thinRB);
		thickRB = (JRadioButtonMenuItem) strokeMenu.add(new JRadioButtonMenuItem("Thick", false));
		thickRB.addItemListener(canvas.controller);
		strokeBG.add(thickRB);
		strokeMenu.addSeparator();
		strokeMenu.add(new JCheckBoxMenuItem("Dashed", false)).addItemListener(canvas.controller);

		// fill menu
		JMenu fillMenu = menuBar.add(new JMenu("Fill"));
		fillMenu.setMnemonic('F');
		fillMenu.add(new JCheckBoxMenuItem("Use Fill", true)).addItemListener(canvas.controller);
		fillMenu.addSeparator();
		ButtonGroup fillBG = new ButtonGroup();
		JRadioButtonMenuItem solidRB, gradientRB, patternRB;
		solidRB = (JRadioButtonMenuItem) fillMenu.add(new JRadioButtonMenuItem("Solid", true));
		solidRB.addItemListener(canvas.controller);
		fillBG.add(solidRB);
		gradientRB = (JRadioButtonMenuItem) fillMenu.add(new JRadioButtonMenuItem("Gradient", false));
		gradientRB.addItemListener(canvas.controller);
		fillBG.add(gradientRB);
		patternRB = (JRadioButtonMenuItem) fillMenu.add(new JRadioButtonMenuItem("Pattern", false));
		patternRB.addItemListener(canvas.controller);
		fillBG.add(patternRB);

		// color menu
		JMenu colorMenu = menuBar.add(new JMenu("Color"));
		colorMenu.setMnemonic('C');
		colorMenu.add(new JMenuItem("Foreground", 'F')).addActionListener(canvas.controller);
		colorMenu.add(new JMenuItem("Background(Clear)", 'B')).addActionListener(canvas.controller);

		// Frame's default Layout Manager is BorderLayout

		canvas.setBackground(Color.white);
		getContentPane().add(canvas, BorderLayout.CENTER);

		// add window closing listener
		addWindowListener(exiter);

		setSize(600, 500);
		pack();
		setVisible(true);
	}

	// preferred size when packed
	public Dimension getPreferredSize() {
		return new Dimension(500, 400);
	}

	// main method
	public static void main(String args[]) {
		new Draw2D("Draw2D Program");
	}

	class ExitController
			extends WindowAdapter implements ActionListener {
		public void windowClosing(WindowEvent we) {
			Draw2D.this.dispose();
			System.exit(0);
		}
		public void actionPerformed(ActionEvent ae) {
			Draw2D.this.dispose();
			System.exit(0);
		}
	} // end of Draw2D.ExitController
}

/** 
 * DrawCanvas on which shapes are drown
 */
class DrawCanvas extends JPanel {
	// shapes
	final static int SHAPE_LINE = 0;
	final static int SHAPE_RECT = 1;
	final static int SHAPE_ROUND_RECT = 2;
	final static int SHAPE_ELLIPSE = 3;
	final static int SHAPE_ARC = 4;
	int shape=SHAPE_LINE;
	// strokes
	final static int STROKE_NORMAL = 0;
	final static int STROKE_THIN = 1;
	final static int STROKE_THICK = 2;
	int stroke = STROKE_NORMAL;
	boolean dashed = false;
	// fills
	final static int FILL_SOLID = 0;
	final static int FILL_GRADIENT = 1;
	final static int FILL_PATTERN = 2;
	int fill = FILL_SOLID;
	boolean filled = true;

	Point startPoint=new Point(), 
		savePoint=new Point(), endPoint=new Point();
	Color fgColor=Color.black;
	Color bgColor=Color.white;
	UIController controller = new UIController();

	// buffered image
	BufferedImage offImage = null;
	Graphics2D offG2 = null;

	// constructor
	DrawCanvas() {
		// register mouse controller
		MouseController mouseController = new MouseController();
		addMouseMotionListener(mouseController);
		addMouseListener(mouseController);
	}

	public void paint(Graphics g) {
		int width = getSize().width;
		int height = getSize().height;
		if (width <=0 || height <= 0)
			return;
		if (offImage == null || offImage.getWidth() != width
			|| offImage.getHeight() != height)
			createBufferImage(width, height);
		if (offImage != null)
			((Graphics2D) g).drawImage(offImage, 0, 0, this);
	}

	void createBufferImage(int width, int height) {
		offImage = (BufferedImage) createImage(width, height);
		if (offImage != null) {
			offG2 = offImage.createGraphics();
			offG2.setBackground(getBackground());
			offG2.clearRect(0, 0, width, height);
		}
	}

	// draw method
	void draw(Graphics2D g, Point startPoint, Point endPoint) {
		// specify stroke
		BasicStroke newStroke = null;
		float dash[]={10.0f};
		switch (stroke) {
		case STROKE_THIN :
			if (dashed)
				newStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
					BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
			else
				newStroke = new BasicStroke(1.0f);
			break;
		case STROKE_THICK :
			if (dashed)
				newStroke = new BasicStroke(5.0f, BasicStroke.CAP_BUTT,
					BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
			else
				newStroke = new BasicStroke(5.0f);
			break;
		case STROKE_NORMAL :
		default :
			if (dashed)
				newStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT,
					BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
			else
				newStroke = new BasicStroke(3.0f);
			break;
		}
		g.setStroke(newStroke);

		// specify current color
		g.setColor(fgColor);

		// specify fill
		if (filled) {
			switch (fill) {
			case FILL_GRADIENT :
				GradientPaint gp = new GradientPaint(startPoint, fgColor,
					endPoint, bgColor);
				g.setPaint(gp);
				break;
			case FILL_PATTERN :
				BufferedImage bi = new BufferedImage(5, 5, 
					BufferedImage.TYPE_INT_RGB);
				Graphics2D big = bi.createGraphics();
				try {
					big.setColor(fgColor);
					big.fillRect(0, 0, 5, 5);
					big.setColor(bgColor);
					big.fillOval(0, 0, 5, 5);
				} finally {
					big.dispose();
				}
				Rectangle r = new Rectangle(0,0,5,5);
				TexturePaint texture = new
					TexturePaint(bi, r);
				g.setPaint(texture);
				break;
			case FILL_SOLID :
			default :
				break;
			}
		}

		Shape newShape = null;

		switch (shape) {
		case SHAPE_RECT :
			newShape = new Rectangle2D.Float(
					(float) startPoint.x, (float) startPoint.y,
					(float) endPoint.x-(float) startPoint.x,
					(float) endPoint.y-(float) startPoint.y);
			break;
		case SHAPE_ROUND_RECT :
			newShape = new RoundRectangle2D.Float(
					(float) startPoint.x, (float) startPoint.y,
					(float) endPoint.x-(float) startPoint.x,
					(float) endPoint.y-(float) startPoint.y,
					(endPoint.x-startPoint.x)/10.0f,
					(endPoint.y-startPoint.y)/10.0f );
			break;
		case SHAPE_ELLIPSE :
			newShape = new Ellipse2D.Float(
					(float) startPoint.x, (float) startPoint.y,
					(float) endPoint.x-(float) startPoint.x,
					(float) endPoint.y-(float) startPoint.y);
			break;
		case SHAPE_ARC :
			newShape = new Arc2D.Float(
					(float) startPoint.x, (float) startPoint.y,
					(float) endPoint.x-(float) startPoint.x,
					(float) endPoint.y-(float) startPoint.y,
					0.0f, 90.0f, Arc2D.PIE);
			break;
		case SHAPE_LINE :
		default :
			newShape = new Line2D.Float(
				(float) startPoint.x, (float) startPoint.y,
				(float) endPoint.x, (float) endPoint.y);
			break;
		} // end of switch
		
		if (filled) 
			g.fill(newShape);
		// always draw outline
		g.draw(newShape);
	}

	class UIController implements ItemListener, ActionListener {
		// event handler
		public void itemStateChanged(ItemEvent ie) {
			Object source=ie.getSource();

			// handle checkbox menu items
			if (source instanceof JCheckBoxMenuItem)  {
				JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) source;
				String label = checkbox.getText();
				if (label.equals("Use Fill"))
					DrawCanvas.this.filled=checkbox.isSelected();
				else if (label.equals("Dashed"))
					DrawCanvas.this.dashed=checkbox.isSelected();
			}
			// handle radio button menu items
			else if (source instanceof JRadioButtonMenuItem) {
				JRadioButtonMenuItem item=(JRadioButtonMenuItem) source;
				if (item.isSelected()) {
					String label = item.getText();
					// handle shape menu
					if (label.equals("Line")) 
						DrawCanvas.this.shape=DrawCanvas.SHAPE_LINE;
					else if (label.equals("Rect")) 
						DrawCanvas.this.shape=DrawCanvas.SHAPE_RECT;
					else if (label.equals("RoundRect")) 
						DrawCanvas.this.shape=DrawCanvas.SHAPE_ROUND_RECT;
					else if (label.equals("Ellipse")) 
						DrawCanvas.this.shape=DrawCanvas.SHAPE_ELLIPSE;
					else if (label.equals("Arc")) 
						DrawCanvas.this.shape=DrawCanvas.SHAPE_ARC;

					// handle stroke menu
					else if (label.equals("Normal")) 
						DrawCanvas.this.stroke=DrawCanvas.STROKE_NORMAL;
					else if (label.equals("Thin")) 
						DrawCanvas.this.stroke=DrawCanvas.STROKE_THIN;
					else if (label.equals("Thick")) 
						DrawCanvas.this.stroke=DrawCanvas.STROKE_THICK;

					// handle fill menu
					else if (label.equals("Solid")) 
						DrawCanvas.this.fill=DrawCanvas.FILL_SOLID;
					else if (label.equals("Gradient")) 
						DrawCanvas.this.fill=DrawCanvas.FILL_GRADIENT;
					else if (label.equals("Pattern")) 
						DrawCanvas.this.fill=DrawCanvas.FILL_PATTERN;
				}
			}

			// always call repaint when selected items changed
			repaint();
		}

		public void actionPerformed(ActionEvent ae) {
			if (ae.getSource() instanceof JMenuItem) {
				JMenuItem item=(JMenuItem) ae.getSource();
				String label = item.getText();
				if (label.equals("Foreground")) {
					fgColor= JColorChooser.showDialog(
						DrawCanvas.this, "Foreground", fgColor);
				}
				else if (label.equals("Background(Clear)")) {
					bgColor = JColorChooser.showDialog(
						DrawCanvas.this, "Background", bgColor);
					setBackground(bgColor);
					offG2.setBackground(bgColor);
					// clear all
					offG2.clearRect(0, 0, offImage.getWidth(), offImage.getHeight());
					repaint();
				}
			}
		}
	} // end of DrawCanvas.UIController class

	class MouseController extends MouseInputAdapter {
		public void mouseDragged(MouseEvent me) {
			Graphics2D g=(Graphics2D) getGraphics();
			try {
				g.setXORMode( // fgColor);
					new Color(255-fgColor.getRed(),
						255-fgColor.getGreen(),
						255-fgColor.getBlue()));
				
				draw(g, startPoint, savePoint);
				endPoint = me.getPoint();
				draw(g, startPoint, endPoint);
				savePoint=endPoint;
			}
			finally {
				g.dispose();
			}
		}
		public void mousePressed(MouseEvent me) {
			startPoint = me.getPoint();
			savePoint = startPoint;
		}
		public void mouseReleased(MouseEvent me) {
			endPoint = me.getPoint();
			Graphics2D g=(Graphics2D) getGraphics();
			try {
				draw(g, startPoint, endPoint);
				if (offG2 != null)
					draw(offG2, startPoint, endPoint);
			}
			finally {
				g.dispose();
			}
		}
	} // end of DrawCanvas.MouseController class
} // end of DrawCanvas class

