/** Walter O'Dell PhD,  wodell@rochester.edu,   5/6/03
  * draws contour lines in the user-defined color and at the user-defined levels 
  * I modified greatly a contour plotting program 'Con'
	  written in C++ by Frederick Henle (fhenle@emmy.smith.edu) and
	  downloaded from: http://math.smith.edu/Local/contours.html
	  These next few lines are excerpted from the header to his program:
 ** 
	 Con is a program for writing 2-D contour plots in PostScript.
	 Copyright (C) 1994 Frederick Henle 
	 This program is free software; you can redistribute it and/or modify it
	 under the terms of the GNU General Public License as published by the Free
 **
   The basic algorithm remains the same, but the program was greatly modified 
   in converting it to Java and in the user-defined input routines.

   Known deficiencies:
   	1. labels not always seen well and/or a separate legend would be nice
   	
   Requirements:
    1. need GenericRecallableDialog class

   Also creates in the plugin directory: 
   		stateClass.class, terrainClass.class, plotClass.class
   works on stacks also.
 */

// package ij.plugin;
import java.awt.*;
import java.awt.image.*;
import ij.*;
import ij.plugin.*;
import ij.process.*;
import ij.gui.*;
import java.awt.event.*;
import java.util.*;
import ij.plugin.frame.Recorder;

class plotClass	{	/* plot description */
  String title;				/* title of plot */
  float xu, yu;       /* half the real dimensions of cells */
  int nlevels;        /* number of contour levels */
  float[] levels;			/* array of contour levels */
  String[] colors;
  plotClass() {
    xu = yu = 0.5f; // default values == 1 mm pixels
  	title = new String("ContourPlot");
  	nlevels = 10;
  	levels = new float[nlevels];
  	colors = new String[nlevels];
  }
}

class terrainClass {	/* shape of data */
  int[][] hori;			  /* array: contour passes below? */
  int[][] vert;			  /* array: contour passes to the left? */
  float[][] hy;				/* array: offset of contour below */
  float[][] vx;				/* array: offset of contour to the left */
  terrainClass(int width, int height) {
  	hori = new int[width+1][height+1];
  	vert = new int[width+1][height+1];
  	hy = new float[width+1][height+1];
  	vx = new float[width+1][height+1];
  }
}

class stateClass {
  int attitude;         /* Vertical or Horizontal? */
  int direction;        /* direction of motion */
  int ii;               /* X coord of current point on path */
  int jj;               /* Y coord of current point on path */
  stateClass() {}
}

public class Contour_Plotter implements PlugIn {
  final int DOWN=0, UP=1, RIGHT=0, LEFT=1, X=0, Y=1;
  int Direction;
  final int North=0, NorthEast=1, East=2, SouthEast=3, South=4, 
  		     SouthWest=5, West=6, NorthWest=7, NumDirections=8;
  int Attitude;
  final int Horizontal=0, Vertical=1;	
  String Label;
  plotClass plot;			/* plot description, reused for each */
  float[][] pixels;       
  float[][][][] contours;	// [image slice][level][pts][xy]   
  private ImagePlus imp;  // set in setup()
  private ImageCanvas Parent_Canvas; 
  private Graphics Parent_Graphics;
	int nSlices, ImgWidth, ImgHeight;
	boolean DebugOn = false;
	final String[] colors = {"green","blue","red","black","white","yellow","orange","cyan"};

  public void run(String arg) {
	if (arg.equals("about"))  {showAbout(); return;}
    imp = WindowManager.getCurrentImage();
    if (imp==null) {
     	IJ.write("Error: no image defined..");
     	return;
   	}
    	//ImageStack imgStack = imp.getStack(); 
    Label = new String("Contour Plotter");
    ImageProcessor ip = imp.getProcessor();
	nSlices = imp.getStack().getSize(); // == nslices
	ImgWidth = ip.getWidth();
	ImgHeight = ip.getHeight();
    Parent_Canvas = imp.getWindow().getCanvas(); 
    Parent_Graphics = Parent_Canvas.getGraphics(); 
		// create gui to enter levels
	GenericRecallableDialog gd = new GenericRecallableDialog(
 				"Contour Plotter Levels", IJ.getInstance());
 	gd.addMessage("Enter level value for each contour\n"+
 			"   Range: "+IJ.d2s(ip.getMin(),1)+" ... "+IJ.d2s(ip.getMax(),1)+
 			"\n   (Enter -1 for no level)");
 	plot = new plotClass();
 	contours = new float[nSlices][plot.nlevels][][]; // will be allocated fully later
 	for (int i=0; i<plot.nlevels; i++) plot.levels[i] = -1f;
 	plot.levels[0] = 95f;
 	plot.levels[1] = 90f;
 	plot.levels[2] = 75f;
 	plot.levels[3] = 50f;
 	for (int i=0; i<plot.nlevels; i++) {
 	  gd.beginRowOfItems();
 	  gd.addNumericField("Level "+(i+1), (double)(plot.levels[i]), 0);
 	  plot.colors[i] = colors[i%colors.length];
 	  gd.addChoice("Color "+(i+1), colors, plot.colors[i]);
 	  gd.endRowOfItems();
	}
	gd.addButtons("Redisplay","Help");
	gd.addButton("Make Contour Plot");
	gd.showDialog(); 
	int cur_slice = 0, prev_slice = 0;
	while (!(gd.wasCanceled())) { // continuously runs through loop
		cur_slice = imp.getCurrentSlice();
		if (cur_slice != prev_slice) {
			prev_slice = cur_slice;
			ReDisplay(cur_slice);
		}
		if (gd.getButtonValue(0)) ReDisplay(cur_slice);
	 	if (gd.getButtonValue(1)) showAbout();
	  	if (gd.getButtonValue(2)) {
   		  for (int i=0; i<plot.nlevels; i++) {
 	  		plot.levels[i] = (float)(gd.getNextNumber());
 			plot.colors[i] = gd.getNextChoice();
 	  	  }
	   	  getContours(plot);
	      ReDisplay(cur_slice);
	   	  IJ.wait(1000);
	  	}
		IJ.wait(500);
	}// end while
  }
	
  void showAbout()  {
    String displayStr = Label+ ": This performs user-defined contour plots.      \n" 
      + "  To apply new levels or colors hit the 'Make Contour Plot' button.     \n"
      + "  To refresh the preexisting display hit the 'Redisplay' button.         ";
	IJ.showMessage(displayStr);
  } // end showAbout()

  public String toString() {
  	return (" "+Label);
  }
	
  // this should be in the Math class, but isn't
  public float sqr(float x) { return x*x; }
	
  public void ReDisplay(int slice) {
	imp.updateAndDraw();
	IJ.wait(10); // give system time to clear screen
	DrawContours(slice-1);
  }
	
  public void getContours(plotClass plot) {
    terrainClass terrain;
    float[][]pixels = new float[ImgWidth][ImgHeight];
    ImageProcessor ip;
		for (int slice=0; slice<nSlices; slice++) {
		  ip = imp.getStack().getProcessor(slice+1);
		  for (int w=0; w<ImgWidth; w++) 
		  	for (int h=0; h<ImgHeight; h++) 
			  pixels[w][h] = ip.getPixelValue(w, h);
    	  for (int k=0; k<plot.nlevels; k++) {
     	  	contours[slice][k] = null; // erase previous versions
   	  		if (plot.levels[k]>=0f) { 
    	  	  terrain = getTerrain (plot.levels[k], plot, pixels);
    	  	  for (int i = 0; i < ImgWidth; i++) 		 // columns
    	      	for (int j = 0; j < ImgHeight; j++) {   // rows
    	      	  if (terrain.vert[i][j] == LEFT)
    	      	  	contours[slice][k] = getContour(contours[slice][k], 
    	      	  							Vertical, North, i, j, plot, terrain);
    	      	  if (terrain.hori[i][j] == UP)
		          	contours[slice][k] = getContour(contours[slice][k], 
		          							Horizontal, East, i, j, plot, terrain);
		  		}
    		} 
    	} // end levels
   	} // end slices
  }

