// Quantum1DCrystal.java (C) 2002 by Paul Falstad, www.falstad.com

import java.awt.*;
import java.applet.Applet;
import java.util.Vector;
import java.util.Random;
import java.lang.Math;
import java.awt.event.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;

import org.netlib.util.*;
import org.netlib.lapack.Dsyevd;

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

class Quantum1DCrystalLayout implements LayoutManager {
    public Quantum1DCrystalLayout() {}
    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) {
	int barwidth = 0;
	int i;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (d.width > barwidth)
		    barwidth = d.width;
	    }
	}
	Insets insets = target.insets();
	int targetw = target.size().width - insets.left - insets.right;
	int cw = targetw-barwidth;
	int targeth = target.size().height - (insets.top+insets.bottom);
	target.getComponent(0).move(insets.left, insets.top);
	target.getComponent(0).resize(cw, targeth);
	cw += insets.left;
	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 || m instanceof DecentScrollbar)
		    d.width = barwidth;
		if (m instanceof Choice && d.width > barwidth)
		    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 Quantum1DCrystal extends Applet implements ComponentListener {
    Quantum1DCrystalFrame qf;
    void destroyFrame() {
	if (qf != null)
	    qf.dispose();
	qf = null;
	repaint();
    }
    boolean started = false;
    public void init() {
	addComponentListener(this);
    }
    void showFrame() {
	if (qf == null) {
	    started = true;
	    qf = new Quantum1DCrystalFrame(this);
	    qf.init();
	    repaint();
	}
    }
    public void paint(Graphics g) {
	String s = "Applet is open in a separate window.";
	if (!started)
	    s = "Applet is starting.";
	else if (qf == null)
	    s = "Applet is finished.";
	else
	    qf.show();
	g.drawString(s, 10, 30);
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) { showFrame(); }
    public void componentResized(ComponentEvent e) {}
    
    public void destroy() {
	if (qf != null)
	    qf.dispose();
	qf = null;
	repaint();
    }
};

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

    Dimension winSize;
    Image dbimage;
    
    Random random;
    int stateCount;
    int elevelCount;
    int maxStateCount = 500;
    int sampleCount = 320;
    int cellSampleCount = 32;
    int potSampleCount = 128;
    int pSampleCount;
    double modes[][];
    double modesLeft[][];
    double dispersion[][];
    double expecte;
    double selectedK;
    double potTrans[];
    double blochTrans[];
    int selectedBand;
    public static final double epsilon = .00001;
    public static final double epsilon2 = .003;
    public static final double phasorBaseEnergy = 1;
    
    public String getAppletInfo() {
	return "Quantum1DCrystal by Paul Falstad";
    }

    Button groundButton;
    Checkbox stoppedCheck;
    CheckboxMenuItem eCheckItem;
    CheckboxMenuItem xCheckItem;
    CheckboxMenuItem pCheckItem;
    CheckboxMenuItem blochCheckItem;
    CheckboxMenuItem dispersionCheckItem;
    CheckboxMenuItem expectCheckItem;
    CheckboxMenuItem probCheckItem;
    CheckboxMenuItem probPhaseCheckItem;
    CheckboxMenuItem reImCheckItem;
    CheckboxMenuItem magPhaseCheckItem;
    CheckboxMenuItem extendedZonesItem;
    CheckboxMenuItem reducedZonesItem;
    CheckboxMenuItem repeatedZonesItem;
    Menu waveFunctionMenu;
    Menu zoneMenu;
    MenuItem exitItem;
    Choice mouseChooser;
    Choice setupChooser;
    Vector setupList;
    Setup setup;
    DecentScrollbar forceBar, speedBar, wellCountBar, energyScaleBar;
    DecentScrollbar massBar, aux1Bar, aux2Bar, aux3Bar, aux4Bar;
    Label aux1Label, aux2Label, aux3Label, aux4Label;
    View viewPotential, viewX, viewP, viewDispersion, viewBloch;
    View viewList[];
    int viewCount;
    double elevels[];
    double dispmax[];
    static final double pi = 3.14159265358979323846;
    double step;
    double func[];
    double funci[];
    double blochr[];
    double blochi[];
    double pdata[], pdatar[], pdatai[];
    double pot[];
    double mass;
    double escale;
    int selectedCoef;
    int selectedPaneHandle;
    int kUpdateState, kUpdateSkip;
    static final int stateBuffer = 5;
    static final int SEL_NONE = 0;
    static final int SEL_POTENTIAL = 1;
    static final int SEL_X = 2;
    static final int SEL_P = 3;
    static final int SEL_STATES = 4;
    static final int SEL_HANDLE = 5;
    static final int SEL_DISPERSION = 6;
    static final int MOUSE_EIGEN = 0;
    static final int MOUSE_EDIT = 1;
    int selection;
    int dragX, dragY;
    int xpoints[], ypoints[];
    boolean dragging;
    boolean startup, selectGround;
    boolean levelsChanged, stateChanged;
    boolean setupModified;
    double t;
    int pause;
    static final int phaseColorCount = 480;
    Color phaseColors[];
    FFT fft;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    Quantum1DCrystalCanvas cv;
    Quantum1DCrystal applet;
    NumberFormat showFormat;

    Quantum1DCrystalFrame(Quantum1DCrystal a) {
	super("1-d Quantum Crystal Applet v1.0b");
	applet = a;
    }

    public void init() {
	startup = true;
	xpoints = new int[5];
	ypoints = new int[5];

	setupList = new Vector();
	Setup s = new FiniteWellSetup();
	while (s != null) {
	    setupList.addElement(s);
	    s = s.createNext();
	}
	selectedCoef = -1;
	setLayout(new Quantum1DCrystalLayout());
	cv = new Quantum1DCrystalCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(cv);

	MenuBar mb = new MenuBar();
	Menu m = new Menu("File");
	mb.add(m);
	m.add(exitItem = getMenuItem("Exit"));
	m = new Menu("View");
	mb.add(m);
	m.add(eCheckItem = getCheckItem("Energy"));
	eCheckItem.setState(true);
	m.add(xCheckItem = getCheckItem("Position"));
	xCheckItem.setState(true);
	m.add(pCheckItem = getCheckItem("Momentum"));
	m.add(blochCheckItem = getCheckItem("Bloch Function"));
	/*m.add(densityCheckItem = getCheckItem("Density of Levels"));
	  densityCheckItem.setState(true);*/
	m.add(dispersionCheckItem = getCheckItem("Dispersion"));
	dispersionCheckItem.setState(true);
	m.addSeparator();
	/*m.add(expectCheckItem = getCheckItem("Expectation Values"));
	  expectCheckItem.setState(true);*/
	Menu m2 = waveFunctionMenu = new Menu("Wave Function");
	m.add(m2);
	m2.add(probCheckItem = getCheckItem("Probability"));
	m2.add(probPhaseCheckItem = getCheckItem("Probability + Phase"));
	probPhaseCheckItem.setState(true);
	m2.add(reImCheckItem = getCheckItem("Real + Imaginary Parts"));
	m2.add(magPhaseCheckItem = getCheckItem("Magnitude + Phase"));

	m = zoneMenu = new Menu("Zones");
	mb.add(m);
	m.add(extendedZonesItem = getCheckItem("Extended Zone Scheme"));
	m.add(reducedZonesItem  = getCheckItem("Reduced Zone Scheme"));
	reducedZonesItem.setState(true);
	m.add(repeatedZonesItem = getCheckItem("Repeated Zone Scheme"));
	setMenuBar(mb);

	setupChooser = new Choice();
	int i;
	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);

	mouseChooser = new Choice();
	mouseChooser.add("Mouse = Set Eigenstate");
	mouseChooser.add("Mouse = Edit Function");
	mouseChooser.addItemListener(this);
	add(mouseChooser);
	mouseChooser.select(MOUSE_EIGEN);

	add(groundButton = new Button("Ground State"));
	groundButton.addActionListener(this);
	stoppedCheck = new Checkbox("Stopped");
	stoppedCheck.addItemListener(this);
	add(stoppedCheck);
	
	add(new Label("Simulation Speed", Label.CENTER));
	add(speedBar = new DecentScrollbar(this, 50, 1, 150));

	add(new Label("Particle Mass", Label.CENTER));
	add(massBar = new DecentScrollbar(this, 100, 10, 500));

	add(new Label("# of Wells Shown", Label.CENTER));
	add(wellCountBar = new DecentScrollbar(this, 5, 1, 50));

	add(new Label("Energy Scale", Label.CENTER));
	add(energyScaleBar = new DecentScrollbar(this, 100, 1, 100));

	add(aux1Label = new Label("Aux 1", Label.CENTER));
	add(aux1Bar = new DecentScrollbar(this, 50, 1, 100));

	add(aux2Label = new Label("Aux 2", Label.CENTER));
	add(aux2Bar = new DecentScrollbar(this, 50, 1, 100));

	add(aux3Label = new Label("Aux 3", Label.CENTER));
	add(aux3Bar = new DecentScrollbar(this, 50, 1, 100));

	add(aux4Label = new Label("Aux 4", Label.CENTER));
	add(aux4Bar = new DecentScrollbar(this, 50, 1, 100));

	try {
	    String param = applet.getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	} catch (Exception e) { }
	
	dispmax = new double[maxStateCount];
	setResolution();
	phaseColors = new Color[phaseColorCount+1];
	for (i = 0; i != phaseColorCount; i++) {
	    int pm = phaseColorCount/6;
	    int a1 = i % pm;
	    int a2 = a1*255/pm;
	    int a3 = 255-a2;
	    Color c = null;
	    switch (i/pm) {
	    case 0: c = new Color(255, a2, 0); break;
	    case 1: c = new Color(a3, 255, 0); break;
	    case 2: c = new Color(0, 255, a2); break;
	    case 3: c = new Color(0, a3, 255); break;
	    case 4: c = new Color(a2, 0, 255); break;
	    case 5: c = new Color(255, 0, a3); break;
	    }
	    phaseColors[i] = c;
	}
	phaseColors[phaseColorCount] = phaseColors[0];

	random = new Random();
	reinit();
	cv.setBackground(Color.black);
	cv.setForeground(Color.lightGray);
	showFormat = DecimalFormat.getInstance();
	showFormat.setMaximumFractionDigits(2);
	resize(750, 600);
	handleResize();
	Dimension x = getSize();
	Dimension screen = getToolkit().getScreenSize();
	setLocation((screen.width  - x.width)/2,
		    (screen.height - x.height)/2);
	show();
    }

    MenuItem getMenuItem(String s) {
	MenuItem mi = new MenuItem(s);
	mi.addActionListener(this);
	return mi;
    }

    CheckboxMenuItem getCheckItem(String s) {
	CheckboxMenuItem mi = new CheckboxMenuItem(s);
	mi.addItemListener(this);
	return mi;
    }

    void reinit() {
	doSetup();
    }

    void handleResize() {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	int tpad = 20;
	int potsize = (viewPotential == null) ? 0 : viewPotential.height+tpad;
	int dispsize = (viewDispersion == null) ? 0 : viewDispersion.height+tpad;
	viewX = viewP = viewDispersion = viewPotential = viewBloch = null;
	viewList = new View[20];
	int i = 0;
	if (eCheckItem.getState())
	    viewList[i++] = viewPotential = new View();
	if (xCheckItem.getState())
	    viewList[i++] = viewX = new View();
	if (blochCheckItem.getState())
	    viewList[i++] = viewBloch = new View();
	if (pCheckItem.getState())
	    viewList[i++] = viewP = new View();
	if (dispersionCheckItem.getState())
	    viewList[i++] = viewDispersion = new View();
	viewCount = i;
	int sizenum = viewCount;
	int toth = winSize.height;

	// preserve size of potential and state panes if possible
	if (potsize > 0 && viewPotential != null) {
	    sizenum--;
	    toth -= potsize;
	}
	if (dispsize > 0 && viewDispersion != null) {
	    sizenum--;
	    toth -= dispsize;
	}
	int cury = 0;
	for (i = 0; i != viewCount; i++) {
	    View v = viewList[i];
	    int h = toth/sizenum;
	    if (v == viewPotential && potsize > 0)
		h = potsize;
	    else if (v == viewDispersion && dispsize > 0)
		h = dispsize;
	    v.x = 0;
	    v.width = winSize.width;
	    v.y = cury+tpad;
	    v.height = h-tpad;
	    v.handle = cury;
	    cury += h;
	}
	setGraphLines();
	dbimage = createImage(d.width, d.height);
    }
    
    void setGraphLines() {
	int i;
	for (i = 0; i != viewCount; i++) {
	    View v = viewList[i];
	    v.mid_y = v.y + v.height/2;
	    v.ymult = .90*v.height/2;
	    v.lower_y = (int) (v.mid_y+v.ymult);
	    v.ymult2 = v.ymult*2;
	}
    }

    void doGround() {
	select(0, 0);
    }

    void doBlank() {
	t = 0;
	// workaround bug in IE
	if (winSize != null && winSize.width > 0)
	    dbimage = createImage(winSize.width, winSize.height);
    }

    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();
    }

    long lastTime;
    public void updateQuantum1DCrystal(Graphics realg) {
	Graphics g = dbimage.getGraphics();
	if (winSize == null || winSize.width == 0)
	    return;
	boolean allQuiet = true;
	double tadd = 0;
	if (!stoppedCheck.getState() && !dragging) {
	    int val = speedBar.getValue();
	    tadd = Math.exp(val/20.)*(.1/5);
	    long sysTime = System.currentTimeMillis();
	    if (lastTime == 0)
		lastTime = sysTime;
	    tadd *= (sysTime-lastTime)*(1/17.);
	    lastTime = sysTime;
	    allQuiet = false;
	} else
	    lastTime = 0;
	Color gray1 = new Color(76,  76,  76);
	Color gray2 = new Color(127, 127, 127);
	g.setColor(cv.getBackground());
	g.fillRect(0, 0, winSize.width, winSize.height);
	g.setColor(cv.getForeground());
	int i;
	int ox = -1, oy = -1;

	for (i = 1; i != viewCount; i++) {
	    g.setColor(i == selectedPaneHandle ? Color.yellow : Color.gray);
	    g.drawLine(0, viewList[i].handle, winSize.width, viewList[i].handle);
	}

	if (levelsChanged || stateChanged) {
	    cv.setCursor(Cursor.getPredefinedCursor(WAIT_CURSOR));
	    if (levelsChanged)
		genStates();
	    else
		getBands(true, selectedK, selectedBand);
	    cv.setCursor(null);
	    levelsChanged = stateChanged = false;
	} else {
	    long tm = System.currentTimeMillis();
	    // spend 50ms updating K
	    while (updateK() &&
		   System.currentTimeMillis() < tm+50)
		;
	}

	ox = -1;
	int j;

	if (viewPotential != null) {
	    int mid_y = viewPotential.mid_y;
	    double ymult = viewPotential.ymult;
	    g.setColor(Color.gray);
	    for (i = 0; i < elevelCount; i += 2) {
		g.setColor(Color.darkGray);
		double dy2 = elevels[i];
		double dy1 = elevels[i+1];
		int y1 = getEnergyY(dy1, viewPotential);
		int y2 = getEnergyY(dy2, viewPotential);
		y2 -= y1;
		if (y2 <= 0)
		    y2 = 1;
		g.fillRect(0, y1, winSize.width, y2);
	    }
	    
	    g.setColor(Color.white);
	    int wc = wellCountBar.getValue();
	    double step = winSize.width/((double) potSampleCount*wc);
	    for (i = 0; i != potSampleCount*wc; i++) {
		int x = (int) (step*i);
		double dy = pot[i & (potSampleCount-1)];
		int y = getEnergyY(dy, viewPotential);
		if (ox != -1)
		    g.drawLine(ox, oy, x, y);
		ox = x;
		oy = y;
	    }

	    int y = getEnergyY(expecte, viewPotential);
	    g.setColor(Color.red);
	    g.drawLine(0, y, winSize.width, y);
	    /*
	    g.setColor(Color.black);
	    g.fillRect(0, 0, winSize.width, 20);
	    viewPotential.drawLabel(g, "Potential");*/
	}

	ox = -1;
	g.setColor(Color.white);

	double maxf = 0;
	for (i = 0; i != sampleCount; i++) {
	    int x = winSize.width * i / sampleCount;
	    double dr = func[i], di = funci[i];
	    double dy = dr*dr+di*di;
	    if (dy > maxf)
		maxf = dy;
	}

	// evolve wave function
	double speed = (expecte+1)*tadd;
	double efr = Math.cos(speed);
	double efi = -Math.sin(speed);
	for (i = 0; i != sampleCount; i++) {
	    double fr = func [i];
	    double fi = funci[i];
	    func [i] = fr*efr - fi*efi;
	    funci[i] = fr*efi + fi*efr;
	    fr = blochr[i];
	    fi = blochi[i];
	    blochr[i] = fr*efr - fi*efi;
	    blochi[i] = fr*efi + fi*efr;
	}
	
	if (viewX != null) {
	    viewX.drawLabel(g, "Wave Function (Position)");
	    // draw X representation
	    int mid_y = viewX.mid_y;
	    double ymult = viewX.ymult;

	    drawFunction(g, viewX, func, funci, sampleCount, 0);

	    if (selectedCoef != -1 && !dragging) {
		g.setColor(Color.yellow);
		ox = -1;
		for (i = 0; i != sampleCount; i++) {
		    int x = winSize.width * i / sampleCount;
		    double dy = modes[selectedCoef][i]/dispmax[selectedCoef];
		    int y = mid_y - (int) (ymult * dy);
		    if (ox != -1)
			g.drawLine(ox, oy, x, y);
		    ox = x;
		    oy = y;
		}
	    }
	}

	if (viewP != null) {
	    viewP.drawLabel(g, "Momentum");
	    // draw P representation

	    g.setColor(gray2);
	    g.drawLine(winSize.width/2, viewP.mid_y-(int) viewP.ymult,
		       winSize.width/2, viewP.mid_y+(int) viewP.ymult);

	    for (i = 0; i != pdatar.length; i++)
		pdatar[i] = pdatai[i] = 0;

	    int btlen = blochTrans.length/4;
	    int btmask = blochTrans.length-1;
	    
	    // I must have missed a sign somewhere; fix it so that waves
	    // are going in right direction
	    int fudge = ((selectedBand & 1) == 1) ? 1 : -1;
	    int kstep = 8;
	    kstep *= -fudge;
	    
	    int pc = pdatar.length/2 + (int) (selectedK*kstep);
	    btlen = 16;

	    for (i = 0; i != btlen; i++) {
		pdatar[pc+kstep*i] = blochTrans[i*2];
		pdatar[pc-kstep*i] = blochTrans[btmask&(-i*2)];
		pdatai[pc+kstep*i] = blochTrans[i*2+1];
		pdatai[pc-kstep*i] = blochTrans[btmask&(-i*2+1)];
	    }
	    for (i = pdatar.length-1; i > 0; i--) {
		if (pdatar[i] == 0 && pdatai[i] == 0) {
		    pdatar[i] = pdatar[i-1]*1e-16;
		    pdatai[i] = pdatai[i-1]*1e-16;
		}
	    }
	    int offset = pSampleCount/4;
	    drawFunction(g, viewP, pdatar, pdatai, pSampleCount/2, offset);
	}

	if (viewDispersion != null) {
	    viewDispersion.drawLabel(g, "Dispersion (E vs. k)");
	    g.setColor(Color.gray);
	    int mid_y = viewDispersion.mid_y;
	    double ymult = viewDispersion.ymult;
	    double e0 = elevels[0]+1;
	    int top = viewDispersion.y+5;
	    for (i = 0; i < elevelCount; i += 2) {
		g.setColor(Color.darkGray);
		double dy2 = elevels[i]-e0;
		double dy1 = elevels[i+1]-e0;
		int y1 = getEnergyY(dy1, viewDispersion);
		int y2 = getEnergyY(dy2, viewDispersion);
		if (y2 < top)
		    continue;
		if (y1 < top)
		    y1 = top;
		y2 -= y1;
		if (y2 <= 0)
		    y2 = 1;
		g.fillRect(0, y1, winSize.width, y2);
	    }
	    
	    g.setColor(Color.white);
	    if (repeatedZonesItem.getState()) {
		viewDispersion.cellw2 = viewDispersion.width/10;
		int cells = (viewDispersion.width/2)/viewDispersion.cellw2 + 1;
		for (i = 0; i != dispersion.length; i++)
		    for (j = -cells; j <= cells; j++)
			drawDispersion(g, i, j, true, true);
	    }

	    if (reducedZonesItem.getState()) {
		viewDispersion.cellw2 = viewDispersion.width/3;
		for (i = 0; i != dispersion.length; i++)
		    drawDispersion(g, i, 0, true, true);
	    }

	    if (extendedZonesItem.getState()) {
		viewDispersion.cellw2 = viewDispersion.width/10;
		boolean xp = true;
		for (i = 0; i != dispersion.length; i++) {
		    drawDispersion(g, i, (i+1)/2, xp, !xp);
		    drawDispersion(g, i, -(i+1)/2, !xp, xp);
		    xp = !xp;
		}
	    }

	    int y = getEnergyY(expecte-e0, viewDispersion);
	    g.setColor(Color.red);
	    if (y >= viewDispersion.y) {
		g.drawLine(0, y, winSize.width, y);
	    }
	    int x = (int) (viewDispersion.cellw2*selectedK*2+
			   viewDispersion.width/2);
	    if (!extendedZonesItem.getState())
		g.drawLine(x, top, x, viewDispersion.y+viewDispersion.height);
	}
	
	if (viewBloch != null) {
	    viewBloch.drawLabel(g, "Bloch Function");
	    drawFunction(g, viewBloch, blochr, blochi, blochr.length, 0);
	}
	
	realg.drawImage(dbimage, 0, 0, this);
	if (!stoppedCheck.getState())
	    cv.repaint(pause);
    }

    int getEnergyY(double e, View v) {
	escale = energyScaleBar.getValue()/100.;
	e = (e+1)*escale-1;
	return v.mid_y - (int) (v.ymult*e);
    }
    
    void drawDispersion(Graphics g, int level, int off, boolean pos, boolean neg) {
	int ox = -1;
	int oy = -1;
	int j;
	double e0 = elevels[0]+1;
	int cellw2 = viewDispersion.cellw2;
	int cellw = cellw2*2;
	int top = viewDispersion.y+5;
	for (j = 0; j != dispersion[0].length; j++) {
	    int cx = off*cellw + viewDispersion.width/2;
	    int x = j*cellw2/(dispersion[0].length-1);
	    int y = getEnergyY(dispersion[level][j]-e0, viewDispersion);
	    if (ox != -1 && y > top && oy > top) {
		if (pos)
		    g.drawLine(cx+ox, oy, cx+x, y);
		if (neg)
		    g.drawLine(cx-ox, oy, cx-x, y);
	    }
	    ox = x;
	    oy = y;
	}
    }
    
    String getUnitText(double v, String u) {
	double va = Math.abs(v);
	if (va < 1e-17)
	    return "0 " + u;
	if (va < 1e-12)
	    return showFormat.format(v*1e15) + " f" + u;
	if (va < 1e-9)
	    return showFormat.format(v*1e12) + " p" + u;
	if (va < 1e-6)
	    return showFormat.format(v*1e9) + " n" + u;
	if (va < 1e-3)
	    return showFormat.format(v*1e6) + " u" + u;
	if (va < 1)
	    return showFormat.format(v*1e3) + " m" + u;
	if (va < 1e3)
	    return showFormat.format(v) + " " + u;
	if (va < 1e6)
	    return showFormat.format(v*1e-3) + " k" + u;
	if (va < 1e9)
	    return showFormat.format(v*1e-6) + " M" + u;
	if (va < 1e12)
	    return showFormat.format(v*1e-9) + " G" + u;
	if (va < 1e15)
	    return showFormat.format(v*1e-12) + " T" + u;
	return v + " " + u;
    }

    String getEnergyText(double e, boolean abs) {
	return getUnitText(convertEnergy(e, abs), "eV");
    }

    String getLengthText(double x) {
	// getEnergyText() assumes a 4 nm screen width
	return getUnitText(pointsToLength(x), "m");
    }

    double pointsToLength(double x) {
	// getEnergyText() assumes a 4 nm screen width
	return 4e-9*x/(sampleCount-2.);
    }

    double convertEnergy(double e, boolean abs) {
	// ground state energy for an electron in a 4 nm infinite well (in eV)
	// (for 1 Angstrom, it's 37.603)
	double base = .023502;
	
	// the calculated ground state energy for a particle of default mass in a
	// well the full width of the window is 0.001891.  So, if we assume the
	// window is 1 angstrom wide, then we multiply the elevels by base/.001891 to
	// get eV.
	if (abs)
	    e += setup.getBaseEnergy();
	return e*base/.0018801;
    }

    int stateColSize, stateSize;

    void drawFunction(Graphics g, View view, double fr[], double fi[],
		      int count, int offset) {
	int i;
	
	double expectx = 0;
	double expectx2 = 0;
	double maxsq = 0;
	double tot = 0;
	int zero = winSize.width/2;
	for (i = 0; i != count; i++) {
	    int x = winSize.width * i / (count-1);
	    int ii = i+offset;
	    double dr = fr[ii];
	    double di = (fi == null) ? 0 : fi[ii];
	    double dy = dr*dr+di*di;
	    if (dy > maxsq)
		maxsq = dy;
	    int dev = x-zero;
	    expectx += dy*dev;
	    expectx2 += dy*dev*dev;
	    tot += dy;
	}
	expectx /= tot;
	expectx2 /= tot;
	double maxnm = Math.sqrt(maxsq);
	double uncert = Math.sqrt(expectx2-expectx*expectx);
	int ox = -1, oy = 0;
	double bestscale = 0;
	if (fi != null &&
	      (probCheckItem.getState() || probPhaseCheckItem.getState()))
	    bestscale = 1/maxsq;
	else
	    bestscale = 1/maxnm;
	view.scale = bestscale;
	/*
	if (!adjustingWaveFunc) {
	    // adjust scale
	    view.scale *= 1.001;
	    if (view.scale > bestscale || view.scale == 0)
		view.scale = bestscale;
	    if (view.scale > 1e8)
		view.scale = 1e8;
	}
	*/
	g.setColor(Color.gray);

	/*
	double scaler = 1e-10;
	while (scaler*view.scale*view.ymult*600 < view.height)
	    scaler *= 10;
	System.out.print(scaler + "\n");
	for (i = 0; i != 20; i++) {
	    int y = (int) (view.ymult * i * view.scale * scaler * 10);
	    System.out.print(i + " " + y + "\n");
	    if (y > view.height/2)
		break;
	    g.drawLine(winSize.width-5, view.mid_y - y,
		       winSize.width, view.mid_y - y);
	}
	*/

	if ((probCheckItem.getState() || probPhaseCheckItem.getState() ||
	     magPhaseCheckItem.getState()) && fi != null) {

	    // draw probability or magnitude
	    g.setColor(Color.white);
	    double mult = view.ymult2*view.scale;
	    for (i = 0; i != count; i++) {
		int x = winSize.width * i / (count-1);
		double dy = 0;
		int ii = i+offset;
		if (!magPhaseCheckItem.getState())
		    dy = (fr[ii]*fr[ii]+fi[ii]*fi[ii]);
		else
		    dy = Math.sqrt(fr[ii]*fr[ii]+fi[ii]*fi[ii]);
		if (!probCheckItem.getState()) {
		    double ang = Math.atan2(fi[ii], fr[ii]);
		    g.setColor(phaseColors[(int)((ang+pi)*phaseColorCount/(2*pi+.2))]);
		}
		int y = view.lower_y - (int) (mult * dy);
		if (y < view.y)
		    y = view.y;
		if (ox != -1) {
		    xpoints[0] = ox;
		    ypoints[0] = view.lower_y+1;
		    xpoints[1] = ox;
		    ypoints[1] = oy;
		    xpoints[2] = x;
		    ypoints[2] = y;
		    xpoints[3] = x;
		    ypoints[3] = view.lower_y+1;
		    g.fillPolygon(xpoints, ypoints, 4);
		}
		ox = x;
		oy = y;
	    }
	} else {
	    g.setColor(Color.darkGray);
	    int mid_y = view.mid_y;
	    g.drawLine(0, mid_y, winSize.width, mid_y);
	    double mult = view.ymult*view.scale;
	    if (fi != null) {
		g.setColor(Color.blue);
		for (i = 0; i != count; i++) {
		    int x = winSize.width * i / (count-1);
		    int ii = i+offset;
		    int y = mid_y - (int) (mult * fi[ii]);
		    if (ox != -1)
			g.drawLine(ox, oy, x, y);
		    ox = x;
		    oy = y;
		}
	    }
	    g.setColor(Color.white);
	    ox = -1;
	    for (i = 0; i != count; i++) {
		int x = winSize.width * i / (count-1);
		int ii = i+offset;
		int y = mid_y - (int) (mult * fr[ii]);
		if (ox != -1)
		    g.drawLine(ox, oy, x, y);
		ox = x;
		oy = y;
	    }
	}
	if (maxsq > 0 && fi != null) {
	    expectx += zero;
	    /*if (uncertaintyCheckItem.getState()) {
		g.setColor(Color.blue);
		g.drawLine((int) (expectx-uncert), view.y,
			   (int) (expectx-uncert), view.y+view.height);
		g.drawLine((int) (expectx+uncert), view.y,
			   (int) (expectx+uncert), view.y+view.height);
			   }*/
	    /*if (expectCheckItem.getState()) {
		g.setColor(Color.red);
		g.drawLine((int) expectx, view.y,
			   (int) expectx, view.y+view.height);
			   }*/
	}
    }

    void edit(MouseEvent e) {
	if (selection == SEL_NONE)
	    return;
	int x = e.getX();
	int y = e.getY();
	switch (selection) {
	case SEL_HANDLE: editHandle(y);   break;
	case SEL_DISPERSION : editDispersion(x, y); break;
	default:         editFunc(x, y);  break;
	}
    }

    void editHandle(int y) {
	int dy = y-viewList[selectedPaneHandle].handle;
	View upper = viewList[selectedPaneHandle-1];
	View lower = viewList[selectedPaneHandle];
	int minheight = 10;
	if (upper.height+dy < minheight || lower.height-dy < minheight)
	    return;
	upper.height += dy;
	lower.height -= dy;
	lower.y += dy;
	lower.handle += dy;
	setGraphLines();
	cv.repaint(pause);
    }

    void editFunc(int x, int y) {
	if (mouseChooser.getSelectedIndex() == MOUSE_EIGEN) {
	    if (selection == SEL_POTENTIAL)
		selectStateByEnergy(y);
	    return;
	}
	if (dragX == x) {
	    editFuncPoint(x, y);
	    dragY = 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.
	    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 editFuncPoint(int x, int y) {
	if (selection != SEL_POTENTIAL)
	    return;
	View v = (selection == SEL_X) ? viewX : viewPotential;
	int wc = wellCountBar.getValue();
	int fullx = potSampleCount*wc;
	int lox = x * fullx / winSize.width;
	int hix = ((x+1) * fullx-1) / winSize.width;
	double val = (v.mid_y - y) / v.ymult;
	double val2 = (v.lower_y - y) / v.ymult2;
	if (val > 1)
	    val = 1;
	if (val < -1)
	    val = -1;
	if (val2 > 1)
	    val2 = 1;
	if (val2 < 0)
	    val2 = 0;
	val  = (val +1)/escale-1;
	val2 = (val2+1)/escale-1;
	if (val > 1)
	    val = 1;
	if (val2 > 1)
	    val2 = 1;
	if (lox < 1)
	    lox = 1;
	if (hix >= sampleCount-1)
	    hix = sampleCount-2;
	for (; lox <= hix; lox++) {
	    pot[lox % potSampleCount] = val;
	    setupModified = true;
	    getPotTrans();
	    levelsChanged = true;
	}
	cv.repaint(pause);
    }

    void editDispersion(int x, int y) {
	x -= viewDispersion.width/2;
	double k = x*.5/viewDispersion.cellw2;

	if (extendedZonesItem.getState()) {
	    int band = 0;
	    while (k > .5) {
		k -= .5;
		band++;
	    }
	    while (k < -.5) {
		k += .5;
		band++;
	    }
	    if ((band & 1) != 0)
		k = (k >= 0) ? .5-k : -.5-k;
	    select(k, band);
	    return;
	}
	
	if (repeatedZonesItem.getState()) {
	    while (k > .5)
		k -= 1;
	    while (k < -.5)
		k += 1;
	}
	if (k > .5)
	    k = .5;
	if (k < -.5)
	    k = -.5;
	int i;
	int besty = 1000;
	int band = 0;
	double e0 = elevels[0]+1;
	for (i = 0; i != dispersion.length; i++) {
	    int ya = getEnergyY(elevels[i*2  ]-e0, viewDispersion);
	    int yb = getEnergyY(elevels[i*2+1]-e0, viewDispersion);
	    if (y < ya && y > yb) {
		besty = -1;
		band = i;
		break;
	    }
	    int yd = Math.abs(y-ya);
	    if (yd < besty) {
		besty = yd;
		band = i;
	    }
	    yd = Math.abs(y-yb);
	    if (yd < besty) {
		besty = yd;
		band = i;
	    }
	}

	select(k, band);
    }

    void select(double k, int band) {
	selectedK = k;
	selectedBand = band;
	stateChanged = true;
	cv.repaint(pause);
    }

    void getPotTrans() {
	potTrans = new double[potSampleCount*2];
	int i;
	for (i = 0; i != potSampleCount; i++)
	    potTrans[i*2] = pot[i];
	FFT fft = new FFT(potSampleCount);
	fft.transform(potTrans, false);
    }
    
    double getPState(int x) {
	double p = (x * pSampleCount/2 / winSize.width) - pSampleCount/4;
	return p*pi/(pSampleCount/2);
    }

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

    public void componentResized(ComponentEvent e) {
	handleResize();
	cv.repaint(pause);
    }
    public void actionPerformed(ActionEvent e) {
	if (e.getSource() == exitItem) {
	    applet.destroyFrame();
	    return;
	}
	cv.repaint();
	if (e.getSource() == groundButton)
	    doGround();
    }

    public void scrollbarValueChanged(DecentScrollbar ds) {
	System.out.print(ds.getValue() + "\n");
	if (ds == massBar)
	    levelsChanged = true;
	if (ds == aux1Bar || ds == aux2Bar || ds == aux3Bar || ds == aux4Bar) {
	    setup.drawPotential();
	    getPotTrans();
	    levelsChanged = true;
	}
	if (ds == wellCountBar) {
	    setResolution();
	    setup.drawPotential();
	    stateChanged = true;
	}
	cv.repaint(pause);
    }

    public void scrollbarFinished(DecentScrollbar ds) {
	/*
	if (ds == massBar || ds == aux1Bar || ds == aux2Bar || ds == aux3Bar ||
	    ds == aux4Bar) {
	    adjustingStates = false;
	    statesChanged = true;
	    cv.repaint(pause);
	}
	*/
    }

    public boolean handleEvent(Event ev) {
	if (ev.id == Event.WINDOW_DESTROY) {
	    applet.destroyFrame();
	    return true;
	}
	return super.handleEvent(ev);
    }

    void setResolution() {
	int wc = wellCountBar.getValue();
	cellSampleCount = 8;
	while (cellSampleCount < 700/wc)
	    cellSampleCount *= 2;
	sampleCount = cellSampleCount * wc;
	//sampleCount++;
	func = new double[sampleCount];
	funci = new double[sampleCount];
	blochr = new double[sampleCount];
	blochi = new double[sampleCount];
	pot = new double[potSampleCount];
	stateChanged = true;
	pSampleCount = 512;
	pdatar = new double[pSampleCount];
	pdatai = new double[pSampleCount];
	fft = new FFT(pSampleCount);
    }

    void genStates() {
	levelsChanged = false;

	if (potTrans == null)
	    getPotTrans();
	
	double lev1[] = getBands(false, 0, 0);
	double lev2[] = getBands(false, .5, 0);

	elevelCount = lev1.length*2;
	elevels = new double[elevelCount];
	stateCount = elevelCount;
	
	// copy levels, and make sure they are in proper order
	// (swapp every other pair)
	int i;
	for (i = 0; i != lev1.length; i++) {
	    int a = (i & 1);
	    elevels[i*2+a]   = lev1[i];
	    elevels[i*2+1-a] = lev2[i];
	}

	// start to calculate the dispersion graph; first get the
	// endpoints, k = 0 and k = .5
	int dispx = 65;
	int ll = lev1.length;
	dispersion = new double[ll][dispx];
	for (i = 0; i != ll; i++) {
	    dispersion[i][0]       = lev1[i];
	    dispersion[i][dispx-1] = lev2[i];
	    
	    // linearly interpolate the middle values for now
	    int j;
	    for (j = 1; j < dispx-1; j++) {
		double q = j/(dispx-1.);
		dispersion[i][j] = lev1[i]*(1-q) + lev2[i]*q;
	    }
	}
	// fill in the rest of the dispersion graph later
	kUpdateState = dispx/2;
	kUpdateSkip  = kUpdateState*2;

	getBands(true, selectedK, selectedBand);
    }

    boolean updateK() {
	// are we done?
	if (kUpdateSkip <= 1)
	    return false;

	// update one k value in the dispersion graph.  at the start,
	// have the endpoints k = 0 and .5, and then we do .25, and then
	// .125 and .375, etc. until it's all done.
	
	int dispx = dispersion[0].length;
	double k = kUpdateState/(2.*(dispx-1));
	double lev3[] = getBands(false, k, 0);
	int j;
	for (j = 0; j != lev3.length; j++) {
	    dispersion[j][kUpdateState] = lev3[j];
	    int hskip = kUpdateSkip/2;
	    int m;
	    // interpolate linearly between the neighbor points that are done
	    for (m = 1; m < hskip; m++) {
		double q = m/(double) hskip;
		dispersion[j][kUpdateState-m] =
		    (1-q)*dispersion[j][kUpdateState] +
		    q*dispersion[j][kUpdateState-hskip];
		dispersion[j][kUpdateState+m] =
		    (1-q)*dispersion[j][kUpdateState] +
		    q*dispersion[j][kUpdateState+hskip];
	    }
	}
	
	// skip to the next point
	kUpdateState += kUpdateSkip;
	if (kUpdateState >= dispx) {
	    // use a finer grid
	    kUpdateSkip /= 2;
	    if (kUpdateSkip == 1)
		return false;
	    kUpdateState = kUpdateSkip/2;
	}
	return true;
    }
    
    int getFourierIndex(int i, int n) {
	return (i > n/2) ? n/2-i : i;
    }
    
    double [] getBands(boolean getStates, double k, int band) {
	//System.out.println("genstates " + k);
	int n = 48;
	int n2 = n*2;
	double h[] = new double[n2*n2];
	int i, j;
	mass = massBar.getValue()*.0002;
	double m1 = 1./mass;
	mass *= .511e6 / .32;

	// calculate matrix element of H.  We convert the complex H to a
	// real H matrix.  To set H(i,j) to a+bi, we set H(i,j)=H(i+n,j+n)=a,
	// H(i+n,j)=-b, H(i,j+n)=b.  The basis functions for the H matrix are
	// the momentum eigenfunctions, e^ikx.  We also have a V matrix
	// (potTrans) calculated by the calling routine using the FFT.
	for (i = 0; i != n; i++) {
	    int ii = getFourierIndex(i, n);
	    double iik = ii+k;
	    h[i+n+n2*(i+n)] = h[i+n2*i] = m1*iik*iik*.01;
	    for (j = 0; j != n; j++) {
		int jj = getFourierIndex(j, n);
		int ij = jj-ii;
		if (ij < 0)
		    ij += potTrans.length/2;
		double re = potTrans[ij*2]/potSampleCount;
		double im = potTrans[ij*2+1]/potSampleCount;
		h[i+n2*j] += re;
		h[i+n+n2*(j+n)] += re;
		h[i+n+j*n2] -= im;
		h[i+n2*(j+n)] += im;
	    }
	}
	
	double work[] = new double[1+5*n2+5*n2*n2];
	int iwork[] = new int[3+5*n2];
	intW info = new intW(0);
	double w[] = new double[n2];

	// use LAPACK to find the eigenvalues and eigenvectors
	Dsyevd.dsyevd(getStates ? "V" : "N", "U", n2,
		      h, 0, n2,
		      w, 0,
		      work, 0, work.length,
		      iwork, 0, iwork.length,
		      info);
	//System.out.println(info.val);

	double levels[] = new double[n2];

	// now get the eigenvalues and sort them
	for (i = 0; i != n2; i++)
	    levels[i] = w[i];

	int si, sj;
	// sort the elevels
	for (si = 1; si < n2; si++) {
	    double v = levels[si];
	    sj = si;
	    while (levels[sj-1] > v) {
		levels[sj] = levels[sj-1]; sj--;
		if (sj <= 0) break;
	    }
	    levels[sj] = v;
	}

	// get the levels
	double levels2[] = new double[20];
	for (i = 0; i != 20; i++)
	    levels2[i] = levels[i*2];

	if (!getStates)
	    return levels2;

	// get an eigenstate by stepping through all them.
	int picker = 0;
	
	for (i = 0; i != stateCount; i++) {
	    for (j = 0; j != n; j++)
		if (levels[i] == w[j])
		    break;
	    if (j == n) {
		System.out.print("can't find elevels! " + i + " " +
				 levels[i] + "\n");
		continue;
	    }
	    w[j] = -1;
	    // is this the eigenstate we want?
	    if (picker++ < band*2)
		continue;

	    // do an inverse FFT to calculate the state
	    double q[] = new double[cellSampleCount*2];
	    int off = n2*j;
	    blochTrans = new double[cellSampleCount*2];
	    int norm = (h[off+1] < 0) ? -1 : 1;
	    for (j = 0; j != n; j++) {
		int qo = getFourierIndex(j, n);
		if (Math.abs(qo) >= cellSampleCount/2)
		    continue;
		if (qo < 0)
		    qo += q.length/2;
		blochTrans[qo*2]   = q[qo*2  ] = norm*h[j+off];
		blochTrans[qo*2+1] = q[qo*2+1] = norm*h[j+n+off];
	    }
	    FFT fft = new FFT(cellSampleCount);
	    fft.transform(q, true);
	    double km = 2*pi*k/cellSampleCount;
	    
	    // I must have missed a sign somewhere; fix it so that waves
	    // are going in right direction
	    int fudge = ((band & 1) == 1) ? 1 : -1;

	    // multiply by the bloch wave to get the required state over
	    // multiple wells.
	    for (j = 0; j != sampleCount; j++) {
		int ji = j % cellSampleCount;
		double re = q[ji*2];
		double im = q[ji*2+1];
		double kr = Math.cos(j*km);
		double ki = -Math.sin(j*km);
		func[j] = re*kr - im*ki;
		funci[j] = fudge*(re*ki + im*kr);
		blochr[j] = re;
		blochi[j] = fudge*im;
	    }
	    expecte = levels[band*2];
	    break;
	}

	return null;
    }

    public void mouseDragged(MouseEvent e) {
	dragging = true;
	edit(e);
    }
    public void mouseMoved(MouseEvent e) {
	int x = e.getX();
	int y = e.getY();
	dragX = x; dragY = y;
	int oldCoef = selectedCoef;
	int oldSelection = selection;
	selectedCoef = -1;
	selectedPaneHandle = -1;
	selection = 0;
	int i;
	for (i = 1; i != viewCount; i++) {
	    int dy = y-viewList[i].handle;
	    if (dy >= -3 && dy <= 3) {
		selectedPaneHandle = i;
		selection = SEL_HANDLE;
	    }
	}
	Cursor cs = null;
	if (selection == SEL_HANDLE)
	    cs = Cursor.getPredefinedCursor(N_RESIZE_CURSOR);
	else if (viewX != null && viewX.contains(x, y))
	    selection = SEL_X;
	else if (viewP != null && viewP.contains(x, y)) {
	    selection = SEL_P;
	    cv.repaint(pause);
	} else if (viewPotential != null && viewPotential.contains(x, y)) {
	    selection = SEL_POTENTIAL;
	}  else if (viewDispersion != null && viewDispersion.contains(x, y)) {
	    selection = SEL_DISPERSION;
	}
	cv.setCursor(cs);
	if (selection != oldSelection || selectedCoef != oldCoef)
	    cv.repaint(pause);
    }

    void selectStateByEnergy(int y) {
	int band = 0;
	double besty = 1000;
	int i, j;
	double k = 0;
	for (i = 0; i != dispersion.length; i++)
	    for (j = 0; j != dispersion[0].length; j++) {
		double ye = getEnergyY(dispersion[i][j], viewPotential);
		// subtract out the energy to break ties
		ye -= dispersion[i][j]*1e-8;
		double yd = Math.abs(y-ye);
		if (yd < besty) {
		    besty = yd;
		    band = i;
		    k = j*.5/(dispersion[0].length-1);
		}
	    }
	select(k, band);
    }

    public void mouseClicked(MouseEvent e) {
	if (e.getClickCount() == 2 && selectedCoef != -1)
	    enterSelectedState();
    }

    void enterSelectedState() {
    }

    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
	if (!dragging) {
	    if (selectedCoef != -1) {
		selectedCoef = -1;
		cv.repaint(pause);
	    }
	    if (selectedPaneHandle != -1) {
		selectedPaneHandle = -1;
		cv.repaint(pause);
	    }
	}
    }
    
    public void mousePressed(MouseEvent e) {
	mouseMoved(e);
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	dragging = true;
	edit(e);
    }
    
    public void mouseReleased(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	/*if (mouseChooser.getSelectedIndex() == MOUSE_EDIT &&
	    selection == SEL_POTENTIAL) {
	    adjustingStates = false;
	    statesChanged = true;
	    }*/
	dragging = false;
	cv.repaint(pause);
    }
    public void itemStateChanged(ItemEvent e) {
	if (e.getItemSelectable() == stoppedCheck) {
	    cv.repaint(pause);
	    return;
	}
	if (e.getItemSelectable() == setupChooser)
	    doSetup();
	if (e.getItemSelectable() instanceof CheckboxMenuItem) {
	    CheckboxMenuItem cmi = (CheckboxMenuItem) e.getItemSelectable();
	    if (!(cmi == extendedZonesItem ||
		  cmi == reducedZonesItem ||
		  cmi == repeatedZonesItem))
		handleResize();
	    cv.repaint(pause);
	}
	int i;
	doRadio(waveFunctionMenu, e);
	doRadio(zoneMenu, e);
    }

    void doRadio(Menu menu, ItemEvent e) {
	int i, j;
	for (i = 0; i != menu.countItems(); i++)
	    if (e.getItemSelectable() == menu.getItem(i)) {
		((CheckboxMenuItem) menu.getItem(i)).setState(true);
		for (j = 0; j != menu.countItems(); j++)
		    if (i != j)
			((CheckboxMenuItem) menu.getItem(j)).setState(false);
	    }
    }

    void doSetup() {
	doBlank();
	int i;
	for (i = 0; i != potSampleCount; i++)
	    pot[i] = 0;
	setup = (Setup)
	    setupList.elementAt(setupChooser.getSelectedIndex());
	aux1Bar.setValue(100);
	aux2Bar.setValue(100);
	aux3Bar.setValue(100);
	aux4Bar.setValue(100);
	selectGround = true;
	setup.select();
	setup.drawPotential();
	getPotTrans();
	setupModified = false;
	levelsChanged = true;
	if (setup.getAuxBarCount() >= 2) {
	    aux2Label.show();
	    aux2Bar.show();
	} else {
	    aux2Label.hide();
	    aux2Bar.hide();
	}
	if (setup.getAuxBarCount() >= 3) {
	    aux3Label.show();
	    aux3Bar.show();
	} else {
	    aux3Label.hide();
	    aux3Bar.hide();
	}
	if (setup.getAuxBarCount() >= 4) {
	    aux4Label.show();
	    aux4Bar.show();
	} else {
	    aux4Label.hide();
	    aux4Bar.hide();
	}
	validate();
	selectedCoef = -1;
    }

    class FFT {
	double wtabf[];
	double wtabi[];
	int size;
	FFT(int sz) {
	    size = sz;
	    if ((size & (size-1)) != 0)
		System.out.println("size must be power of two!");
	    calcWTable();
	}
	
	void calcWTable() {
	    // calculate table of powers of w
	    wtabf = new double[size];
	    wtabi = new double[size];
	    int i;
	    for (i = 0; i != size; i += 2) {
		double pi = 3.1415926535;
		double th = pi*i/size;
		wtabf[i  ] = Math.cos(th);
		wtabf[i+1] = Math.sin(th);
		wtabi[i  ] = wtabf[i];
		wtabi[i+1] = -wtabf[i+1];
	    }
	}
    
	void transform(double data[], boolean inv) {
	    int i;
	    int j = 0;
	    int size2 = size*2;

	    if ((size & (size-1)) != 0)
		System.out.println("size must be power of two!");
	
	    // bit-reversal
	    double q;
	    int bit;
	    for (i = 0; i != size2; i += 2) {
		if (i > j) {
		    q = data[i]; data[i] = data[j]; data[j] = q;
		    q = data[i+1]; data[i+1] = data[j+1]; data[j+1] = q;
		}
		// increment j by one, from the left side (bit-reversed)
		bit = size;
		while ((bit & j) != 0) {
		    j &= ~bit;
		    bit >>= 1;
		}
		j |= bit;
	    }

	    // amount to skip through w table
	    int tabskip = size << 1;
	    double wtab[] = (inv) ? wtabi : wtabf;
	
	    int skip1, skip2, ix, j2;
	    double wr, wi, d1r, d1i, d2r, d2i, d2wr, d2wi;
	
	    // unroll the first iteration of the main loop
	    for (i = 0; i != size2; i += 4) {
		d1r = data[i];
		d1i = data[i+1];
		d2r = data[i+2];
		d2i = data[i+3];
		data[i  ] = d1r+d2r;
		data[i+1] = d1i+d2i;
		data[i+2] = d1r-d2r;
		data[i+3] = d1i-d2i;
	    }
	    tabskip >>= 1;
	
	    // unroll the second iteration of the main loop
	    int imult = (inv) ? -1 : 1;
	    for (i = 0; i != size2; i += 8) {
		d1r = data[i];
		d1i = data[i+1];
		d2r = data[i+4];
		d2i = data[i+5];
		data[i  ] = d1r+d2r;
		data[i+1] = d1i+d2i;
		data[i+4] = d1r-d2r;
		data[i+5] = d1i-d2i;
		d1r = data[i+2];
		d1i = data[i+3];
		d2r = data[i+6]*imult;
		d2i = data[i+7]*imult;
		data[i+2] = d1r-d2i;
		data[i+3] = d1i+d2r;
		data[i+6] = d1r+d2i;
		data[i+7] = d1i-d2r;
	    }
	    tabskip >>= 1;
	
	    for (skip1 = 16; skip1 <= size2; skip1 <<= 1) {
		// skip2 = length of subarrays we are combining
		// skip1 = length of subarray after combination
		skip2 = skip1 >> 1;
		tabskip >>= 1;
		for (i = 0; i != 1000; i++);
		// for each subarray
		for (i = 0; i < size2; i += skip1) {
		    ix = 0;
		    // for each pair of complex numbers (one in each subarray)
		    for (j = i; j != i+skip2; j += 2, ix += tabskip) {
			wr = wtab[ix];
			wi = wtab[ix+1];
			d1r = data[j];
			d1i = data[j+1];
			j2 = j+skip2;
			d2r = data[j2];
			d2i = data[j2+1];
			d2wr = d2r*wr - d2i*wi;
			d2wi = d2r*wi + d2i*wr;
			data[j]    = d1r+d2wr;
			data[j+1]  = d1i+d2wi;
			data[j2  ] = d1r-d2wr;
			data[j2+1] = d1i-d2wi;
		    }
		}
	    }
	}
    }

    abstract class Setup {
	abstract String getName();
	abstract void select();
	abstract Setup createNext();
	void fudgeLevels() { }
	boolean allowLeftRight() { return false; }
	abstract void drawPotential();
	int getAuxBarCount() { return 2; }
	void getInfo(String s[], int offset) { }
	double getBaseEnergy() { return -1; }
    };

    class FiniteWellSetup extends Setup {
	String getName() { return "Finite Wells"; }
	void select() {
	    aux1Label.setText("Well Width");
	    aux1Bar.setValue(98);
	    aux2Label.setText("Well Depth");
	}
	int getWidth() {
	    return (potSampleCount-1)*aux1Bar.getValue()/100;
	}
	double getTop() {
	    return -1+(aux2Bar.getValue()-1)/49.5;
	}
	void drawPotential() {
	    int i;
	    int width = getWidth();
	    double top = getTop();
	    for (i = 0; i != width; i++)
		pot[i] = -1;
	    for (; i < potSampleCount; i++)
		pot[i] = top;
	}
	/*
	void getInfo(String s[], int o) {
	    s[o] = "Well width = " + getLengthText(potSampleCount-getOffset()*2);
	    s[o] += ", Well depth = " + getEnergyText(1-getFloor(), false);
	}
	*/
	Setup createNext() { return new FiniteWellPairSetup(); }
    }
    class FiniteWellPairSetup extends Setup {
	String getName() { return "Well Pairs"; }
	void select() {
	    aux1Label.setText("Well Width");
	    aux2Bar.setValue(80);
	    aux2Label.setText("Well Separation");
	    aux2Bar.setValue(100);
	    aux3Label.setText("Well Depth 1");
	    aux3Bar.setValue(100);
	    aux4Label.setText("Well Depth 2");
	    aux4Bar.setValue(90);
	}
	int getAuxBarCount() { return 4; }
	int getWidth() {
	    return (aux1Bar.getValue()*3/4)*(potSampleCount/2)/110+1;
	}
	void drawPotential() {
	    int i;
	    int width = getWidth();
	    // min sep = 1+width; max sep = potSampleCount/2
	    double sepFrac = aux2Bar.getValue()/100.;
	    int sep = (int) ((1+width)*(1-sepFrac) + (potSampleCount/2)*sepFrac);
	    double level1 = 1-aux3Bar.getValue()/50.;
	    double level2 = 1-aux4Bar.getValue()/50.;
	    double top = 1;
	    double space = level1 < level2 ? (level1+1) : (level2+1);
	    top -= space;
	    level1 -= space;
	    level2 -= space;
	    for (i = 0; i != potSampleCount; i++)
		pot[i] = top;
	    for (i = 0; i != width; i++) {
		pot[i] = level1;
		pot[i+sep] = level2;
	    }
	}
	/*void getInfo(String s[], int o) {
	    s[o] = "Well width = " + getLengthText(getWidth());
	    s[o] += ", Well depth = " + getEnergyText(1-getFloor(), false);
	    s[o+1] = "Well separation = " + getLengthText(getSep()*2);
	    }*/
	Setup createNext() { return new FiniteWellPairCoupledSetup(); }
    }
    class FiniteWellPairCoupledSetup extends Setup {
	String getName() { return "Coupled Well Pairs"; }
	void select() {
	    aux1Label.setText("Well Separation");
	    aux1Bar.setValue(1);
	    aux2Label.setText("Wall Potential");
	    aux2Bar.setValue(50);
	}
	int getWidth() { return potSampleCount/4; }
	double getWallEnergy() { return  -1+(aux2Bar.getValue()-1)/50.; }
	void drawPotential() {
	    int i;
	    int width = getWidth();
	    // min sep = 1+width; max sep = potSampleCount/2
	    double sepFrac = aux1Bar.getValue()/100.;
	    int sep = (int) ((1+width)*(1-sepFrac) + (potSampleCount/2)*sepFrac);
	    double floor = -1;
	    double infloor = -1+(aux2Bar.getValue()-1)/49.;
	    if (infloor > 1)
		infloor = 1;
	    for (i = 0; i != potSampleCount; i++)
		pot[i] = 1;
	    for (i = 0; i != width; i++)
		pot[i] = pot[i+sep] = -1;
	    for (i = width; i != sep; i++)
		pot[i] = infloor;
	}
	/*void getInfo(String s[], int o) {
	    s[o] = "Well width = " + getLengthText(getWidth());
	    s[o] += ", Well depth = " + getEnergyText(2, false);
	    s[o+1] = "Well separation = " + getLengthText(getSep()*2);
	    s[o+1] += ", Wall potential = " + getEnergyText(getWallEnergy(), true);
	    }*/
	Setup createNext() { return new HarmonicWellSetup(); }
    }
    class HarmonicWellSetup extends Setup {
	String getName() { return "Harmonic"; }
	void select() {
	    aux1Label.setText("Well Width");
	    aux2Label.setText("Well Depth");
	}
	int getAuxBarCount() { return 2; }
	double getFloor() { return 1-(aux2Bar.getValue())/50.; }
	double getTop() { return -1+(aux2Bar.getValue())/50.; }
	void drawPotential() {
	    int i;
	    double top = getTop();
	    int width = aux1Bar.getValue()*(potSampleCount-1)/100;
	    double a = (top+1)/((width/2)*(width/2.));
	    for (i = 0; i != width; i++) {
		double xx = (i-width/2.);
		pot[i] = -1+xx*xx*a;
		if (pot[i] > top)
		    pot[i] = top;
	    }
	    for (; i < potSampleCount; i++)
		pot[i] = top;
	}
	Setup createNext() { return new CoulombWellArraySetup(); }
    }
    class CoulombWellArraySetup extends Setup {
	String getName() { return "Coulomb-Like"; }
	void select() {
	    aux1Label.setText("Well Width");
	    aux1Bar.setValue(40);
	}
	int getAuxBarCount() { return 1; }
	void drawPotential() {
	    int i;
	    double s = (aux1Bar.getValue())/200.;
	    int width = potSampleCount;
	    s *= width/2;
	    double a = -2*width*s/(2*s-width);
	    double b = 1+2*a/width;
	    double add = 1-(b-2*a/(width/2.));
	    b += add;
	    for (i = 0; i != potSampleCount; i++) {
		double xx  = (i-width/2.);
		double xx2 = width-Math.abs(xx);
		pot[i] = b-a/Math.abs(xx)-a/xx2;
		if (pot[i] < -1)
		    pot[i] = -1;
	    }
	}
	Setup createNext() { return new FreeParticleSetup(); }
    }
    class FreeParticleSetup extends Setup {
	String getName() { return "Free Particle"; }
	void select() {}
	int getAuxBarCount() { return 0; }
	void drawPotential() {
	    int i;
	    for (i = 0; i != potSampleCount; i++)
		pot[i] = -1;
	}
	Setup createNext() { return new SinusoidalLatticeSetup(); }
    }
    class SinusoidalLatticeSetup extends Setup {
	String getName() { return "Sinusoidal"; }
	void select() {
	    aux1Label.setText("Well Depth");
	}
	int getAuxBarCount() { return 1; }
	void drawPotential() {
	    double amp = aux1Bar.getValue()/100.;
	    int buf = potSampleCount/10;
	    int i;
	    for (i = 0; i != potSampleCount; i++) {
		double xx = i*2*pi/potSampleCount;
		pot[i] = -1+amp*(1-Math.cos(xx));
	    }
	}
	Setup createNext() { return null; }
    }

    class View extends Rectangle {
	int mid_y, lower_y, handle;
	double ymult, ymult2, scale;
	int cellw2;
	void drawLabel(Graphics g, String str) {
	    g.setColor(Color.white);
	    centerString(g, str, y-5);
	}
    }
};

