package ij.gui;

import java.awt.*;
import java.util.Properties;
import java.awt.image.*;
import ij.process.ImageProcessor;
import ij.measure.*;
import ij.plugin.frame.Recorder;
import ij.plugin.frame.RoiManager;
import ij.macro.*;
import ij.*;
import ij.util.*;
import java.awt.event.*;
import java.util.*;

/** This is a Canvas used to display images in a Window. */
public class ImageCanvas extends Canvas implements MouseListener, MouseMotionListener, Cloneable {

	protected static Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
	protected static Cursor handCursor = new Cursor(Cursor.HAND_CURSOR);
	protected static Cursor moveCursor = new Cursor(Cursor.MOVE_CURSOR);
	protected static Cursor crosshairCursor = new Cursor(Cursor.CROSSHAIR_CURSOR);

	public static boolean usePointer = Prefs.usePointerCursor;
	
	protected ImagePlus imp;
	protected boolean imageUpdated;
	protected Rectangle srcRect;
	protected int imageWidth, imageHeight;
	protected int xMouse; // current cursor offscreen x location 
	protected int yMouse; // current cursor offscreen y location
	
	private boolean showCursorStatus = true;
	private int sx2, sy2;
	private boolean disablePopupMenu;
	private boolean showAllROIs;
	private static Color zoomIndicatorColor;
	private static Font smallFont, largeFont;
	private Rectangle[] labelRects;
    private boolean maxBoundsReset;
    private Vector displayList;
    private boolean labelListItems;
    private Color listColor;
    private BasicStroke listStroke;
    private static final int LIST_OFFSET = 100000;
    private static Color showAllColor = Prefs.getColor(Prefs.SHOW_ALL_COLOR, new Color(128, 255, 255));
    private static Color labelColor;
    private int resetMaxBoundsCount;
		
	protected ImageJ ij;
	protected double magnification;
	protected int dstWidth, dstHeight;

	protected int xMouseStart;
	protected int yMouseStart;
	protected int xSrcStart;
	protected int ySrcStart;
	protected int flags;
	
	private Image offScreenImage;
	private int offScreenWidth = 0;
	private int offScreenHeight = 0;
	
	
	public ImageCanvas(ImagePlus imp) {
		this.imp = imp;
		ij = IJ.getInstance();
		int width = imp.getWidth();
		int height = imp.getHeight();
		imageWidth = width;
		imageHeight = height;
		srcRect = new Rectangle(0, 0, imageWidth, imageHeight);
		setDrawingSize(imageWidth, (int)(imageHeight));
		magnification = 1.0;
 		addMouseListener(this);
 		addMouseMotionListener(this);
 		addKeyListener(ij);  // ImageJ handles keyboard shortcuts
		setFocusTraversalKeysEnabled(false);
	}
		
	void updateImage(ImagePlus imp) {
		this.imp = imp;
		int width = imp.getWidth();
		int height = imp.getHeight();
		imageWidth = width;
		imageHeight = height;
		srcRect = new Rectangle(0, 0, imageWidth, imageHeight);
		setDrawingSize(imageWidth, (int)imageHeight);
		magnification = 1.0;
	}

	/** Update this ImageCanvas to have the same zoom and scale settings as the one specified. */
	void update(ImageCanvas ic) {
		if (ic==null || ic==this || ic.imp==null)
			return;
		if (ic.imp.getWidth()!=imageWidth || ic.imp.getHeight()!=imageHeight)
			return;
		srcRect = new Rectangle(ic.srcRect.x, ic.srcRect.y, ic.srcRect.width, ic.srcRect.height);
		setMagnification(ic.magnification);
		setDrawingSize(ic.dstWidth, ic.dstHeight);
	}

	public void setDrawingSize(int width, int height) {
	    dstWidth = width;
	    dstHeight = height;
		setSize(dstWidth, dstHeight);
	}
		
	/** ImagePlus.updateAndDraw calls this method to get paint 
		to update the image from the ImageProcessor. */
	public void setImageUpdated() {
		imageUpdated = true;
	}

	public void update(Graphics g) {
		paint(g);
	}

    public void paint(Graphics g) {
		Roi roi = imp.getRoi();
		if (roi != null) {
			roi.updatePaste();
			if (Prefs.doubleBuffer && !IJ.isMacOSX())
				{paintDoubleBuffered(g); return;}
		}
		try {
			if (imageUpdated) {
				imageUpdated = false;
				imp.updateImage();
			}
			Java2.setBilinearInterpolation(g, Prefs.interpolateScaledImages);
			Image img = imp.getImage();
			if (img!=null)
 				g.drawImage(img, 0, 0, (int)(srcRect.width*magnification), (int)(srcRect.height*magnification),
				srcRect.x, srcRect.y, srcRect.x+srcRect.width, srcRect.y+srcRect.height, null);
			if (showAllROIs) showAllROIs(g);
			if (displayList!=null) drawDisplayList(g);
			if (roi != null) roi.draw(g);
			if (srcRect.width<imageWidth || srcRect.height<imageHeight)
				drawZoomIndicator(g);
			if (IJ.debugMode) showFrameRate(g);
		}
		catch(OutOfMemoryError e) {IJ.outOfMemory("Paint");}
    }
    
    void showAllROIs(Graphics g) {
		RoiManager rm=RoiManager.getInstance();
		if (rm==null) return;
		initGraphics(g, null);
		Hashtable rois = rm.getROIs();
		java.awt.List list = rm.getList();
		int n = list.getItemCount();
		if (labelRects==null || labelRects.length!=n)
			labelRects = new Rectangle[n];
		for (int i=0; i<n; i++) {
			String label = list.getItem(i);
			Roi roi = (Roi)rois.get(label);
			if (roi==null) continue;
			if (Prefs.showAllSliceOnly && imp.getStackSize()>1) {
				int slice = getSliceNumber(roi.getName());
				if (slice==-1 || slice==imp.getCurrentSlice())
					drawRoi(g, roi, i);
			} else
				drawRoi(g, roi, i);
		}
    }
    