  protected terrainClass getTerrain (float conValue, plotClass plot, float[][] pixels) {
   	float xu = plot.xu;            /* half real width of cell */
   	float yu = plot.yu;            /* half real height of cell */
   	int width = pixels.length;		  /* number of columns of data */
   	int height = pixels[0].length;      /* number of rows of data */
		terrainClass tp = new terrainClass(width, height);
		
    /* map edges first */
    for (int j = 0; j < height; j++) {
      if (pixels[0][j] >= conValue) {
				tp.vert[0][j] = RIGHT;
		    tp.vx[0][j] = xu;
  		}
    	else  tp.vert[0][j] = -1;
    	if (pixels[width-1][j] >= conValue) {
		   	tp.vert[width][j] = LEFT;
		    tp.vx[width][j] = -xu;
  		}
    	else  tp.vert[width][j] = -1;
    }
	  for (int i = 0; i < width; i++) {
	    if (pixels[i][0] >= conValue) {
		    tp.hori[i][0] = UP;
		   	tp.hy[i][0] = yu;
		  }
	    else  tp.hori[i][0] = -1;
	    if (pixels[i][height-1] >= conValue) {
		    tp.hori[i][height] = DOWN;
		    tp.hy[i][height] = -yu;
		  }
	    else  tp.hori[i][height] = -1;
	  }
	  /* now map interior */
	  for (int i = 1; i < width; i++) 
	    for (int j = 0; j < height; j++) {
	 	    if (pixels[i][j] <= conValue) {
		      if (pixels[i-1][j] >= conValue && pixels[i-1][j] != pixels[i][j]) {
		        tp.vert[i][j] = LEFT;
		        tp.vx[i][j] = (xu * (pixels[i-1][j] + pixels[i][j] - 2 * conValue) / 
		      					(pixels[i-1][j] - pixels[i][j]));
		      }
		      else tp.vert[i][j] = -1;
		    }
		    if (pixels[i][j] >= conValue) {
		      if (pixels[i-1][j] <= conValue && pixels[i-1][j] != pixels[i][j]) {
		        tp.vert[i][j] = RIGHT;
		        tp.vx[i][j] = (xu * (pixels[i-1][j] + pixels[i][j] - 2 * conValue) / 
		      					(pixels[i-1][j] - pixels[i][j]));
		      }
		      else tp.vert[i][j] = -1;
		    }
	    }
	  for (int i = 0; i < width; i++) 
   	 	for (int j = 1; j < height; j++) {
		    if (pixels[i][j] <= conValue) {
		      if (pixels[i][j-1] >= conValue && pixels[i][j-1] != pixels[i][j]) {
		        tp.hori[i][j] = DOWN;
		        tp.hy[i][j] = (yu * (pixels[i][j-1] + pixels[i][j] - 2 * conValue) / 
		      					(pixels[i][j-1] - pixels[i][j]));
		      }
		      else tp.hori[i][j] = -1;
		    }
		    if (pixels[i][j] >= conValue) {
		      if (pixels[i][j-1] <= conValue && pixels[i][j-1] != pixels[i][j]) {
		        tp.hori[i][j] = UP;
		        tp.hy[i][j] = (yu * (pixels[i][j-1] + pixels[i][j] - 2 * conValue) / 
		      					(pixels[i][j-1] - pixels[i][j]));
		      }
		      else tp.hori[i][j] = -1;
		    }
	    }
	  return tp;
	} // end mapterrain

  public float[][] getContour(float[][] prevpts, int verthort, int direction, int i, int j, 
  								plotClass plot, terrainClass tp) {
    boolean is_next;            /* GetNext () says contour continues */
    float xu = plot.xu;         /* half real width of cell */
    float xw = 2 * xu;          /* real width of cell */
    float yu = plot.yu;         /* half real height of cell */
    float yw = 2 * yu;          /* real height of cell */
    int npts = 0;               /* number of points on current path */
    stateClass s = new stateClass(); /* state of path search */
    s.attitude = verthort;		  /* preparations */
    s.direction = direction;
    s.ii = i;
    s.jj = j;
   	float[][] pts = new float[100][2];
	
	  if (verthort==Vertical)  pts[npts][X] = i * xw + tp.vx[i][j];
    else pts[npts][X] = i * xw + xu;
    if (verthort==Vertical)  pts[npts][Y] =  j * yw + yu;
    else pts[npts][Y] =  j * yw + tp.hy[i][j];
    npts++;
    if (verthort==Vertical)  tp.vert[i][j] = -1;
    else tp.hori[i][j] = -1;      
    is_next = GetNext (ImgWidth, ImgHeight, tp, s);
    while (is_next) {
     	if (s.attitude == Vertical) {
   			pts[npts][X] = s.ii * xw + tp.vx[s.ii][s.jj];
   			pts[npts][Y] = s.jj * yw + yu;
   		}
    	else {
       	pts[npts][X] = s.ii * xw + xu;
       	pts[npts][Y] = s.jj * yw +tp.hy[s.ii][s.jj];
      }
      if (s.attitude == Vertical)
	  		tp.vert[s.ii][s.jj] = -1;
      else tp.hori[s.ii][s.jj] = -1;
      npts++;
      if (npts >= pts.length) { // increase array size
      	float[][] tmppts = new float[pts.length*2][2];
      	for (int m=0; m<pts.length; m++)  {
      		tmppts[m][X] = pts[m][X];
      		tmppts[m][Y] = pts[m][Y];
      	}
      	pts = tmppts;
	  	}
      is_next = GetNext(ImgWidth, ImgHeight, tp, s);
    } // end while loop 
    if (verthort==Vertical) tp.vert[i][j] = -1;
   	else  tp.hori[i][j] = -1;
   	 	 	
   	// addpts to existing conpts array, or start new one
   	if (prevpts == null) {
   		float[][] tmppts = new float[npts][2];
        for (int m=0; m<npts; m++)  {
      		tmppts[m][X] = pts[m][X];
      		tmppts[m][Y] = pts[m][Y];
      	}
      	return tmppts;
    }
   	if (npts<=3) return prevpts; // this algorithum is prone to losing 1 or 2 pixels
   	float[][] tmppts = new float[prevpts.length+npts][2];
      for (int m=0; m<prevpts.length; m++)  {
      	tmppts[m][X] = prevpts[m][X];
      	tmppts[m][Y] = prevpts[m][Y];
      }
      for (int m=0; m<npts; m++)  {
      	tmppts[prevpts.length+ m][X] = pts[m][X];
      	tmppts[prevpts.length+ m][Y] = pts[m][Y];
      }
      return tmppts;
  }
	
	public void DrawContours(int slice_at) {
		int ci;
		for (int i=0; i<contours[0].length; i++) {
		  for(ci=0; ci<colors.length && plot.colors[i]!=colors[ci]; ci++) ;
		  switch (ci) {
		    case 0:// "green":
	  	  		Parent_Graphics.setColor(java.awt.Color.green); break;
			case 1:// "blue":
	  	  		Parent_Graphics.setColor(java.awt.Color.blue); break;
			case 2:// "red":
	  	  		Parent_Graphics.setColor(java.awt.Color.red); break;
			case 3:// "black":
	  	  		Parent_Graphics.setColor(java.awt.Color.black); break;
			case 4:// "white":
	  	  		Parent_Graphics.setColor(java.awt.Color.white); break;
			case 5:// "yellow":
	  	  		Parent_Graphics.setColor(java.awt.Color.yellow); break;
			case 6:// "orange":
	  	  		Parent_Graphics.setColor(java.awt.Color.orange); break;
			case 7:// "purple":
	  	  		Parent_Graphics.setColor(java.awt.Color.cyan); break;
	  	  default:
				Parent_Graphics.setColor(java.awt.Color.green); break;
		  }
		  if (contours[slice_at][i]!=null && contours[slice_at][i].length>0) 
				DrawArray(contours[slice_at][i], IJ.d2s(plot.levels[i], 0));
		}
	}
	
