package mb;
import javax.swing.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
import java.io.*;
import java.util.Vector;

public class MandelbrotCanvas extends JPanel {
	
	private BufferedImage OSI;
	private int imageWidth, imageHeight;
	private int hOffset, vOffset;
	private MBScrollPane scrollPane;
	
	private JFileChooser fileDialog;

	private MandComp comp;
	private int[] palette = new int[260];  // only 253 used; extra space for rotating palette  -- now, only 252 are used!
	private Color[] mandelColors = { Color.black, Color.white, Color.gray, Color.red,
				Color.green, Color.blue, Color.cyan, Color.magenta, Color.yellow };
	private boolean stretchPalette;
	
	private double xmin, xmax, ymin, ymax;
	private Vector limitsHistory = new Vector();
	private int currentLimitsInHistory;
	
	private int maxIterations = 250;
	private int renderStyle;  // 1 = ColumnByColumn; 2 = Progressive
	
	private Timer animator;
	private int selectedPalette = 1, selectedRenderType = 1, 
	        selectedMandelbrotColor = 1, selectedIterations = 1, paletteOffset = 0;

	private boolean dragging;
	private boolean zoomIn;
	private boolean recenter;
	private int startX, startY;
	private Rectangle dragRect;
	private Color hiliteColor = Color.white;
	
	public MandelbrotCanvas() {
		this(800,600);
	}
	