	int getSliceNumber(String label) {
		if (label==null) return -1;
		int slice = -1;
		if (label.length()>4 && label.charAt(4)=='-' && label.length()>=14)
			slice = (int)Tools.parseDouble(label.substring(0,4),-1);
		return slice;
	}

    void drawDisplayList(Graphics g) {
		initGraphics(g, listColor);
    	int n = displayList.size();
    	for (int i=0; i<n; i++)
    		drawRoi(g, (Roi)displayList.elementAt(i), labelListItems?i+LIST_OFFSET:-1);
    	if (listStroke!=null) ((Graphics2D)g).setStroke(new BasicStroke());
    }
    
    void initGraphics(Graphics g, Color c) {
		if (smallFont==null) {
			smallFont = new Font("SansSerif", Font.PLAIN, 9);
			largeFont = new Font("SansSerif", Font.PLAIN, 12);
		}
		if (labelColor==null) {
			int red = showAllColor.getRed();
			int green = showAllColor.getGreen();
			int blue = showAllColor.getBlue();
			if ((red+green+blue)/3<128)
				labelColor = Color.white;
			else
				labelColor = Color.black;
		}
		if (c!=null) {
			g.setColor(c);
			if (listStroke!=null) ((Graphics2D)g).setStroke(listStroke);
		} else
			g.setColor(showAllColor);
    }
    
    void drawRoi(Graphics g, Roi roi, int index) {
		if (roi.getType()==Roi.COMPOSITE) {
			roi.setImage(imp);
			Color c = roi.getColor();
			if (index==-1 && listColor!=null)
				roi.setColor(listColor);
			else
				roi.setColor(showAllColor);
			roi.draw(g);
			roi.setColor(c);
			if (index>=0) drawRoiLabel(g, index, roi.getBounds());
		} else {
			Color c = index==-1?roi.getInstanceColor():null;
			Color saveg = null;
			if (c!=null) {
				saveg = g.getColor();
				g.setColor(c);
			}
			Polygon p = roi.getPolygon();
			int x1=0, y1=0, x2=0, y2=0;
			for (int j=0; j<p.npoints; j++) {
				x2 = screenX(p.xpoints[j]);
				y2 = screenY(p.ypoints[j]);
				if (j>0) g.drawLine(x1, y1, x2, y2);
				x1=x2; y1=y2;
			}
			if (roi.isArea()&&p.npoints>0) {
				int x0 = screenX(p.xpoints[0]);
				int y0 = screenY(p.ypoints[0]);
				g.drawLine(x1, y1, x0, y0);
			}
			if (index>=0) drawRoiLabel(g, index, roi.getBounds());
			if (saveg!=null) g.setColor(saveg);
		}
    }
    
	void drawRoiLabel(Graphics g, int index, Rectangle r) {
		int x = screenX(r.x);
		int y = screenY(r.y);
		double mag = getMagnification();
		int width = (int)(r.width*mag);
		int height = (int)(r.height*mag);
		int size = width>40 && height>40?12:9;
		if (size==12)
			g.setFont(largeFont);
		else
			g.setFont(smallFont);
		boolean drawingList = index >= LIST_OFFSET;
		if (drawingList) index -= LIST_OFFSET;
		String label = "" + (index+1);
		FontMetrics metrics = g.getFontMetrics();
		int w = metrics.stringWidth(label);
		x = x + width/2 - w/2;
		y = y + height/2 + Math.max(size/2,6);
		int h =  metrics.getHeight();
		g.fillRoundRect(x-1, y-h+2, w+1, h-3, 5, 5);
		if (!drawingList)
			labelRects[index] = new Rectangle(x-1, y-h+2, w+1, h-3);
		g.setColor(labelColor);
		g.drawString(label, x, y-2);
		g.setColor(showAllColor);
	} 

	void drawZoomIndicator(Graphics g) {
		int x1 = 10;
		int y1 = 10;
		double aspectRatio = (double)imageHeight/imageWidth;
		int w1 = 64;
		if (aspectRatio>1.0)
			w1 = (int)(w1/aspectRatio);
		int h1 = (int)(w1*aspectRatio);
		if (w1<4) w1 = 4;
		if (h1<4) h1 = 4;
		int w2 = (int)(w1*((double)srcRect.width/imageWidth));
		int h2 = (int)(h1*((double)srcRect.height/imageHeight));
		if (w2<1) w2 = 1;
		if (h2<1) h2 = 1;
		int x2 = (int)(w1*((double)srcRect.x/imageWidth));
		int y2 = (int)(h1*((double)srcRect.y/imageHeight));
		if (zoomIndicatorColor==null)
			zoomIndicatorColor = new Color(128, 128, 255);
		g.setColor(zoomIndicatorColor);
		g.drawRect(x1, y1, w1, h1);
		if (w2*h2<=200 || w2<10 || h2<10)
			g.fillRect(x1+x2, y1+y2, w2, h2);
		else
			g.drawRect(x1+x2, y1+y2, w2, h2);
	}