  public void DrawArray(float[][] pts, String label) {
		int pt_right, Npts = pts.length;
		final int X=0, Y=1;
		int pt_at;
		for(pt_at=0;pt_at<Npts;pt_at++) {
			pt_right = pt_at +1;
			if( pt_right>=Npts ) pt_right = 0;
			if (pts[pt_at]==null) break;
			if (pts[pt_right]==null) break;
			// put separation between distict regions -- defined as separated
			// by > 2 pixels
			if (Math.sqrt(sqr(pts[pt_right][X] - pts[pt_at][X]) +
						  sqr(pts[pt_right][Y] - pts[pt_at][Y]) ) < 2f)
			  Parent_Graphics.drawLine(
					(int)(Parent_Canvas.screenX((int)(pts[pt_right][X]+0.5))), 
					(int)(Parent_Canvas.screenY((int)(pts[pt_right][Y]+0.5))),
					(int)(Parent_Canvas.screenX((int)(pts[pt_at][X]+0.5))), 
					(int)(Parent_Canvas.screenY((int)(pts[pt_at][Y]+0.5))));
		}
		// print text at end of contour
		if (label == null || Npts == 0) return;
		Parent_Graphics.drawString(label, 
					(int)(Parent_Canvas.screenX((int)(pts[Npts-1][X]+0.5))), 
					(int)(Parent_Canvas.screenY((int)(pts[Npts-1][Y]+0.5))) );
	}

  /* GetNext () finds the next direction for the contour path. */
  boolean GetNext(int nx, int ny, terrainClass tp, stateClass sp) {
    int next;               /* proposed next value for sp.direction */
    int turn;               /* directional offset from sp.direction */

    for (turn = 2; turn >= -2; turn--) {
      /* mod to fix abnormal termination sgi by 
       * P. B. Gharpure (gharpure@vissgi.cvrti.utah.edu)
       */
      next = (sp.direction + turn + NumDirections) % NumDirections;
      switch (next) {
        case North:
          if (sp.attitude == Vertical) {
            if (sp.jj < ny - 1 && tp.vert[sp.ii][sp.jj+1] == LEFT) {
              sp.direction = North;
              sp.jj++;
              return true;
            }
            else break;
          }
          else                  /* Horizontal */
            break;

        case NorthEast:
          if (sp.attitude == Vertical) {
            if (sp.ii < nx && tp.hori[sp.ii][sp.jj+1] == UP) {
              sp.direction = NorthEast;
              sp.attitude = Horizontal;
              sp.jj++;
              return true;
            }
            else  break;
          }
          else      {            /* Horizontal */
            if (sp.jj < ny && tp.vert[sp.ii+1][sp.jj] == LEFT) {
              sp.direction = NorthEast;
              sp.attitude = Vertical;
              sp.ii++;
              return true;
            }
            else break;
          }

        case East:
          if (sp.attitude == Vertical) 
            break;
          else {               /* Horizontal */
            if (sp.ii < nx - 1 && tp.hori[sp.ii+1][sp.jj] == UP) {
              sp.direction = East;
              sp.ii++;
              return true;
            }
            else break;
          }
        case SouthEast: 
          if (sp.attitude == Vertical) {
            if (sp.ii < nx && tp.hori[sp.ii][sp.jj] == UP) {
              sp.direction = SouthEast;
              sp.attitude = Horizontal;
              return true;
            }
            else break;
          }
          else     {             /* Horizontal */
            if (sp.jj > 0 && tp.vert[sp.ii+1][sp.jj-1] == RIGHT) {
              sp.direction = SouthEast;
              sp.attitude = Vertical;
              sp.jj--;
              sp.ii++;
              return true;
            }
         	 	else break;
					}
        case South:
          if (sp.attitude == Vertical) {
            if (sp.jj > 0 && tp.vert[sp.ii][sp.jj-1] == RIGHT) {
              sp.direction = South;
              sp.jj--;
              return true;
            }
            else break;
          }
          else                  /* Horizontal */
            break;

        case SouthWest:
          if (sp.attitude == Vertical) {
            if (sp.ii > 0 && tp.hori[sp.ii-1][sp.jj] == DOWN) {
              sp.direction = SouthWest;
              sp.attitude = Horizontal;
              sp.ii--;
              return true;
            }
            else break;
          }
          else {                 /* Horizontal */
            if (sp.jj > 0 && tp.vert[sp.ii][sp.jj-1] == RIGHT) {
              sp.direction = SouthWest;
              sp.attitude = Vertical;
              sp.jj--;
              return true;
            }
          	else break;
					}
        case West:
          if (sp.attitude == Vertical)
            break;
          else {		/* Horizontal */
	        	if (sp.ii > 0 && tp.hori[sp.ii-1][sp.jj] == DOWN) {
		      		sp.direction = West;
		      		sp.ii--;
		     			return true;
	        	}
	        	else break;
					}
        case NorthWest:
          if (sp.attitude == Vertical) {
            if (sp.ii > 0 && tp.hori[sp.ii-1][sp.jj+1] == DOWN) {
              sp.direction = NorthWest;
              sp.attitude = Horizontal;
              sp.jj++;
              sp.ii--;
              return true;
            }
            else break;
          }
          else {                 /* Horizontal */
            if (sp.jj < ny && tp.vert[sp.ii][sp.jj] == LEFT) {
              sp.direction = NorthWest;
              sp.attitude = Vertical;
              return true;
            }
            else break;
          }
	    default:
	      IJ.write("internal error in GetNext ()\n");
        return false;
      }
    }
    return false;     /* couldn't find good next direction */
  }
} // end class ContourPlotter

// package ij.gui;  if integrated into ImageJ proper, would likely belong to the ij.gui group
/* import java.awt.*;
import java.awt.event.*;
import ij.*;
import ij.gui.*;
import java.util.*;
import ij.plugin.frame.Recorder;
*/

/** Walter O'Dell PhD,  wodell@rochester.edu,   11/6/02
  * Overview of GenericDialogPlus and GenericRecallableDialog classes:
  *		these classes enable the specification of scrollbars, buttons, and
  *		the objects available in the GenericDialog class that remain active 
  *		until the dialog window is actively closed.
  *
  * Attributes:
  *		Scrollbars: enables scrollbars for integer, float and/or double values.
  *				float/double values are facilitated by maintaining a scaling factor 
  *				for each scrollbar, and an Ndigits parameter.
  *				Keyboard arrow keys adjust values of most recently mouse-activated scrollbar.
  *		Buttons: enables buttons that perform actions (besides 'exit')
  *
  *		rowOfItems(): enables placement of multiple objects in a row, 
  *				e.g. the scrollbar title, current value and the scrollbar itsself.
  *		addButtons(): enables easy specification of multiple buttons across a row
  *		getBooleanValue(int index_to_list); getNumericValue(); getButtonValue(); getScrollbarValue()
  *				enables access to the value of any individual object without having to go 
  *				through the entire list of like-objects with getNext...() functions.
  *
  * minor changes to the parent GenericDialog were needed, as described in the header 
  *	for the temporary GenericDialog2 class, namely:
	*   1. Changed 'private' attribute to 'protected' for several variables in parent 
	*			 class to facilitate use of these variables by GenericDialogPlus class
	*		2. Added variables (int) x; // GridBagConstraints height variable
	*									and  (Container) activePanel; // facilitates row of items option
	*				and code altered to initialize and update these new variables.
	* It is hoped that these modifications will be made to the parent GenericDialog class
	* in future versions of ImageJ, negating the need for the GenericDialog2 class
  **/
