// EMStatic.java (c) 2001 by Paul Falstad, www.falstad.com

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
import java.applet.Applet;
import java.applet.AudioClip;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.util.Random;
import java.util.Arrays;
import java.lang.Math;
import java.text.NumberFormat;
import java.awt.event.*;

class EMStaticCanvas extends Canvas {
    EMStaticFrame pg;
    EMStaticCanvas(EMStaticFrame p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(300,400);
    }
    public void update(Graphics g) {
	pg.updateEMStatic(g);
    }
    public void paint(Graphics g) {
	pg.updateEMStatic(g);
    }
};

class EMStaticLayout implements LayoutManager {
    public EMStaticLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
	return new Dimension(500, 500);
    }
    public Dimension minimumLayoutSize(Container target) {
	return new Dimension(100,100);
    }
    public void layoutContainer(Container target) {
	Insets insets = target.insets();
	int targetw = target.size().width - insets.left - insets.right;
	int cw = targetw* 2/3;
	int targeth = target.size().height - (insets.top+insets.bottom);
	target.getComponent(0).move(insets.left, insets.top);
	target.getComponent(0).resize(cw, targeth);
	int barwidth = targetw - cw;
	cw += insets.left;
	int i;
	int h = insets.top;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (m instanceof Scrollbar)
		    d.width = barwidth;
		if (m instanceof Label) {
		    h += d.height/5;
		    d.width = barwidth;
		}
		m.move(cw, h);
		m.resize(d.width, d.height);
		h += d.height;
	    }
	}
    }
};

public class EMStatic extends Applet {
    static EMStaticFrame ogf;
    void destroyFrame() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
    }

    public static void main(String args[]) {
        ogf = new EMStaticFrame(null);
        ogf.init();
    }

    public void init() {
	ogf = new EMStaticFrame(this);
	ogf.init();
    }
    public void destroy() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
    }
};