	// Use double buffer to reduce flicker when drawing complex ROIs.
	// Author: Erik Meijering
	void paintDoubleBuffered(Graphics g) {
		final int srcRectWidthMag = (int)(srcRect.width*magnification);
		final int srcRectHeightMag = (int)(srcRect.height*magnification);
		if (offScreenImage==null || offScreenWidth!=srcRectWidthMag || offScreenHeight!=srcRectHeightMag) {
			offScreenImage = createImage(srcRectWidthMag, srcRectHeightMag);
			offScreenWidth = srcRectWidthMag;
			offScreenHeight = srcRectHeightMag;
		}
		Roi roi = imp.getRoi();
		try {
			if (imageUpdated) {
				imageUpdated = false;
				imp.updateImage();
			}
			Graphics offScreenGraphics = offScreenImage.getGraphics();
			Java2.setBilinearInterpolation(offScreenGraphics, Prefs.interpolateScaledImages);
			Image img = imp.getImage();
			if (img!=null)
				offScreenGraphics.drawImage(img, 0, 0, srcRectWidthMag, srcRectHeightMag,
					srcRect.x, srcRect.y, srcRect.x+srcRect.width, srcRect.y+srcRect.height, null);
			if (showAllROIs) showAllROIs(offScreenGraphics);
			if (displayList!=null) drawDisplayList(offScreenGraphics);
			if (roi!=null) roi.draw(offScreenGraphics);
			if (srcRect.width<imageWidth ||srcRect.height<imageHeight)
				drawZoomIndicator(offScreenGraphics);
			if (IJ.debugMode) showFrameRate(offScreenGraphics);
			g.drawImage(offScreenImage, 0, 0, null);
		}
		catch(OutOfMemoryError e) {IJ.outOfMemory("Paint");}
	}

    long firstFrame;
    int frames, fps;
        
	void showFrameRate(Graphics g) {
		frames++;
		if (System.currentTimeMillis()>firstFrame+1000) {
			firstFrame=System.currentTimeMillis();
			fps = frames;
			frames=0;
		}
		g.setColor(Color.white);
		g.fillRect(10, 12, 50, 15);
		g.setColor(Color.black);
		g.drawString((int)(fps+0.5) + " fps", 10, 25);
	}

    public Dimension getPreferredSize() {
        return new Dimension(dstWidth, dstHeight);
    }

    int count;
    
    /*
    public Graphics getGraphics() {
     	Graphics g = super.getGraphics();
		IJ.write("getGraphics: "+count++);
		if (IJ.altKeyDown())
			throw new IllegalArgumentException("");
    	return g;
    }
    */

	/** Returns the current cursor location. */
	public Point getCursorLoc() {
		return new Point(xMouse, yMouse);
	}

	/** Returns the mouse event modifiers. */
	public int getModifiers() {
		return flags;
	}

	/** Sets the cursor based on the current tool and cursor location. */
	public void setCursor(int sx, int sy, int ox, int oy) {
		xMouse = ox;
		yMouse = oy;
		Roi roi = imp.getRoi();
		ImageWindow win = imp.getWindow();
		if (win==null)
			return;
		if (IJ.spaceBarDown()) {
			setCursor(handCursor);
			return;
		}
		int id = Toolbar.getToolId();
		switch (Toolbar.getToolId()) {
			case Toolbar.MAGNIFIER:
				setCursor(moveCursor);
				break;
			case Toolbar.HAND:
				setCursor(handCursor);
				break;
			default:  //selection tool
				if (id==Toolbar.SPARE1 || id>=Toolbar.SPARE2) {
					if (Prefs.usePointerCursor) setCursor(defaultCursor); else setCursor(crosshairCursor);
				} else if (roi!=null && roi.getState()!=roi.CONSTRUCTING && roi.isHandle(sx, sy)>=0)
					setCursor(handCursor);
				else if (Prefs.usePointerCursor || (roi!=null && roi.getState()!=roi.CONSTRUCTING && roi.contains(ox, oy)))
					setCursor(defaultCursor);
				else
					setCursor(crosshairCursor);
		}
	}
		
	/**Converts a screen x-coordinate to an offscreen x-coordinate.*/
	public int offScreenX(int sx) {
		return srcRect.x + (int)(sx/magnification);
	}
		
	/**Converts a screen y-coordinate to an offscreen y-coordinate.*/
	public int offScreenY(int sy) {
		return srcRect.y + (int)(sy/magnification);
	}
	
	/**Converts a screen x-coordinate to a floating-point offscreen x-coordinate.*/
	public double offScreenXD(int sx) {
		return srcRect.x + sx/magnification;
	}
		
	/**Converts a screen y-coordinate to a floating-point offscreen y-coordinate.*/
	public double offScreenYD(int sy) {
		return srcRect.y + sy/magnification;

	}
	
	/**Converts an offscreen x-coordinate to a screen x-coordinate.*/
	public int screenX(int ox) {
		return  (int)((ox-srcRect.x)*magnification);
	}
	
	/**Converts an offscreen y-coordinate to a screen y-coordinate.*/
	public int screenY(int oy) {
		return  (int)((oy-srcRect.y)*magnification);
	}

	/**Converts a floating-point offscreen x-coordinate to a screen x-coordinate.*/
	public int screenXD(double ox) {
		return  (int)((ox-srcRect.x)*magnification);
	}
	
	/**Converts a floating-point offscreen x-coordinate to a screen x-coordinate.*/
	public int screenYD(double oy) {
		return  (int)((oy-srcRect.y)*magnification);
	}

	public double getMagnification() {
		return magnification;
	}
		
	public void setMagnification(double magnification) {
		setMagnification2(magnification);
	}
		
	void setMagnification2(double magnification) {
		if (magnification>32.0) magnification = 32.0;
		if (magnification<0.03125) magnification = 0.03125;
		this.magnification = magnification;
		imp.setTitle(imp.getTitle());
	}