class GenericDialogPlus extends GenericDialog2 
					implements AdjustmentListener, KeyListener, FocusListener {
	/** Maximum number of each component (numeric field, checkbox, etc). */
	public static final int MAX_ITEMS = 20;
	protected Scrollbar[] scrollbars;
	protected double[] SBscales; 
	protected double[] SBcurValues; 
	private Label[] SBcurValueLabels; 
  protected int sbIndex, SBtotal;
  private int[] SBdigits; // Ndigits to right of decimal pt (0==integer)
  protected int x;
  protected int SBlastTouched; // last scrollbar touched; needed for arrow key usage

	// second panel(s) enables placement of multiple items on same line (row)
	private Panel twoPanel;
	private GridBagConstraints tmpc;
	private GridBagLayout tmpgrid;
	private int tmpy;
	
	public GenericDialogPlus(String title) {
		super(title);
	}

  /** Creates a new GenericDialog using the specified title and parent frame. */
  public GenericDialogPlus(String title, Frame parent) {
		super(title, parent);
	}

  /** access the value of the i'th checkbox */
  public boolean getBooleanValue(int i) {
		if (checkbox==null)
			return false;
		// else
		Checkbox cb = (Checkbox)(checkbox.elementAt(i));
		return cb.getState();
  }

  /** access the value of the i'th numeric field */
  public double getNumericValue(int i) {
		if (numberField==null)
			return 0;
		// else
		TextField tf = (TextField)numberField.elementAt(i);
		String theText = tf.getText();
		String originalText = (String)defaultText.elementAt(i);
		double defaultValue = ((Double)(defaultValues.elementAt(i))).doubleValue();
		double value;
		if (theText.equals(originalText))
			value = defaultValue;
		else {
			Double d = getValue(theText);
			if (d!=null)
				value = d.doubleValue();
			else {
				// invalidNumber = true;
				value = 0.0;
			}
		}
		return value;
  }

  public void beginRowOfItems() {
   	tmpc = c; tmpgrid = grid;  tmpy = y;
   	twoPanel = new Panel();
   	activePanel = twoPanel;
   	grid = new GridBagLayout();
		twoPanel.setLayout(grid);
   	c = new GridBagConstraints();
   	x = y = 0;
	}
	public void endRowOfItems() {
		activePanel = this;
		c = tmpc;  grid = tmpgrid;  y = tmpy;
   	c.gridwidth = 1;
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(0, 0, 0, 0);
		grid.setConstraints(twoPanel, c);
		add(twoPanel);
		x = 0;
		y++;
	}

 /** Adds adjustable scrollbar field.
	* param label	the label
	* param defaultValue	initial state
	* param digits	the number of digits to the right of the decimal place
	* param minval	the range minimum (left side value of slider)
	* param maxval	the range maximum (right side value of slider)
	*/    
  public void addScrollBar(String label, double defaultValue, int digits,
   					double minval, double maxval) {
   	// use default 100 clicks
    addScrollBar(label, defaultValue, digits, minval, maxval, 100);
  }
  public void addScrollBar(String label, double defaultValue, int digits,
   					double minval, double maxval, int maxClicks) {
   	if (sbIndex >= MAX_ITEMS) {
   		IJ.write("  cannot add another slider, have maxed out at: "+sbIndex);
   		return;
   	}
    if (scrollbars==null) {
    	scrollbars = new Scrollbar[MAX_ITEMS]; 
			SBscales = new double[MAX_ITEMS]; 
			SBcurValues = new double[MAX_ITEMS]; 
			SBcurValueLabels = new Label[MAX_ITEMS]; 
			SBdigits = new int[MAX_ITEMS];
		}    		
		// create new panel that is 3 cells wide for SBlabel, SB, SBcurVal
		Panel sbPanel = new Panel();
		GridBagLayout sbGrid = new GridBagLayout();
		GridBagConstraints sbc  = new GridBagConstraints();
		sbPanel.setLayout(sbGrid);

   	// label
		Label theLabel = new Label(label);
		sbc.insets = new Insets(5, 0, 0, 0);
		sbc.gridx = 0; sbc.gridy = 0;
		sbc.anchor = GridBagConstraints.WEST;
		sbGrid.setConstraints(theLabel, sbc);
		sbPanel.add(theLabel);
		
		// scrollbar: only works with integer values so use scaling to mimic float/double
		SBscales[sbIndex] = Math.pow(10.0, digits);
		SBcurValues[sbIndex] = defaultValue;
		int visible = (int)Math.round((maxval-minval)* SBscales[sbIndex]/10.0);
		scrollbars[sbIndex] = new Scrollbar(Scrollbar.HORIZONTAL, 
					(int)Math.round(defaultValue*SBscales[sbIndex]), 
					visible, /* 'visible' == width of bar inside slider == 
						  increment taken when click inside slider window */
					(int)Math.round(minval*SBscales[sbIndex]), 
					(int)Math.round(maxval*SBscales[sbIndex] +visible) );
					/* Note that the actual maximum value of the scroll bar is 
					the maximum minus the visible. The left side of the bubble 
					indicates the value of the scroll bar. */
		scrollbars[sbIndex].addAdjustmentListener(this);
		scrollbars[sbIndex].setUnitIncrement(Math.max(1,
				(int)Math.round((maxval-minval)*SBscales[sbIndex]/maxClicks)));
		sbc.gridx = 1;
		sbc.ipadx = 75; // set the scrollbar width (internal padding) to 75 pixels
		sbGrid.setConstraints(scrollbars[sbIndex], sbc);
		sbPanel.add(scrollbars[sbIndex]);
		sbc.ipadx = 0;  // reset
		
		// current value label
		SBdigits[sbIndex] = digits;
		SBcurValueLabels[sbIndex] = new Label(IJ.d2s(SBcurValues[sbIndex], digits));

		sbc.gridx = 2;
		sbc.insets = new Insets(5, 5, 0, 0);
		sbc.anchor = GridBagConstraints.EAST;
		sbGrid.setConstraints(SBcurValueLabels[sbIndex], sbc);
		sbPanel.add(SBcurValueLabels[sbIndex]);
		
		c.gridwidth = 2; // this panel will take up one grid in overall GUI
		c.gridx = x; c.gridy = y;
		c.insets = new Insets(0, 0, 0, 0);
		c.anchor = GridBagConstraints.CENTER;
		grid.setConstraints(sbPanel, c);
		activePanel.add(sbPanel);

		sbIndex++; 
		if (activePanel == this) { x=0; y++; }
		else x++;
		SBtotal = sbIndex;
  } // end scrollbar field

  public void setScrollBarUnitIncrement(int inc) {
    scrollbars[sbIndex-1].setUnitIncrement(inc);
	}
	
	/** Returns the contents of the next scrollbar field. */
  public double getNextScrollBar() {
		if (scrollbars[sbIndex]==null)
			return -1.0;
		else return SBcurValues[sbIndex++];
  }
  /** Returns the contents of scrollbar field 'i' */
  public double getScrollBarValue(int i) {
		if (i<0 || i>=SBtotal || scrollbars[i]==null)
			return -1.0;
		else return SBcurValues[i];
  }
  /** Sets the contents of scrollbar field 'i' to 'value' */
  public void setScrollBarValue(int i, double value) {
		if (i<0 || i>=SBtotal || scrollbars[i]==null)  return;
		scrollbars[i].setValue((int)Math.round(value*SBscales[i]));
		SBcurValues[i] = value;
		SBcurValueLabels[i].setText(IJ.d2s(SBcurValues[i], SBdigits[i]));
  }

  public synchronized void adjustmentValueChanged(AdjustmentEvent e) {
		for (int i=0; i<SBtotal; i++) {
		  if (e.getSource()==scrollbars[i]) {
				SBcurValues[i] = scrollbars[i].getValue()/ SBscales[i];
		    setScrollBarValue(i,SBcurValues[i]);
		    SBlastTouched = i; // set keyboard input to be directed to this scrollbar
		  }
		}
		sbIndex = 0; // reset for next call to getNextScrollBar()
	}
	 
  /** Displays this dialog box. */
  public void showDialog() {
		sbIndex = 0; 
		super.showDialog();
  }
}