interface DecentScrollbarListener {
    abstract void scrollbarValueChanged(DecentScrollbar ds);
    abstract void scrollbarFinished(DecentScrollbar dc);
}

// this is a scrollbar that notifies us when the user is _done_ fiddling
// with the value.
class DecentScrollbar extends Canvas
  implements MouseListener, MouseMotionListener {
    int value, lo, hi;
    DecentScrollbarListener listener;
    DecentScrollbar(DecentScrollbarListener parent, 
		    int start, int lo_, int hi_) {
	value = start;
	lo = lo_;
	hi = hi_;
	listener = parent;
	addMouseListener(this);
	addMouseMotionListener(this);
	gray1 = new Color(104, 104, 104);
	gray2 = new Color(168, 168, 168);
	gray3 = new Color(192, 192, 192);
	gray4 = new Color(224, 224, 224);
    }
    Color gray1, gray2, gray3, gray4;
    public Dimension getPreferredSize() {
	return new Dimension(20,20);
    }
    static final int tw = 8;
    boolean dragging;
    int thumbpos, dragoffset;
    public void paint(Graphics g) {
	Dimension size = getSize();
	int w = size.width;
	int h = size.height;
	int x = thumbpos = (value-lo)*(w-2-tw)/(hi-lo)+1;
	g.setColor(gray2);
	g.fillRect(0, 0, w, h);
	g.setColor(gray3);
	g.fillRect(x, 2, tw, h-4);
	g.setColor(gray4);
	g.drawLine(0, h-1, w, h-1);
	g.drawLine(w-1, 1, w-1, h-1);
	g.drawLine(x, 1, x+tw-1, 1);
	g.drawLine(x, 1, x, h-2);
	g.setColor(gray1);
	g.drawLine(0, 0, w-1, 0);
	g.drawLine(0, 0, 0, h-1);
	g.drawLine(x+tw-1, 2, x+tw-1, h-2);
	g.drawLine(x+1, h-2, x+tw-1, h-2);
    }
    int getValue() { return value; }
    boolean setValue(int v) {
	if (v < lo)
	    v = lo;
	if (v > hi)
	    v = hi;
	if (value == v)
	    return false;
	value = v;
	repaint();
	return true;
    }
    public void mousePressed(MouseEvent e) {
	if (thumbpos <= e.getX() && thumbpos+tw >= e.getX()) {
	    dragging = true;
	    dragoffset = e.getX()-thumbpos;
	}
    }
    public void mouseReleased(MouseEvent e) {
	if (dragging)
	    listener.scrollbarFinished(this);
	dragging = false;
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mouseDragged(MouseEvent e) {
	if (!dragging)
	    return;
	int x = e.getX()-dragoffset;
	Dimension size = getSize();
	int v = (x-1)*(hi-lo)/(size.width-2-tw)+lo;
	if (setValue(v))
	    listener.scrollbarValueChanged(this);
    }
    public void mouseMoved(MouseEvent e) {
    }
};