	public Rectangle getSrcRect() {
		return srcRect;
	}
	
	void setSrcRect(Rectangle srcRect) {
		this.srcRect = srcRect;
	}
		
	/** Enlarge the canvas if the user enlarges the window. */
	void resizeCanvas(int width, int height) {
		ImageWindow win = imp.getWindow();
		//IJ.log("resizeCanvas: "+srcRect+" "+imageWidth+"  "+imageHeight+" "+width+"  "+height+" "+dstWidth+"  "+dstHeight+" "+win.maxBounds);
		if (!maxBoundsReset&& (width>dstWidth||height>dstHeight)&&win!=null&&win.maxBounds!=null&&width!=win.maxBounds.width-10) {
			if (resetMaxBoundsCount!=0)
				resetMaxBounds(); // Works around problem that prevented window from being larger than maximized size
			resetMaxBoundsCount++;
		}
		if (IJ.altKeyDown())
			{fitToWindow(); return;}
		if (srcRect.width<imageWidth || srcRect.height<imageHeight) {
			if (width>imageWidth*magnification)
				width = (int)(imageWidth*magnification);
			if (height>imageHeight*magnification)
				height = (int)(imageHeight*magnification);
			setDrawingSize(width, height);
			srcRect.width = (int)(dstWidth/magnification);
			srcRect.height = (int)(dstHeight/magnification);
			if ((srcRect.x+srcRect.width)>imageWidth)
				srcRect.x = imageWidth-srcRect.width;
			if ((srcRect.y+srcRect.height)>imageHeight)
				srcRect.y = imageHeight-srcRect.height;
			repaint();
		}
		//IJ.log("resizeCanvas2: "+srcRect+" "+dstWidth+"  "+dstHeight+" "+width+"  "+height);
	}
	
	public void fitToWindow() {
			ImageWindow win = imp.getWindow();
			if (win==null) return;
			Rectangle bounds = win.getBounds();
			Insets insets = win.getInsets();
			int sliderHeight = (win instanceof StackWindow)?20:0;
			double xmag = (double)(bounds.width-10)/srcRect.width;
			double ymag = (double)(bounds.height-(10+insets.top+sliderHeight))/srcRect.height;
			setMagnification(Math.min(xmag, ymag));
			srcRect = new Rectangle(0, 0, imageWidth, imageHeight);
			setDrawingSize((int)(imageWidth*magnification), (int)(imageHeight*magnification));
	}
    
	void setMaxBounds() {
		if (maxBoundsReset) {
			maxBoundsReset = false;
			ImageWindow win = imp.getWindow();
			if (win!=null && !IJ.isLinux() && win.maxBounds!=null) {
				win.setMaximizedBounds(win.maxBounds);
				win.setMaxBoundsTime = System.currentTimeMillis();
			}
		}
	}

	void resetMaxBounds() {
		ImageWindow win = imp.getWindow();
		if (win!=null && (System.currentTimeMillis()-win.setMaxBoundsTime)>500L) {
			win.setMaximizedBounds(win.maxWindowBounds);
			maxBoundsReset = true;
		}
	}
	
	private static final double[] zoomLevels = {
		1/72.0, 1/48.0, 1/32.0, 1/24.0, 1/16.0, 1/12.0, 
		1/8.0, 1/6.0, 1/4.0, 1/3.0, 1/2.0, 0.75, 1.0, 1.5,
		2.0, 3.0, 4.0, 6.0, 8.0, 12.0, 16.0, 24.0, 32.0 };
	
	public static double getLowerZoomLevel(double currentMag) {
		double newMag = zoomLevels[0];
		for (int i=0; i<zoomLevels.length; i++) {
		if (zoomLevels[i] < currentMag)
			newMag = zoomLevels[i];
		else
			break;
		}
		return newMag;
	}

	public static double getHigherZoomLevel(double currentMag) {
		double newMag = 32.0;
		for (int i=zoomLevels.length-1; i>=0; i--) {
			if (zoomLevels[i]>currentMag)
				newMag = zoomLevels[i];
			else
				break;
		}
		return newMag;
	}

	/** Zooms in by making the window bigger. If it can't
		be made bigger, then make the source rectangle 
		(srcRect) smaller and center it at (x,y). */
	public void zoomIn(int x, int y) {
		if (magnification>=32) return;
		double newMag = getHigherZoomLevel(magnification);
		int newWidth = (int)(imageWidth*newMag);
		int newHeight = (int)(imageHeight*newMag);
		Dimension newSize = canEnlarge(newWidth, newHeight);
		if (newSize!=null) {
			setDrawingSize(newSize.width, newSize.height);
			if (newSize.width!=newWidth || newSize.height!=newHeight)
				adjustSourceRect(newMag, x, y);
			else
				setMagnification(newMag);
			imp.getWindow().pack();
		} else
			adjustSourceRect(newMag, x, y);
		repaint();
		if (srcRect.width<imageWidth || srcRect.height<imageHeight)
			resetMaxBounds();
	}
	
	void adjustSourceRect(double newMag, int x, int y) {
		//IJ.log("adjustSourceRect1: "+newMag+" "+dstWidth+"  "+dstHeight);
		int w = (int)Math.round(dstWidth/newMag);
		if (w*newMag<dstWidth) w++;
		int h = (int)Math.round(dstHeight/newMag);
		if (h*newMag<dstHeight) h++;
		x = offScreenX(x);
		y = offScreenY(y);
		Rectangle r = new Rectangle(x-w/2, y-h/2, w, h);
		if (r.x<0) r.x = 0;
		if (r.y<0) r.y = 0;
		if (r.x+w>imageWidth) r.x = imageWidth-w;
		if (r.y+h>imageHeight) r.y = imageHeight-h;
		srcRect = r;
		setMagnification(newMag);
		//IJ.log("adjustSourceRect2: "+srcRect+" "+dstWidth+"  "+dstHeight);
	}
	