/** create a GenericDialog that remains active to allow for repeated 
  * runs through target program, enabling the use of add-on buttons to 
  * perform different and repeatable functions.
  * See code at bottom and WindowLevelAdjuster class for implementation examples 
 	*/
class GenericRecallableDialog extends GenericDialogPlus
			implements AdjustmentListener, KeyListener, FocusListener {
	private Button[] buttons = new Button[MAX_ITEMS]; 
	private boolean[] buttons_touched = new boolean[MAX_ITEMS]; 
	private int butIndex, butTot;
	Thread thread;
	public final int WEST=0, CENTER=1; // location flags

	public GenericRecallableDialog(String title) {
 		super(title);
 		setModal(false);
 	}
 	
 	public GenericRecallableDialog(String title, Frame parent) {
		super(title, parent);
 		setModal(false);
  }

  /** changes from parent showDialog(): remove accept button */
  public void showDialog() {
		nfIndex = 0;
		sfIndex = 0;
		cbIndex = 0;
		choiceIndex = 0;
		sbIndex = 0;
		butIndex = 0;
		if (macro) {
			//IJ.write("showDialog: "+macroOptions);
			dispose();
			return;
		}
    if (stringField!=null&&numberField==null) {
    	TextField tf = (TextField)(stringField.elementAt(0));
    	tf.selectAll();
    }
		cancel = new Button(" Done "); // changed from "Cancel"
		cancel.addActionListener(this);
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.CENTER;
		c.insets = new Insets(5, 0, 0, 0); // top,left,bot,right coords
		grid.setConstraints(cancel, c);
		add(cancel);
    if (IJ.isMacintosh())
      setResizable(false);
		pack();
		GUI.center(this);
		setVisible(true);
		IJ.wait(250); // work around for Sun/WinNT bug
  }

	/** the keyboard input (arrow keys) will be caught by whichever button 
		* has the current focus, but will affect the scrollbar last touched	*/
	public void keyPressed(KeyEvent e) {
		int keyCode = e.getKeyCode();
		IJ.setKeyDown(keyCode);
		if (scrollbars[SBlastTouched] != null) {
			// left is 37, right is 39;  numpad4 is 100, numpad6 is 102
			if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_NUMPAD4) {
				SBcurValues[SBlastTouched] -= 
						scrollbars[SBlastTouched].getUnitIncrement()/SBscales[SBlastTouched];
				setScrollBarValue(SBlastTouched,SBcurValues[SBlastTouched]);
			}
			if (keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_NUMPAD6) {
				SBcurValues[SBlastTouched] += 
						scrollbars[SBlastTouched].getUnitIncrement()/SBscales[SBlastTouched];
				setScrollBarValue(SBlastTouched,SBcurValues[SBlastTouched]);
			}
		}
	}

	public void actionPerformed(ActionEvent e) {
		wasCanceled = (e.getSource()==cancel);
		nfIndex = 0; // reset these so that call to getNext...() will work
		sfIndex = 0;
		cbIndex = 0;
		sbIndex = 0; 
		choiceIndex = 0;
		for (int i=0; i<butTot; i++)
			buttons_touched[i] = (e.getSource()==buttons[i]);
		butIndex = 0;
		if (wasCanceled) { setVisible(false); dispose(); }
	}

  /** Adds a button to the dialog window */
  public void addButton(String text) {
    	addButton(text, WEST);
  }
  public void addButton(String text, int location) {
   	if (butIndex >= MAX_ITEMS) {
   		IJ.write("  cannot add another button, have maxed out at: "+butIndex);
   		return;
   	}
		buttons[butIndex] = new Button(text);
		buttons[butIndex].addActionListener(this);
		buttons[butIndex].addKeyListener(this); // for scrollbar keyboard control

		c.gridwidth = 1;
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		if (location == CENTER)
		  c.anchor = GridBagConstraints.CENTER;
		c.insets = new Insets(5, 0, 5, 0);
		grid.setConstraints(buttons[butIndex], c);
		activePanel.add(buttons[butIndex]);
		buttons_touched[butIndex] = false;
		butIndex++; butTot = butIndex; y++;
  }

  /** adds 2(3,4) buttons in a row to the dialog window. 
  	* Easily extendable to add more buttons */
  public void addButtons(String text1, String text2) {
   	Panel butPanel = new Panel();
   	GridBagLayout butGrid = new GridBagLayout();
		butPanel.setLayout(butGrid);
   	addButtonToPanel(text1, butPanel, butGrid, 0);
   	addButtonToPanel(text2, butPanel, butGrid, 1);
   	c.gridwidth = 2;
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(5, 0, 5, 0);
		grid.setConstraints(butPanel, c);
		activePanel.add(butPanel);
		y++;
	}
  public void addButtons(String text1, String text2, String text3) {
   	Panel butPanel = new Panel();
   	GridBagLayout butGrid = new GridBagLayout();
		butPanel.setLayout(butGrid);
   	addButtonToPanel(text1, butPanel, butGrid, 0); // label, panel, row in grid
   	addButtonToPanel(text2, butPanel, butGrid, 1);
   	addButtonToPanel(text3, butPanel, butGrid, 2);
   	c.gridwidth = 2;
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(5, 0, 5, 0);
		grid.setConstraints(butPanel, c);
		activePanel.add(butPanel);
		y++;
	}
  public void addButtons(String text1, String text2, String text3, String text4){
   	Panel butPanel = new Panel();
   	GridBagLayout butGrid = new GridBagLayout();
		butPanel.setLayout(butGrid);
   	addButtonToPanel(text1, butPanel, butGrid, 0); // label, panel, row in grid
   	addButtonToPanel(text2, butPanel, butGrid, 1);
   	addButtonToPanel(text3, butPanel, butGrid, 2);
   	addButtonToPanel(text4, butPanel, butGrid, 3);
   	c.gridwidth = 1;
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(5, 0, 5, 0);
		grid.setConstraints(butPanel, c);
		activePanel.add(butPanel);
		y++;
	}
  public void addButtonToPanel(String text, Panel panel, 
    							 GridBagLayout grid, int row) {
    if (butIndex >= MAX_ITEMS) {
   		IJ.write("  cannot add another button, have maxed out at: "+butIndex);
   		return;
   	}
		GridBagConstraints butc  = new GridBagConstraints();
		buttons[butIndex] = new Button(text);
		buttons[butIndex].addActionListener(this);	
		buttons[butIndex].addKeyListener(this);	
		butc.gridx = row; butc.gridy = 0;
		butc.anchor = GridBagConstraints.WEST;
		butc.insets = new Insets(0, 5, 0, 10);
		grid.setConstraints(buttons[butIndex], butc);
		panel.add(buttons[butIndex]);
		buttons_touched[butIndex] = false;
		butIndex++; butTot = butIndex; 
  }

  /** Returns the contents of the next buttons_touched field. */
  public boolean getNextButton() {
		if (butIndex>=butTot)
			return false;
		if (buttons_touched[butIndex]) {
			buttons_touched[butIndex++] = false;
			return true;
		}
		butIndex++;
		return false; // else
  }

  /** Returns the contents of button 'i' field. */
  public boolean getButtonValue(int i) {
		if (i<0 || i>=butTot)
			return false;
		else if (!buttons_touched[i]) 
			return false; 
		buttons_touched[i] = false; // reset vale to false
		return true; // else
  }
}