	public MandelbrotCanvas(int width, int height) {
		imageWidth = width;
		imageHeight = height;
		setBackground(Color.lightGray);  
		setPreferredSize(new Dimension(imageWidth,imageHeight));
		setOpaque(true);
		for (int i = 0; i < 250; i++)
			palette[i] = Color.getHSBColor(((float)i)/250,1,1).getRGB();
		palette[250] = 0;  // Mandelbrot color
		palette[251] = 0xFFFFFF;  // formerly drag rect drawing color  -- no longer referenced anywhere, never changes
		palette[252] = Color.lightGray.getRGB();   // background color -- never changes
		xmin = -2.333;
		xmax = 1;
		ymin = -1.25;
		ymax = 1.25;
		checkAspect();
		limitsHistory.addElement(new double[] {xmin,xmax,ymin,ymax});
		comp = new ColumnsMandComp();
		renderStyle = 1;
		addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent evt) {
				dragging = false;
				dragRect = null;
				zoomIn = !(evt.isMetaDown() || evt.isControlDown());
				recenter = !evt.isShiftDown();
				startX = evt.getX();
				startY = evt.getY();
			}
			public void mouseReleased(MouseEvent evt) {
				if (dragging) {
					dragging = false;
					if (dragRect != null) {
						repaint(dragRect.x - 2, dragRect.y - 2, dragRect.width + 4, dragRect.height + 4);
						doZoomInOnRect(dragRect);
						dragRect = null;
					}
				}
				else if (evt.getX()+hOffset < imageWidth && evt.getY() + vOffset < imageHeight){
					if (zoomIn)
						doZoom(evt.getX()+hOffset, evt.getY()+vOffset, 0.5, recenter);
					else
						doZoom(evt.getX()+hOffset, evt.getY()+vOffset, 2.0, recenter);
				}
			}
		});
		addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseDragged(MouseEvent evt) {
				int x = evt.getX();
				int y = evt.getY();
				if (x < 0)
					x = 0;
				if (x > getWidth()-1)
					x = getWidth()-1;
				if (x > imageWidth-hOffset)
					x = imageWidth-hOffset;
				if (y < 0)
					y = 0;
				if (y > getHeight()-1)
					y = getHeight()-1;
				if (y > imageHeight - vOffset)
					y = imageHeight - vOffset;
				if (!dragging && Math.abs(startX - x) < 4 && (Math.abs(startY - y) < 4))
					return;
				dragging = true;
				if (Math.abs(startX -x) < 3 || (Math.abs(startY - y) < 3)) {
					if (dragRect != null) {
						repaint(dragRect.x - 2, dragRect.y - 2, dragRect.width + 4, dragRect.height + 4);
						dragRect = null;
					}
					return;
				}
				if (dragRect == null)
					dragRect = new Rectangle();
				repaint(dragRect.x - 2, dragRect.y - 2, dragRect.width + 4, dragRect.height + 4);
				int w = Math.abs(x - startX);
				int h = Math.abs(y - startY);
				double aspect = (double)imageWidth / imageHeight;
				double rectAspect = (double)w / h;
				if (aspect > rectAspect)
					w = (int)(w*aspect/rectAspect+0.499);
				else if (aspect < rectAspect)
					h = (int)(h*rectAspect/aspect+0.499);
				dragRect.setBounds(startX,startY,0,0);
				dragRect.add(x>startX? startX+w : startX-w, y>startY? startY+h : startY-h);
				repaint(dragRect.x - 2, dragRect.y - 2, dragRect.width + 4, dragRect.height + 4);
			}
		});
	}
	
	public JPanel getScrollPane() {
		if (scrollPane == null)
			scrollPane = new MBScrollPane();
		return scrollPane;
	}
	
	private void forceStopDraw() {
		if (comp.isRunning()) {
			comp.cancel();
			try {
				for (int i = 0; i < 20 && comp.isRunning(); i++)
				   Thread.sleep(100);
			}
			catch (InterruptedException e) {
			}
		}
	}
	
	public void setCanvasSize(int width, int height) {
		if (width == imageWidth && height == imageHeight)
			return;
		forceStopDraw();
		OSI = null;
		imageHeight = height;
		imageWidth = width;
		checkAspect();
		setPreferredSize(new Dimension(width,height));
		makeImage();
		if (scrollPane != null)
			scrollPane.check(true);
		doDraw();
	}
	
	private void checkAspect() {
		double width = xmax - xmin;
		double height = ymax - ymin;
		double aspect = width/height;
		double windowAspect = (double)imageWidth/(double)imageHeight;
		if (aspect < windowAspect) {
			double newWidth = width*windowAspect/aspect;
			double center = (xmax + xmin)/2;
			xmax = center + newWidth/2;
			xmin = center - newWidth/2;
		}
		else if (aspect > windowAspect) {
			double newHeight = height*aspect/windowAspect;
			double center = (ymax+ymin)/2;
			ymax = center + newHeight/2;
			ymin = center - newHeight/2;
		}
	}
	
	private void setPalette(int paletteNum) {
		createPalette(paletteNum);
		checkHiliteColor();
		installNewPalette();
	}
	
	private void checkHiliteColor() {
		if (selectedPalette == 9 || selectedPalette == 10 || selectedPalette == 2)
			hiliteColor = Color.RED;
		else if (selectedMandelbrotColor == 2)
			hiliteColor= Color.lightGray;
		else
			hiliteColor = Color.WHITE;
	}
	
	private void createPalette(int paletteNum) {
		paletteOffset = 0;
		for (int i = 0; i < 250; i++) {
			Color c;
			float x = (float)i/250;
			switch (paletteNum) {
			   case 2:
			   	c = new Color(i,i,i);
			   	break;
			   case 3:
			   	c = new Color(i,0,0);
			   	break;
			   case 4:
			   	c = new Color(0,i,0);
			   	break;
			   case 5:
			   	c = new Color(0,0,i);
			   	break;
			   case 6:
			   	c = new Color(i,i,0);
			   	break;
			   case 7:
			   	c = new Color(i,250-i,0);
			   	break;
			   case 8:
			   	c = new Color(250-i,i,i);
			   	break;
			   case 9:
			   	c = (i % 2 == 0)? Color.WHITE : Color.BLACK;
			   	break;
			   case 10:
			   	c = new Color(250-i,250-i,250-i);
			   	break;
			   default:
			   	c = Color.getHSBColor(x,1,1);
			   	break;
			}
			palette[i] = c.getRGB();
		}		
	}
	
	private void installNewPalette() {
		if (OSI == null)
			return;
		WritableRaster r = OSI.getRaster();
		IndexColorModel cm = new IndexColorModel(8,253,palette,0,false,-1,DataBuffer.TYPE_BYTE);
		OSI = new BufferedImage(cm,r,false,null);
		repaint();
	}
	
	public void setMandelbrotColor(int index) {
		if (index >= 0 && index < mandelColors.length) {
			selectedMandelbrotColor = index+1;
			palette[250] = mandelColors[index].getRGB();
			checkHiliteColor();
			installNewPalette();
		}
	}
	public int getRenderStyle() {
		return renderStyle;
	}
	public void setRenderStyle(int renderStyle) {
		if (comp.isRunning() || renderStyle == this.renderStyle 
				|| renderStyle < 1 || renderStyle > 3)
			return;
		this.renderStyle = renderStyle;
		if (renderStyle == 1)
			comp = new ColumnsMandComp();
		else if (renderStyle == 2)
			comp = new ProgressiveBlocksMandComp();
		else if (renderStyle == 3)
			comp = new ProgressiveLinesMandComp();
	}
	public BufferedImage getImage() {
		return OSI;
	}
	public double getXmax() {
		return xmax;
	}
	public double getXmin() {
		return xmin;
	}
	public double getYmax() {
		return ymax;
	}
	public double getYmin() {
		return ymin;
	}
	public void setLimits(double xmin, double xmax, double ymin, double ymax) {
		this.xmin = xmin;
		this.xmax = xmax;
		this.ymin = ymin;
		this.ymax = ymax;
		checkAspect();
		limitsHistory.setSize(currentLimitsInHistory+1);
		limitsHistory.addElement(new double[] {xmin,xmax,ymin,ymax});
		currentLimitsInHistory++;
		if (currentLimitsInHistory > 0)
			controlMenu.enableItem(7);
		else
			controlMenu.disableItem(7);
		controlMenu.disableItem(8);
		doDraw();
	}
	public void restoreDefaultLimits() {
		setLimits(-2.3333,1,-1.25,1.25);
	}

	public int getSelectedMandelbrotColor() {
		return selectedMandelbrotColor;
	}
	public int getSelectedPalette() {
		return selectedPalette;
	}
	public boolean getStretchPalette() {
		return stretchPalette;
	}
	public int getSelectedIterations() {
		return selectedIterations;
	}
	public int getPaletteOffset() {
		return paletteOffset;
	}

	synchronized public void setPaletteOffset(int offset) {
		if (offset == paletteOffset || offset < 0 || offset >= 250)
			return;
		stopAnimation();
		int[] oldPalette = palette;
		palette = (int[])oldPalette.clone();
		int move = offset - paletteOffset;
		for (int i = 0; i < 250; i++) {
			int currentPos = i - move;
			if (currentPos >= 250)
				currentPos = currentPos - 250;
			else if (currentPos < 0)
				currentPos = 250 + currentPos;
			palette[i] = oldPalette[currentPos];
		}
		paletteOffset = offset;
	}
	
	public void install(Example example) {
		stopDrawing();
		
		stretchPalette = example.stretchPalette;
		paletteMenu.selectRadioItem(stretchPalette? 13: 12);

		int newMBColor = example.mandelbrotColorIndex;
		if (!(example.mandelbrotColorIndex > 0 && example.mandelbrotColorIndex <= mandelColors.length))
			newMBColor = 1;		
		mandelbrotColorMenu.selectRadioItem(newMBColor);
		selectedMandelbrotColor = newMBColor;
		palette[250] = mandelColors[newMBColor-1].getRGB();

		int newPaletteIndex = example.paletteIndex;
		if (!(example.paletteIndex > 0 && example.paletteIndex <= 10))
			newPaletteIndex = 1;
		paletteMenu.selectRadioItem(newPaletteIndex);
		createPalette(newPaletteIndex);
		selectedPalette = newPaletteIndex;
		
		int newIterations = example.iterationsIndex;
		if (!(example.iterationsIndex > 0 && example.iterationsIndex <= 14))
			newIterations = 3;
		iterationsMenu.doCommand(newIterations);
		iterationsMenu.selectRadioItem(newIterations);

		checkHiliteColor();
		setPaletteOffset(example.paletteOffset);
		installNewPalette();
		currentLimitsInHistory = -1;
		setLimits(example.xmin,example.xmax,example.ymin,example.ymax);
	}

	
	
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		if (OSI == null) {
			makeImage();
			if (OSI != null)
				comp.install(this,500);
		}
		if (OSI == null) {
			g.setColor(Color.gray);
			g.fillRect(0,0,getWidth(),getHeight());
			g.setColor(new Color(255,200,200));
			g.drawString("OUT OF MEMORY.  Try a smaller image??", 20,20);
		}
		else {
			g.setColor(Color.gray);
			if (imageWidth-hOffset < getWidth())
				g.fillRect(imageWidth-hOffset,0,getWidth()-imageWidth+hOffset,getHeight());
			if (imageHeight-vOffset < getHeight())
				g.fillRect(0,imageHeight-vOffset,imageWidth-hOffset,getHeight()-imageHeight-vOffset);
			g.drawImage(OSI,-hOffset,-vOffset,this);
			if (dragRect != null) {
				g.setColor(hiliteColor);
				g.drawRect(dragRect.x, dragRect.y, dragRect.width, dragRect.height);
				g.drawRect(dragRect.x-1, dragRect.y-1, dragRect.width+2, dragRect.height+2);
			}
		}
	}
	
	private void makeImage() {
		OSI = null;
		try {
			//OSI = new BufferedImage(imageWidth,imageHeight,BufferedImage.TYPE_INT_RGB);
			IndexColorModel cm = new IndexColorModel(8,253,palette,0,false,
					-1,DataBuffer.TYPE_BYTE);
			OSI = new BufferedImage(imageWidth,imageHeight,BufferedImage.TYPE_BYTE_INDEXED,cm);
		}
		catch (OutOfMemoryError e) {
			OSI = null;
			return;
		}  
        Graphics g = OSI.getGraphics();
        g.setColor(Color.lightGray);
        g.fillRect(0,0,imageWidth,imageHeight);
        g.dispose();
	}
	
	synchronized public void doDraw() {
		forceStopDraw();
		if (OSI == null)
			makeImage();
		else {
			Graphics g = OSI.getGraphics();
			g.setColor(getBackground());
			g.fillRect(0,0,imageWidth,imageHeight);
			g.dispose();
		}
		repaint();
		if (OSI != null)
			comp.install(this);
	}
	
	synchronized public void stopDrawing() {
		comp.cancel();
		stopAnimation();
	}
		
	public int getMaxIterations() {
		return maxIterations;
	}
	
	public void setMaxIterations(int iter) {
		maxIterations = iter;
	}
		
	public void newData(int x, int y, int width, int height) {
		repaint(x-hOffset,y-vOffset,width,height);
	}
	
	public boolean isRunning() {
		return comp.isRunning();
	}
	
	public void startedRunning() {
		// to be called from MandComp only
		fileMenu.disableItem(3);
		controlMenu.disableItem(1);
		controlMenu.enableItem(2);
		paletteMenu.disableItem(12);
		paletteMenu.disableItem(13);
		renderMenu.setEnabled(false);
		iterationsMenu.setEnabled(false);
	}
	
	public void doneRunning() {
		// to be called from MandComp only
		fileMenu.enableItem(3);
		controlMenu.enableItem(1);
		controlMenu.disableItem(2);
		paletteMenu.enableItem(12);
		paletteMenu.enableItem(13);
		renderMenu.setEnabled(true);
		iterationsMenu.setEnabled(true);
	}
	
	public void doZoom(double zoomFactor) {
		doZoom(imageWidth/2,imageHeight/2,zoomFactor,false);
	}
	
	private void doZoom(int x, int y, double zoomFactor, boolean recenter) {
		double newWidth = (xmax - xmin)*zoomFactor;
		double newHeight = (ymax - ymin)*zoomFactor;
		double pixelWidth = newWidth/imageWidth;
		double pixelHeight = newHeight/imageHeight;
		double centerX = xmin + x*(xmax - xmin)/imageWidth;
		double centerY = ymax - y*(ymax - ymin)/imageHeight;
		double newXmin,newXmax,newYmin,newYmax;
		if (recenter) {
			newXmin = centerX - newWidth/2;
			newYmax = centerY + newHeight/2;
		}
		else {
			newXmin = centerX - x*pixelWidth;
			newYmax = centerY + y*pixelHeight;
		}
		newYmin = newYmax - newHeight;
		newXmax = newXmin + newWidth;
		setLimits(newXmin, newXmax, newYmin, newYmax);
	}
	
		
	private void doZoomInOnRect(Rectangle rect) {
		double pixelWidth = (xmax - xmin)/imageWidth;
		double pixelHeight = (ymax - ymin)/imageHeight;
		double newXmin,newXmax,newYmin,newYmax;
		newXmin = xmin + pixelWidth*(rect.x+hOffset);
		newYmax = ymax - pixelHeight*(rect.y+vOffset);
		double newWidth = pixelWidth*rect.width;
		double newHeight = pixelHeight*rect.height;
		newXmax = newXmin + newWidth;
		newYmin = newYmax - newHeight;
		setLimits(newXmin, newXmax, newYmin, newYmax);
	}
	
		
	synchronized private void startAnimation() {
		if (OSI == null || (animator != null && animator.isRunning()))
			return;
		animator = new Timer(50,new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				rotatePalette();
			}
		});
		paletteMenu.setItemName(15,"Stop Palette Animation");
		animator.start();
	}
	
	synchronized private void stopAnimation() {
		if (animator == null)
			return;
		paletteMenu.setItemName(15,"Start Palette Animation");
		animator.stop();
		animator = null;
	}
	
	
	private void rotatePalette() {
		int jump = (selectedPalette == 1)? 1 : 3;
		int newOffset = paletteOffset - jump;
		if (newOffset < 0)
			newOffset = 250 + newOffset;
		if (newOffset >= 250)
			newOffset = newOffset - 250;
		paletteOffset = newOffset;
		for (int i = 0; i < jump; i++)
			palette[253+i] = palette[i];
		for (int i = 0; i < 250-jump; i++)
			palette[i] = palette[i+jump];
		for (int i = 250-jump; i < 250; i++)
			palette[i] = palette[i+jump+3];
		installNewPalette();
	}
	
	public void doSave() {
		if (fileDialog == null)      
			fileDialog = new JFileChooser(".");  // Selects current directory.
		File selectedFile = new File("mandelbrot.params");
		fileDialog.setSelectedFile(selectedFile);  // Go to directory from last save image operation
		fileDialog.setDialogTitle("Save Current Parameters to File");
		int option = fileDialog.showSaveDialog(this);
		if (option != JFileChooser.APPROVE_OPTION)
			return;  // user canceled
		selectedFile = fileDialog.getSelectedFile();
		if (selectedFile.exists()) {
			int response = JOptionPane.showConfirmDialog(this,"The file \"" + selectedFile.getName()
					+ "\" already exists.\nDo you want to replace it?", "Confirm Save",
					JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
			if (response == JOptionPane.NO_OPTION)
				return;
		}
		try {
			PrintWriter out = new PrintWriter(new FileWriter(selectedFile));
			out.println("%MBJava 1.0 -- File created " + new java.util.Date());
			if (out.checkError())
				throw new IOException("Unable write to the file.");
			out.println(xmin);
			out.println(xmax);
			out.println(ymin);
			out.println(ymax);
			out.println(selectedPalette);
			out.println(selectedMandelbrotColor);
			out.println(selectedIterations);
			out.println(paletteOffset);
			out.println(stretchPalette? 1 : 0);
			if (out.checkError())
				throw new IOException("Error occurred after some data was written.");
		}
		catch (Exception e) {
			JOptionPane.showMessageDialog(this,"Sorry, but an error occurred while trying to save the data:\n" + e);
		}	
	}
	
	public void doOpen() {
		if (fileDialog == null)      
			fileDialog = new JFileChooser(".");  // Selects current directory.
		File selectedFile;
		fileDialog.setDialogTitle("Read Parameters from File");
		fileDialog.setSelectedFile(null);
		int option = fileDialog.showOpenDialog(this);
		if (option != JFileChooser.APPROVE_OPTION)
			return;  // user canceled
		selectedFile = fileDialog.getSelectedFile();
		BufferedReader in;
		Example example = new Example();
		try {
			in = new BufferedReader(new FileReader(selectedFile));
		}
		catch (Exception e) {
			JOptionPane.showMessageDialog(this,"Sorry, but an error occurred while trying to open the file:\n" + e);
			return;
		}
		try {
			String line = in.readLine();
			boolean knownfile = line.startsWith("%MBJava");
			if (knownfile)
				line = in.readLine();
			example.xmin = Double.parseDouble(line);
			example.xmax = Double.parseDouble(in.readLine());
			example.ymin = Double.parseDouble(in.readLine());
			example.ymax = Double.parseDouble(in.readLine());
			if (example.xmin < -100 || example.xmax > 100 || example.ymin < -100 || 
					example.ymax > 100 || example.xmax <= example.xmin || 
					example.ymax <= example.ymin)
				throw new Exception("Illegal values found in file for xmin, xmax, ymin, ymax.");
			try {
				example.paletteIndex = Integer.parseInt(in.readLine());
				example.mandelbrotColorIndex = Integer.parseInt(in.readLine());
				example.iterationsIndex = Integer.parseInt(in.readLine());
				example.paletteOffset = Integer.parseInt(in.readLine());
				int sp = Integer.parseInt(in.readLine());
				example.stretchPalette = (sp == 1);
			}
			catch (NullPointerException e) {
			}
			catch (NumberFormatException e) {
			}
			install(example);
		}
		catch (Exception e) {
			JOptionPane.showMessageDialog(this,"Sorry, but an error occurred while trying to write the data:\n" + e);
		}	
	}
	
	public void doSavePNG() {
		if (fileDialog == null)      
			fileDialog = new JFileChooser(".");  // Selects current directory.
		File selectedFile = new File(fileDialog.getSelectedFile(),"mandelbrot.png");
		fileDialog.setSelectedFile(selectedFile); 
		fileDialog.setDialogTitle("Save PNG Image");
		int option = fileDialog.showSaveDialog(this);
		if (option != JFileChooser.APPROVE_OPTION)
			return;  // user canceled
		selectedFile = fileDialog.getSelectedFile();
		if (selectedFile.exists()) {
			int response = JOptionPane.showConfirmDialog(this,"The file \"" + selectedFile.getName()
					+ "\" already exists.\nDo you want to replace it?", "Confirm Save",
					JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
			if (response == JOptionPane.NO_OPTION)
				return;
		}
		try {
			if ( ! ImageIO.write(this.getImage(),"PNG",selectedFile) )
				JOptionPane.showMessageDialog(this,"Sorry, but the PNG image format is not available\n" 
						+ "on this system.  Image cannot be saved.");
		}
		catch (Exception e) {
			JOptionPane.showMessageDialog(this,"Sorry, but an error occurred while trying to save the image:\n" + e);
		}		
	}

	private void disposeParentFrame() {
		Component c = getParent();
		while (c != null && !(c instanceof Frame))
			c = c.getParent();
		if (c != null)
			((Frame)c).dispose();
	}
	

	private class MBScrollPane extends JPanel implements AdjustmentListener {
		JScrollBar hScroll, vScroll;
		int oldWidth, oldHeight;
		boolean hScrollerShown, vScrollerShown;
		public MBScrollPane() {
			setLayout(new BorderLayout());
			add(MandelbrotCanvas.this,BorderLayout.CENTER);
			hScroll = new JScrollBar(JScrollBar.HORIZONTAL);
			hScroll.addAdjustmentListener(this);
			vScroll = new JScrollBar(JScrollBar.VERTICAL);
			vScroll.addAdjustmentListener(this);
			addComponentListener(new ComponentAdapter() {
				public void componentResized(ComponentEvent evt) {
					check(false);
				}
			});
		}
		public void check(boolean force) {
			if (OSI != null && (force || oldWidth != getWidth() || oldHeight != getHeight())) {
				boolean needsValidate = false;
				int newWidth = getWidth();
				boolean needsVisible = newWidth < imageWidth;
				if (needsVisible != hScrollerShown) {
					if (needsVisible)
						add(hScroll,BorderLayout.SOUTH);
					else
						remove(hScroll);
					needsValidate = true;
					hScrollerShown = needsVisible;
				}
				int newHeight = getHeight();
				needsVisible = newHeight < imageHeight;
				if (needsVisible != vScrollerShown) {
					if (needsVisible)
						add(vScroll,BorderLayout.EAST);
					else
						remove(vScroll);
					needsValidate = true;
					vScrollerShown = needsVisible;
				}
				if (needsValidate) {
					MandelbrotCanvas.this.invalidate();
					validate();
				}
				if (hScrollerShown) {
					int newValue = hOffset;
					if (imageWidth - hOffset < MandelbrotCanvas.this.getWidth()) {
						newValue = imageWidth - MandelbrotCanvas.this.getWidth();
						hOffset = newValue;
					}
					hScroll.setValues(newValue,MandelbrotCanvas.this.getWidth(),0,imageWidth);
					hScroll.setBlockIncrement(MandelbrotCanvas.this.getWidth() - 10);
				}
				else
					hOffset = 0;
				if (vScrollerShown) {
					int newValue = vOffset;
					if (imageHeight - vOffset < MandelbrotCanvas.this.getHeight()) {
						newValue = imageHeight - MandelbrotCanvas.this.getHeight();
						vOffset = newValue;
					}
					vScroll.setValues(newValue,MandelbrotCanvas.this.getHeight(),0,imageHeight);
					vScroll.setBlockIncrement(MandelbrotCanvas.this.getHeight() - 10);
				}
				else
					vOffset = 0;
				oldWidth = newWidth;
				oldHeight = newHeight;
				MandelbrotCanvas.this.repaint();
			}
		}
		public void adjustmentValueChanged(AdjustmentEvent e) {
			if (hScrollerShown)
				hOffset = hScroll.getValue();
			else
				hOffset = 0;
			if (vScrollerShown)
				vOffset = vScroll.getValue();
			else
				vOffset = 0;
			MandelbrotCanvas.this.repaint();
		}
		
	}

	
	
	private xMenu fileMenu = new xMenu("File") {
		protected void create() {
			add("Save Param File...", "control S");
			add("Open Param File...", "control O");
			add("Save PNG Image...", "control G");
			addSeparator();
			add("Quit","control Q");
		}
		protected void doCommand(int commandNum) {
			if (commandNum == 5) {
				disposeParentFrame();
			}
			else if (commandNum == 1)
				doSave();
			else if (commandNum == 2)
				doOpen();
			else if (commandNum == 3)
				doSavePNG();
		}
	};
	
	private xMenu canvasSizeMenu = new xMenu("ImageSize") {
		protected void create() {
			add("Current Window Size","control 0");
			add("160x128","control 1");
			add("320x256","control 2");
			add("512x384","control 3");
			add("640x480","control 4");
			add("800x600","control 5");
			add("1024x768","control 6");
			add("1280x1024","control 7");
			add("1600x1200","control 8");
			add("1280x960");
			add("1280x854");
		}
		protected void doCommand(int commandNum) {
			switch(commandNum-1) {
			case 0:
				if (scrollPane != null)
					setCanvasSize(scrollPane.getWidth(), scrollPane.getHeight());
				else
					setCanvasSize(getWidth(),getHeight());
				break;
			case 1:
				setCanvasSize(160,128);
				break;
			case 2:
				setCanvasSize(320,256);
				break;
			case 3:
				setCanvasSize(512,384);
				break;
			case 4:
				setCanvasSize(640,480);
				break;
			case 5:
				setCanvasSize(800,600);
				break;
			case 6:
				setCanvasSize(1024,768);
				break;
			case 7:
				setCanvasSize(1280,1024);
				break;
			case 8:
				setCanvasSize(1600,1200);
				break;
			case 9:
				setCanvasSize(1280,960);
				break;
			case 10:
				setCanvasSize(1280,854);
				break;
			}
		}
	};
	
	private xMenu controlMenu = new xMenu("Control") {
		protected void create() {
			add("Start Drawing","control D");
			add("Stop Drawing","control X");
			addSeparator();
			add("Zoom In","control I");
			add("Zoom Out","shift control I");
			add("Restore All Defaults","control R");
			add("Undo Change of Limits","control Z");
			add("Redo Change of Limits","shift control Z");
			add("Restore Default Limits");
			add("Show Limits","control L");
			disableItem(7);
			disableItem(8);
		}
		protected void doCommand(int commandNum) {
			if (commandNum == 1)
				doDraw();
			else if (commandNum == 2)
				stopDrawing();
			else if (commandNum == 4)
				doZoom(0.5);
			else if (commandNum == 5)
				doZoom(2.0);
			else if (commandNum == 6)
				install(new Example());
			else if (commandNum == 7) {
				if (currentLimitsInHistory > 0) {
					currentLimitsInHistory--;
					if (currentLimitsInHistory == 0)
						disableItem(7);
					enableItem(8);
					double[] limits = (double[])limitsHistory.elementAt(currentLimitsInHistory);
					xmin = limits[0];
					xmax = limits[1];
					ymin = limits[2];
					ymax = limits[3];
					checkAspect();
					doDraw();
				}
			}
			else if (commandNum == 8) {
				if (currentLimitsInHistory < limitsHistory.size() - 1) {
					currentLimitsInHistory++;
					if (currentLimitsInHistory == limitsHistory.size() - 1)
						disableItem(8);
					enableItem(7);
					double[] limits = (double[])limitsHistory.elementAt(currentLimitsInHistory);
					xmin = limits[0];
					xmax = limits[1];
					ymin = limits[2];
					ymax = limits[3];
					checkAspect();
					doDraw();
				}
			}
			else if (commandNum == 9)
				restoreDefaultLimits();
			else if (commandNum == 10) {
				String message = "Current limits:\n\n"
					+ "   xmin = " + getXmin() + "\n"
					+ "   xmax = " + getXmax() + "\n"
					+ "   ymin = " + getYmin() + "\n"
					+ "   ymax = " + getYmax() + "\n";
				JOptionPane.showMessageDialog(this,message);
			}
			else if (commandNum == 12)
				disposeParentFrame();
		}
	};
	
	private xMenu iterationsMenu = new xMenu("MaxIterations") {
		private int[] values; 
		public void create() {
			values = new int[] { 250, 500, 1000, 1500, 2000, 2500, 
					3000, 4000, 5000, 7500, 10000, 15000, 20000, 25000, 
					35000, 50000, 75000, 100000 };
			String[] names = new String[values.length];
			for (int i = 0; i < values.length; i++)
				names[i] = "" + values[i];
			addRadioGroup(names,0);
		}
		public void doCommand(int command) {
			selectedIterations = command;
			setMaxIterations(values[command-1]);
		}
	};

	private xMenu renderMenu = new xMenu("RenderType") {
		public void create() {
			addRadioGroup(new String[] { "Column-by-Column", "Progressive (Blocks)",
					"Progressive (Lines)" }, 0);
		}
		public void doCommand(int command) {
			selectedRenderType = command;
			setRenderStyle(command);
		}
	};
	
	private xMenu mandelbrotColorMenu = new xMenu("MandelbrotColor") {
		public void create() {
			String[] names = new String[] { "Black", "White", "Gray", "Red",
					"Green", "Blue", "Cyan", "Magenta", "Yellow" };
			addRadioGroup(names,0);
		}
		public void doCommand(int command) {
			selectedMandelbrotColor = command;
			setMandelbrotColor(command-1);
		}
	};
	
	private xMenu paletteMenu = new xMenu("Palette") {
		public void create() {
			String[] names = new String[] { "Spectrum", "GrayScale", "RedScale","GreenScale",
					"BlueScale", "YellowScale", "Green-to-Red", "Red-to-Cyan", "Alernating Black/White",
					"Reverse Gray Scale" };
			addRadioGroup(names,0);
			JMenuItem item = (JMenuItem)getMenuComponent(0);
			item.setAccelerator(KeyStroke.getKeyStroke("control E"));
			addSeparator();
			addRadioGroup(new String[] { "Duplicate Palette", "Stretch Palette" }, 0);
			addSeparator();
			add("Start Palette Animation", "control A");
			add("Restore Default Palette Offset", "control F");
		}
		public void doCommand(int command) {
			if (command <= 10) {
				selectedPalette = command;
				setPalette(command);
			}
			else if (command == 12)
				stretchPalette = false;
			else if (command == 13)
				stretchPalette = true;
			else if (command == 15) {
				synchronized(this) {
					if (animator != null)
						stopAnimation();
					else
						startAnimation();
				}
			}
			else if (command == 16)
				setPalette(selectedPalette);
		}
	};
	
	public JMenuBar createMenus(boolean addFileMenu, boolean addCloseCommand) {
		JMenuBar menus = new JMenuBar();
		if (addFileMenu)
			menus.add(fileMenu);
		menus.add(controlMenu);
		if (!addFileMenu && addCloseCommand) {
			controlMenu.addSeparator();
			controlMenu.add("Close", "control W");
		}
		menus.add(iterationsMenu);
		menus.add(paletteMenu);
		menus.add(mandelbrotColorMenu);
		menus.add(canvasSizeMenu);
		menus.add(renderMenu);
		return menus;
	}
	
	
}