	protected Dimension canEnlarge(int newWidth, int newHeight) {
		if ((flags&Event.SHIFT_MASK)!=0 || IJ.shiftKeyDown())
			return null;
		ImageWindow win = imp.getWindow();
		if (win==null) return null;
		Rectangle r1 = win.getBounds();

		Insets insets = win.getInsets();
		Point loc = getLocation();
		if (loc.x>insets.left+5 || loc.y>insets.top+5) {
			r1.width = newWidth+insets.left+insets.right+10;
			r1.height = newHeight+insets.top+insets.bottom+10;
			if (win instanceof StackWindow) r1.height+=20;
		} else {
			r1.width = r1.width - dstWidth + newWidth+10;
			r1.height = r1.height - dstHeight + newHeight+10;
		}
		Rectangle max = win.getMaxWindow();
		boolean fitsHorizontally = r1.x+r1.width<max.x+max.width;
		boolean fitsVertically = r1.y+r1.height<max.y+max.height;
		if (fitsHorizontally && fitsVertically)
			return new Dimension(newWidth, newHeight);
		else if (fitsVertically && newHeight<dstWidth)
			return new Dimension(dstWidth, newHeight);
		else if (fitsHorizontally && newWidth<dstHeight)
			return new Dimension(newWidth, dstHeight);
		else
			return null;
	}
		
	/**Zooms out by making the source rectangle (srcRect)  
		larger and centering it on (x,y). If we can't make it larger,  
		then make the window smaller.*/
	public void zoomOut(int x, int y) {
		if (magnification<=0.03125)
			return;
		double oldMag = magnification;
		double newMag = getLowerZoomLevel(magnification);
		double srcRatio = (double)srcRect.width/srcRect.height;
		double imageRatio = (double)imageWidth/imageHeight;
		double initialMag = imp.getWindow().getInitialMagnification();
		if (Math.abs(srcRatio-imageRatio)>0.05) {
			double scale = oldMag/newMag;
			int newSrcWidth = (int)Math.round(srcRect.width*scale);
			int newSrcHeight = (int)Math.round(srcRect.height*scale);
			if (newSrcWidth>imageWidth) newSrcWidth=imageWidth;
			if (newSrcHeight>imageHeight) newSrcHeight=imageHeight;
			int newSrcX = srcRect.x - (newSrcWidth - srcRect.width)/2;
			int newSrcY = srcRect.y - (newSrcHeight - srcRect.height)/2;
			if (newSrcX<0) newSrcX = 0;
			if (newSrcY<0) newSrcY = 0;
			srcRect = new Rectangle(newSrcX, newSrcY, newSrcWidth, newSrcHeight);
			//IJ.log(newMag+" "+srcRect+" "+dstWidth+" "+dstHeight);
			int newDstWidth = (int)(srcRect.width*newMag);
			int newDstHeight = (int)(srcRect.height*newMag);
			setMagnification(newMag);
			setMaxBounds();
			//IJ.log(newDstWidth+" "+dstWidth+" "+newDstHeight+" "+dstHeight);
			if (newDstWidth<dstWidth || newDstHeight<dstHeight) {
				//IJ.log("pack");
				setDrawingSize(newDstWidth, newDstHeight);
				imp.getWindow().pack();
			} else
				repaint();
			return;
		}
		if (imageWidth*newMag>dstWidth) {
			int w = (int)Math.round(dstWidth/newMag);
			if (w*newMag<dstWidth) w++;
			int h = (int)Math.round(dstHeight/newMag);
			if (h*newMag<dstHeight) h++;
			x = offScreenX(x);
			y = offScreenY(y);
			Rectangle r = new Rectangle(x-w/2, y-h/2, w, h);
			if (r.x<0) r.x = 0;
			if (r.y<0) r.y = 0;
			if (r.x+w>imageWidth) r.x = imageWidth-w;
			if (r.y+h>imageHeight) r.y = imageHeight-h;
			srcRect = r;
		} else {
			srcRect = new Rectangle(0, 0, imageWidth, imageHeight);
			setDrawingSize((int)(imageWidth*newMag), (int)(imageHeight*newMag));
			//setDrawingSize(dstWidth/2, dstHeight/2);
			imp.getWindow().pack();
		}
		//IJ.write(newMag + " " + srcRect.x+" "+srcRect.y+" "+srcRect.width+" "+srcRect.height+" "+dstWidth + " " + dstHeight);
		setMagnification(newMag);
		//IJ.write(srcRect.x + " " + srcRect.width + " " + dstWidth);
		setMaxBounds();
		repaint();
	}

	public void unzoom() {
		double imag = imp.getWindow().getInitialMagnification();
		if (magnification==imag)
			return;
		srcRect = new Rectangle(0, 0, imageWidth, imageHeight);
		ImageWindow win = imp.getWindow();
		setDrawingSize((int)(imageWidth*imag), (int)(imageHeight*imag));
		setMagnification(imag);
        setMaxBounds();
		win.pack();
		setMaxBounds();
		repaint();
	}
		