/** example implementation for a GenericRecallableDialog
    excerpted from an InterActiveSnake program

 		GenericRecallableDialog gd = new GenericRecallableDialog("Snake Attributes", IJ.getInstance());
		gd.addScrollBar("Bending Energy Coeff", curEnergy_Bend, 1, 0.0, 50.0);
													// label, 				curVal,      ndigits, min, max
		gd.addScrollBar("Stretch Energy Coeff", curEnergy_Stretch, 1, 0.0, 10.0);
		gd.addScrollBar("Snake Point Sep[mm]", cur_ptSep, 0, 1.0, 15.0);
		gd.addButtons("Screen Select Mode", "Regularize Spacing");
		gd.addButtons("Save Slice", "Save All", "Redisplay", "Help");
		gd.addButton("Recompute Snake",gd.CENTER);
		gd.addCheckbox("Show Snake Point Labels",ShowLabels);
		gd.showDialog(); 
		IJ.wait(500); // give system time to initialize GUI
		while (!(gd.wasCanceled())) { // continuously runs through loop until actively canceled
		  // if this dialog window does not have the focus, don't waste CPU time on it
		  if (gd.getFocusOwner() == null) IJ.wait(500); 
		  else {
		  	// check the "Screen Select Mode" button
				if (gd.getButtonValue(0)) Toolbar.getInstance().setTool(Toolbar.CROSSHAIR);
				
				// check for new values of the scrollbars
		  	prev_ptSep = cur_ptSep;
		  	prev_Ebend = curEnergy_Bend;
		  	prev_Estretch = curEnergy_Stretch;
		  	curEnergy_Bend = (float)(gd.getScrollBarValue(0)); 
		  	curEnergy_Stretch = (float)(gd.getScrollBarValue(1));
		  	cur_ptSep = (float)(gd.getScrollBarValue(2));
		  	
		  	// if new values were found, or if the user hits either the "Regularize Spacing"
		  	//  button or the "Recompute Snake" button, then do new calculation
				if (gd.getButtonValue(1) || gd.getButtonValue(6) || prev_ptSep!=cur_ptSep
							|| prev_Ebend!=curEnergy_Bend || prev_Estretch!=curEnergy_Stretch) {
		    	UpdateAttributes(curEnergy_Bend, curEnergy_Stretch, cur_ptSep);
			  	IJ.wait(500); // give snake time to work
		  	}
		  	
		  	// check the other buttons for activity
				if (gd.getButtonValue(2)) snakeStack.Write("this");
				if (gd.getButtonValue(3)) snakeStack.Write("all");
				if (gd.getButtonValue(4)) working_canvas.update(working_graphics);
				if (gd.getButtonValue(5)) showAbout();
				
				// check the checkbox for activity -- note the order of checking does not matter
				if (gd.getBooleanValue(0)!=ShowLabels ) {
					ShowLabels = !ShowLabels;
					working_canvas.update(working_graphics);
				}
	 	 	} 
		}	 
		// exit the dialog window cleanly
		gd.setVisible(false);   
		gd.dispose();
		
 **/

/** Walter O'Dell PhD,  wodell@rochester.edu,   11/6/02
 *	alterations from original GenericDialog class in ImageJ v1.28 :
 *	  changed global variable attributes from 'private' to 'protected'
 *				for variables: (Buttons) cancel, okay; 
 *											 (boolean) wasCanceled, macro
 *											 (int) y, nfIndex, sfIndex, cbIndex, choiceIndex
 *											 (GridBagLayout) grid
 *											 (GridBagConstraints) c
 *			added variables: (Container) activePanel
 *										 	 (int) x; // height location of object in panel
 *		
 *	all changes within the code are denoted with "// WO " and are associated 
 *	with settings of the 'activePanel' and 'x' variables.  These variables are
 *	needed to implement having multiple objects occupying the same height  
 *	location within the dialog window -- a string of objects across the panel 
 *
 ** This class is a customizable modal dialog box. */