class EMStaticFrame extends Frame
  implements ComponentListener, ActionListener, AdjustmentListener,
             MouseMotionListener, MouseListener, ItemListener {
    
    Thread engine = null;

    Dimension winSize;
    Image dbimage;
    
    int gridSizeX;
    int gridSizeY;
    int windowWidth = 50;
    int windowHeight = 50;
    int windowOffsetX = 0;
    int windowOffsetY = 0;
    int chargeRadius = 1;
    public static final double chargeAmt = .5;

    public String getAppletInfo() {
	return "EMStatic by Paul Falstad";
    }

    Button blankButton;
    Checkbox stoppedCheck;
    Checkbox currentCheck;
    Checkbox equipCheck;
    Choice modeChooser;
    Choice viewChooser;
    Choice setupChooser;
    Choice accuracyChooser;
    Vector setupList;
    Setup setup;
    Scrollbar resBar;
    Scrollbar brightnessBar;
    Scrollbar adjustBar;
    Scrollbar equipBar;
    Label adjustLabel;
    GridElement grid[][];
    SolverGrid solverGrids[];
    Charge charges[];
    static final int chargeMax = 20;
    static final int MODE_MOVE = 0;
    static final int MODE_DELETE = 1;
    static final int MODE_FQPLUS = 2;
    static final int MODE_FQMINUS = 3;
    static final int MODE_CLEAR = 4;
    static final int MODE_CONDUCTOR = 5;
    static final int MODE_CPLUS = 6;
    static final int MODE_CMINUS = 7;
    static final int MODE_QPLUS = 8;
    static final int MODE_QMINUS = 9;
    static final int MODE_DIELEC = 10;
    static final int MODE_FLOAT = 11;
    static final int MODE_ADJUST = 12; // same as next one
    static final int MODE_ADJ_CONDUCT = 12;
    static final int MODE_ADJ_DIELEC = 13;
    static final int MODE_ADJ_POT = 14;
    static final int MODE_ADJ_CHARGE = 15;
    
    static final int VIEW_E = 0;
    static final int VIEW_E_LINES = 1;
    static final int VIEW_POT = 2;
    static final int VIEW_A = 3;
    static final int VIEW_B = 4;
    static final int VIEW_J = 5;
    static final int VIEW_Q = 6;
    static final int VIEW_D = 7;
    static final int VIEW_P = 8;
    static final int VIEW_P_CHARGE = 9;
    static final int VIEW_TYPE = 10;
    static final int VIEW_Q_J = 11;
    static final int VIEW_E_Q = 12;
    static final int VIEW_E_LINES_Q = 13;
    static final int VIEW_E_J = 14;
    static final int VIEW_E_LINES_J = 15;
    static final int VIEW_E_Q_J = 16;
    static final int VIEW_E_LINES_Q_J = 17;
    static final int VIEW_E_POT = 18;
    static final int VIEW_E_LINES_POT = 19;
    static final int VIEW_E_POT_COND = 20;
    static final int VIEW_E_LINES_POT_COND = 21;
    static final int VIEW_E_POT_J = 22;
    static final int VIEW_E_LINES_POT_J = 23;
    static final int VIEW_B_J = 24;
    static final int VIEW_E_B_Q_J = 25;
    static final int VIEW_E_LINES_B_Q_J = 26;
    static final int VIEW_EX = 27;
    static final int VIEW_EY = 28;
    static final int VIEW_DX = 29;
    static final int VIEW_DY = 30;
    static final int VIEW_NONE = -1;
    int dragX, dragY;
    int selectedCharge;
    boolean dragging, stopCalc;
    boolean dragClear;
    boolean dragSet;
    boolean objDragMap[][];
    boolean changedCharges, changedConductors, changedMagField;
    double t;
    int pause;
    int chargeCount = 0;
    int adjustSelectX1, adjustSelectY1, adjustSelectX2, adjustSelectY2;

    EMStaticCanvas cv;
    EMStatic applet;

    EMStaticFrame(EMStatic a) {
	super("Electrostatics Applet");
	applet = a;
    }

    public void init() {
	// build setup list
	setupList = new Vector();
	Setup s = new SingleChargeSetup();
	int i = 0;
	while (s != null) {
	    setupList.addElement(s);
	    s = s.createNext();
	    if (i++ == 300) {
		System.out.print("setup loop?\n");
		break;
	    }
	}

	charges = new Charge[chargeMax];
	setLayout(new EMStaticLayout());
	cv = new EMStaticCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(cv);

	setupChooser = new Choice();
	for (i = 0; i != setupList.size(); i++)
	    setupChooser.add("Setup: " +
			     ((Setup) setupList.elementAt(i)).getName());
	setup = (Setup) setupList.elementAt(0);
	setupChooser.addItemListener(this);
	add(setupChooser);

	//add(new Label("Mouse mode:", Label.CENTER));
	modeChooser = new Choice();
	modeChooser.add("Mouse = Move Object");
	modeChooser.add("Mouse = Delete Object");
	modeChooser.add("Mouse = Add + Draggable Charge");
	modeChooser.add("Mouse = Add - Draggable Charge");
	modeChooser.add("Mouse = Clear Square");
	modeChooser.add("Mouse = Add Conductor (Gnd)");
	modeChooser.add("Mouse = Add + Conductor");
	modeChooser.add("Mouse = Add - Conductor");
	modeChooser.add("Mouse = Add + Charge Square");
	modeChooser.add("Mouse = Add - Charge Square");
	modeChooser.add("Mouse = Add Dielectric");
	modeChooser.add("Mouse = Make Floater");
	modeChooser.add("Mouse = Adjust Conductivity");
	modeChooser.add("Mouse = Adjust Dielectric");
	modeChooser.add("Mouse = Adjust Potential");
	modeChooser.add("Mouse = Adjust Charge");
	modeChooser.addItemListener(this);
	modeChooser.select(MODE_MOVE);
	add(modeChooser);

	viewChooser = new Choice();
	viewChooser.add("Show Electric Field (E)");
	viewChooser.add("Show E lines");
	viewChooser.add("Show Potential (Phi)");
	viewChooser.add("Show Vector Potential (A)");
	viewChooser.add("Show Magnetic Field (B)");
	viewChooser.add("Show Current (j)");
	viewChooser.add("Show Charge (rho)");
	viewChooser.add("Show Displacement (D)");
	viewChooser.add("Show Polarization (P)");
	viewChooser.add("Show Polarization Charge");
	viewChooser.add("Show Material Type");
	viewChooser.add("Show rho/j");
	viewChooser.add("Show E/rho");
	viewChooser.add("Show E lines/rho");
	viewChooser.add("Show E/j");
	viewChooser.add("Show E lines/j");
	viewChooser.add("Show E/rho/j");
	viewChooser.add("Show E lines/rho/j");
	viewChooser.add("Show E/Phi");
	viewChooser.add("Show E lines/Phi");
	viewChooser.add("Show E/Phi in conductors");
	viewChooser.add("Show E lines/Phi in cond.");
	viewChooser.add("Show E/Phi/j");
	viewChooser.add("Show E lines/Phi/j");
	viewChooser.add("Show B/j");
	viewChooser.add("Show E/B/rho/j");
	viewChooser.add("Show E lines/B/rho/j");
	viewChooser.add("Show Ex");
	viewChooser.add("Show Ey");
	viewChooser.add("Show Dx");
	viewChooser.add("Show Dy");
	viewChooser.addItemListener(this);
	add(viewChooser);
	viewChooser.select(VIEW_E_Q_J);

	accuracyChooser = new Choice();
	accuracyChooser.add("Low Accuracy");
	accuracyChooser.add("Medium Accuracy");
	accuracyChooser.add("High Accuracy");
	accuracyChooser.add("Highest Accuracy");
	accuracyChooser.select(1);
	accuracyChooser.addItemListener(this);
	add(accuracyChooser);

	add(blankButton = new Button("Clear All"));
	blankButton.addActionListener(this);
	stoppedCheck = new Checkbox("Stop Calculation");
	stoppedCheck.addItemListener(this);
	add(stoppedCheck);
	currentCheck = new Checkbox("Enable Current", false);
	currentCheck.addItemListener(this);
	add(currentCheck);
	equipCheck = new Checkbox("Draw Equipotentials", true);
	equipCheck.addItemListener(this);
	add(equipCheck);
	
	add(new Label("Resolution", Label.CENTER));
	add(resBar = new Scrollbar(Scrollbar.HORIZONTAL, 44, 4, 24, 90));
	resBar.addAdjustmentListener(this);
	setResolution();

	add(new Label("Brightness", Label.CENTER));
	add(brightnessBar = new Scrollbar(Scrollbar.HORIZONTAL,
				    10, 1, 1, 2000));
	brightnessBar.addAdjustmentListener(this);

	add(new Label("Equipotential Count", Label.CENTER));
	add(equipBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 2, 30));
	equipBar.addAdjustmentListener(this);

	add(adjustLabel = new Label("", Label.CENTER));
	add(adjustBar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 0, 102));
	adjustBar.addAdjustmentListener(this);

	add(new Label("http://www.falstad.com"));

	try {
	    String param = applet.getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	} catch (Exception e) { }
	reinit();
	setModeChooser();
	setup = (Setup) setupList.elementAt(0);
	cv.setBackground(Color.black);
	cv.setForeground(Color.lightGray);
	
	resize(660, 500);
	handleResize();
	show();
    }

    void reinit() {
	chargeCount = 0;
	adjustSelectX1 = -1;
	grid = new GridElement[gridSizeX][gridSizeY];
	int i, j;
	for (i = 0; i != gridSizeX; i++)
	    for (j = 0; j != gridSizeY; j++)
		grid[i][j] = new GridElement();
	solverGrids = new SolverGrid[16];
	for (i = 0; i != 16; i++)
	    solverGrids[i] = new SolverGrid();
	doSetup();
    }
    
    void handleResize() {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	dbimage = createImage(d.width, d.height);
    }

    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            applet.destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }
    
    void doBlank() {
	int x, y;
	for (x = 0; x < gridSizeX; x++)
	    for (y = 0; y < gridSizeY; y++)
		grid[x][y].clear();
	chargeCount = 0;
	floatCharge = 0;
	changedCharges = changedConductors = true;
    }

    // add a dielectric on lower half of screen
    void doDielec(double d) {
	int x, y;
	for (x = 0; x < gridSizeX; x++)
	    for (y = gridSizeY/2; y < gridSizeY; y++) {
		grid[x][y].dielec = d;
	    }
	changedConductors = true;
    }

    void addUniformField() {
	conductFillRect(0, windowOffsetY,
			gridSizeX-1, windowOffsetY, 1, 1);
	int y = windowOffsetY+windowHeight-1;
	conductFillRect(0, y, gridSizeX-1, y, -1, 1);
    }

    void calcExceptions() {
	int x, y;
	// if walls are in place on border, need to extend that through
	// hidden area to avoid "leaks"
	for (x = 0; x < gridSizeX; x++)
	    for (y = 0; y < windowOffsetY; y++) {
		copyConductor(x, y, x, windowOffsetY);
		copyConductor(x, gridSizeY-y-1,
			      x, windowOffsetY+windowHeight-1);
	    }
	for (y = 0; y < gridSizeY; y++)
	    for (x = 0; x < windowOffsetX; x++) {
		copyConductor(x, y, windowOffsetX, y);
		copyConductor(gridSizeX-x-1, y,
			      windowOffsetX+windowWidth-1, y);
	    }

	// find all dielectric and conductor boundaries and mark them
	for (x = 1; x != gridSizeX-1; x++)
	    for (y = 1; y != gridSizeY-1; y++) {
		GridElement e1 = grid[x][y-1];
		GridElement e2 = grid[x][y+1];
		GridElement e3 = grid[x-1][y];
		GridElement e4 = grid[x+1][y];
		GridElement e0 = grid[x][y];
		e0.boundary = (e1.dielec != e0.dielec ||
			       e2.dielec != e0.dielec ||
			       e3.dielec != e0.dielec ||
			       e4.dielec != e0.dielec ||
			       e1.conductor != e0.conductor ||
			       e2.conductor != e0.conductor ||
			       e3.conductor != e0.conductor ||
			       e4.conductor != e0.conductor);
	    }
    }

    void copyConductor(int x1, int y1, int x2, int y2) {
	grid[x1][y1].conductor = grid[x2][y2].conductor;
	grid[x1][y1].floater = grid[x2][y2].floater;
	grid[x1][y1].conductivity = grid[x2][y2].conductivity;
	if (grid[x1][y1].conductor)
	    grid[x1][y1].pot = grid[x2][y2].pot;
    }

    int getPanelHeight() { return winSize.height / 3; }

    void centerString(Graphics g, String s, int y) {
	FontMetrics fm = g.getFontMetrics();
        g.drawString(s, (winSize.width-fm.stringWidth(s))/2, y);
    }

    public void paint(Graphics g) {
	cv.repaint();
    }

    boolean calculateNotice;

    public void updateEMStatic(Graphics realg) {
	if (winSize == null || winSize.width == 0)
	    return;
        Graphics g = null;
	g = dbimage.getGraphics();
	if (!calculateNotice && !stoppedCheck.getState() && !stopCalc &&
	    (changedConductors || changedCharges)) {
	    FontMetrics fm = g.getFontMetrics();
	    g.setColor(Color.black);
	    String cs = "Calculating...";
	    g.fillRect(0, winSize.height-30, 20+fm.stringWidth(cs), 30);
	    g.setColor(Color.white);
	    g.drawString(cs, 10, winSize.height-10);
	    cv.repaint(0);
	    calculateNotice = true;
	    realg.drawImage(dbimage, 0, 0, this);
	    return;
	}
	calculateNotice = false;

	g.setColor(cv.getBackground());
	g.fillRect(0, 0, winSize.width, winSize.height);
	int i, j;

	double mult = brightnessBar.getValue() / 5.0;
	int ix = 0;
	int k, l;

	int viewScalar, viewVector, viewScalarCond, viewVectorCond;
	viewScalar = viewScalarCond = viewVector = viewVectorCond = VIEW_NONE;
	int v = viewChooser.getSelectedIndex();
	boolean showLines = false, conductLines = false;
	boolean needA = false;
	switch (v) {
	case VIEW_POT:
	case VIEW_Q:
	case VIEW_P_CHARGE:
	case VIEW_TYPE:
	    viewScalar = viewScalarCond = v;
	    break;
	case VIEW_B:
	case VIEW_DX:
	case VIEW_DY:
	case VIEW_EX:
	case VIEW_EY:
	    viewScalar = viewScalarCond = v;
	    needA = true;
	    break;
	case VIEW_E:
	case VIEW_D:
	case VIEW_P:
	case VIEW_J:
	    viewVector = viewVectorCond = v;
	    break;
	case VIEW_A:
	    viewVector = viewVectorCond = v;
	    needA = true;
	    break;
	case VIEW_E_J:
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    break;
	case VIEW_E_LINES_J:
	    viewVectorCond = VIEW_J;
	    showLines = true;
	    break;
	case VIEW_E_LINES:
	    showLines = conductLines = true;
	    break;
	case VIEW_Q_J:
	    viewScalar = viewScalarCond = VIEW_Q;
	    viewVector = viewVectorCond = VIEW_J;
	    break;
	case VIEW_E_Q:
	    viewScalar = viewScalarCond = VIEW_Q;
	    viewVector = viewVectorCond = VIEW_E;
	    break;
	case VIEW_E_LINES_Q:
	    viewScalar = viewScalarCond = VIEW_Q;
	    showLines = conductLines = true;
	    break;
	case VIEW_E_LINES_B_Q_J:
	    viewScalar = VIEW_B;
	    viewScalarCond = VIEW_Q;
	    viewVectorCond = VIEW_J;
	    showLines = true;
	    needA = true;
	    break;
	case VIEW_E_POT:
	    viewScalar = viewScalarCond = VIEW_POT;
	    viewVector = viewVectorCond = VIEW_E;
	    break;
	case VIEW_E_LINES_POT:
	    viewScalar = viewScalarCond = VIEW_POT;
	    showLines = conductLines = true;
	    break;
	case VIEW_E_POT_J:
	    viewScalar = viewScalarCond = VIEW_POT;
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    break;
	case VIEW_E_LINES_POT_J:
	    viewScalar = viewScalarCond = VIEW_POT;
	    viewVectorCond = VIEW_J;
	    showLines = true;
	    break;
	case VIEW_E_POT_COND:
	    viewScalar = VIEW_NONE;
	    viewScalarCond = VIEW_POT;
	    viewVector = VIEW_E;
	    break;
	case VIEW_E_LINES_POT_COND:
	    viewScalar = VIEW_NONE;
	    viewScalarCond = VIEW_POT;
	    showLines = true;
	    break;
	case VIEW_E_Q_J:
	    viewScalar = viewScalarCond = VIEW_Q;
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    break;
	case VIEW_E_LINES_Q_J:
	    viewScalar = viewScalarCond = VIEW_Q;
	    viewVectorCond = VIEW_J;
	    showLines = true;
	    break;
	case VIEW_B_J:
	    viewScalar = viewScalarCond = VIEW_B;
	    viewVector = viewVectorCond = VIEW_J;
	    needA = true;
	    break;
	case VIEW_E_B_Q_J:
	    viewScalar = VIEW_B;
	    viewScalarCond = VIEW_Q;
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    needA = true;
	    break;
	}
	doCalc(needA);
	if (stopCalc) {
	    viewVector = viewVectorCond = viewScalar = VIEW_NONE;
	    if (viewScalarCond != VIEW_POT)
		viewScalarCond = VIEW_NONE;
	}
	for (j = 0; j != windowHeight; j++) {
	    ix = winSize.width*(j*winSize.height/windowHeight);
	    for (i = 0; i != windowWidth; i++) {
		int x = i*winSize.width/windowWidth;
		int y = j*winSize.height/windowHeight;
		int x2 = (i+1)*winSize.width/windowWidth;
		int y2 = (j+1)*winSize.height/windowHeight;
		int i2 = i+windowOffsetX;
		int j2 = j+windowOffsetY;
		int vs = viewScalar;
		int vv = viewVector;
		int col_r = 0, col_g = 0, col_b = 0;
		GridElement ge = grid[i2][j2];

		// highlight selected object
		if (ge.conductor || ge.dielec != 1 || ge.charge != 0) {
		    col_r = col_g = col_b = 64;
		    if (objDragMap != null) {
			try {
			    if (objDragMap[i2-dragObjX][j2-dragObjY]) {
				col_r = col_g = col_b = 128;
			    }
			} catch (Exception e) { }
		    }
		    if (ge.conductor) {
			vv = viewVectorCond;
			vs = viewScalarCond;
		    }
		}

		if (vs != VIEW_NONE) {
		    double dy = 0;
		    switch (vs) {
		    case VIEW_POT: dy = ge.pot * .2 * mult; break;
		    case VIEW_Q:   dy = .4*getCharge(i2, j2) * mult; break;
		    case VIEW_EX:
			dy = getEField(ge, grid[i2-1][j2], grid[i2+1][j2]) *
			    mult;
			break;
		    case VIEW_EY:
			dy = getEField(ge, grid[i2][j2-1], grid[i2][j2+1]) *
			    mult;
			break;
		    case VIEW_DX:
			dy = getDField(ge, grid[i2-1][j2], grid[i2+1][j2], 0) *
			    mult;
			break;
		    case VIEW_DY:
			dy = getDField(ge, grid[i2][j2-1], grid[i2][j2+1], 0) *
			    mult;
			break;
		    case VIEW_B:
			// calculate curl
			double daydx = grid[i2-1][j2].ay-grid[i2+1][j2].ay;
			double daxdy = grid[i2][j2+1].ax-grid[i2][j2-1].ax;
			dy = (daydx+daxdy)*mult;
			break;
		    case VIEW_P_CHARGE:
			dy = (getPCharge(ge, grid[i2-1][j2], grid[i2+1][j2]) +
			      getPCharge(ge, grid[i2][j2-1], grid[i2][j2+1]))
			    *.4*mult;
			vs = VIEW_Q;
			break;
		    }
		    if (dy < -1)
			dy = -1;
		    if (dy > 1)
			dy = 1;
		    if (vs == VIEW_TYPE) {
			double dr = 0, dg = 0, db = 0;
			if (ge.conductor) {
			    if (ge.floater > 0)
				dr = db = 1;
			    else
				dg = db = ge.conductivity;
			} else if (ge.dielec != 1) {
			    dr = ge.dielec/10;
			    dg = dr*.5;
			} else if (ge.charge != 0) {
			    dy = ge.charge*mult;
			    if (dy < 0)
				col_b = col_b+(int) (-dy*(255-col_b));
			    else {
				col_r = col_r+(int) (dy*(255-col_r));
				col_g = col_g+(int) (dy*(255-col_g));
			    }
			}
			col_r = col_r+(int) (clamp(dr)*(255-col_r));
			col_g = col_g+(int) (clamp(dg)*(255-col_g));
			col_b = col_b+(int) (clamp(db)*(255-col_b));
		    } else if (vs == VIEW_Q) {
			if (dy < 0)
			    col_b = col_b+(int) (-dy*(255-col_b));
			else {
			    col_r = col_r+(int) (dy*(255-col_r));
			    col_g = col_g+(int) (dy*(255-col_g));
			}
		    } else {
			if (dy < 0)
			    col_r = col_r+(int) (-dy*(255-col_r));
			else
			    col_g = col_g+(int) (dy*(255-col_g));
		    }
		}
		int col = (255<<24) | (col_r<<16) | (col_g<<8) | col_b;
		g.setColor(new Color(col));
		g.fillRect(x, y, x2-x, y2-y);
		ge.col = col;
		if (vv != VIEW_NONE) {
		    double dx = 0, dy = 0;
		    switch (vv) {
		    case VIEW_E:
			if (!ge.boundary) {
			    dx = -grid[i2+1][j2].pot + grid[i2-1][j2].pot;
			    dy = -grid[i2][j2+1].pot + grid[i2][j2-1].pot;
			} else {
			    dx = getEField(ge, grid[i2-1][j2], grid[i2+1][j2]);
			    dy = getEField(ge, grid[i2][j2-1], grid[i2][j2+1]);
			}
			break;
		    case VIEW_D:
			dx = getDField(ge, grid[i2-1][j2], grid[i2+1][j2], 0);
			dy = getDField(ge, grid[i2][j2-1], grid[i2][j2+1], 0);
			break;
		    case VIEW_P:
			dx = getDField(ge, grid[i2-1][j2], grid[i2+1][j2], 1);
			dy = getDField(ge, grid[i2][j2-1], grid[i2][j2+1], 1);
			break;
		    case VIEW_J:
			dx = ge.jx;
			dy = ge.jy;
			break;
		    case VIEW_A:
			dx = ge.ax * .3;
			dy = ge.ay * .3;
			break;
		    }
		    double dn = java.lang.Math.sqrt(dx*dx+dy*dy);
		    if (dn > 0) {
			dx /= dn;
			dy /= dn;
		    }
		    dn *= mult;
		    if (vv == VIEW_J) {
			if (dn > 1) {
			    if (dn > 2)
				dn = 2;
			    dn -= 1;
			    col_r = col_g = 255;
			    col_b = col_b+(int) (dn*(255-col_b));
			} else {
			    col_r = col_r+(int) (dn*(255-col_r));
			    col_g = col_g+(int) (dn*(255-col_g));
			}
		    } else {
			if (dn > 1) {
			    if (dn > 2)
				dn = 2;
			    dn -= 1;
			    col_g = 255;
			    col_r = col_r+(int) (dn*(255-col_r));
			    col_b = col_b+(int) (dn*(255-col_b));
			} else
			    col_g = col_g+(int) (dn*(255-col_g));
		    }
		    col = (255<<24) | (col_r<<16) | (col_g<<8) | col_b;
		    int sw2 = (x2-x)/2;
		    int sh2 = (y2-y)/2;
		    g.setColor(new Color(col));

		    // draw arrow
		    int x1 = x+sw2-(int) (sw2*dx);
		    int y1 = y+sh2-(int) (sh2*dy);
		    x2 = x+sw2+(int) (sw2*dx);
		    y2 = y+sh2+(int) (sh2*dy);
		    g.drawLine(x1, y1, x2, y2);
		    int as = 3;
		    g.drawLine(x2, y2,
			       (int) ( dy*as-dx*as+x2),
			       (int) (-dx*as-dy*as+y2));
		    g.drawLine(x2, y2,
			       (int) (-dy*as-dx*as+x2),
			       (int) ( dx*as-dy*as+y2));
		}
	    }
	}
	if (!stopCalc) {
	    if (showLines)
		renderLines(g, conductLines);
	    if (equipCheck.getState())
		renderEquips(g);
	}
	
	// draw charges
	chargeRadius = winSize.width*5/(windowWidth*4);
	for (i = 0; i < chargeCount; i++) {
	    Charge src = charges[i];
	    int xx = src.getScreenX();
	    int yy = src.getScreenY();
	    int rad = chargeRadius;
	    //g.setColor(src.v < 0 ? Color.blue : Color.yellow);
	    double dy = src.v * mult * .4;
	    if (dy < 0) {
		int b = (int) (-dy*(255-64))+64;
		if (b > 255)
		    b = 255;
		g.setColor(new Color(64, 64, b));
	    } else {
		int r = (int) (dy*(255-64))+64;
		if (r > 255)
		    r = 255;
		g.setColor(new Color(r, r, 64));
	    }
	    g.fillOval(xx-rad, yy-rad, rad*2, rad*2);
	    if (i == selectedCharge) {
		g.setColor(Color.white);
		g.drawOval(xx-rad, yy-rad, rad*2, rad*2);
	    }
	    g.setColor(Color.black);
	    g.drawLine(xx-rad/2, yy, xx+rad/2, yy);
	    if (src.v > 0)
		g.drawLine(xx, yy-rad/2, xx, yy+rad/2);
	}

	// highlight adjustment area
	if (adjustSelectX1 != -1) {
	    g.setColor(Color.cyan);
	    int lx1 = (int) (adjustSelectX1*winSize.width /windowWidth);
	    int ly1 = (int) (adjustSelectY1*winSize.height/windowHeight);
	    int lx2 = (int) ((adjustSelectX2+1)*winSize.width /windowWidth);
	    int ly2 = (int) ((adjustSelectY2+1)*winSize.height/windowHeight);
	    g.drawRect(lx1, ly1, lx2-lx1-1, ly2-ly1-1);
	}

	// calculate charge of selected object
	if (objDragMap != null) {
	    NumberFormat nf = NumberFormat.getInstance();
	    nf.setMaximumFractionDigits(3);
	    FontMetrics fm = g.getFontMetrics();
	    g.setColor(Color.black);
	    String cs = "Q = " + nf.format(getSelObjCharge());
	    g.fillRect(0, winSize.height-30, 20+fm.stringWidth(cs), 30);
	    g.setColor(Color.white);
	    g.drawString(cs, 10, winSize.height-10);
	}

	realg.drawImage(dbimage, 0, 0, this);
    }

    double clamp(double x) {
	return (x < 0) ? 0 : (x > 1) ? 1 : x;
    }

    boolean solveCurrent;

    void doCalc(boolean needA) {
	if (stoppedCheck.getState() || stopCalc) {
	    if (changedConductors || changedCharges) {
		// clear out fields if something changed but calculation
		// is turned off
		int i, j;
		for (i = 0; i != gridSizeX; i++)
		    for (j = 0; j != gridSizeY; j++) {
			GridElement ge = grid[i][j];
			ge.jx = ge.jy = ge.ax = ge.ay = 0;
			if (!ge.conductor)
			    ge.pot = 0;
		    }
	    }
	    return;
	}
	boolean hasPath = false;
	if (changedConductors) {
	    calcExceptions();
	    hasPath = findCurrentPath();
	}
	SolverElement sg[][] = new SolverElement[gridSizeX][gridSizeY];
	int i, j;
	if (hasPath) {
	    // calculate potential (and then current) on current path
	    // using standard solver.  To do that, we ignore all grid
	    // squares not on the current path, and treat current path
	    // squares as dielectrics where the dielectric constant is
	    // the conductivity.
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j] = new SolverElement();
		    se.charge = 0;
		    se.boundary = true;
		    if (ge.currentPath) {
			se.pot = (j == 0) ? 1 : (j == gridSizeY-1) ? -1 : 0;
			
			// conductors are only at the top and bottom of the
			// grid, because "conductor" means "fixed potential"
			// as far as the solver is concerned.
			se.conductor = (j == 0 || j == gridSizeY-1);

			se.ignore = false;
			se.dielec = ge.conductivity;
		    } else {
			// ignore everything not on the current path
			se.ignore = true;
			se.dielec = 0;
			se.pot = 0;
		    }
		}
	    solveCurrent = true;
	    doSolve(sg, 0, gridSizeX);
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j];
		    if (ge.currentPath && i > 0 && i < gridSizeX-1 && 
			j > 0 && j < gridSizeY-1) {
			ge.pot = se.pot;

			// calculate current.  have to go to some extra
			// work here to make current appear smooth
			// across conductor boundaries
			double d1 = (grid[i-1][j].currentPath) ?
			    sg[i-1][j].pot : ge.pot;
			double d2 = (grid[i+1][j].currentPath) ?
			    sg[i+1][j].pot : ge.pot;
			double d3 = (grid[i][j-1].currentPath) ?
			    sg[i][j-1].pot : ge.pot;
			double d4 = (grid[i][j+1].currentPath) ?
			    sg[i][j+1].pot : ge.pot;
			ge.jx = (d1-ge.pot)*(grid[i-1][j].conductivity) +
			    (ge.pot-d2)*ge.conductivity;
			ge.jy = (d3-ge.pot)*(grid[i][j-1].conductivity) +
			    (ge.pot-d4)*ge.conductivity;
		    } else {
			// ground conductors and zero out current on all
			// non-current-path squares.
			ge.jx = ge.jy = 0;
			if (ge.conductor)
			    ge.pot = 0;
		    }
		}
	    changedMagField = changedConductors = true;
	} else if (changedConductors) {
	    // if there is no current path, zero out the magnetic field
	    changedMagField = false;
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    ge.ax = ge.ay = ge.jx = ge.jy = 0;
		}
	}

	if (changedConductors || changedCharges) {
	    // calculate potential on all non-conducting squares
	    boolean floater = false;
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j] = new SolverElement();
		    se.dielec = ge.dielec;
		    if (ge.conductor) {
			ge.charge = 0;
			if (ge.floater > 0) {
			    // for floaters, zero potential to zero for now
			    ge.pot = 0;
			    floater = true;
			}
			// GridElement.dielec applies to the material
			// between this square and the ones to the right
			// of it and below it.  But it's not possible from
			// the UI to create a square that is both dielectric
			// and conducting.  So we do some fudging to make it
			// possible to fill the area between two conductors
			// with dielectric.
			if (i < gridSizeX-1 && !grid[i+1][j].conductor)
			    se.dielec = grid[i+1][j].dielec;
			else if (j < gridSizeY-1 && !grid[i][j+1].conductor)
			    se.dielec = grid[i][j+1].dielec;
		    }
		    se.charge = ge.charge;
		    se.ignore = false;
		    se.pot = ge.pot;
		    se.conductor = ge.conductor;
		    se.boundary = ge.boundary;
		}
	    solveCurrent = false;
	    doSolve(sg, 0, gridSizeX);
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j];
		    ge.pot = se.pot;
		}
	    
	    if (floater)
		doFloater(sg);
	    else
		floatCharge = 0;
	}

	if (changedMagField && needA) {
	    // calculate vector potential from current
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j] = new SolverElement();
		    se.charge = ge.jx*.01;
		    se.dielec = 1;
		}
	    doSolve(sg, 0, gridSizeX);
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j];
		    ge.ax = se.pot;
		    se.charge = ge.jy*.01;
		    se.pot = 0;
		}
	    doSolve(sg, 0, gridSizeX);
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j];
		    ge.ay = se.pot;
		}
	    changedMagField = false;
	}
	changedConductors = changedCharges = false;
    }

    void checkAdjConductor(int x, int y, Point adj) {
	if (adj.x == -2)
	    return;
	if (grid[x][y].conductor && grid[x][y].floater == 0) {
	    if (adj.x >= 0 && grid[x][y].pot != grid[adj.x][adj.y].pot) {
		adj.x = -2;
	    } else {
		adj.x = x;
		adj.y = y;
	    }
	}
    }

    double floatCap, floatCharge = 0, floatExtCharge;
    void doFloater(SolverElement sg[][]) {
	// set the potential on the floating conductor
	int i, j;
	floatExtCharge = 0;
	double fp1 = 0;
	Point adj = new Point(-1, 0);

	// calculate charge on the floater with potential at ground.
	// also see if the floater is touching a fixed conductor.
	for (i = 0; i != gridSizeX; i++)
	    for (j = 0; j != gridSizeY; j++) {
		GridElement ge = grid[i][j];
		if (ge.floater > 0) {
		    fp1 = ge.pot;
		    floatExtCharge += getCharge(i, j);
		    checkAdjConductor(i+1, j, adj);
		    checkAdjConductor(i-1, j, adj);
		    checkAdjConductor(i, j+1, adj);
		    checkAdjConductor(i, j-1, adj);
		}
	    }
	double adjPot = 0;
	boolean isAdj = false;
	if (adj.x == -2)
	    System.out.print("two floating potentials!\n");
	else if (adj.x != -1) {
	    // it is touching a fixed conductor, so set its potential
	    // to be the same
	    isAdj = true;
	    adjPot = grid[adj.x][adj.y].pot;
	    //System.out.print("adjacent " + adjPot + "\n");
	}
	//System.out.print("floatExtCharge " + floatExtCharge + " " + fp1 + "\n");
	if (changedConductors) {
	    double fp = 0;
	    // calculate potential at all points on grid if floater is
	    // raised to a potential of 1.  ground all other conductors
	    // and delete all other charges.  Store this in floatPot.
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j];
		    se.pot = (ge.conductor && ge.floater > 0) ? 1 : 0;
		    se.charge = 0;
		    //se.dielec = ge.dielec;
		    //se.ignore = false;
		    //se.conductor = ge.conductor;
		    //se.boundary = ge.boundary;
		}
	    solveCurrent = false;
	    doSolve(sg, 0, gridSizeX);
	    floatCap = 0;
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    SolverElement se = sg[i][j];
		    double pot = ge.floatPot = se.pot;
		    if (ge.floater > 0) {
			fp = pot;
			// calculate charge on floater at unit potential
			if (!grid[i+1][j].conductor)
			    floatCap -= sg[i+1][j].pot-pot;
			if (!grid[i-1][j].conductor)
			    floatCap -= sg[i-1][j].pot-pot;
			if (!grid[i][j+1].conductor)
			    floatCap -= sg[i][j+1].pot-pot;
			if (!grid[i][j-1].conductor)
			    floatCap -= sg[i][j-1].pot-pot;
		    }
		}
	    //System.out.print("charge2 " + floatCap + " " + fp + "\n");
	}
	double mult = 0;
	if (isAdj)
	    // fix potential at adjacent conductor's potential
	    mult = adjPot;
	else
	    // raise potential just enough so that the charge on the
	    // floater is same as last time (floatCharge).
	    mult = (floatCharge-floatExtCharge)/floatCap;

	// add in a multiple of floatPot to every square on the grid.
	for (i = 0; i != gridSizeX; i++)
	    for (j = 0; j != gridSizeY; j++) {
		GridElement ge = grid[i][j];
		ge.pot += ge.floatPot*mult;
	    }
	if (isAdj) {
	    // calculate new charge on floater if we are touching a
	    // conductor
	    double charge2 = 0;
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    GridElement ge = grid[i][j];
		    if (ge.floater > 0)
			charge2 += getCharge(i, j);
		}
	    floatCharge = charge2;
	}
	//System.out.print("charge3 " + charge2 + " " + floatCharge + "\n");
    }

    // solve poisson's equation using a multigrid-like technique
    void doSolve(SolverElement g1[][], int step, int size) {
	int i, j;
	int size1 = size-1;
	if (size > 3) {
	    // get a good starting guess by halving the resolution and
	    // calling doSolve recursively.
	    int size2 = size/2+1;
	    //System.out.print("starting " + size2 + "\n");
	    SolverElement g2[][] = solverGrids[step].grid;
	    if (g2 == null)
		g2 = solverGrids[step].grid = new SolverElement[size2][size2];
	    for (i = 0; i != size2; i++)
		for (j = 0; j != size2; j++) {
		    int i2 = i*2;
		    int j2 = j*2;
		    if (i2 >= size)
			i2 = size1;
		    if (j2 >= size)
			j2 = size1;
		    if (g2[i][j] == null)
			g2[i][j] = new SolverElement();
		    double c = g1[i2][j2].charge;
		    double d = g1[i2][j2].dielec;
		    boolean b = g1[i2][j2].boundary;
		    int ig = (g1[i2][j2].ignore) ? 1 : 0;
		    int sq = 1;
		    if (i2 < size1) {
			c += g1[i2+1][j2].charge;
			d += g1[i2+1][j2].dielec;
			b |= g1[i2+1][j2].boundary;
			ig += (g1[i2+1][j2].ignore) ? 1 : 0;
			sq++;
			if (j2 < size1) {
			    c += g1[i2+1][j2+1].charge;
			    d += g1[i2+1][j2+1].dielec;
			    b |= g1[i2+1][j2+1].boundary;
			    ig += (g1[i2+1][j2+1].ignore) ? 1 : 0;
			    sq++;
			}
		    }
		    if (j2 < size1) {
			c += g1[i2][j2+1].charge;
			d += g1[i2][j2+1].dielec;
			b |= g1[i2][j2+1].boundary;
			ig += (g1[i2][j2+1].ignore) ? 1 : 0; // XXX use byte
			sq++;
		    }
		    g2[i][j].charge = c;
		    g2[i][j].dielec = d/sq;
		    g2[i][j].boundary = b;
		    g2[i][j].ignore = (ig == sq) ? true : false;
		    if (solveCurrent)
			g2[i][j].dielec = (ig == sq) ? 0 : d/(4-ig);
		    int cc = 0;
		    double cpot = 0;
		    if (g1[i2  ][j2  ].conductor) {
			cc++; cpot += g1[i2][j2].pot;
		    }
		    if (i2 < size1 && g1[i2+1][j2  ].conductor) {
			cc++; cpot += g1[i2+1][j2].pot;
		    }
		    if (j2 < size1 && g1[i2  ][j2+1].conductor) {
			cc++; cpot += g1[i2][j2+1].pot;
		    }
		    if (i2 < size1 && j2 < size1 &&
			g1[i2+1][j2+1].conductor) {
			cc++; cpot += g1[i2+1][j2+1].pot;
		    }
		    if (cc > 0 && g2[i][j].charge == 0) {
			g2[i][j].conductor = true;
			g2[i][j].pot = cpot/cc;
		    } else {
			g2[i][j].conductor = false;
			g2[i][j].pot = 0;
		    }
		}

	    // ok, now call doSolve recursively
	    doSolve(g2, step+1, size2);

	    // get the coarser solution and map it back to this grid
	    for (i = 1; i != size1; i++)
		for (j = 1; j != size1; j++) {
		    if (!g1[i][j].conductor)
			g1[i][j].pot = g2[i/2][j/2].pot;
		}
	}
	//System.out.print("size " + size + "\n");
	int iters = 0;
	double tol = 0;
	int maxiter = 200;
	switch (accuracyChooser.getSelectedIndex()) {
	case 0: tol = 30*5e-6; break;
	case 1: tol = 15*5e-6; break;
	case 2: tol = 15e-6;   maxiter = 400; break;
	case 3:
	    tol = 1e-7;
	    if (step == 0)
		maxiter = 20000;
	    else
		maxiter = 1000;
	    break;
	}
	double err = 0;
	if (step > 1) {
	    if (maxiter < 400)
		maxiter = 400;
	    tol /= 5;
	}
	if (step == 0 && maxiter < 1000)
	    tol /= 2;

	// now use relaxation to solve poisson's equation
	while (true) {
	    err = 0;
	    for (i = 1; i != size1; i++)
		for (j = 1; j != size1; j++) {
		    SolverElement ge = g1[i][j];
		    if (ge.conductor || ge.ignore)
			continue;
		    double previ, nexti, prevj, nextj, np;
		    if (ge.boundary) {
			// we do extra work for dielectric boundaries
			// or squares adjacent to "ignore" squares.
			previ = g1[i-1][j].pot*g1[i-1][j].dielec;
			nexti = g1[i+1][j].pot*g1[i  ][j].dielec;
			prevj = g1[i][j-1].pot*g1[i][j-1].dielec;
			nextj = g1[i][j+1].pot*g1[i][j  ].dielec;
			double div = (g1[i-1][j].dielec + g1[i][j].dielec +
				      g1[i][j-1].dielec + g1[i][j].dielec);
			if (solveCurrent) {
			    if (g1[i-1][j].ignore)
				{ previ = 0; div -= g1[i-1][j].dielec; }
			    if (g1[i+1][j].ignore)
				{ nexti = 0; div -= g1[i][j].dielec; }
			    if (g1[i][j-1].ignore)
				{ prevj = 0; div -= g1[i][j-1].dielec; }
			    if (g1[i][j+1].ignore)
				{ nextj = 0; div -= g1[i][j].dielec; }
			}
			np = (nexti+previ+nextj+prevj)/div + ge.charge/ge.dielec;
		    } else {
			// make common case fast
			previ = g1[i-1][j].pot;
			nexti = g1[i+1][j].pot;
			prevj = g1[i][j-1].pot;
			nextj = g1[i][j+1].pot;
			np = (nexti+previ+nextj+prevj)*.25 + ge.charge/ge.dielec;
		    }
		    err += (np > ge.pot) ? np-ge.pot : ge.pot-np;
		    ge.pot = np;
		}
	    iters++;
	    if (err/(size*size) < tol || iters == maxiter)
		break;
	}
	//System.out.print("size " + size + " iters " + iters + " " + err/(size*size) + "\n");
    }

    boolean findCurrentPath() {
	if (!currentCheck.getState())
	    return false;
	int i, j;
	for (j = 0; j != gridSizeY; j++) {
	    for (i = 0; i != gridSizeX; i++) {
		GridElement ge = grid[i][j];
		ge.currentPath = false;
	    }
	}
	boolean returnVal = currentPathSearch(0, 1);
	returnVal |= currentPathSearch(gridSizeY-1, -1);
	return returnVal;
    }

    boolean currentPathSearch(int y, int pot) {
	// do a depth-first search (flood fill) to see if there is any
	// path from row y to the opposite side of the grid.  While
	// we're at it, set the potential (in case there is no path,
	// or in case there are some conductors touching that row
	// which are not connected to the current path).
	int i;
	Vector stack = null;
	for (i = 0; i != gridSizeX; i++)
	    if (grid[i][y].conductor) {
		if (stack == null)
		    stack = new Vector();
		stack.addElement(new Point(i, y));
	    }
	if (stack == null)
	    return false;
	boolean returnVal = false;
	while (stack.size() > 0) {
	    Point x = (Point) stack.elementAt(stack.size()-1);
	    stack.removeElementAt(stack.size()-1);
	    GridElement ge = grid[x.x][x.y];
	    if (!ge.conductor || ge.currentPath)
		continue;
	    ge.currentPath = true;
	    ge.pot = pot;
	    if (x.x > 0)
		stack.addElement(new Point(x.x-1, x.y));
	    if (x.y > 0)
		stack.addElement(new Point(x.x, x.y-1));
	    else if (y != 0)
		returnVal = true;
	    if (x.x < gridSizeX-1)
		stack.addElement(new Point(x.x+1, x.y));
	    if (x.y < gridSizeY-1)
		stack.addElement(new Point(x.x, x.y+1));
	    else if (y == 0)
		returnVal = true;
	}
	return returnVal;
    }

    // calculate charge at a particular grid location
    double getCharge(int x, int y) {
	GridElement ge = grid[x][y];

	// this was determined by trial and error
	final double scale = 3.72;

	if (!ge.conductor)
	    return ge.charge*scale;
	double c = ge.charge*scale;
	c -= (grid[x+1][y].pot-ge.pot)*(grid[x+1][y].dielec);
	//if (!grid[x-1][y].conductor)
	c -= (grid[x-1][y].pot-ge.pot)*(grid[x-1][y].dielec);
	//if (!grid[x][y+1].conductor)
	c -= (grid[x][y+1].pot-ge.pot)*(grid[x][y+1].dielec);
	//if (!grid[x][y-1].conductor)
	c -= (grid[x][y-1].pot-ge.pot)*(grid[x][y-1].dielec);
	return c;
    }

    // calculate E field with some extra work to do one-sided
    // derivatives at conductor and dielectric boundaries.
    // ge = square we want the E field for, gn = next square,
    // gp = previous square.
    double getEField(GridElement ge, GridElement gp, GridElement gn) {
	if (ge.conductor && !gn.conductor && !gp.conductor)
	    return -gn.pot + gp.pot;
	if (ge.dielec != gp.dielec || ge.conductor != gp.conductor)
	    return 2*(ge.pot-gn.pot);
	if (ge.conductor != gn.conductor)
	    return 2*(gp.pot-ge.pot);
	return -gn.pot + gp.pot;
    }

    // calculate D and P with some extra work to do one-sided
    // derivatives at conductor and dielectric boundaries.
    // p = 0 to calculate D, 1 to calculate P.
    double getDField(GridElement ge, GridElement gp, GridElement gn,
		     double p) {
	if (ge.conductor && !gn.conductor && !gp.conductor)
	    return (ge.pot-gn.pot)*(ge.dielec-p) +
		(gp.pot-ge.pot)*(gp.dielec-p);
	if (ge.dielec != gp.dielec || ge.conductor != gp.conductor)
	    return 2*(ge.pot-gn.pot)*(ge.dielec-p);
	if (ge.conductor != gn.conductor)
	    return 2*(gp.pot-ge.pot)*(gp.dielec-p);
	return (ge.pot-gn.pot)*(ge.dielec-p) + (gp.pot-ge.pot)*(gp.dielec-p);
    }

    // get polarization charge
    double getPCharge(GridElement ge, GridElement gp, GridElement gn) {
	if (ge.dielec == gp.dielec)
	    return 0;
	return (ge.dielec-1)*(gn.pot-ge.pot) - (gp.dielec-1)*(ge.pot-gp.pot);
    }

    int abs(int x) {
	return x < 0 ? -x : x;
    }

    int sign(int x) {
	return (x < 0) ? -1 : (x == 0) ? 0 : 1;
    }

    byte linegrid[][];

    // render field lines
    void renderLines(Graphics g, boolean inConduct) {
	double x = 0, y = 0;
	g.setColor(Color.white);
	final double lspacing = 1.5;
	int cgridw = (int) (windowWidth*lspacing);
	int cgridh = (int) (windowHeight*lspacing);
	if (linegrid == null)
	    linegrid = new byte[cgridw+1][cgridh+1];
	double startx = -1, starty = 0;
	int linemax = 0;
	double mult = brightnessBar.getValue() / 5.0;
	boolean doArrow = false;
	int dir = 1;
	double olddn = -1;
	int oldcol = -1;
	int gridsearchx = 0, gridsearchy = 0;
	int i, j;
	for (i = 0; i != cgridw; i++)
	    for (j = 0; j != cgridh; j++)
		linegrid[i][j] = 0;
	while (true) {
	    if (linemax-- == 0 || x == 0) {
		if (dir == 1) {
		    while (true) {
			if (linegrid[gridsearchx][gridsearchy] == 0)
			    break;
			if (++gridsearchx == cgridw) {
			    if (++gridsearchy == cgridh)
				break;
			    gridsearchx = 0;
			}
		    }
		    if (gridsearchx == cgridw && gridsearchy == cgridh)
			break;
		    startx = gridsearchx/lspacing;
		    starty = gridsearchy/lspacing;
		}
		x = startx+.5/lspacing;
		y = starty+.5/lspacing;
		linemax = 40; // was 100
		doArrow = (dir == -1);
		dir = -dir;
	    }
	    if (x < 0 || y < 0 || x >= windowWidth || y >= windowHeight) {
		x = 0;
		continue;
	    }
	    int cgx = (int) (x*lspacing);
	    int cgy = (int) (y*lspacing);
	    if (++linegrid[cgx][cgy] > 2) {
		x = 0;
		continue;
	    }
	    if (linegrid[cgx][cgy] == 1)
		doArrow = true;
	    int xi = windowOffsetX+(int) x;
	    int yi = windowOffsetY+(int) y;
	    GridElement ge = grid[xi][yi];
	    if (!inConduct && ge.conductor) {
		x = 0;
		continue;
	    }
	    double dx, dy;
	    if (!ge.boundary) {
		dx = -grid[xi+1][yi].pot + grid[xi-1][yi].pot;
		dy = -grid[xi][yi+1].pot + grid[xi][yi-1].pot;
	    } else {
		dx = getEField(ge, grid[xi-1][yi], grid[xi+1][yi]);
		dy = getEField(ge, grid[xi][yi-1], grid[xi][yi+1]);
	    }
	    double dn = java.lang.Math.sqrt(dx*dx+dy*dy);
	    if (dn == 0) {
		x = 0;
		continue;
	    }
	    dx /= dn;
	    dy /= dn;
	    double oldx = x;
	    double oldy = y;
	    x += .5*dx*dir;
	    y += .5*dy*dir;
	    dn *= mult;
	    int col = grid[xi][yi].col;
	    if (dn != olddn || col != oldcol) {
		int col_r = (col>>16) & 255;
		int col_g = (col>> 8) & 255;
		int col_b = col & 255;
		if (dn > 1) {
		    if (dn > 2)
			dn = 2;
		    dn -= 1;
		    col_g = 255;
		    col_r = col_r+(int) (dn*(255-col_r));
		    col_b = col_b+(int) (dn*(255-col_b));
		} else
		    col_g = col_g+(int) (dn*(255-col_g));
		col = (255<<24) | (col_r<<16) | (col_g<<8) | col_b;
		g.setColor(new Color(col));
		olddn = dn;
		oldcol = col;
	    }
	    int lx1 = (int) (oldx*winSize.width /windowWidth);
	    int ly1 = (int) (oldy*winSize.height/windowHeight);
	    int lx2 = (int) (x*winSize.width /windowWidth);
	    int ly2 = (int) (y*winSize.height/windowHeight);
	    g.drawLine(lx1, ly1, lx2, ly2);
	    if (doArrow) {
		doArrow = false;
		if ((cgx & 3) == 0 && (cgy & 3) == 0) {
		    int as = 5;
		    g.drawLine(lx2, ly2,
			       (int) ( dy*as-dx*as+lx2),
			       (int) (-dx*as-dy*as+ly2));
		    g.drawLine(lx2, ly2,
			       (int) (-dy*as-dx*as+lx2),
			       (int) ( dx*as-dy*as+ly2));
		}
	    }
	}
    }

    // render equipotentials
    void renderEquips(Graphics g) {
	int x, y;
	g.setColor(Color.lightGray);
	for (x = 0; x != windowWidth; x++)
	    for (y = 0; y != windowHeight; y++) {
		// try all possible edge combinations
		tryEdge(g, x, y,  x+1, y,  x, y+1,  x+1, y+1);
		tryEdge(g, x, y,  x+1, y,  x, y,    x, y+1);
		tryEdge(g, x, y,  x+1, y,  x+1, y,  x+1, y+1);
		tryEdge(g, x, y,  x, y+1,  x+1, y,  x+1, y+1);
		tryEdge(g, x, y,  x, y+1,  x, y+1,  x+1, y+1);
		tryEdge(g, x+1, y, x+1, y+1, x, y+1, x+1, y+1);
	    }
    }

    // interpolate between two points
    void interpPoint(GridElement ep1, GridElement ep2,
		     int x1, int y1, int x2, int y2, double pval, Point pos) {
	double interp2 = (pval-ep1.pot)/(ep2.pot-ep1.pot);
	double interp1 = 1-interp2;
	pos.x = (int) ((x1+.5)*winSize.width*interp1/windowWidth +
		       (x2+.5)*winSize.width*interp2/windowWidth);
	pos.y = (int) ((y1+.5)*winSize.height*interp1/windowHeight +
		       (y2+.5)*winSize.height*interp2/windowHeight);
    }

    // check to see if pval is between the potential at ep1 and ep2
    boolean spanning(GridElement ep1, GridElement ep2, double pval) {
	if (ep1.pot == ep2.pot)
	    return false;
	return !((ep1.pot < pval && ep2.pot < pval) ||
		 (ep1.pot > pval && ep2.pot > pval));
    }

    // try to draw any equipotentials between edge (x1,y1)-(x2,y2)
    // and edge (x3,y3)-(x4,y4).
    void tryEdge(Graphics g, int x1, int y1, int x2, int y2,
		 int x3, int y3, int x4, int y4) {
	int i;
	double emult = equipBar.getValue() * .1;
	double mult = 1/(brightnessBar.getValue() * emult * .1);
	GridElement ep1 = grid[x1+windowOffsetX][y1+windowOffsetY];
	GridElement ep2 = grid[x2+windowOffsetX][y2+windowOffsetY];
	GridElement ep3 = grid[x3+windowOffsetX][y3+windowOffsetY];
	GridElement ep4 = grid[x4+windowOffsetX][y4+windowOffsetY];
	double pmin = min(min(ep1.pot, ep2.pot), min(ep3.pot, ep4.pot));
	double pmax = max(max(ep1.pot, ep2.pot), max(ep3.pot, ep4.pot));
	int imin = (int) (pmin/mult);
	int imax = (int) (pmax/mult);
	//double bmult = brightnessBar.getValue() / 5.0;
	for (i = imin; i <= imax; i++) {
	    double pval = i*mult;
	    if (!(spanning(ep1, ep2, pval) && spanning(ep3, ep4, pval)))
		continue;
	    Point pa = new Point();
	    Point pb = new Point();
	    interpPoint(ep1, ep2, x1, y1, x2, y2, pval, pa);
	    interpPoint(ep3, ep4, x3, y3, x4, y4, pval, pb);
	    /*double dy = pval*.2*bmult;
	    if (dy < 0) {
		dy = max(-1, dy);
		g.setColor(new Color((int) (-dy*(255-64))+64, 64, 64));
	    } else {
		dy = min(1, dy);
		g.setColor(new Color(64, (int) (dy*(255-64))+64, 64));
		}*/
	    g.drawLine(pa.x, pa.y, pb.x, pb.y);
	}
    }

    void dragCharge(int x, int y) {
	Charge s = charges[selectedCharge];
	if (!(x >= 0 && y >= 0 && x < windowWidth && y < windowHeight))
	    return;
	x += windowOffsetX;
	y += windowOffsetY;
	if (x == s.x && y == s.y)
	    return;
	if (!legalChargePos(x, y, selectedCharge))
	    return;
	int ox = s.x;
	int oy = s.y;
	grid[ox][oy].charge = 0;
	s.x = x; s.y = y;
	GridElement ge = grid[s.x][s.y];
	ge.charge = s.v;
	changedCharges = true;
	cv.repaint(pause);
    }

    boolean emptySquare(int x, int y) {
	if (grid[x][y].conductor)
	    return false;
	if (grid[x][y].charge != 0)
	    return false;
	return true;
    }

    // get the charge on the selected object
    double getSelObjCharge() {
	int x,y;
	double c = 0;
	for (x = 0; x != gridSizeX; x++)
	    for (y = 0; y != gridSizeY; y++) {
		if (objDragMap[x][y])
		    c += getCharge(x+dragObjX, y+dragObjY);
	    }
	return c;
    }

    int min(int a, int b) { return (a < b) ? a : b; }
    int max(int a, int b) { return (a > b) ? a : b; }
    double min(double a, double b) { return (a < b) ? a : b; }
    double max(double a, double b) { return (a > b) ? a : b; }

    void edit(MouseEvent e) {
	int x = e.getX();
	int y = e.getY();
	if (selectedCharge != -1) {
	    x = x*windowWidth/winSize.width;
	    y = y*windowHeight/winSize.height;
	    dragCharge(x, y);
	    return;
	}
	switch (modeChooser.getSelectedIndex()) {
	case MODE_FQPLUS:
	case MODE_FQMINUS:
	case MODE_MOVE:
	case MODE_FLOAT:
	    return;
	}

	if (modeChooser.getSelectedIndex() >= MODE_ADJUST) {
	    int xp = x*windowWidth/winSize.width;
	    int yp = y*windowHeight/winSize.height;
	    if (adjustSelectX1 == -1) { 
		adjustSelectX1 = adjustSelectX2 = xp;
		adjustSelectY1 = adjustSelectY2 = yp;
		adjustBar.enable();
		return;
	    }
	    adjustSelectX1 = max(0, min(xp, adjustSelectX1));
	    adjustSelectX2 = min(windowWidth-1,  max(xp, adjustSelectX2));
	    adjustSelectY1 = max(0, min(yp, adjustSelectY1));
	    adjustSelectY2 = min(windowHeight-1, max(yp, adjustSelectY2));
	    adjustBar.enable();
	    cv.repaint(pause);
	    return;
	}

	if (dragX == x && dragY == y)
	    editFuncPoint(x, y);
	else {
	    // need to draw a line from old x,y to new x,y and
	    // call editFuncPoint for each point on that line.  yuck.
	    if (abs(y-dragY) > abs(x-dragX)) {
		// y difference is greater, so we step along y's
		// from min to max y and calculate x for each step
		int x1 = (y < dragY) ? x : dragX;
		int y1 = (y < dragY) ? y : dragY;
		int x2 = (y > dragY) ? x : dragX;
		int y2 = (y > dragY) ? y : dragY;
		dragX = x;
		dragY = y;
		for (y = y1; y <= y2; y++) {
		    x = x1+(x2-x1)*(y-y1)/(y2-y1);
		    editFuncPoint(x, y);
		}
	    } else {
		// x difference is greater, so we step along x's
		// from min to max x and calculate y for each step
		int x1 = (x < dragX) ? x : dragX;
		int y1 = (x < dragX) ? y : dragY;
		int x2 = (x > dragX) ? x : dragX;
		int y2 = (x > dragX) ? y : dragY;
		dragX = x;
		dragY = y;
		for (x = x1; x <= x2; x++) {
		    y = y1+(y2-y1)*(x-x1)/(x2-x1);
		    editFuncPoint(x, y);
		}
	    }
	}
    }

    void clearFloaters() {
	int i, j;
	for (i = 0; i != gridSizeX; i++)
	    for (j = 0; j != gridSizeY; j++)
		grid[i][j].floater = 0;
	changedConductors = true;
    }

    void editFuncPoint(int x, int y) {
	int xp = x*windowWidth/winSize.width+windowOffsetX;
	int yp = y*windowHeight/winSize.height+windowOffsetY;
	GridElement ge = grid[xp][yp];
	if (!dragSet && !dragClear) {
	    dragClear = (ge.conductor || ge.charge != 0 || ge.dielec != 1);
	    dragSet = !dragClear;
	}
	if (ge.conductor && ge.floater > 0)
	    clearFloaters();
	ge.conductor = false;
	ge.jx = ge.jy = ge.charge = 0;
	ge.dielec = 1;
	stopCalc = true;
	switch (modeChooser.getSelectedIndex()) {
	case MODE_CLEAR:
	    dragClear = true;
	    dragSet = false;
	    break;
	case MODE_CONDUCTOR:
	    if (dragSet)
		addConductor(xp, yp, 0);
	    break;
	case MODE_CPLUS:
	    if (dragSet)
		addConductor(xp, yp, 1);
	    break;
	case MODE_CMINUS:
	    if (dragSet)
		addConductor(xp, yp, -1);
	    break;
	case MODE_DIELEC:
	    if (dragSet)
		ge.dielec = 2;
	    break;
	case MODE_QPLUS:
	    if (dragSet)
		ge.charge = chargeAmt;
	    break;
	case MODE_QMINUS:
	    if (dragSet)
		ge.charge = -chargeAmt;
	    break;
	}
	changedCharges = changedConductors = true;
	cv.repaint(pause);
    }

    void addCharge(int x, int y, double amt) {
	if (chargeCount == chargeMax)
	    return;
	if (!legalChargePos(x, y, -1))
	    return;
	charges[chargeCount++] = new Charge(x, y, amt);
	grid[x][y].charge = amt;
	changedCharges = true;
	cv.repaint(pause);
    }

    void deleteCharge(int num) {
	Charge c = charges[num];
	grid[c.x][c.y].charge = 0;
	for (; num < chargeCount; num++)
	    charges[num] = charges[num+1];
	chargeCount--;
	changedCharges = true;
	selectedCharge = -1;
	cv.repaint(pause);
    }

    boolean legalChargePos(int x, int y, int orig) {
	Charge s = (orig == -1) ? null : charges[orig];
	int i, j;
	for (i = -1; i <= 1; i++)
	    for (j = -1; j <= 1; j++) {
		// skip our own charge square
		if (s != null && s.x == x+i && s.y == y+j)
		    continue;
		// touching a conductor or bound charge?
		if (!emptySquare(x+i, y+j))
		    return false;
	    }
	// check for touching other charges
	for (i = 0; i != chargeCount; i++) {
	    if (i == orig)
		continue;
	    Charge s2 = charges[i];
	    if (abs(s2.x-x) <= 2 && abs(s2.y-y) <= 2)
		return false;
	}
	return true;
    }

    void selectCharge(MouseEvent me) {
	int x = me.getX();
	int y = me.getY();
	int i;
	int sc = selectedCharge;
	selectedCharge = -1;
	for (i = 0; i != chargeCount; i++) {
	    Charge src = charges[i];
	    int sx = src.getScreenX();
	    int sy = src.getScreenY();
	    int r2 = (sx-x)*(sx-x)+(sy-y)*(sy-y);
	    if (chargeRadius*chargeRadius > r2) {
		selectedCharge = i;
		break;
	    }
	}
	if (sc != selectedCharge)
	    cv.repaint(pause);
    }

    boolean matchElement(GridElement ge1, GridElement ge2) {
	if (ge1.conductor && ge2.conductor && (ge1.pot == ge2.pot ||
					       currentCheck.getState()) &&
	    ge1.floater == ge2.floater && ge1.conductivity == ge2.conductivity)
	    return true;
	if (ge1.charge != 0 && ge1.charge == ge2.charge)
	    return true;
	if (ge1.dielec != 1 && ge1.dielec == ge2.dielec)
	    return true;
	return false;
    }

    void selectObject(int xo, int yo) {
	dragObjX = dragObjY = 0;
	int xp = xo*windowWidth/winSize.width+windowOffsetX;
	int yp = yo*windowHeight/winSize.height+windowOffsetY;
	boolean oldSel1 = objDragMap != null;
	boolean oldSel2 = oldSel1 && objDragMap[xp][yp];
	GridElement ge1 = grid[xp][yp];
	if (!(ge1.conductor || ge1.dielec != 1 || ge1.charge != 0)) {
	    objDragMap = null;
	    if (oldSel1)
		cv.repaint(pause);
	    return;
	}
	if (objDragMap != null && objDragMap[xp][yp])
	    return;
	objDragMap = new boolean[gridSizeX][gridSizeY];
	Vector stack = new Vector();
	stack.addElement(new Point(xp, yp));

	// depth-first search to find object at mouse position
	while (stack.size() > 0) {
	    Point x = (Point) stack.elementAt(stack.size()-1);
	    stack.removeElementAt(stack.size()-1);
	    if (objDragMap[x.x][x.y])
		continue;
	    GridElement ge = grid[x.x][x.y];
	    if (!matchElement(ge, ge1))
		continue;
	    if (x.x == windowOffsetX || x.x == windowOffsetX+windowWidth-1 ||
		x.y == windowOffsetY || x.y == windowOffsetY+windowHeight-1) {
		objDragMap = null;
		if (oldSel1)
		    cv.repaint(pause);
		return;
	    }
	    objDragMap[x.x][x.y] = true;
	    stack.addElement(new Point(x.x-1, x.y));
	    stack.addElement(new Point(x.x, x.y-1));
	    stack.addElement(new Point(x.x+1, x.y));
	    stack.addElement(new Point(x.x, x.y+1));
	}
	dragBoundX1 = 1000;
	dragBoundY1 = 1000;
	dragBoundX2 = 0;
	dragBoundY2 = 0;
	int xi, yi;
	for (xi = 0; xi != gridSizeX; xi++)
	    for (yi = 0; yi != gridSizeY; yi++) {
		if (!objDragMap[xi][yi])
		    continue;
		if (xi < dragBoundX1)
		    dragBoundX1 = xi;
		if (yi < dragBoundY1)
		    dragBoundY1 = yi;
		if (xi > dragBoundX2)
		    dragBoundX2 = xi;
		if (yi > dragBoundY2)
		    dragBoundY2 = yi;
	    }
	if (!oldSel2)
	    cv.repaint(pause);
    }

    int dragObjX, dragObjY;
    int dragBoundX1, dragBoundX2, dragBoundY1, dragBoundY2;

    void dragObject(int xe, int ye) {
	int xp2 = xe*windowWidth/winSize.width+windowOffsetX;
	int yp2 = ye*windowHeight/winSize.height+windowOffsetY;
	int xp1 = dragX*windowWidth/winSize.width+windowOffsetX;
	int yp1 = dragY*windowHeight/winSize.height+windowOffsetY;
	int dx = xp2-xp1;
	int dy = yp2-yp1;
	if (dx == dragObjX && dy == dragObjY)
	    return;
	int xi, yi;
	if (!tryDrag(dx, dy)) {
	    // if we can't drag to the mouse position then see how close
	    // we can get.
	    for (;;) {
		if (dx != dragObjX) {
		    dx = (dx > dragObjX) ? dx-1 : dx+1;
		    if (tryDrag(dx, dy))
			break;
		}
		if (dy != dragObjY) {
		    dy = (dy > dragObjY) ? dy-1 : dy+1;
		    if (tryDrag(dx, dy))
			break;
		}
		if (dx == dragObjX && dy == dragObjY)
		    return;
	    }
	}
	GridElement template = null;
	for (xi = dragBoundX1; xi <= dragBoundX2; xi++)
	    for (yi = dragBoundY1; yi <= dragBoundY2; yi++) {
		int xi1 = xi+dragObjX;
		int yi1 = yi+dragObjY;
		if (objDragMap[xi][yi]) {
		    // clear old position
		    GridElement ge = grid[xi1][yi1];
		    template = ge.copy();
		    ge.clear();
		}
	    }
	for (xi = dragBoundX1; xi <= dragBoundX2; xi++)
	    for (yi = dragBoundY1; yi <= dragBoundY2; yi++) {
		int xi2 = xi+dx;
		int yi2 = yi+dy;
		if (objDragMap[xi][yi]) {
		    // set new position
		    GridElement ge = grid[xi2][yi2];
		    ge.set(template);
		}
	    }
	dragObjX = dx;
	dragObjY = dy;
	changedConductors = true;
	cv.repaint(pause);
    }

    boolean tryDrag(int dx, int dy) {
	int xi, yi;
	if (dragBoundX1+dx <= windowOffsetX || dragBoundY1+dy <= windowOffsetY ||
	    dragBoundX2+dx >= windowOffsetX+windowWidth-1 ||
	    dragBoundY2+dy >= windowOffsetY+windowHeight-1)
	    return false;
	for (xi = dragBoundX1; xi <= dragBoundX2; xi++)
	    for (yi = dragBoundY1; yi <= dragBoundY2; yi++) {
		int xi1 = xi+dx-dragObjX;
		int yi1 = yi+dy-dragObjY;
		int xi2 = xi+dx;
		int yi2 = yi+dy;
		try {
		    // don't let us move object on top of something,
		    // unless it's just the present location of the object.
		    if (!objDragMap[xi1][yi1] &&
			objDragMap[xi][yi] &&
			(grid[xi2][yi2].conductor ||
			 grid[xi2][yi2].dielec != 1 ||
			 grid[xi2][yi2].charge != 0))
			return false;
		    if (objDragMap[xi][yi]) {
			// don't let us move objects on top of free charges
			int i;
			for (i = 0; i != chargeCount; i++) {
			    Charge s = charges[i];
			    if (abs(s.x-xi2) <= 1 && abs(s.y-yi2) <= 1)
				return false;
			}
		    }
		} catch (Exception e) {
		    return false;
		}
	    }
	return true;
    }

    void deleteObject(int xp, int yp) {
	Vector stack = new Vector();
	stack.addElement(new Point(xp, yp));
	GridElement ge1 = grid[xp][yp].copy();
	while (stack.size() > 0) {
	    Point x = (Point) stack.elementAt(stack.size()-1);
	    stack.removeElementAt(stack.size()-1);
	    if (x.x < 0 || x.x >= gridSizeX ||
		x.y < 0 || x.y >= gridSizeY)
		continue;
	    GridElement ge = grid[x.x][x.y];
	    if (!matchElement(ge, ge1))
		continue;
	    ge.clear();
	    stack.addElement(new Point(x.x-1, x.y));
	    stack.addElement(new Point(x.x, x.y-1));
	    stack.addElement(new Point(x.x+1, x.y));
	    stack.addElement(new Point(x.x, x.y+1));
	}
	changedConductors = true;
	cv.repaint(pause);
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) {
	cv.repaint();
    }

    public void componentResized(ComponentEvent e) {
	handleResize();
	cv.repaint(100);
    }
    public void actionPerformed(ActionEvent e) {
	if (e.getSource() == blankButton) {
	    doBlank();
	    cv.repaint(pause);
	}
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
	System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
	cv.repaint(pause);
	if (e.getSource() == resBar) {
	    setResolution();
	    reinit();
	}
	if (e.getSource() == adjustBar)
	    doAdjust();
    }

    void setResolution() {
	windowWidth = windowHeight = resBar.getValue()+1;
	windowOffsetX = windowOffsetY = 20;
	gridSizeX = windowWidth + windowOffsetX*2;
	gridSizeY = windowHeight + windowOffsetY*2;
	linegrid = null;
    }

    void setResolution(int x) {
	resBar.setValue(x);
	setResolution();
	reinit();
    }

    void doAdjust() {
	if (adjustSelectX1 == -1)
	    return;
	int vali = adjustBar.getValue();
	if (vali < 1)
	    vali = 1;
	if (vali > 99)
	    vali = 100;
	float val = vali/100.f;
	int x, y;
	boolean create = true;
	for (y = adjustSelectY1; y <= adjustSelectY2; y++)
	    for (x = adjustSelectX1; x <= adjustSelectX2; x++) {
		GridElement oe = grid[x+windowOffsetX][y+windowOffsetY];
		if (oe.conductor || oe.dielec != 1)
		    create = false;
	    }
	boolean adjustFloat = false;
	double pot = 0;
	for (y = adjustSelectY1; y <= adjustSelectY2; y++)
	    for (x = adjustSelectX1; x <= adjustSelectX2; x++) {
		GridElement oe = grid[x+windowOffsetX][y+windowOffsetY];
		switch (modeChooser.getSelectedIndex()) {
		case MODE_ADJ_CONDUCT:
		    if (oe.conductor)
			oe.conductivity = val;
		    changedConductors = true;
		    break;
		case MODE_ADJ_DIELEC:
		    if (oe.dielec != 1 || create)
			oe.dielec = (vali-1)/10.+1.1;
		    changedConductors = true;
		    break;
		case MODE_ADJ_CHARGE:
		    if (vali <= 1)
			val = 0;
		    if (vali == 50)
			val = .51f;
		    if (oe.charge != 0)
			oe.charge = val*2-1;
		    changedConductors = true;
		    break;
		case MODE_ADJ_POT:
		    if (vali <= 1)
			val = 0;
		    pot = val*2-1;
		    if (create)
			addConductor(x+windowOffsetX, y+windowOffsetY);
		    if (oe.conductor) {
			if (oe.floater > 0)
			    adjustFloat = true;
			else {
			    oe.pot = pot;
			    changedConductors = true;
			}
		    }
		    break;
		}
	    }
	if (adjustFloat) {
	    // they're trying to change potential of floater, so change
	    // floatCharge appropriately.
	    floatCharge = floatExtCharge + floatCap*pot;
	    //System.out.print("float pot = " + pot + "\n");
	    changedCharges = true;
	}
	cv.repaint(pause);
	if (modeChooser.getSelectedIndex() == MODE_ADJ_CHARGE) {
	    // one of the free charges may have been modified
	    int i;
	    for (i = 0; i != chargeCount; i++) {
		Charge src = charges[i];
		src.v = grid[src.x][src.y].charge;
	    }
	}
    }

    public void mouseDragged(MouseEvent e) {
	dragging = true;
	if (objDragMap != null && modeChooser.getSelectedIndex() == MODE_MOVE)
	    dragObject(e.getX(), e.getY());
	else
	    edit(e);
    }
    public void mouseMoved(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
	    return;
	int x = e.getX();
	int y = e.getY();
	dragX = x; dragY = y;
	int panelHeight = getPanelHeight();
	selectCharge(e);
	int md = modeChooser.getSelectedIndex();
	if ((md == MODE_MOVE || md == MODE_DELETE || md == MODE_FLOAT) &&
	      selectedCharge == -1)
	    selectObject(x, y);
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	adjustSelectX1 = -1;
	adjustBar.disable();
	int xp = e.getX()*windowWidth/winSize.width+windowOffsetX;
	int yp = e.getY()*windowHeight/winSize.height+windowOffsetY;
	switch (modeChooser.getSelectedIndex()) {
	case MODE_FQPLUS:
	    if (selectedCharge == -1)
		addCharge(xp, yp, chargeAmt);
	    break;
	case MODE_FQMINUS:
	    if (selectedCharge == -1)
		addCharge(xp, yp, -chargeAmt);
	    break;
	case MODE_MOVE:
	    dragging = true;
	    break;
	case MODE_FLOAT:
	    if (objDragMap != null) {
		clearFloaters();
		int i, j;
		double ch = 0;
		for (i = 0; i != gridSizeX; i++)
		    for (j = 0; j != gridSizeY; j++)
			if (objDragMap[i][j]) {
			    grid[i][j].floater = 1;
			    ch += getCharge(i, j);
			}
		floatCharge = ch;
		changedConductors = true;
		cv.repaint(pause);
	    }
	    break;
	case MODE_DELETE:
	    if (selectedCharge != -1)
		deleteCharge(selectedCharge);
	    else
		deleteObject(xp, yp);
	    break;
	default:
	    dragging = true;
	    edit(e);
	    break;
	}
    }
    public void mouseReleased(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	dragging = dragSet = dragClear = stopCalc = false;
	if (objDragMap != null) {
	    objDragMap = null;
	    selectObject(e.getX(), e.getY());
	}
	cv.repaint();
    }
    public void itemStateChanged(ItemEvent e) {
	cv.repaint(pause);
	if (e.getItemSelectable() == setupChooser)
	    doSetup();
	if (e.getItemSelectable() == modeChooser)
	    setModeChooser();
	if (e.getItemSelectable() == accuracyChooser ||
	    e.getItemSelectable() == currentCheck)
	    changedConductors = true;
    }

    void setModeChooser() {
	if (modeChooser.getSelectedIndex() < MODE_ADJUST) {
	    adjustLabel.hide();
	    adjustBar.hide();
	    validate();
	    adjustSelectX1 = -1;
	    return;
	}
	switch (modeChooser.getSelectedIndex()) {
	case MODE_ADJ_CONDUCT: adjustLabel.setText("Conductivity"); break;
	case MODE_ADJ_DIELEC:  adjustLabel.setText("Dielectric Constant"); break;
	case MODE_ADJ_POT:     adjustLabel.setText("Potential"); break;
	case MODE_ADJ_CHARGE:  adjustLabel.setText("Charge"); break;
	}
	adjustLabel.show();
	adjustBar.show();
	if (adjustSelectX1 == -1)
	    adjustBar.disable();
	else
	    adjustBar.enable();
	validate();
    }

    void doSetup() {
	t = 0;
	doBlank();
	currentCheck.setState(false);
	brightnessBar.setValue(90);
	modeChooser.select(MODE_MOVE);
	setModeChooser();
	setup = (Setup)
	    setupList.elementAt(setupChooser.getSelectedIndex());
	setup.select();
    }

    class Charge {
	int x;
	int y;
	double v;
	Charge(int xx, int yy, double vv) { x = xx; y = yy; v = vv; }
	int getScreenX() {
	    return ((x-windowOffsetX) * winSize.width+winSize.width/2)
		/windowWidth;
	}
	int getScreenY() {
	    return ((y-windowOffsetY) * winSize.height+winSize.height/2)
		/windowHeight;
	}
    };

    class GridElement {
	// potential
	double pot;
	// current
	double jx, jy;
	// vector potential
	double ax, ay;
	// dielectric constant (1 = space)
	double dielec;
	// conductivity (1 = highest)
	double conductivity;
	// bound charge
	double charge;

	// potential at this square if floater is at unit potential
	// and all other charges and charged conductors are zeroed out
	double floatPot;

	// color (used for drawing field lines)
	int col;

	// true if this is a conductor
	boolean conductor;
	// true if this is a conducting or dielectric boundary
	boolean boundary;
	// true if this is carrying current
	boolean currentPath;
	// 1 if this is a floater, otherwise 0 (may allow multiple floaters
	// later on)
	byte floater;

	void clear() {
	    pot = charge = 0;
	    dielec = conductivity = 1;
	    conductor = false;
	    floater = 0;
	}
	GridElement copy() {
	    GridElement ge = new GridElement();
	    ge.pot = pot;
	    ge.dielec = dielec;
	    ge.conductivity = conductivity;
	    ge.conductor = conductor;
	    ge.charge = charge;
	    ge.floater = floater;
	    return ge;
	}
	void set(GridElement ge) {
	    pot = ge.pot;
	    dielec = ge.dielec;
	    conductivity = ge.conductivity;
	    conductor = ge.conductor;
	    charge = ge.charge;
	    floater = ge.floater;
	}
    };

    // element in one of the solver grids
    class SolverElement {
	double charge, dielec, pot;
	boolean conductor, boundary;

	// true if we should skip this square (used for non-conducting
	// squares when calculating current)
	boolean ignore;
    };

    class SolverGrid {
	SolverElement grid[][];
    };

    abstract class Setup {
	abstract String getName();
	void select() {}
	void deselect() {}
	void valueChanged(Scrollbar s) {}
	void doStep() {}
	abstract Setup createNext();
	Setup() { }
    };

    class SingleChargeSetup extends Setup {
	String getName() { return "Single Charge"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x, y, chargeAmt);
	}
	Setup createNext() { return new DoubleChargeSetup(); }
    }
    class DoubleChargeSetup extends Setup {
	String getName() { return "Double Charge"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x, y-6, chargeAmt);
	    addCharge(x, y+6, chargeAmt);
	}
	Setup createNext() { return new DipoleChargeSetup(); }
    }
    class DipoleChargeSetup extends Setup {
	String getName() { return "Dipole Charge"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x, y-5, chargeAmt);
	    addCharge(x, y+5, -chargeAmt);
	}
	Setup createNext() { return new ChargePlaneSetup(); }
    }
    class ChargePlaneSetup extends Setup {
	String getName() { return "Charge + Plane"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x, y-5, chargeAmt);
	    conductFillRect(windowOffsetX+1, y,
			    windowOffsetX+windowWidth-2, y+2, 0, 1);
	}
	Setup createNext() { return new DipoleUniformSetup(); }
    }
    class DipoleUniformSetup extends Setup {
	String getName() { return "Dipole + Uniform"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x, y-4, chargeAmt);
	    addCharge(x, y+4, -chargeAmt);
	    addUniformField();
	}
	Setup createNext() { return new QuadChargeSetup(); }
    }
    class QuadChargeSetup extends Setup {
	String getName() { return "Quadrupole Charge"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x+4, y-4,  chargeAmt);
	    addCharge(x+4, y+4, -chargeAmt);
	    addCharge(x-4, y-4, -chargeAmt);
	    addCharge(x-4, y+4,  chargeAmt);
	}
	Setup createNext() { return new ConductingPlanesSetup(); }
    }
    class ConductingPlanesSetup extends Setup {
	String getName() { return "Conducting Planes"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    int sep = 4;
	    int w = windowWidth*2/6;
	    conductFillRect(x-w, y-sep-2, x+w, y-sep,    1, 1);
	    conductFillRect(x-w, y+sep,   x+w, y+sep+2, -1, 1);
	    brightnessBar.setValue(35);
	}
	Setup createNext() { return new ChargedPlanesSetup(); }
    }
    class ChargedPlanesSetup extends Setup {
	String getName() { return "Charged Planes"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    int sep = 4;
	    int w = windowWidth*2/6;
	    int n, i;
	    double c = 1./(w*3);
	    for (i = 0; i != 3; i++)
		for (n = -w; n <= w; n++) {
		    grid[x+n][y-sep-i].charge = c;
		    grid[x+n][y+sep+i].charge = -c;
		}
	    brightnessBar.setValue(35);
	}
	Setup createNext() { return new ConductingCylinderSetup(); }
    }

    void doCylinder(double p, int floater) {
	int x = gridSizeX/2;
	int y = gridSizeY/2;
	int r = 8;
	int n;
	for (n = -r+1; n < r; n++) {
	    int a = (int) java.lang.Math.sqrt(r*r-n*n-.01);
	    int a2;
	    for (a2 = -a; a2 != a; a2++) {
		addConductor(x+n, y+a2, p);
		grid[x+n][y+a2].floater = (byte) floater;
	    }
	}
    }

    void doCylinderCharge(double p, int r, int xo) {
	int x = gridSizeX/2;
	int y = gridSizeY/2;
	int n;
	for (n = -r+1; n < r; n++) {
	    int a = (int) java.lang.Math.sqrt(r*r-n*n-.01);
	    int a2;
	    for (a2 = -a; a2 != a; a2++)
		grid[x+n+xo][y+a2].charge += p;
	}
    }

    class ConductingCylinderSetup extends Setup {
	String getName() { return "Conducting Cylinder"; }
	void select() {
	    doCylinder(1, 0);
	}
	Setup createNext() { return new GroundedCylinderSetup(); }
    }
    class GroundedCylinderSetup extends Setup {
	String getName() { return "Grounded Cyl + Charge"; }
	void select() {
	    doCylinder(0, 0);
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    int r = 7;
	    addCharge(x, y+r*2, chargeAmt);
	}
	Setup createNext() { return new GroundedCylinderUniformSetup(); }
    }
    class GroundedCylinderUniformSetup extends Setup {
	String getName() { return "Grounded Cyl + Field"; }
	void select() {
	    doCylinder(0, 0);
	    addUniformField();
	}
	Setup createNext() { return new ChargedCylinderSetup(); }
    }
    class ChargedCylinderSetup extends Setup {
	String getName() { return "Charged Cylinder"; }
	void select() {
	    doCylinderCharge(.005, 10, 0);
	    brightnessBar.setValue(50);
	}
	Setup createNext() { return new ChargedHollowCylinder1Setup(); }
    }
    class ChargedHollowCylinder1Setup extends Setup {
	String getName() { return "Charged Hollow Cyl 1"; }
	void select() {
	    doCylinderCharge(.005, 10, 0);
	    doCylinderCharge(-.005, 5, 0);
	}
	Setup createNext() { return new ChargedHollowCylinder2Setup(); }
    }
    class ChargedHollowCylinder2Setup extends Setup {
	String getName() { return "Charged Hollow Cyl 2"; }
	void select() {
	    doCylinderCharge(.005, 10, 0);
	    doCylinderCharge(-.005, 5, 2);
	}
	Setup createNext() { return new FloatingCylinderSetup(); }
    }
    class FloatingCylinderSetup extends Setup {
	String getName() { return "Floating Cyl + Charge"; }
	void select() {
	    doCylinder(1, 1);
	    addCharge(gridSizeX/2+7, gridSizeY/2+7, chargeAmt);
	}
	Setup createNext() { return new FloatingCylinder2Setup(); }
    }
    class FloatingCylinder2Setup extends Setup {
	String getName() { return "Floating Cyl + Plates"; }
	void select() {
	    doCylinder(1, 1);
	    conductFillRect(gridSizeX/2-windowWidth/3, windowOffsetY,
			    gridSizeX/2+windowWidth/3, windowOffsetY+2, 1, 1);
	    conductFillRect(gridSizeX/2-windowWidth/3,
			    windowOffsetY+windowHeight-3,
			    gridSizeX/2+windowWidth/3,
			    windowOffsetY+windowHeight-1, -1, 1);
	}
	Setup createNext() { return new ConductingBoxSetup(); }
    }
    class ConductingBoxSetup extends Setup {
	String getName() { return "Conducting Box"; }
	void select() {
	    int i;
	    int d = windowWidth/5;
	    for (i = d-2; i <= d; i++)
		conductDrawRect(gridSizeX/2-i, gridSizeY/2-i,
				gridSizeX/2+i, gridSizeY/2+i, 1, 1);
	}
	Setup createNext() { return new SharpPointSetup(); }
    }
    class SharpPointSetup extends Setup {
	String getName() { return "Sharp Point"; }
	void select() {
	    conductFillRect(gridSizeX/2-1, gridSizeY/2-1,
			    gridSizeX/2+1, gridSizeY-1, 1, 1);
	}
	Setup createNext() { return new CornerSetup(); }
    }
    class CornerSetup extends Setup {
	String getName() { return "Corner"; }
	void select() {
	    conductFillRect(gridSizeX/2-1, gridSizeY/2-1,
			    gridSizeX/2+1, gridSizeY-1, 1, 1);
	    conductFillRect(gridSizeX/2-1, gridSizeY/2-1,
			    gridSizeX-1, gridSizeY/2+1, 1, 1);
	}
	Setup createNext() { return new Angle45Setup(); }
    }
    class Angle45Setup extends Setup {
	String getName() { return "45 Degrees"; }
	void select() {
	    int i;
	    int d = 4;
	    for (i = -1; i != windowWidth/2+d*2; i++)
		conductFillRect(gridSizeX/2+i-d-2, gridSizeY/2+d-i,
				gridSizeX/2+i-d+1, gridSizeY/2+d-i, 1, 1);
	    conductFillRect(gridSizeX/2-d, gridSizeY/2-1+d,
			    gridSizeX-1, gridSizeY/2+1+d, 1, 1);
	}
	Setup createNext() { return new Angle135Setup(); }
    }
    class Angle135Setup extends Setup {
	String getName() { return "135 Degrees"; }
	void select() {
	    int i;
	    int d = 0;
	    for (i = -1; i != windowWidth/2+2; i++)
		conductFillRect(gridSizeX/2+i-d-2, gridSizeY/2+d-i,
				gridSizeX/2+i-d+1, gridSizeY/2+d-i, 1, 1);
	    conductFillRect(0, gridSizeY/2-1+d,
			    gridSizeX/2-d, gridSizeY/2+1+d, 1, 1);
	}
	Setup createNext() { return new DielectricCylinderSetup(); }
    }
    
    void doDielecCylinder() {
	int x = gridSizeX/2;
	int y = gridSizeY/2;
	int r = 8;
	int n;
	for (n = -r+1; n < r; n++) {
	    int a = (int) java.lang.Math.sqrt(r*r-n*n-.01);
	    int a2;
	    for (a2 = -a; a2 != a; a2++)
		grid[x+n][y+a2].dielec = 5;
	}
    }

    class DielectricCylinderSetup extends Setup {
	String getName() { return "Dielectric Cylinder"; }
	void select() {
	    doDielecCylinder();
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    int r = 8;
	    addCharge(x+r*3/2, y+r*3/2, chargeAmt);
	}
	Setup createNext() { return new DielectricCylinderFieldSetup(); }
    }
    class DielectricCylinderFieldSetup extends Setup {
	String getName() { return "Dielectric Cyl + Field"; }
	void select() {
	    doDielecCylinder();
	    addUniformField();
	}
	Setup createNext() { return new Dielectric1Setup(); }
    }
    class Dielectric1Setup extends Setup {
	String getName() { return "Dielectric 1"; }
	void select() {
	    doDielec(6);
	    addCharge(gridSizeX/2, gridSizeY/2-5, chargeAmt);
	    brightnessBar.setValue(250);
	}
	Setup createNext() { return new Dielectric2Setup(); }
    }
    class Dielectric2Setup extends Setup {
	String getName() { return "Dielectric 2"; }
	void select() {
	    doDielec(6);
	    addCharge(gridSizeX/2, gridSizeY/2+5, chargeAmt);
	    brightnessBar.setValue(250);
	}
	Setup createNext() { return new DielectricDipoleSetup(); }
    }
    class DielectricDipoleSetup extends Setup {
	String getName() { return "Dielectric + Dipole"; }
	void select() {
	    doDielec(3);
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    addCharge(x+8, y-4, chargeAmt);
	    addCharge(x-8, y+4, -chargeAmt);
	}
	Setup createNext() { return new DielecCapSetup(); }
    }
    class DielecCapSetup extends Setup {
	String getName() { return "Dielectric Capacitor"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    int sep = 2;
	    int w = windowWidth/4;
	    conductFillRect(x-w, y-sep-2, x+w, y-sep,    1, 1);
	    conductFillRect(x-w, y+sep,   x+w, y+sep+2, -1, 1);
	    int i, j;
	    for (i = -w+2; i <= w-2; i++)
		for (j = -sep+1; j < sep; j++)
		    grid[x+i][y+j].dielec = 5;
	    brightnessBar.setValue(12);
	}
	Setup createNext() { return new ConductingPlanesGapSetup(); }
    }
    class ConductingPlanesGapSetup extends Setup {
	String getName() { return "Conducting Planes w/ Gap"; }
	void select() {
	    int y = gridSizeY/2;
	    int d = 4;
	    conductFillRect(0, y-1,             gridSizeX/2-d-1, y+1, 1, 1);
	    conductFillRect(gridSizeX/2+d, y-1, gridSizeX-1, y+1, -1, 1);
	}
	Setup createNext() { return new SlottedConductingPlaneSetup(); }
    }
    class SlottedConductingPlaneSetup extends Setup {
	String getName() { return "Slotted Conducting Plane"; }
	void select() {
	    int y = gridSizeY/2;
	    int d = 4;
	    conductFillRect(0, y-1,             gridSizeX/2-d-1, y+1, 0, 1);
	    conductFillRect(gridSizeX/2+d, y-1, gridSizeX-1, y+1, 0, 1);
	    conductFillRect(0, windowOffsetY, gridSizeX-1, windowOffsetY,
			    1, 1);
	    brightnessBar.setValue(960);
	}
	Setup createNext() { return new Shielding1Setup(); }
    }
    
    class Shielding1Setup extends Setup {
	String getName() { return "Shielding 1"; }
	void select() {
	    int i;
	    for (i = 6; i <= 8; i++)
		conductDrawRect(gridSizeX/2-i, gridSizeY/2-i,
				gridSizeX/2+i, gridSizeY/2+i, 0, 1);
	    addUniformField();
	}
	Setup createNext() { return new Shielding2Setup(); }
    }
    class Shielding2Setup extends Setup {
	String getName() { return "Shielding 2"; }
	void select() {
	    int i;
	    int s1 = windowWidth/4;
	    int s2 = s1+2;
	    for (i = s1; i <= s2; i++)
		conductDrawRect(gridSizeX/2-i, gridSizeY/2-i,
				gridSizeX/2+i, gridSizeY/2+i, 0, 1);
	    addCharge(gridSizeX/2, gridSizeY/2, chargeAmt);
	}
	Setup createNext() { return new BoxOneSideSetup(); }
    }
    class BoxOneSideSetup extends Setup {
	String getName() { return "Box w/ One Live Side"; }
	void select() {
	    int i;
	    int s1 = windowWidth/4;
	    int s2 = s1+2;
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    for (i = s1; i <= s2; i++) {
		conductDrawRect(x-i, y-i, x+i, y+i, 0, 1);
		grid[x-s1+1][y-i].conductor = false;
		grid[x+s1-1][y-i].conductor = false;
	    }
	    conductFillRect(x-s1+2, y-s2, x+s1-2, y-s1, 1, 1);
	}
	Setup createNext() { return new QuadrupoleLensSetup(); }
    }
    class QuadrupoleLensSetup extends Setup {
	String getName() { return "Quadrupole Lens"; }
	void select() {
	    int x;
	    int w = gridSizeX/2-1;
	    int h = windowWidth/4;
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    for (x = -w; x <= w; x++) {
		int yd = (int) java.lang.Math.sqrt(x*x+h*h);
		int y;
		for (y = yd; y <= w; y++) {
		    addConductor(cx+x, cy+y, -1);
		    addConductor(cx+x, cy-y, -1);
		    addConductor(cx+y, cy+x,  1);
		    addConductor(cx-y, cy+x,  1);
		}
	    }
	    brightnessBar.setValue(24);
	}
	Setup createNext() { return new ConductingWireSetup(); }
    }
    class ConductingWireSetup extends Setup {
	String getName() { return "Wire w/ Current"; }
	void select() {
	    int x = gridSizeX/2;
	    int d = 8;
	    conductFillRect(x-d/2, 0, x+d/2, gridSizeY-1, 0, 1);
	    currentCheck.setState(true);
	}
	Setup createNext() { return new ResistorSetup(); }
    }
    class ResistorSetup extends Setup {
	String getName() { return "Resistor"; }
	void select() {
	    int x = gridSizeX/2;
	    int d = 8;
	    conductFillRect(x-d/2, 0, x+d/2, gridSizeY/2-6, 0, 1);
	    conductFillRect(x-d/2, gridSizeY/2+6, x+d/2, gridSizeY-1, 0, 1);
	    conductFillRect(x-d/2+1, gridSizeY/2-5,
			    x+d/2-1, gridSizeY/2+5, 0, .1);
	    currentCheck.setState(true);
	}
	Setup createNext() { return new ResistorsParallelSetup(); }
    }
    class ResistorsParallelSetup extends Setup {
	String getName() { return "Resistors in Parallel"; }
	void select() {
	    int x = gridSizeX/2;
	    int d = 8;
	    int i, j;
	    int d2 = d/2;
	    conductFillRect(x-d2, 0,
			    x+d2, gridSizeY/2-d2-1, 0, 1);
	    conductFillRect(x-d2, gridSizeY/2+d2+1,
			    x+d2, gridSizeY-1, 0, 1);
	    conductFillRect(x-windowWidth/4, gridSizeY/2-d,
			    x+windowWidth/4, gridSizeY/2-d2-1, 0, 1);
	    conductFillRect(x-windowWidth/4, gridSizeY/2+d2+1,
			    x+windowWidth/4, gridSizeY/2+d, 0, 1);
	    conductFillRect(x-windowWidth/4,   gridSizeY/2-d2,
			    x-windowWidth/4+4, gridSizeY/2+d2, 0, .6);
	    conductFillRect(x+windowWidth/4-4, gridSizeY/2-d2,
			    x+windowWidth/4,   gridSizeY/2+d2, 0, .1);
	    conductFillRect(x-2, gridSizeY/2-d2,
			    x+2, gridSizeY/2+d2, 0, .04);
	    currentCheck.setState(true);
	}
	Setup createNext() { return new Current2D1Setup(); }
    }
    class Current2D1Setup extends Setup {
	String getName() { return "Current in 2D 1"; }
	void select() {
	    int x = gridSizeX/2;
	    int y = gridSizeY/2;
	    int d = windowWidth/3;
	    int i, j;
	    int d2 = 4;
	    conductFillRect(x-d, y-d, x+d, y+d, 0, 1);
	    conductFillRect(x-d, 0, x-d+d2, y, 0, 1);
	    conductFillRect(x+d-d2, 0, x+d, gridSizeY-1, 0, 1);
	    for (i = -3; i <= 3; i++)
		for (j = -3; j <= 3; j++)
		    grid[x+i][y+j].conductor = false;
	    currentCheck.setState(true);
	}
	Setup createNext() { return new Current2D2Setup(); }
    }
    class Current2D2Setup extends Setup {
	String getName() { return "Current in 2D 2"; }
	void select() {
	    int x = gridSizeX/2;
	    int d = 8;
	    int i, j;
	    for (i = 0; i != d; i++)
		for (j = 0; j != gridSizeY; j++)
		    addConductor(x+i-d/2, j);
	    for (i = -windowWidth/4; i < windowWidth/4; i++)
		for (j = gridSizeY/2-d; j <= gridSizeY/2+d; j++)
		    addConductor(x+i, j);
	    currentCheck.setState(true);
	}
	Setup createNext() { return null; }
    }

    void addConductor(int x, int y) {
	addConductor(x, y, 0, 1);
    }
    void addConductor(int x, int y, double p) {
	addConductor(x, y, p, 1);
    }
    void addConductor(int x, int y, double p, double cv) {
	GridElement ge = grid[x][y];
	ge.conductor = true;
	ge.pot = p;
	ge.conductivity = cv;
	ge.floater = 0;
	//ge.damp = .1;
    }
    void conductFillRect(int x, int y, int x2, int y2, double p, double cv) {
	int i, j;
	for (i = x; i <= x2; i++)
	    for (j = y; j <= y2; j++)
		addConductor(i, j, p, cv);
    }
    void conductDrawRect(int x, int y, int x2, int y2, double p, double cv) {
	int i, j;
	for (i = x; i <= x2; i++) {
	    addConductor(i, y, p, cv);
	    addConductor(i, y2, p, cv);
	}
	for (j = y; j <= y2; j++) {
	    addConductor(x,  j, p, cv);
	    addConductor(x2, j, p, cv);
	}
    }
}