	protected void scroll(int sx, int sy) {
		int ox = xSrcStart + (int)(sx/magnification);  //convert to offscreen coordinates
		int oy = ySrcStart + (int)(sy/magnification);
		//IJ.log("scroll: "+ox+" "+oy+" "+xMouseStart+" "+yMouseStart);
		int newx = xSrcStart + (xMouseStart-ox);
		int newy = ySrcStart + (yMouseStart-oy);
		if (newx<0) newx = 0;
		if (newy<0) newy = 0;
		if ((newx+srcRect.width)>imageWidth) newx = imageWidth-srcRect.width;
		if ((newy+srcRect.height)>imageHeight) newy = imageHeight-srcRect.height;
		srcRect.x = newx;
		srcRect.y = newy;
		//IJ.log(sx+"  "+sy+"  "+newx+"  "+newy+"  "+srcRect);
		imp.draw();
		Thread.yield();
	}	
	
	Color getColor(int index){
		IndexColorModel cm = (IndexColorModel)imp.getProcessor().getColorModel();
		//IJ.write(""+index+" "+(new Color(cm.getRGB(index))));
		return new Color(cm.getRGB(index));
	}
	
	protected void setDrawingColor(int ox, int oy, boolean setBackground) {
		//IJ.write("setDrawingColor: "+setBackground+this);
		int type = imp.getType();
		int[] v = imp.getPixel(ox, oy);
		switch (type) {
			case ImagePlus.GRAY8: {
				if (setBackground)
					setBackgroundColor(getColor(v[0]));
				else
					setForegroundColor(getColor(v[0]));
				break;
			}
			case ImagePlus.GRAY16: case ImagePlus.GRAY32: {
				double min = imp.getProcessor().getMin();
				double max = imp.getProcessor().getMax();
				double value = (type==ImagePlus.GRAY32)?Float.intBitsToFloat(v[0]):v[0];
				int index = (int)(255.0*((value-min)/(max-min)));
				if (index<0) index = 0;
				if (index>255) index = 255;
				if (setBackground)
					setBackgroundColor(getColor(index));
				else
					setForegroundColor(getColor(index));
				break;
			}
			case ImagePlus.COLOR_RGB: case ImagePlus.COLOR_256: {
				Color c = new Color(v[0], v[1], v[2]);
				if (setBackground)
					setBackgroundColor(c);
				else
					setForegroundColor(c);
				break;
			}
		}
		Color c;
		if (setBackground)
			c = Toolbar.getBackgroundColor();
		else {
			c = Toolbar.getForegroundColor();
			imp.setColor(c);
		}
		IJ.showStatus("("+c.getRed()+", "+c.getGreen()+", "+c.getBlue()+")");
	}
	
	private void setForegroundColor(Color c) {
		Toolbar.setForegroundColor(c);
		if (Recorder.record)
			Recorder.record("setForegroundColor", c.getRed(), c.getGreen(), c.getBlue());
	}

	private void setBackgroundColor(Color c) {
		Toolbar.setBackgroundColor(c);
		if (Recorder.record)
			Recorder.record("setBackgroundColor", c.getRed(), c.getGreen(), c.getBlue());
	}

	public void mousePressed(MouseEvent e) {
		if (ij==null) return;
		showCursorStatus = true;
		int toolID = Toolbar.getToolId();
		ImageWindow win = imp.getWindow();
		if (win!=null && win.running2 && toolID!=Toolbar.MAGNIFIER) {
			win.running2 = false;
			return;
		}
		
		int x = e.getX();
		int y = e.getY();
		flags = e.getModifiers();
		//IJ.log("Mouse pressed: " + e.isPopupTrigger() + "  " + ij.modifiers(flags));		
		//if (toolID!=Toolbar.MAGNIFIER && e.isPopupTrigger()) {
		if (toolID!=Toolbar.MAGNIFIER && (e.isPopupTrigger() || (flags & Event.META_MASK)!=0)) {
			handlePopupMenu(e);
			return;
		}

		int ox = offScreenX(x);
		int oy = offScreenY(y);
		xMouse = ox; yMouse = oy;
		if (IJ.spaceBarDown()) {
			// temporarily switch to "hand" tool of space bar down
			setupScroll(ox, oy);
			return;
		}
		if (showAllROIs) {
			Roi roi = imp.getRoi();
			if (!(roi!=null && (roi.contains(ox, oy)||roi.isHandle(x, y)>=0)) && roiManagerSelect(x, y))
 				return;
		}

		switch (toolID) {
			case Toolbar.MAGNIFIER:
				if (IJ.shiftKeyDown())
					zoomToSelection(ox, oy);
				else if ((flags & (Event.ALT_MASK|Event.META_MASK|Event.CTRL_MASK))!=0)
					zoomOut(x, y);
				else
					zoomIn(x, y);
				break;
			case Toolbar.HAND:
				setupScroll(ox, oy);
				break;
			case Toolbar.DROPPER:
				setDrawingColor(ox, oy, IJ.altKeyDown());
				break;
			case Toolbar.WAND:
				Roi roi = imp.getRoi();
				if (roi!=null && roi.contains(ox, oy)) {
					Rectangle r = roi.getBounds();
					if (r.width==imageWidth && r.height==imageHeight)
						imp.killRoi();
					else if (!e.isAltDown()) {
						handleRoiMouseDown(e);
						return;
					}
				}
				if (roi!=null) {
					int handle = roi.isHandle(x, y);
					if (handle>=0) {
						roi.mouseDownInHandle(handle, x, y);
						return;
					}
				}
				setRoiModState(e, roi, -1);
				int npoints = IJ.doWand(ox, oy);
				if (Recorder.record && npoints>0)
					Recorder.record("doWand", ox, oy);
				break;
			case Toolbar.OVAL:
				if (Toolbar.getBrushSize()>0)
					new RoiBrush();
				else
					handleRoiMouseDown(e);
				break;
			case Toolbar.SPARE1: case Toolbar.SPARE2: case Toolbar.SPARE3: 
			case Toolbar.SPARE4: case Toolbar.SPARE5: case Toolbar.SPARE6:
			case Toolbar.SPARE7: case Toolbar.SPARE8: case Toolbar.SPARE9:
				Toolbar.getInstance().runMacroTool(toolID);
				break;
			default:  //selection tool
				handleRoiMouseDown(e);
		}
	}
	