class GenericDialog2 extends Dialog implements ActionListener,
TextListener, FocusListener, ItemListener, KeyListener {

	protected Vector defaultValues,defaultText,numberField,stringField,checkbox,choice;
	protected Component theLabel;
	protected TextArea textArea1,textArea2;
	// WO 4/26/02 changed all from 'private' to 'protected'
	protected Button cancel, okay;
  protected boolean wasCanceled;
  protected int x; // WO added
  protected int y;
  protected int nfIndex, sfIndex, cbIndex, choiceIndex;
	protected GridBagLayout grid;
	protected GridBagConstraints c; 
	private boolean firstNumericField=true;
	private boolean invalidNumber;
	private boolean firstPaint = true;
	private Hashtable labels;
	protected boolean macro;
	private String macroOptions;

	protected Container activePanel; // WO added

    /** Creates a new GenericDialog with the specified title. Uses the current image
    	image window as the parent frame or the ImageJ frame if no image windows
    	are open. Dialog parameters are recorded by ImageJ's command recorder but
    	this requires that the first word of each label be unique. */
	public GenericDialog2(String title) {
		this(title, WindowManager.getCurrentImage()!=null?
			(Frame)WindowManager.getCurrentImage().getWindow():IJ.getInstance());
	}

    /** Creates a new GenericDialog using the specified title and parent frame. */
  public GenericDialog2(String title, Frame parent) {
		super(parent, title, true);
		grid = new GridBagLayout();
		c = new GridBagConstraints();
		setLayout(grid);
		macroOptions = Macro.getOptions();
		macro = macroOptions!=null;
		addKeyListener(this);
		activePanel = this; // WO added
  }

	//void showFields(String id) {
	//	String s = id+": ";
	//	for (int i=0; i<maxItems; i++)
	//		if (numberField[i]!=null)
	//			s += i+"='"+numberField[i].getText()+"' ";
	//	IJ.write(s);
	//}

	/** Adds a numeric field. The first word of the label must be
		unique or command recording will not work.
	* @param label			the label
	* @param defaultValue	value to be initially displayed
	* @param digits			number of digits to right of decimal point
	*/
  public void addNumericField(String label, double defaultValue, int digits) {
		Label theLabel = makeLabel(label);
		c.gridx = x; // WO prev: c.gridx = 0; 
		c.gridy = y;
		c.anchor = GridBagConstraints.EAST;
		c.gridwidth = 1;
		if (firstNumericField)
			c.insets = new Insets(5, 0, 3, 0);
		else
			c.insets = new Insets(0, 0, 3, 0);
		grid.setConstraints(theLabel, c);
		activePanel.add(theLabel); // WO prev: add(theLabel);

		if (numberField==null) {
			numberField = new Vector(5);
			defaultValues = new Vector(5);
			defaultText = new Vector(5);
		}
		TextField tf = new TextField(IJ.d2s(defaultValue, digits), 6);
		tf.addActionListener(this);
		tf.addTextListener(this);
		tf.addFocusListener(this);
		tf.addKeyListener(this);
		numberField.addElement(tf);
		defaultValues.addElement(new Double(defaultValue));
		defaultText.addElement(tf.getText());
		x++; c.gridx = x; // WO prev: c.gridx = 1; 
		c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		grid.setConstraints(tf, c);
		tf.setEditable(true);
		if (firstNumericField) tf.selectAll();
		firstNumericField = false;
		activePanel.add(tf); // WO prev: add(tf);
		if (Recorder.record || macro)
			saveLabel(tf, label);
		if (activePanel == this) { x=0; y++; } // WO prev: y++;
		else x++; // WO added
    }

    private Label makeLabel(String label) {
    	if (IJ.isMacintosh())
    		label += " ";
		return new Label(label);
    }

    private void saveLabel(Component component, String label) {
    	if (labels==null)
    		labels = new Hashtable();
		labels.put(component, label);
    }

	/** Adds an 8 column text field.
	* @param label			the label
	* @param defaultText		the text initially displayed
	*/
	public void addStringField(String label, String defaultText) {
		addStringField(label, defaultText, 8);
	}

	/** Adds a text field.
	* @param label			the label
	* @param defaultText		text initially displayed
	* @param columns			width of the text field
	*/
	public void addStringField(String label, String defaultText, int columns) {
		Label theLabel = makeLabel(label);
		c.gridx = x; //WO prev: c.gridx = 0; 
		c.gridy = y;
		c.anchor = GridBagConstraints.EAST;
		c.gridwidth = 1;
		if (stringField==null) {
			stringField = new Vector(4);
			c.insets = new Insets(5, 0, 5, 0);
		} else
			c.insets = new Insets(0, 0, 5, 0);
		grid.setConstraints(theLabel, c);
		activePanel.add(theLabel); // WO prev: add(theLabel);

		TextField tf = new TextField(defaultText, columns);
		tf.addActionListener(this);
		tf.addTextListener(this);
		tf.addFocusListener(this);
		tf.addKeyListener(this);
		x++; c.gridx = x; //WO prev: c.gridx = 1; 
		c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		grid.setConstraints(tf, c);
		tf.setEditable(true);
		activePanel.add(tf); // WO prev: add(tf);
		stringField.addElement(tf);
		if (Recorder.record || macro)
			saveLabel(tf, label);
		if (activePanel == this) { x=0; y++; } // WO added
		else x++; // WO prev: y++;
    }

	/** Adds a checkbox.
	* @param label			the label
	* @param defaultValue	the initial state
	*/
    public void addCheckbox(String label, boolean defaultValue) {
    	if (checkbox==null) {
    		checkbox = new Vector(4);
			c.insets = new Insets(15, 20, 0, 0);
    	} else
			c.insets = new Insets(0, 20, 0, 0);
		c.gridx = x; // WO prev: c.gridx = 0; 
		c.gridy = y;
		c.gridwidth = 2;
		c.anchor = GridBagConstraints.WEST;
		Checkbox cb = new Checkbox(label);
		grid.setConstraints(cb, c);
		cb.setState(defaultValue);
		cb.addItemListener(this);
		cb.addKeyListener(this);
		activePanel.add(cb); // WO prev: add(cb);
		checkbox.addElement(cb);
		//ij.IJ.write("addCheckbox: "+ y+" "+cbIndex);
		if (Recorder.record || macro)
			saveLabel(cb, label);
		if (activePanel == this) { x=0; y++; } // WO added
		else x++; // WO prev:  y++;
    }

	/** Adds a group of checkboxs using a grid layout.
	* @param rows			the number of rows
	* @param columns		the number of columns
	* @param labels			the labels
	* @param defaultValues	the initial states
	*/
    public void addCheckboxGroup(int rows, int columns, String[] labels, boolean[] defaultValues) {
    	Panel panel = new Panel();
    	panel.setLayout(new GridLayout(rows,columns,10,0));
    	int startCBIndex = cbIndex;
    	int i1 = 0;
    	int[] index = new int[labels.length];
    	if (checkbox==null)
    		checkbox = new Vector(12);
    	for (int row=0; row<rows; row++) {
			for (int col=0; col<columns; col++) {
				int i2 = col*rows+row;
				if (i2>=labels.length)
					break;
				index[i1] = i2;
				Checkbox cb = new Checkbox(labels[i1]);
				checkbox.addElement(cb);
				cb.setState(defaultValues[i1]);
				if (Recorder.record || macro)
					saveLabel(cb, labels[i1]);
				panel.add(cb);
 				i1++;
			}
		}
		c.gridx = x; // WO prev: c.gridx = 0; 
		c.gridy = y;
		c.gridwidth = 2;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(10, 0, 0, 0);
		grid.setConstraints(panel, c);
		activePanel.add(panel); // WO prev: add(panel); 
		if (activePanel == this) { x=0; y++; } // WO added
		else x++; // WO prev: y++;
    }

    /** Adds a popup menu.
   * @param label	the label
   * @param items	the menu items
   * @param defaultItem	the menu item initially selected
   */
   public void addChoice(String label, String[] items, String defaultItem) {
		Label theLabel = makeLabel(label);
		c.gridx = x; // WO prev: c.gridx = 0; 
		c.gridy = y;
		c.anchor = GridBagConstraints.EAST;
		c.gridwidth = 1;
		if (choice==null) {
			choice = new Vector(4);
			c.insets = new Insets(5, 0, 5, 0);
		} else
			c.insets = new Insets(0, 0, 5, 0);
		grid.setConstraints(theLabel, c);
		activePanel.add(theLabel); // WO prev: add(theLabel);
		Choice thisChoice = new Choice();
		thisChoice.addKeyListener(this);
		thisChoice.addItemListener(this);
		for (int i=0; i<items.length; i++)
			thisChoice.addItem(items[i]);
		thisChoice.select(defaultItem);
		x++; c.gridx = x; // WO prev: c.gridx = 1; 
		c.gridy = y;
		c.anchor = GridBagConstraints.WEST;
		grid.setConstraints(thisChoice, c);
		activePanel.add(thisChoice);  // WO prev: add(thisChoice);
		choice.addElement(thisChoice);
		if (Recorder.record || macro)
			saveLabel(thisChoice, label);
		if (activePanel == this) { x=0; y++; } // WO added
		else x++; // WO prev: y++;
    }

    /** Adds a message consisting of one or more lines of text. */
    public void addMessage(String text) {
    	if (text.indexOf('\n')>=0)
			theLabel = new MultiLineLabel(text);
		else
			theLabel = new Label(text);
		//theLabel.addKeyListener(this);
		c.gridx = x; // WO  prev: = 0; 
		c.gridy = y;
		c.gridwidth = 2;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(text.equals("")?0:10, 20, 0, 0);
		grid.setConstraints(theLabel, c);
		activePanel.add(theLabel); // WO prev: add(theLabel);
		if (activePanel == this) { x=0; y++; } // WO added
		else x++; // WO prev: y++;
    }

	/** Adds one or two (side by side) text areas.
	* @param text1	initial contents of the first text area
	* @param text2	initial contents of the second text area or null
	* @param rows	the number of rows
	* @param rows	the number of columns
	*/
    public void addTextAreas(String text1, String text2, int rows, int columns) {
    	if (textArea1!=null)
    		return;
    	Panel panel = new Panel();
		textArea1 = new TextArea(text1,rows,columns,TextArea.SCROLLBARS_NONE);
		//textArea1.append(text1);
		panel.add(textArea1);
		if (text2!=null) {
			textArea2 = new TextArea(text2,rows,columns,TextArea.SCROLLBARS_NONE);
			//textArea2.append(text2);
			panel.add(textArea2);
		}
		c.gridx = x; // WO prev: = 0; 
		c.gridy = y;
		c.gridwidth = 2;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(15, 20, 0, 0);
		grid.setConstraints(panel, c);
		activePanel.add(panel); // WO prev: add(panel);
		if (activePanel == this) { x=0; y++; } // WO added
		else x++; // WO prev: y++;
    }

	/** Returns true if the user clicks on "Cancel". */
    public boolean wasCanceled() {
    	if (wasCanceled)
    		Macro.abort();
    	return wasCanceled;
    }

	/** Returns the contents of the next numeric field. */
   public double getNextNumber() {
		if (numberField==null)
			return -1.0;
		TextField tf = (TextField)numberField.elementAt(nfIndex);
		String theText = tf.getText();
		if (macro) {
			String label = (String)labels.get((Object)tf);
			theText = Macro.getValue(macroOptions, label, theText);
			//IJ.write("getNextNumber: "+label+"  "+theText);
		}	
		String originalText = (String)defaultText.elementAt(nfIndex);
		double defaultValue = ((Double)(defaultValues.elementAt(nfIndex))).doubleValue();
		double value;
		if (theText.equals(originalText))
			value = defaultValue;
		else {
			Double d = getValue(theText);
			if (d!=null)
				value = d.doubleValue();
			else {
				invalidNumber = true;
				value = 0.0;
			}
		}
		if (Recorder.record)
			recordOption(tf, trim(theText));
		nfIndex++;
		return value;
    }

	private String trim(String value) {
		if (value.endsWith(".0"))
			value = value.substring(0, value.length()-2);
		if (value.endsWith(".00"))
			value = value.substring(0, value.length()-3);
		return value;
	}

	private void recordOption(Component component, String value) {
		String label = (String)labels.get((Object)component);
		Recorder.recordOption(label, value);
	}

	private void recordCheckboxOption(Checkbox cb) {
		String label = (String)labels.get((Object)cb);
		if (cb.getState() && label!=null)
			Recorder.recordOption(label);
	}

 	protected Double getValue(String theText) {
 		Double d;
 		try {d = new Double(theText);}
		catch (NumberFormatException e){
			d = null;
		}
		return d;
	}

	/** Returns true if one or more of the numeric fields contained an invalid number. */
   public boolean invalidNumber() {
    	boolean wasInvalid = invalidNumber;
    	invalidNumber = false;
    	return wasInvalid;
    }

  	/** Returns the contents of the next text field. */
   public String getNextString() {
   		String theText;
		if (stringField==null)
			return "";
		TextField tf = (TextField)(stringField.elementAt(sfIndex));
		theText = tf.getText();
		if (macro) {
			String label = (String)labels.get((Object)tf);
			theText = Macro.getValue(macroOptions, label, theText);
			//IJ.write("getNextString: "+label+"  "+theText);
		}	
		if (Recorder.record)
			recordOption(tf, theText);
		sfIndex++;
		return theText;
    }

  	/** Returns the state of the next checkbox. */
    public boolean getNextBoolean() {
		if (checkbox==null)
			return false;
		Checkbox cb = (Checkbox)(checkbox.elementAt(cbIndex));
		if (Recorder.record)
			recordCheckboxOption(cb);
		boolean state = cb.getState();
		if (macro) {
			String label = (String)labels.get((Object)cb);
			String key = Macro.trimKey(label);
			state = macroOptions.indexOf(key+" ")>=0;
			//IJ.write("getNextBoolean: "+label+"  "+state);
		}
		cbIndex++;
		return state;
    }

  	/** Returns the selected item in the next popup menu. */
    public String getNextChoice() {
		if (choice==null)
			return "";
		Choice thisChoice = (Choice)(choice.elementAt(choiceIndex));
		String item = thisChoice.getSelectedItem();
		if (macro) {
			String label = (String)labels.get((Object)thisChoice);
			item = Macro.getValue(macroOptions, label, item);
			//IJ.write("getNextChoice: "+label+"  "+item);
		}	
		if (Recorder.record)
			recordOption(thisChoice, item);
		choiceIndex++;
		return item;
    }

  	/** Returns the index of the selected item in the next popup menu. */
    public int getNextChoiceIndex() {
		if (choice==null)
			return -1;
		Choice thisChoice = (Choice)(choice.elementAt(choiceIndex));
		int index = thisChoice.getSelectedIndex();
		if (macro) {
			String label = (String)labels.get((Object)thisChoice);
			String oldItem = thisChoice.getSelectedItem();
			int oldIndex = thisChoice.getSelectedIndex();
			String item = Macro.getValue(macroOptions, label, oldItem);
			thisChoice.select(item);
			index = thisChoice.getSelectedIndex();
			if (index==oldIndex && !item.equals(oldItem)) {
				IJ.showMessage(getTitle(), "\""+item+"\" is not a vaid choice for \""+label+"\"");
				Macro.abort();
			}

		}	
		if (Recorder.record)
			recordOption(thisChoice, thisChoice.getSelectedItem());
		choiceIndex++;
		return index;
  }

  /** Returns the contents of the next text area. */
  public String getNextText() {
    String text;
    if (textArea1!=null) {
			textArea1.selectAll();
			text = textArea1.getText();
			textArea1 = null;
    } else if (textArea2!=null) {
			textArea2.selectAll();
			text = textArea2.getText();
			textArea2 = null;
		} else
			text = null;
		return text;
  }

  /** Displays this dialog box. */
  public void showDialog() {
		nfIndex = 0;
		sfIndex = 0;
		cbIndex = 0;
		choiceIndex = 0;
		if (macro) {
			//IJ.write("showDialog: "+macroOptions);
			dispose();
			return;
		}
    if (stringField!=null&&numberField==null) {
    	TextField tf = (TextField)(stringField.elementAt(0));
    	tf.selectAll();
    }
		Panel buttons = new Panel();
    buttons.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
		cancel = new Button("Cancel");
		cancel.addActionListener(this);
		okay = new Button("  OK  ");
		okay.addActionListener(this);
		if (IJ.isMacintosh()) {
			buttons.add(cancel);
			buttons.add(okay);
		} else {
			buttons.add(okay);
			buttons.add(cancel);
		}
		c.gridx = 0; c.gridy = y;
		c.anchor = GridBagConstraints.EAST;
		c.gridwidth = 2;
		c.insets = new Insets(15, 0, 0, 0);
		grid.setConstraints(buttons, c);
		activePanel.add(buttons); // WO prev: add(buttons); 
    if (IJ.isMacintosh())
      setResizable(false);
		pack();
		setup();
		GUI.center(this);
		IJ.wait(250); // work around for Sun/WinNT bug
  }

	protected void setup() {
	}

	public void actionPerformed(ActionEvent e) {
		wasCanceled = (e.getSource()==cancel);
		setVisible(false);
		dispose();
	}

	public void textValueChanged(TextEvent e) {
	}

	public void itemStateChanged(ItemEvent e) {
	}

	public void focusGained(FocusEvent e) {
		Component c = e.getComponent();
		if (c instanceof TextField)
			((TextField)c).selectAll();
	}

	public void focusLost(FocusEvent e) {
		Component c = e.getComponent();
		if (c instanceof TextField)
			((TextField)c).select(0,0);
	}

 	public void keyPressed(KeyEvent e) {
		int keyCode = e.getKeyCode();
		IJ.setKeyDown(keyCode);
	}

	public void keyReleased(KeyEvent e) {
		IJ.setKeyUp(e.getKeyCode());
	}
		
	public void keyTyped(KeyEvent e) {}

	public Insets getInsets() {
    	Insets i= super.getInsets();
    	return new Insets(i.top+10, i.left+10, i.bottom+10, i.right+10);
	}

  public void paint(Graphics g) {
    super.paint(g);
      if (firstPaint && numberField!=null) {
      	TextField tf = (TextField)(numberField.elementAt(0));
    	tf.requestFocus();
    	firstPaint = false;
    }
  }
    	
}