	void zoomToSelection(int x, int y) {
		IJ.setKeyUp(IJ.ALL_KEYS);
		String macro =
			"args = split(getArgument);\n"+
			"x1=parseInt(args[0]); y1=parseInt(args[1]); flags=20;\n"+
			"while (flags&20!=0) {\n"+
				"getCursorLoc(x2, y2, z, flags);\n"+
				"if (x2>=x1) x=x1; else x=x2;\n"+
				"if (y2>=y1) y=y1; else y=y2;\n"+
				"makeRectangle(x, y, abs(x2-x1), abs(y2-y1));\n"+
				"wait(10);\n"+
			"}\n"+
			"run('To Selection');\n";
		new MacroRunner(macro, x+" "+y);
	}

    boolean roiManagerSelect(int x, int y) {
		RoiManager rm=RoiManager.getInstance();
		if (rm==null) return false;
		Hashtable rois = rm.getROIs();
		java.awt.List list = rm.getList();
		int n = list.getItemCount();
		if (labelRects==null || labelRects.length!=n) return false;
		for (int i=0; i<n; i++) {
			if (labelRects[i]!=null && labelRects[i].contains(x,y)) {
				//rm.select(i);
				// this needs to run on a separate thread, at least on OS X
				new ij.macro.MacroRunner("roiManager('select', "+i+"); roiManager('update');");
				return true;
			}
		}
		return false;
    }

	protected void setupScroll(int ox, int oy) {
		xMouseStart = ox;
		yMouseStart = oy;
		xSrcStart = srcRect.x;
		ySrcStart = srcRect.y;
	}

	protected void handlePopupMenu(MouseEvent e) {
		if (disablePopupMenu) return;
		if (IJ.debugMode) IJ.log("show popup: " + (e.isPopupTrigger()?"true":"false"));
		int x = e.getX();
		int y = e.getY();
		Roi roi = imp.getRoi();
		if (roi!=null && (roi.getType()==Roi.POLYGON || roi.getType()==Roi.POLYLINE || roi.getType()==Roi.ANGLE)
		&& roi.getState()==roi.CONSTRUCTING) {
			roi.handleMouseUp(x, y); // simulate double-click to finalize
			roi.handleMouseUp(x, y); // polygon or polyline selection
			return;
		}
		PopupMenu popup = Menus.getPopupMenu();
		if (popup!=null) {
			add(popup);
			if (IJ.isMacOSX()) IJ.wait(10);
			popup.show(this, x, y);
		}
	}
	
	public void mouseExited(MouseEvent e) {
		//autoScroll(e);
		ImageWindow win = imp.getWindow();
		if (win!=null)
			setCursor(defaultCursor);
		IJ.showStatus("");
	}

	/*
	public void autoScroll(MouseEvent e) {
		Roi roi = imp.getRoi();
		if (roi==null || roi.getState()!=roi.CONSTRUCTING || srcRect.width>=imageWidth || srcRect.height>=imageHeight
		|| !(roi.getType()==Roi.POLYGON || roi.getType()==Roi.POLYLINE || roi.getType()==Roi.ANGLE))
			return;
		int sx = e.getX();
		int sy = e.getY();
		xMouseStart = srcRect.x+srcRect.width/2;
		yMouseStart = srcRect.y+srcRect.height/2;
		Rectangle r = roi.getBounds();
		Dimension size = getSize();
		int deltax=0, deltay=0;
		if (sx<0)
			deltax = srcRect.width/4;
		else if (sx>size.width)
			deltax = -srcRect.width/4;
		if (sy<0)
			deltay = srcRect.height/4;
		else if (sy>size.height)
			deltay = -srcRect.height/4;
		//IJ.log("autoscroll: "+sx+" "+sy+" "+deltax+" "+deltay+" "+r);
		scroll(screenX(xMouseStart+deltax), screenY(yMouseStart+deltay));
	}
	*/

	public void mouseDragged(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		xMouse = offScreenX(x);
		yMouse = offScreenY(y);
		flags = e.getModifiers();
		//IJ.log("mouseDragged: "+flags);
		if (flags==0)  // workaround for Mac OS 9 bug
			flags = InputEvent.BUTTON1_MASK;
		if (Toolbar.getToolId()==Toolbar.HAND || IJ.spaceBarDown())
			scroll(x, y);
		else {
			IJ.setInputEvent(e);
			Roi roi = imp.getRoi();
			if (roi != null)
				roi.handleMouseDrag(x, y, flags);
		}
	}

	void handleRoiMouseDown(MouseEvent e) {
		int sx = e.getX();
		int sy = e.getY();
		int ox = offScreenX(sx);
		int oy = offScreenY(sy);
		Roi roi = imp.getRoi();
		int handle = roi!=null?roi.isHandle(sx, sy):-1;		
		setRoiModState(e, roi, handle);
		if (roi!=null) {
			Rectangle r = roi.getBounds();
			int type = roi.getType();
			if (type==Roi.RECTANGLE && r.width==imp.getWidth() && r.height==imp.getHeight()
			&& roi.getPasteMode()==Roi.NOT_PASTING) {
				imp.killRoi();
				return;
			}
			if (handle>=0) {
				roi.mouseDownInHandle(handle, sx, sy);
				return;
			}
			if (roi.contains(ox, oy)) {
				if (roi.modState==Roi.NO_MODS)
					roi.handleMouseDown(sx, sy);
				else {
					imp.killRoi();
					imp.createNewRoi(sx,sy);
				}
				return;
			}
			if ((type==Roi.POLYGON || type==Roi.POLYLINE || type==Roi.ANGLE)
			&& roi.getState()==roi.CONSTRUCTING)
				return;
			int tool = Toolbar.getToolId();
			if ((tool==Toolbar.POLYGON||tool==Toolbar.POLYLINE||tool==Toolbar.ANGLE)&& !(IJ.shiftKeyDown()||IJ.altKeyDown())) {
				imp.killRoi();
				return;
			}
		}
		imp.createNewRoi(sx,sy);
	}
	
	void setRoiModState(MouseEvent e, Roi roi, int handle) {
		if (roi==null || (handle>=0 && roi.modState==Roi.NO_MODS))
			return;
		if (roi.state==Roi.CONSTRUCTING)
			return;
		int tool = Toolbar.getToolId();
		if (tool>Toolbar.FREEROI && tool!=Toolbar.WAND && tool!=Toolbar.POINT)
			{roi.modState = Roi.NO_MODS; return;}
		if (e.isShiftDown())
			roi.modState = Roi.ADD_TO_ROI;
		else if (e.isAltDown())
			roi.modState = Roi.SUBTRACT_FROM_ROI;
		else
			roi.modState = Roi.NO_MODS;
		//IJ.log("setRoiModState: "+roi.modState+" "+ roi.state);
	}
	
	/** Disable/enable popup menu. */
	public void disablePopupMenu(boolean status) {
		disablePopupMenu = status;
	}

	public void setShowAllROIs(boolean showAllROIs) {
		this.showAllROIs = showAllROIs;
	}

	public boolean getShowAllROIs() {
		return showAllROIs;
	}
	
	/** Returns the color used for "Show All" mode. */
	public static Color getShowAllColor() {
			return showAllColor;
	}

	/** Sets the color used used for "Show All" mode. */
	public static void setShowAllColor(Color c) {
		if (c==null) return;
		showAllColor = c;
		labelColor = null;
		ImagePlus img = WindowManager.getCurrentImage();
		if (img!=null) {
			ImageCanvas ic = img.getCanvas();
			if (ic!=null && ic.getShowAllROIs()) img.draw();
		}
	}

	public void setDisplayList(Vector list) {
		displayList = list;
		listColor = null;
		if (list!=null&&list.size()>0&&((Roi)list.elementAt(0)).getInstanceColor()!=null)
			labelListItems = false;
		else
			labelListItems = true;
		repaint();
	}

	public void setDisplayList(Shape shape, Color color, BasicStroke stroke) {
		if (shape==null)
			{setDisplayList(null); return;}
		Roi roi = new ShapeRoi(shape);
		roi.setInstanceColor(color);
		Vector list = new Vector();
		list.addElement(roi);
		displayList = list;
		labelListItems = false;
		listColor = color;
		listStroke = stroke;
		repaint();
	}
	
	public Vector getDisplayList() {
		return displayList;
	}

	/** Called by IJ.showStatus() to prevent status bar text from
		being overwritten until the cursor moves at least 12 pixels. */
	public void setShowCursorStatus(boolean status) {
		showCursorStatus = status;
		if (status==true)
			sx2 = sy2 = -1000;
		else {
			sx2 = screenX(xMouse);
			sy2 = screenY(yMouse);
		}
	}

	public void mouseReleased(MouseEvent e) {
		flags = e.getModifiers();
		flags &= ~InputEvent.BUTTON1_MASK; // make sure button 1 bit is not set
		flags &= ~InputEvent.BUTTON2_MASK; // make sure button 2 bit is not set
		flags &= ~InputEvent.BUTTON3_MASK; // make sure button 3 bit is not set
		Roi roi = imp.getRoi();
		if (roi != null) {
			Rectangle r = roi.getBounds();
			int type = roi.getType();
			if ((r.width==0 || r.height==0)
			&& !(type==Roi.POLYGON||type==Roi.POLYLINE||type==Roi.ANGLE||type==Roi.LINE)
			&& !(roi instanceof TextRoi)
			&& roi.getState()==roi.CONSTRUCTING
			&& type!=roi.POINT)
				imp.killRoi();
			else
				roi.handleMouseUp(e.getX(), e.getY());
		}
	}

	public void mouseMoved(MouseEvent e) {
		if (ij==null) return;
		int sx = e.getX();
		int sy = e.getY();
		int ox = offScreenX(sx);
		int oy = offScreenY(sy);
		flags = e.getModifiers();
		setCursor(sx, sy, ox, oy);
		IJ.setInputEvent(e);
		Roi roi = imp.getRoi();
		if (roi!=null && (roi.getType()==Roi.POLYGON || roi.getType()==Roi.POLYLINE || roi.getType()==Roi.ANGLE) 
		&& roi.getState()==roi.CONSTRUCTING) {
			PolygonRoi pRoi = (PolygonRoi)roi;
			pRoi.handleMouseMove(ox, oy);
		} else {
			if (ox<imageWidth && oy<imageHeight) {
				ImageWindow win = imp.getWindow();
				// Cursor must move at least 12 pixels before text
				// displayed using IJ.showStatus() is overwritten.
				if ((sx-sx2)*(sx-sx2)+(sy-sy2)*(sy-sy2)>144)
					showCursorStatus = true;
				if (win!=null&&showCursorStatus) win.mouseMoved(ox, oy);
			} else
				IJ.showStatus("");

		}
	}
	
	public void mouseClicked(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}

}