// Circuit.java (c) 2005,2008 by Paul Falstad, www.falstad.com

import java.io.InputStream;
import java.awt.*;
import java.awt.image.*;
import java.applet.Applet;
import java.util.Vector;
import java.io.File;
import java.util.Random;
import java.util.Arrays;
import java.lang.Math;
import java.net.URL;
import java.awt.event.*;
import java.io.FilterInputStream;
import java.io.ByteArrayOutputStream;
import java.util.StringTokenizer;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

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

class CircuitLayout implements LayoutManager {
    public CircuitLayout() {}
    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* 8/10;
	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 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 Circuit extends Applet implements ComponentListener {
    CircuitFrame ogf;
    void destroyFrame() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
    boolean started = false;
    public void init() {
	addComponentListener(this);
    }
    
    void showFrame() {
	if (ogf == null) {
	    started = true;
	    ogf = new CircuitFrame(this);
	    ogf.init();
	    repaint();
	}
    }

    public void toggleSwitch(int x) { ogf.toggleSwitch(x); }
    
    public void paint(Graphics g) {
	String s = "Applet is open in a separate window.";
	if (!started)
	    s = "Applet is starting.";
	else if (ogf == null)
	    s = "Applet is finished.";
	else if (ogf.useFrame)
	    ogf.triggerShow();
	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) {
	if (ogf != null)
	    ogf.componentResized(e);
    }
    
    public void destroy() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
};

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

    Dimension winSize;
    Image dbimage;
    
    Random random;
    public static final int sourceRadius = 7;
    public static final double freqMult = 3.14159265*2*4;
    
    public String getAppletInfo() {
	return "Circuit by Paul Falstad";
    }

    Container main;
    Label titleLabel;
    Button resetButton;
    Button dumpMatrixButton;
    MenuItem exportItem, importItem, exitItem;
    Menu optionsMenu;
    Checkbox stoppedCheck;
    CheckboxMenuItem dotsCheckItem;
    CheckboxMenuItem voltsCheckItem;
    CheckboxMenuItem powerCheckItem;
    CheckboxMenuItem smallGridCheckItem;
    CheckboxMenuItem showValuesCheckItem;
    //CheckboxMenuItem conductanceCheckItem;
    CheckboxMenuItem euroResistorCheckItem;
    CheckboxMenuItem printableCheckItem;
    CheckboxMenuItem conventionCheckItem;
    Scrollbar speedBar;
    Scrollbar currentBar;
    Label powerLabel;
    Scrollbar powerBar;
    double currentMult, powerMult;
    PopupMenu elmMenu;
    MenuItem elmEditMenuItem;
    MenuItem elmDeleteMenuItem;
    MenuItem elmScopeMenuItem;
    PopupMenu scopeMenu;
    PopupMenu transScopeMenu;
    PopupMenu mainMenu;
    CheckboxMenuItem scopeVMenuItem;
    CheckboxMenuItem scopeIMenuItem;
    CheckboxMenuItem scopeMaxMenuItem;
    CheckboxMenuItem scopeFreqMenuItem;
    CheckboxMenuItem scopePowerMenuItem;
    CheckboxMenuItem scopeIbMenuItem;
    CheckboxMenuItem scopeIcMenuItem;
    CheckboxMenuItem scopeIeMenuItem;
    CheckboxMenuItem scopeVbeMenuItem;
    CheckboxMenuItem scopeVbcMenuItem;
    CheckboxMenuItem scopeVceMenuItem;
    CheckboxMenuItem scopeVIMenuItem;
    CheckboxMenuItem scopeXYMenuItem;
    MenuItem scopeSelectYMenuItem;
    Class addingClass;
    int mouseMode = -1;
    String mouseModeStr = "hello";
    Font unitsFont;
    static final double pi = 3.14159265358979323846;
    static final int MODE_ADD_ELM = 0;
    static final int MODE_DRAG_ALL = 1;
    static final int MODE_DRAG_ROW = 2;
    static final int MODE_DRAG_COLUMN = 3;
    static final int MODE_DRAG_SELECTED = 4;
    static final int MODE_DRAG_POST = 5;
    static final int infoWidth = 120;
    static final int SCOPEVAL_POWER = 1;
    static final int SCOPEVAL_IB = 1;
    static final int SCOPEVAL_IC = 2;
    static final int SCOPEVAL_IE = 3;
    static final int SCOPEVAL_VBE = 4;
    static final int SCOPEVAL_VBC = 5;
    static final int SCOPEVAL_VCE = 6;
    int dragX, dragY;
    int selectedSource;
    int gridSize, gridMask, gridRound;
    boolean dragging;
    boolean analyzeFlag;
    boolean dumpMatrix;
    boolean useBufferedImage;
    double t;
    int pause = 10;
    int colorScaleCount = 32;
    Color colorScale[];
    Color whiteColor, selectColor, lightGrayColor;
    int scopeSelected = -1;
    int menuScope = -1;
    int hintType = -1, hintItem1, hintItem2;
    String stopMessage;
    double timeStep;
    static final int HINT_LC = 1;
    static final int HINT_RC = 2;
    static final int HINT_3DB_C = 3;
    static final int HINT_TWINT = 4;
    static final int HINT_3DB_L = 5;
    Vector elmList;
    Vector setupList;
    CircuitElm dragElm, menuElm, mouseElm, stopElm;
    int mousePost = -1;
    CircuitElm plotXElm, plotYElm;
    int draggingPost;
    SwitchElm heldSwitchElm;
    double circuitMatrix[][], circuitRightSide[],
	origRightSide[], origMatrix[][];
    RowInfo circuitRowInfo[];
    int circuitPermute[];
    boolean circuitNonLinear;
    int voltageSourceCount;
    double voltageRange = 5;
    int circuitMatrixSize, circuitMatrixFullSize;
    boolean circuitNeedsMap;
    public boolean useFrame;
    int scopeCount;
    Scope scopes[];
    int scopeColCount[];
    EditDialog editDialog;
    ImportDialog impDialog;
    Class dumpTypes[];
    String muString = "u";
    String ohmString = "ohm";
    Rectangle circuitArea;
    Point ps1, ps2;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    CircuitCanvas cv;
    Circuit applet;
    NumberFormat showFormat, shortFormat, noCommaFormat;

    CircuitFrame(Circuit a) {
	super("Circuit Simulator v1.3e");
	applet = a;
	useFrame = false;
    }

    public void init() {
	String startCircuit = null;
	String startLabel = null;
	String euroResistor = null;
	String useFrameStr = null;
	boolean printable = false;
	boolean convention = true;

	try {
	    String param = applet.getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	    startCircuit = applet.getParameter("startCircuit");
	    startLabel   = applet.getParameter("startLabel");
	    euroResistor = applet.getParameter("euroResistors");
	    useFrameStr  = applet.getParameter("useFrame");
	    String x = applet.getParameter("whiteBackground");
	    if (x != null && x.equalsIgnoreCase("true"))
		printable = true;
	    x = applet.getParameter("conventionalCurrent");
	    if (x != null && x.equalsIgnoreCase("true"))
		convention = false;
	} catch (Exception e) { }
	
	if (startLabel == null)
	    startLabel = "LRC Circuit";
	if (startCircuit == null)
	    startCircuit = "lrc.txt";
	boolean euro = (euroResistor != null && euroResistor.equalsIgnoreCase("true"));
	useFrame = (useFrameStr == null || !useFrameStr.equalsIgnoreCase("false"));
	if (useFrame)
	    main = this;
	else
	    main = applet;
	
	unitsFont = new Font("SansSerif", 0, 10);
	String os = System.getProperty("os.name");
	String jv = System.getProperty("java.class.version");
	double jvf = new Double(jv).doubleValue();
	if (jvf >= 48) {
	    muString = "\u03bc";
	    ohmString = "\u03a9";
	    useBufferedImage = true;
	}
	
	dumpTypes = new Class[300];
	// these characters are reserved
	dumpTypes[(int)'o'] = Scope.class;
	dumpTypes[(int)'h'] = Scope.class;
	dumpTypes[(int)'$'] = Scope.class;

	main.setLayout(new CircuitLayout());
	cv = new CircuitCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	main.add(cv);

	mainMenu = new PopupMenu();
	MenuBar mb = null;
	if (useFrame)
	    mb = new MenuBar();
	Menu m = new Menu("File");
	if (useFrame)
	    mb.add(m);
	else
	    mainMenu.add(m);
	m.add(importItem = getMenuItem("Import"));
	m.add(exportItem = getMenuItem("Export"));
	m.addSeparator();
	m.add(exitItem   = getMenuItem("Exit"));

	m = new Menu("Scope");
	if (useFrame)
	    mb.add(m);
	else
	    mainMenu.add(m);
	m.add(getMenuItem("Stack All", "stackAll"));
	m.add(getMenuItem("Unstack All", "unstackAll"));

	optionsMenu = m = new Menu("Options");
	if (useFrame)
	    mb.add(m);
	else
	    mainMenu.add(m);
	m.add(dotsCheckItem = getCheckItem("Show Current"));
	dotsCheckItem.setState(true);
	m.add(voltsCheckItem = getCheckItem("Show Voltage"));
	voltsCheckItem.setState(true);
	m.add(powerCheckItem = getCheckItem("Show Power"));
	m.add(showValuesCheckItem = getCheckItem("Show Values"));
	showValuesCheckItem.setState(true);
	//m.add(conductanceCheckItem = getCheckItem("Show Conductance"));
	m.add(smallGridCheckItem = getCheckItem("Small Grid"));
	m.add(euroResistorCheckItem = getCheckItem("European Resistors"));
	euroResistorCheckItem.setState(euro);
	m.add(printableCheckItem = getCheckItem("White Background"));
	printableCheckItem.setState(printable);
	m.add(conventionCheckItem = getCheckItem("Conventional Current Motion"));
	conventionCheckItem.setState(convention);
	
	Menu circuitsMenu = new Menu("Circuits");
	if (useFrame)
	    mb.add(circuitsMenu);
	else
	    mainMenu.add(circuitsMenu);
	
	mainMenu.add(getClassCheckItem("Add Wire", "WireElm"));
	mainMenu.add(getClassCheckItem("Add Resistor", "ResistorElm"));
	
	Menu passMenu = new Menu("Passive Components");
	mainMenu.add(passMenu);
	passMenu.add(getClassCheckItem("Add Capacitor", "CapacitorElm"));
	passMenu.add(getClassCheckItem("Add Inductor", "InductorElm"));
	passMenu.add(getClassCheckItem("Add Switch", "SwitchElm"));
	passMenu.add(getClassCheckItem("Add Push Switch", "PushSwitchElm"));
	passMenu.add(getClassCheckItem("Add DPST Switch", "Switch2Elm"));
	passMenu.add(getClassCheckItem("Add Transformer", "TransformerElm"));
	passMenu.add(getClassCheckItem("Add Tapped Transformer",
				       "TappedTransformerElm"));
	passMenu.add(getClassCheckItem("Add Transmission Line", "TransLineElm"));
	
	Menu inputMenu = new Menu("Inputs/Outputs");
	mainMenu.add(inputMenu);
	inputMenu.add(getClassCheckItem("Add Ground", "GroundElm"));
	inputMenu.add(getClassCheckItem("Add Voltage Source (2-terminal)", "DCVoltageElm"));
	inputMenu.add(getClassCheckItem("Add A/C Source (2-terminal)", "ACVoltageElm"));
	inputMenu.add(getClassCheckItem("Add Voltage Source (1-terminal)", "RailElm"));
	inputMenu.add(getClassCheckItem("Add A/C Source (1-terminal)", "ACRailElm"));
	inputMenu.add(getClassCheckItem("Add Square Wave (1-terminal)", "SquareRailElm"));
	inputMenu.add(getClassCheckItem("Add Analog Output", "OutputElm"));
	inputMenu.add(getClassCheckItem("Add Logic Input", "LogicInputElm"));
	inputMenu.add(getClassCheckItem("Add Logic Output", "LogicOutputElm"));
	inputMenu.add(getClassCheckItem("Add Clock", "ClockElm"));
	inputMenu.add(getClassCheckItem("Add A/C Sweep", "SweepElm"));
	inputMenu.add(getClassCheckItem("Add Var. Voltage", "VarRailElm"));
	inputMenu.add(getClassCheckItem("Add Antenna", "AntennaElm"));
	inputMenu.add(getClassCheckItem("Add Current Source", "CurrentElm"));
	inputMenu.add(getClassCheckItem("Add LED", "LEDElm"));
	
	Menu activeMenu = new Menu("Active Components");
	mainMenu.add(activeMenu);
	activeMenu.add(getClassCheckItem("Add Diode", "DiodeElm"));
	activeMenu.add(getClassCheckItem("Add Transistor (bipolar, NPN)",
				    "NTransistorElm"));
	activeMenu.add(getClassCheckItem("Add Transistor (bipolar, PNP)",
				    "PTransistorElm"));
	activeMenu.add(getClassCheckItem("Add Op Amp (- on top)", "OpAmpElm"));
	activeMenu.add(getClassCheckItem("Add Op Amp (+ on top)",
				    "OpAmpSwapElm"));
	activeMenu.add(getClassCheckItem("Add MOSFET (n-channel)",
				    "NMosfetElm"));
	activeMenu.add(getClassCheckItem("Add MOSFET (p-channel)",
				    "PMosfetElm"));
	activeMenu.add(getClassCheckItem("Add JFET (n-channel)",
					 "NJfetElm"));
	activeMenu.add(getClassCheckItem("Add Analog Switch (SPST)", "AnalogSwitchElm"));
	activeMenu.add(getClassCheckItem("Add Analog Switch (SPDT)", "AnalogSwitch2Elm"));

	Menu gateMenu = new Menu("Logic Gates");
	mainMenu.add(gateMenu);
	gateMenu.add(getClassCheckItem("Add Inverter", "InverterElm"));
	gateMenu.add(getClassCheckItem("Add NAND Gate", "NandGateElm"));
	gateMenu.add(getClassCheckItem("Add NOR Gate", "NorGateElm"));
	gateMenu.add(getClassCheckItem("Add AND Gate", "AndGateElm"));
	gateMenu.add(getClassCheckItem("Add OR Gate", "OrGateElm"));
	gateMenu.add(getClassCheckItem("Add XOR Gate", "XorGateElm"));

	Menu chipMenu = new Menu("Chips");
	mainMenu.add(chipMenu);
	chipMenu.add(getClassCheckItem("Add D Flip-Flop", "DFlipFlopElm"));
	chipMenu.add(getClassCheckItem("Add JK Flip-Flop", "JKFlipFlopElm"));
	chipMenu.add(getClassCheckItem("Add 7 Segment LED", "SevenSegElm"));
	chipMenu.add(getClassCheckItem("Add VCO", "VCOElm"));
	chipMenu.add(getClassCheckItem("Add Phase Comparator", "PhaseCompElm"));
	chipMenu.add(getClassCheckItem("Add Counter", "CounterElm"));
	chipMenu.add(getClassCheckItem("Add Decade Counter", "DecadeElm"));
	chipMenu.add(getClassCheckItem("Add 555 Timer", "TimerElm"));
	chipMenu.add(getClassCheckItem("Add DAC", "DACElm"));
	chipMenu.add(getClassCheckItem("Add ADC", "ADCElm"));
	chipMenu.add(getClassCheckItem("Add Latch", "LatchElm"));
	
	Menu otherMenu = new Menu("Other");
	mainMenu.add(otherMenu);
	otherMenu.add(getClassCheckItem("Add Text", "TextElm"));
	otherMenu.add(getClassCheckItem("Add Scope Probe", "ProbeElm"));
	otherMenu.add(getCheckItem("Drag All", "DragAll"));
	otherMenu.add(getCheckItem("Drag Row", "DragRow"));
	otherMenu.add(getCheckItem("Drag Column", "DragColumn"));
	otherMenu.add(getCheckItem("Drag Selected", "DragSelected"));
	otherMenu.add(getCheckItem("Drag Post", "DragPost"));
	
	main.add(mainMenu);

	main.add(resetButton = new Button("Reset"));
	resetButton.addActionListener(this);
	dumpMatrixButton = new Button("Dump Matrix");
	dumpMatrixButton.addActionListener(this);
	stoppedCheck = new Checkbox("Stopped");
	stoppedCheck.addItemListener(this);
	main.add(stoppedCheck);
	
	main.add(new Label("Simulation Speed", Label.CENTER));

	// was max of 140
	main.add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 3, 1, 0, 260));
	speedBar.addAdjustmentListener(this);

	main.add(new Label("Current Speed", Label.CENTER));
	currentBar = new Scrollbar(Scrollbar.HORIZONTAL,
				   50, 1, 1, 100);
	currentBar.addAdjustmentListener(this);
	main.add(currentBar);

	main.add(powerLabel = new Label("Power Brightness", Label.CENTER));
	main.add(powerBar = new Scrollbar(Scrollbar.HORIZONTAL,
				    50, 1, 1, 100));
	powerBar.addAdjustmentListener(this);
	powerBar.disable();
	powerLabel.disable();

	main.add(new Label("www.falstad.com"));

	if (useFrame)
	    main.add(new Label(""));
	Font f = new Font("SansSerif", 0, 10);
	Label l;
	l = new Label("Current Circuit:");
	l.setFont(f);
	titleLabel = new Label("Label");
	titleLabel.setFont(f);
	if (useFrame) {
	    main.add(l);
	    main.add(titleLabel);
	}

	setGrid();
	elmList = new Vector();
	setupList = new Vector();
	colorScale = new Color[colorScaleCount];
	int i;
	for (i = 0; i != colorScaleCount; i++) {
	    double v = i*2./colorScaleCount - 1;
	    if (v < 0) {
		int n1 = (int) (128*-v)+127;
		int n2 = (int) (127*(1+v));
		colorScale[i] = new Color(n1, n2, n2);
	    } else {
		int n1 = (int) (128*v)+127;
		int n2 = (int) (127*(1-v));
		colorScale[i] = new Color(n2, n1, n2);
	    }
	}

	scopes = new Scope[20];
	scopeColCount = new int[20];
	scopeCount = 0;
	
	random = new Random();
	ps1 = new Point();
	ps2 = new Point();
	cv.setBackground(Color.black);
	cv.setForeground(Color.lightGray);
	showFormat = DecimalFormat.getInstance();
	showFormat.setMaximumFractionDigits(2);
	shortFormat = DecimalFormat.getInstance();
	shortFormat.setMaximumFractionDigits(1);
	noCommaFormat = DecimalFormat.getInstance();
	noCommaFormat.setMaximumFractionDigits(10);
	noCommaFormat.setGroupingUsed(false);
	
	elmMenu = new PopupMenu();
	elmMenu.add(elmEditMenuItem = getMenuItem("Edit"));
	elmMenu.add(elmDeleteMenuItem = getMenuItem("Delete"));
	elmMenu.add(elmScopeMenuItem = getMenuItem("View in Scope"));
	main.add(elmMenu);
	
	scopeMenu = buildScopeMenu(false);
	transScopeMenu = buildScopeMenu(true);

	getSetupList(circuitsMenu, false);
	if (useFrame)
	    setMenuBar(mb);
	if (stopMessage == null)
	    readSetupFile(startCircuit, startLabel);

	if (useFrame) {
	    Dimension screen = getToolkit().getScreenSize();
	    resize(800, 640);
	    handleResize();
	    Dimension x = getSize();
	    setLocation((screen.width  - x.width)/2,
			(screen.height - x.height)/2);
	    show();
	} else {
	    if (!powerCheckItem.getState()) {
		main.remove(powerBar);
		main.remove(powerLabel);
		main.validate();
	    }
	    hide();
	    handleResize();
	    applet.validate();
	}
	main.requestFocus();
    }

    boolean shown = false;
    
    public void triggerShow() {
	if (!shown)
	    show();
	shown = true;
    }
    
    PopupMenu buildScopeMenu(boolean t) {
	PopupMenu m = new PopupMenu();
	m.add(getMenuItem("Remove", "remove"));
	m.add(getMenuItem("Speed 2x", "speed2"));
	m.add(getMenuItem("Speed 1/2x", "speed1/2"));
	m.add(getMenuItem("Scale 2x", "scale"));
	m.add(getMenuItem("Stack", "stack"));
	m.add(getMenuItem("Unstack", "unstack"));
	if (t) {
	    m.add(scopeIbMenuItem = getCheckItem("Show Ib"));
	    m.add(scopeIcMenuItem = getCheckItem("Show Ic"));
	    m.add(scopeIeMenuItem = getCheckItem("Show Ie"));
	    m.add(scopeVbeMenuItem = getCheckItem("Show Vbe"));
	    m.add(scopeVbcMenuItem = getCheckItem("Show Vbc"));
	    m.add(scopeVceMenuItem = getCheckItem("Show Vce"));
	} else {
	    m.add(scopeVMenuItem = getCheckItem("Show Voltage"));
	    m.add(scopeIMenuItem = getCheckItem("Show Current"));
	    m.add(scopePowerMenuItem = getCheckItem("Show Power Consumed"));
	    m.add(scopeMaxMenuItem = getCheckItem("Show Peak Value"));
	    m.add(scopeFreqMenuItem = getCheckItem("Show Frequency"));
	    m.add(scopeVIMenuItem = getCheckItem("Show V versus I"));
	    m.add(scopeXYMenuItem = getCheckItem("Plot X/Y"));
	    m.add(scopeSelectYMenuItem = getMenuItem("Select Y", "selecty"));
	}
	main.add(m);
	return m;
    }
    
    MenuItem getMenuItem(String s) {
	MenuItem mi = new MenuItem(s);
	mi.addActionListener(this);
	return mi;
    }

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

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

    CheckboxMenuItem getClassCheckItem(String s, String t) {
	try {
	    Class c = Class.forName("CircuitFrame$" + t);
	    CircuitElm elm = constructElement(c, 0, 0);
	    register(c, elm);
	    elm.delete();
	} catch (Exception ee) {
	    ee.printStackTrace();
	}
	return getCheckItem(s, t);
    }
    
    CheckboxMenuItem getCheckItem(String s, String t) {
	CheckboxMenuItem mi = new CheckboxMenuItem(s);
	mi.addItemListener(this);
	mi.setActionCommand(t);
	return mi;
    }

    void register(Class c, CircuitElm elm) {
	int t = elm.getDumpType();
	if (t == 0) {
	    System.out.println("no dump type: " + c);
	    return;
	}
	Class dclass = elm.getDumpClass();
	if (dumpTypes[t] == dclass)
	    return;
	if (dumpTypes[t] != null) {
	    System.out.println("dump type conflict: " + c + " " +
			       dumpTypes[t]);
	    return;
	}
	dumpTypes[t] = dclass;
    }
    
    void handleResize() {
        winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	dbimage = main.createImage(winSize.width, winSize.height);
	int h = winSize.height / 5;
	/*if (h < 128 && winSize.height > 300)
	  h = 128;*/
	circuitArea = new Rectangle(0, 0, winSize.width, winSize.height-h);
	int i;
	int minx = 1000, maxx = 0, miny = 1000, maxy = 0;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    // centered text causes problems when trying to center the circuit,
	    // so we special-case it here
	    if (!ce.isCenteredText()) {
		minx = min(ce.x, min(ce.x2, minx));
		maxx = max(ce.x, max(ce.x2, maxx));
	    }
	    miny = min(ce.y, min(ce.y2, miny));
	    maxy = max(ce.y, max(ce.y2, maxy));
	}
	// center circuit; we don't use snapGrid() because that rounds
	int dx = gridMask & ((circuitArea.width -(maxx-minx))/2-minx);
	int dy = gridMask & ((circuitArea.height-(maxy-miny))/2-miny);
	if (dx+minx < 0)
	    dx = gridMask & (-minx);
	if (dy+miny < 0)
	    dy = gridMask & (-miny);
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    ce.move(dx, dy);
	}
    }

    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            applet.destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }
    
    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();
    }

    static final int resct = 6;
    long lastTime = 0, lastFrameTime, lastIterTime, secTime = 0;
    int frames = 0;
    int steps = 0;
    int framerate = 0, steprate = 0;

    public void updateCircuit(Graphics realg) {
	CircuitElm realMouseElm;
	if (winSize == null || winSize.width == 0)
	    return;
	if (analyzeFlag) {
	    analyzeCircuit();
	    analyzeFlag = false;
	}
	if (editDialog != null)
	    mouseElm = editDialog.elm;
	realMouseElm = mouseElm;
	if (mouseElm == null)
	    mouseElm = stopElm;
	setupScopes();
        Graphics g = null;
	g = dbimage.getGraphics();
	selectColor = Color.cyan;
	if (printableCheckItem.getState()) {
	    whiteColor = Color.black;
	    lightGrayColor = Color.black;
	    g.setColor(Color.white);
	} else {
	    whiteColor = Color.white;
	    lightGrayColor = Color.lightGray;
	    g.setColor(Color.black);
	}
	g.fillRect(0, 0, winSize.width, winSize.height);
	if (!stoppedCheck.getState()) {
	    try {
		runCircuit();
	    } catch (Exception e) {
		e.printStackTrace();
		analyzeFlag = true;
		cv.repaint();
		return;
	    }
	}
	if (!stoppedCheck.getState()) {
	    long sysTime = System.currentTimeMillis();
	    if (lastTime != 0) {
		int inc = (int) (sysTime-lastTime);
		double c = currentBar.getValue();
		c = java.lang.Math.exp(c/3.5-14.2);
		currentMult = 1.7 * inc * c;
		if (!conventionCheckItem.getState())
		    currentMult = -currentMult;
	    }
	    if (sysTime-secTime >= 1000) {
		framerate = frames; steprate = steps;
		frames = 0; steps = 0;
		secTime = sysTime;
	    }
	    lastTime = sysTime;
	} else
	    lastTime = 0;
	powerMult = Math.exp(powerBar.getValue()/4.762-7);
	
	int i;
	Font oldfont = g.getFont();
	for (i = 0; i != elmList.size(); i++) {
	    if (powerCheckItem.getState())
		g.setColor(Color.gray);
	    /*else if (conductanceCheckItem.getState())
	      g.setColor(Color.white);*/
	    getElm(i).draw(g);
	}
	if (mouseMode == MODE_DRAG_ROW || mouseMode == MODE_DRAG_COLUMN)
	    for (i = 0; i != elmList.size(); i++) {
		CircuitElm ce = getElm(i);
		ce.drawPost(g, ce.x , ce.y );
		ce.drawPost(g, ce.x2, ce.y2);
	    }
	/*if (mouseElm != null) {
	    g.setFont(oldfont);
	    g.drawString("+", mouseElm.x+10, mouseElm.y);
	    }*/
	if (dragElm != null &&
	      (dragElm.x != dragElm.x2 || dragElm.y != dragElm.y2))
	    dragElm.draw(g);
	g.setFont(oldfont);
	int ct = scopeCount;
	if (stopMessage != null)
	    ct = 0;
	for (i = 0; i != ct; i++)
	    scopes[i].draw(g);
	g.setColor(whiteColor);
	int ybase = circuitArea.height;
	if (stopMessage != null) {
	    g.drawString(stopMessage, 10, ybase+15);
	} else {
	    String info[] = new String[10];
	    if (mouseElm != null) {
		if (mousePost == -1)
		    mouseElm.getInfo(info);
		else
		    info[0] = "V = " +
			getUnitText(mouseElm.getPostVoltage(mousePost), "V");
		/*
		for (i = 0; i != mouseElm.getPostCount(); i++)
		    info[0] += " " + mouseElm.nodes[i];
		if (mouseElm.getVoltageSourceCount() > 0)
		    info[0] += ";" + (mouseElm.getVoltageSource()+nodeList.size());
		*/
	    } else {
		showFormat.setMinimumFractionDigits(2);
		info[0] = "t = " + getUnitText(t, "s");
		showFormat.setMinimumFractionDigits(0);
	    }
	    if (hintType != -1) {
		for (i = 0; info[i] != null; i++)
		    ;
		String s = getHint();
		if (s == null)
		    hintType = -1;
		else
		    info[i] = s;
	    }
	    int x = 0;
	    if (ct != 0)
		x = scopes[ct-1].rightEdge() + 20;
	    x = max(x, winSize.width*2/3);
	    for (i = 0; info[i] != null; i++)
		g.drawString(info[i], x,
			     ybase+15*(i+1));
	}
	mouseElm = realMouseElm;
	frames++;
	/*g.setColor(Color.white);
	g.drawString("Framerate: " + framerate, 10, 10);
	g.drawString("Steprate: " + steprate,  10, 30);
	g.drawString("Steprate/iter: " + (steprate/getIterCount()),  10, 50);
	g.drawString("iterc: " + (getIterCount()),  10, 70);*/
	lastFrameTime = lastTime;

	realg.drawImage(dbimage, 0, 0, this);
	if (!stoppedCheck.getState() && circuitMatrix != null)
	    cv.repaint(pause);
    }

    void setupScopes() {
	int i;
	
	// check scopes to make sure the elements still exist, and remove
	// unused scopes/columns
	int pos = -1;
	for (i = 0; i < scopeCount; i++) {
	    if (locateElm(scopes[i].elm) < 0)
		scopes[i].setElm(null);
	    if (scopes[i].elm == null) {
		int j;
		for (j = i; j != scopeCount; j++)
		    scopes[j] = scopes[j+1];
		scopeCount--;
		i--;
		continue;
	    }
	    if (scopes[i].position > pos+1)
		scopes[i].position = pos+1;
	    pos = scopes[i].position;
	}
	while (scopeCount > 0 && scopes[scopeCount-1].elm == null)
	    scopeCount--;
	int h = winSize.height - circuitArea.height;
	pos = 0;
	for (i = 0; i != scopeCount; i++)
	    scopeColCount[i] = 0;
	for (i = 0; i != scopeCount; i++) {
	    pos = max(scopes[i].position, pos);
	    scopeColCount[scopes[i].position]++;
	}
	int colct = pos+1;
	int iw = infoWidth;
	if (colct <= 2)
	    iw = iw*3/2;
	int w = (winSize.width-iw) / colct;
	int marg = 10;
	if (w < marg*2)
	    w = marg*2;
	pos = -1;
	int colh = 0;
	int row = 0;
	int speed = 0;
	for (i = 0; i != scopeCount; i++) {
	    Scope s = scopes[i];
	    if (s.position > pos) {
		pos = s.position;
		colh = h / scopeColCount[pos];
		row = 0;
		speed = s.speed;
	    }
	    if (s.speed != speed) {
		s.speed = speed;
		s.resetGraph();
	    }
	    Rectangle r = new Rectangle(pos*w, winSize.height-h+colh*row,
					w-marg, colh);
	    row++;
	    if (!r.equals(s.rect))
		s.setRect(r);
	}
    }
    
    String getHint() {
	CircuitElm c1 = getElm(hintItem1);
	CircuitElm c2 = getElm(hintItem2);
	if (c1 == null || c2 == null)
	    return null;
	if (hintType == HINT_LC) {
	    if (!(c1 instanceof InductorElm))
		return null;
	    if (!(c2 instanceof CapacitorElm))
		return null;
	    InductorElm ie = (InductorElm) c1;
	    CapacitorElm ce = (CapacitorElm) c2;
	    return "res.f = " + getUnitText(1/(2*pi*Math.sqrt(ie.inductance*
						    ce.capacitance)), "Hz");
	}
	if (hintType == HINT_RC) {
	    if (!(c1 instanceof ResistorElm))
		return null;
	    if (!(c2 instanceof CapacitorElm))
		return null;
	    ResistorElm re = (ResistorElm) c1;
	    CapacitorElm ce = (CapacitorElm) c2;
	    return "RC = " + getUnitText(re.resistance*ce.capacitance,
					 "s");
	}
	if (hintType == HINT_3DB_C) {
	    if (!(c1 instanceof ResistorElm))
		return null;
	    if (!(c2 instanceof CapacitorElm))
		return null;
	    ResistorElm re = (ResistorElm) c1;
	    CapacitorElm ce = (CapacitorElm) c2;
	    return "f.3db = " +
		getUnitText(1/(2*pi*re.resistance*ce.capacitance), "Hz");
	}
	if (hintType == HINT_3DB_L) {
	    if (!(c1 instanceof ResistorElm))
		return null;
	    if (!(c2 instanceof InductorElm))
		return null;
	    ResistorElm re = (ResistorElm) c1;
	    InductorElm ie = (InductorElm) c2;
	    return "f.3db = " +
		getUnitText(re.resistance/(2*pi*ie.inductance), "Hz");
	}
	if (hintType == HINT_TWINT) {
	    if (!(c1 instanceof ResistorElm))
		return null;
	    if (!(c2 instanceof CapacitorElm))
		return null;
	    ResistorElm re = (ResistorElm) c1;
	    CapacitorElm ce = (CapacitorElm) c2;
	    return "fc = " +
		getUnitText(1/(2*pi*re.resistance*ce.capacitance), "Hz");
	}
	return null;
    }

    public void toggleSwitch(int n) {
	int i;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    if (ce instanceof SwitchElm) {
		n--;
		if (n == 0) {
		    ((SwitchElm) ce).toggle();
		    analyzeFlag = true;
		    cv.repaint();
		    return;
		}
	    }
	}
    }
    
    void needAnalyze() {
	analyzeFlag = true;
	cv.repaint();
    }
    
    class CircuitNode {
	int x, y;
	Vector links;
	boolean internal;
	CircuitNode() { links = new Vector(); }
    }
    class CircuitNodeLink {
	int num;
	CircuitElm elm;
    }
    // info about each row/column of the matrix for simplification purposes
    class RowInfo {
	static final int ROW_NORMAL = 0;  // ordinary value
	static final int ROW_CONST  = 1;  // value is constant
	static final int ROW_EQUAL  = 2;  // value is equal to another value
	int nodeEq, type, mapCol, mapRow;
	double value;
	boolean rsChanges; // row's right side changes
	boolean lsChanges; // row's left side changes
	boolean dropRow;   // row is not needed in matrix
	RowInfo() { type = ROW_NORMAL; }
    }

    Vector nodeList;
    CircuitElm voltageSources[];

    CircuitNode getCircuitNode(int n) {
	if (n >= nodeList.size())
	    return null;
	return (CircuitNode) nodeList.elementAt(n);
    }

    CircuitElm getElm(int n) {
	if (n >= elmList.size())
	    return null;
	return (CircuitElm) elmList.elementAt(n);
    }
    
    void analyzeCircuit() {
	if (elmList.isEmpty())
	    return;
	stopMessage = null;
	stopElm = null;
	int i, j;
	int vscount = 0;
	nodeList = new Vector();
	boolean gotGround = false;
	boolean gotRail = false;
	CircuitElm volt = null;

	//System.out.println("ac1");
	// look for voltage or ground element
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    if (ce instanceof GroundElm) {
		gotGround = true;
		break;
	    }
	    if (ce instanceof RailElm)
		gotRail = true;
	    if (volt == null && ce instanceof VoltageElm)
		volt = ce;
	}

	// if no ground, and no rails, then the voltage elm's first terminal
	// is ground
	if (!gotGround && volt != null && !gotRail) {
	    CircuitNode cn = new CircuitNode();
	    Point pt = volt.getPost(0);
	    cn.x = pt.x;
	    cn.y = pt.y;
	    nodeList.addElement(cn);
	} else {
	    // otherwise allocate extra node for ground
	    CircuitNode cn = new CircuitNode();
	    cn.x = cn.y = -1;
	    nodeList.addElement(cn);
	}
	//System.out.println("ac2");

	// allocate nodes and voltage sources
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    int inodes = ce.getInternalNodeCount();
	    int ivs = ce.getVoltageSourceCount();
	    int posts = ce.getPostCount();
	    
	    // allocate a node for each post and match posts to nodes
	    for (j = 0; j != posts; j++) {
		Point pt = ce.getPost(j);
		int k;
		for (k = 0; k != nodeList.size(); k++) {
		    CircuitNode cn = getCircuitNode(k);
		    if (pt.x == cn.x && pt.y == cn.y)
			break;
		}
		if (k == nodeList.size()) {
		    CircuitNode cn = new CircuitNode();
		    cn.x = pt.x;
		    cn.y = pt.y;
		    CircuitNodeLink cnl = new CircuitNodeLink();
		    cnl.num = j;
		    cnl.elm = ce;
		    cn.links.addElement(cnl);
		    ce.setNode(j, nodeList.size());
		    nodeList.addElement(cn);
		} else {
		    CircuitNodeLink cnl = new CircuitNodeLink();
		    cnl.num = j;
		    cnl.elm = ce;
		    getCircuitNode(k).links.addElement(cnl);
		    ce.setNode(j, k);
		    // if it's the ground node, make sure the node voltage is 0,
		    // cause it may not get set later
		    if (k == 0)
			ce.setNodeVoltage(j, 0);
		}
	    }
	    for (j = 0; j != inodes; j++) {
		CircuitNode cn = new CircuitNode();
		cn.x = cn.y = -1;
		cn.internal = true;
		CircuitNodeLink cnl = new CircuitNodeLink();
		cnl.num = j+posts;
		cnl.elm = ce;
		cn.links.addElement(cnl);
		ce.setNode(cnl.num, nodeList.size());
		nodeList.addElement(cn);
	    }
	    vscount += ivs;
	}
	voltageSources = new CircuitElm[vscount];
	vscount = 0;
	circuitNonLinear = false;
	//System.out.println("ac3");

	// determine if circuit is nonlinear
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    if (ce.nonLinear())
		circuitNonLinear = true;
	    int ivs = ce.getVoltageSourceCount();
	    for (j = 0; j != ivs; j++) {
		voltageSources[vscount] = ce;
		ce.setVoltageSource(j, vscount++);
	    }
	}
	voltageSourceCount = vscount;

	int matrixSize = nodeList.size()-1 + vscount;
	circuitMatrix = new double[matrixSize][matrixSize];
	circuitRightSide = new double[matrixSize];
	origMatrix = new double[matrixSize][matrixSize];
	origRightSide = new double[matrixSize];
	circuitMatrixSize = circuitMatrixFullSize = matrixSize;
	circuitRowInfo = new RowInfo[matrixSize];
	circuitPermute = new int[matrixSize];
	int vs = 0;
	for (i = 0; i != matrixSize; i++)
	    circuitRowInfo[i] = new RowInfo();
	circuitNeedsMap = false;
	
	// stamp linear circuit elements
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    ce.stamp();
	}
	//System.out.println("ac4");

	// determine nodes that are unconnected
	boolean closure[] = new boolean[nodeList.size()];
	boolean tempclosure[] = new boolean[nodeList.size()];
	boolean changed = true;
	closure[0] = true;
	while (changed) {
	    changed = false;
	    for (i = 0; i != elmList.size(); i++) {
		CircuitElm ce = getElm(i);
		// loop through all ce's nodes to see if they are connected
		// to other nodes not in closure
		for (j = 0; j < ce.getPostCount(); j++) {
		    if (!closure[ce.getNode(j)]) {
			if (ce.hasGroundConnection(j))
			    closure[ce.getNode(j)] = changed = true;
			continue;
		    }
		    int k;
		    for (k = 0; k != ce.getPostCount(); k++) {
			if (j == k)
			    continue;
			int kn = ce.getNode(k);
			if (ce.getConnection(j, k) && !closure[kn]) {
			    closure[kn] = true;
			    changed = true;
			}
		    }
		}
	    }
	    if (changed)
		continue;

	    // connect unconnected nodes
	    for (i = 0; i != nodeList.size(); i++)
		if (!closure[i] && !getCircuitNode(i).internal) {
		    System.out.println("node " + i + " unconnected");
		    stampResistor(0, i, 1e8);
		    closure[i] = true;
		    changed = true;
		    break;
		}
	}
	//System.out.println("ac5");

	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    // look for inductors with no current path
	    if (ce instanceof InductorElm) {
		FindPathInfo fpi = new FindPathInfo(FPI_INDUCT, ce,
						    ce.getNode(1));
		if (!fpi.findPath(ce.getNode(0))) {
		    System.out.println(ce + " no path");
		    ce.reset();
		}
	    }
	    // look for current sources with no current path
	    if (ce instanceof CurrentElm) {
		FindPathInfo fpi = new FindPathInfo(FPI_INDUCT, ce,
						    ce.getNode(1));
		if (!fpi.findPath(ce.getNode(0))) {
		    stop("No path for current source!", ce);
		    return;
		}
	    }
	    // look for voltage source loops
	    if (ce instanceof VoltageElm && ce.getPostCount() == 2) {
		FindPathInfo fpi = new FindPathInfo(FPI_VOLTAGE, ce,
						    ce.getNode(1));
		if (fpi.findPath(ce.getNode(0))) {
		    stop("Voltage source loop with no resistance!", ce);
		    return;
		}
	    }
	    // look for shorted caps, or caps w/ voltage but no R
	    if (ce instanceof CapacitorElm) {
		FindPathInfo fpi = new FindPathInfo(FPI_SHORT, ce,
						    ce.getNode(1));
		if (fpi.findPath(ce.getNode(0))) {
		    System.out.println(ce + " shorted");
		    ce.reset();
		} else {
		    fpi = new FindPathInfo(FPI_CAP_V, ce, ce.getNode(1));
		    if (fpi.findPath(ce.getNode(0))) {
			stop("Capacitor loop with no resistance!", ce);
			return;
		    }
		}
	    }
	}
	//System.out.println("ac6");

	// simplify the matrix; this speeds things up quite a bit
	for (i = 0; i != matrixSize; i++) {
	    int qm = -1, qp = -1;
	    double qv = 0;
	    RowInfo re = circuitRowInfo[i];
	    /*System.out.println("row " + i + " " +
	      re.lsChanges + " " + re.rsChanges);*/
	    if (re.lsChanges || re.dropRow || re.rsChanges)
		continue;
	    double rsadd = 0;

	    // look for rows that can be removed
	    for (j = 0; j != matrixSize; j++) {
		double q = circuitMatrix[i][j];
		if (circuitRowInfo[j].type == RowInfo.ROW_CONST) {
		    // keep a running total of const values that have been
		    // removed already
		    rsadd -= circuitRowInfo[j].value*q;
		    continue;
		}
		if (q == 0)
		    continue;
		if (qp == -1) {
		    qp = j;
		    qv = q;
		    continue;
		}
		if (qm == -1 && q == -qv) {
		    qm = j;
		    continue;
		}
		break;
	    }
	    //System.out.println("line " + i + " " + qp + " " + qm);
	    /*if (qp != -1 && circuitRowInfo[qp].lsChanges) {
		System.out.println("lschanges");
		continue;
	    }
	    if (qm != -1 && circuitRowInfo[qm].lsChanges) {
		System.out.println("lschanges");
		continue;
		}*/
	    if (j == matrixSize) {
		if (qp == -1) {
		    stop("Matrix error", null);
		    return;
		}
		RowInfo elt = circuitRowInfo[qp];
		if (qm == -1) {
		    // we found a row with only one nonzero entry; that value
		    // is a constant
		    int k;
		    for (k = 0; elt.type == RowInfo.ROW_EQUAL && k < 100; k++) {
			// follow the chain
			/*System.out.println("following equal chain from " +
			  i + " " + qp + " to " + elt.nodeEq);*/
			qp = elt.nodeEq;
			elt = circuitRowInfo[qp];
		    }
		    if (elt.type == RowInfo.ROW_EQUAL) {
			// break equal chains
			elt.type = RowInfo.ROW_NORMAL;
			continue;
		    }
		    if (elt.type != RowInfo.ROW_NORMAL) {
			System.out.println("type already " + elt.type + " for " + qp + "!");
			continue;
		    }
		    elt.type = RowInfo.ROW_CONST;
		    elt.value = (circuitRightSide[i]+rsadd)/qv;
		    circuitRowInfo[i].dropRow = true;
		    //System.out.println(qp + " * " + qv + " = const " + elt.value);
		    i = -1; // start over from scratch
		} else if (circuitRightSide[i]+rsadd == 0) {
		    // we found a row with only two nonzero entries, and one
		    // is the negative of the other; the values are equal
		    if (elt.type != RowInfo.ROW_NORMAL) {
			//System.out.println("swapping");
			int qq = qm;
			qm = qp; qp = qq;
			elt = circuitRowInfo[qp];
			if (elt.type != RowInfo.ROW_NORMAL) {
			    // we should follow the chain here, but this
			    // hardly ever happens so it's not worth worrying
			    // about
			    System.out.println("swap failed");
			    continue;
			}
		    }
		    elt.type = RowInfo.ROW_EQUAL;
		    elt.nodeEq = qm;
		    circuitRowInfo[i].dropRow = true;
		    //System.out.println(qp + " = " + qm);
		}
	    }
	}
	//System.out.println("ac7");

	// find size of new matrix
	int nn = 0;
	for (i = 0; i != matrixSize; i++) {
	    RowInfo elt = circuitRowInfo[i];
	    if (elt.type == RowInfo.ROW_NORMAL) {
		elt.mapCol = nn++;
		//System.out.println("col " + i + " maps to " + elt.mapCol);
		continue;
	    }
	    if (elt.type == RowInfo.ROW_EQUAL) {
		RowInfo e2 = null;
		// resolve chains of equality; 100 max steps to avoid loops
		for (j = 0; j != 100; j++) {
		    e2 = circuitRowInfo[elt.nodeEq];
		    if (e2.type != RowInfo.ROW_EQUAL)
			break;
		    if (i == e2.nodeEq)
			break;
		    elt.nodeEq = e2.nodeEq;
		}
	    }
	    if (elt.type == RowInfo.ROW_CONST)
		elt.mapCol = -1;
	}
	for (i = 0; i != matrixSize; i++) {
	    RowInfo elt = circuitRowInfo[i];
	    if (elt.type == RowInfo.ROW_EQUAL) {
		RowInfo e2 = circuitRowInfo[elt.nodeEq];
		if (e2.type == RowInfo.ROW_CONST) {
		    // if something is equal to a const, it's a const
		    elt.type = e2.type;
		    elt.value = e2.value;
		    elt.mapCol = -1;
		    //System.out.println(i + " = [late]const " + elt.value);
		} else {
		    elt.mapCol = e2.mapCol;
		    //System.out.println(i + " maps to: " + e2.mapCol);
		}
	    }
	}
	//System.out.println("ac8");

	/*
	System.out.println("matrixSize = " + matrixSize);
	for (j = 0; j != circuitMatrixSize; j++) {
	    for (i = 0; i != circuitMatrixSize; i++)
		System.out.print(circuitMatrix[j][i] + " ");
	    System.out.print("  " + circuitRightSide[j] + "\n");
	}
	System.out.print("\n");
	*/

	// make the new, simplified matrix
	int newsize = nn;
	double newmatx[][] = new double[newsize][newsize];
	double newrs  []   = new double[newsize];
	int ii = 0;
	for (i = 0; i != matrixSize; i++) {
	    RowInfo rri = circuitRowInfo[i];
	    if (rri.dropRow) {
		rri.mapRow = -1;
		continue;
	    }
	    newrs[ii] = circuitRightSide[i];
	    rri.mapRow = ii;
	    for (j = 0; j != matrixSize; j++) {
		RowInfo ri = circuitRowInfo[j];
		if (ri.type == RowInfo.ROW_CONST)
		    newrs[ii] -= ri.value*circuitMatrix[i][j];
		else
		    newmatx[ii][ri.mapCol] += circuitMatrix[i][j];
	    }
	    ii++;
	}

	circuitMatrix = newmatx;
	circuitRightSide = newrs;
	matrixSize = circuitMatrixSize = newsize;
	for (i = 0; i != matrixSize; i++)
	    origRightSide[i] = circuitRightSide[i];
	for (i = 0; i != matrixSize; i++)
	    for (j = 0; j != matrixSize; j++)
		origMatrix[i][j] = circuitMatrix[i][j];
	circuitNeedsMap = true;

	/*
	System.out.println("matrixSize = " + matrixSize + " " + circuitNonLinear);
	for (j = 0; j != circuitMatrixSize; j++) {
	    for (i = 0; i != circuitMatrixSize; i++)
		System.out.print(circuitMatrix[j][i] + " ");
	    System.out.print("  " + circuitRightSide[j] + "\n");
	}
	System.out.print("\n");
	*/

	// if a matrix is linear, we can do the lu_factor here instead of
	// needing to do it every frame
	if (!circuitNonLinear) {
	    if (!lu_factor(circuitMatrix, circuitMatrixSize, circuitPermute)) {
		stop("Singular matrix!", null);
		return;
	    }
	}
    }

    void stop(String s, CircuitElm ce) {
	stopMessage = s;
	circuitMatrix = null;
	stopElm = ce;
	stoppedCheck.setState(true);
	analyzeFlag = false;
	cv.repaint();
    }
    
    static final int FPI_INDUCT  = 1;
    static final int FPI_VOLTAGE = 2;
    static final int FPI_SHORT   = 3;
    static final int FPI_CAP_V   = 4;
    class FindPathInfo {
	boolean used[];
	int dest;
	CircuitElm firstElm;
	int type;
	FindPathInfo(int t, CircuitElm e, int d) {
	    dest = d;
	    type = t;
	    firstElm = e;
	    used = new boolean[nodeList.size()];
	}
	boolean findPath(int n1) {
	    if (n1 == dest)
		return true;
	    if (used[n1]) {
		//System.out.println("used " + n1);
		return false;
	    }
	    used[n1] = true;
	    int i;
	    for (i = 0; i != elmList.size(); i++) {
		CircuitElm ce = getElm(i);
		if (ce == firstElm)
		    continue;
		if (type == FPI_INDUCT) {
		    if (ce instanceof CurrentElm)
			continue;
		}
		if (type == FPI_VOLTAGE) {
		    if (!(ce.isWire() || ce instanceof VoltageElm))
			continue;
		}
		if (type == FPI_SHORT && !ce.isWire())
		    continue;
		if (type == FPI_CAP_V) {
		    if (!(ce.isWire() || ce instanceof CapacitorElm ||
			  ce instanceof VoltageElm))
			continue;
		}
		if (n1 == 0) {
		    // look for posts which have a ground connection;
		    // our path can go through ground
		    int j;
		    for (j = 0; j != ce.getPostCount(); j++)
			if (ce.hasGroundConnection(j) &&
			      findPath(ce.getNode(j))) {
			    used[n1] = false;
			    return true;
			}
		}
		int j;
		for (j = 0; j != ce.getPostCount(); j++) {
		    //System.out.println(ce + " " + ce.getNode(j));
		    if (ce.getNode(j) == n1)
			break;
		}
		if (j == ce.getPostCount())
		    continue;
		if (ce.hasGroundConnection(j) && findPath(0)) {
		    //System.out.println(ce + " has ground");
		    used[n1] = false;
		    return true;
		}
		if (type == FPI_INDUCT && ce instanceof InductorElm) {
		    double c = ce.getCurrent();
		    if (j == 0)
			c = -c;
		    //System.out.println("matching " + c + " to " + firstElm.getCurrent());
		    //System.out.println(ce + " " + firstElm);
		    if (Math.abs(c-firstElm.getCurrent()) > 1e-10)
			continue;
		}
		int k;
		for (k = 0; k != ce.getPostCount(); k++) {
		    if (j == k)
			continue;
		    //System.out.println(ce + " " + ce.getNode(j) + "-" + ce.getNode(k));
		    if (ce.getConnection(j, k) && findPath(ce.getNode(k))) {
			//System.out.println("got findpath " + n1);
			used[n1] = false;
			return true;
		    }
		    //System.out.println("back on findpath " + n1);
		}
	    }
	    used[n1] = false;
	    //System.out.println(n1 + " failed");
	    return false;
	}
    }

    // stamp independent voltage source #vs, from n1 to n2, amount v
    void stampVoltageSource(int n1, int n2, int vs, double v) {
	int vn = nodeList.size()+vs;
	stampMatrix(vn, n1, -1);
	stampMatrix(vn, n2, 1);
	stampRightSide(vn, v);
	stampMatrix(n1, vn, 1);
	stampMatrix(n2, vn, -1);
    }

    // use this if the amount of voltage is going to be updated in doStep()
    void stampVoltageSource(int n1, int n2, int vs) {
	int vn = nodeList.size()+vs;
	stampMatrix(vn, n1, -1);
	stampMatrix(vn, n2, 1);
	stampRightSide(vn);
	stampMatrix(n1, vn, 1);
	stampMatrix(n2, vn, -1);
    }
    
    void updateVoltageSource(int n1, int n2, int vs, double v) {
	int vn = nodeList.size()+vs;
	stampRightSide(vn, v);
    }
    
    void stampResistor(int n1, int n2, double r) {
	double r0 = 1/r;
	if (Double.isNaN(r0) || Double.isInfinite(r0)) {
	    System.out.print("bad resistance " + r + " " + r0 + "\n");
	    int a = 0;
	    a /= a;
	}
	stampMatrix(n1, n1, r0);
	stampMatrix(n2, n2, r0);
	stampMatrix(n1, n2, -r0);
	stampMatrix(n2, n1, -r0);
    }

    void stampConductance(int n1, int n2, double r0) {
	stampMatrix(n1, n1, r0);
	stampMatrix(n2, n2, r0);
	stampMatrix(n1, n2, -r0);
	stampMatrix(n2, n1, -r0);
    }

    // current from cn1 to cn2 is equal to voltage from vn1 to 2, divided by g
    void stampVCCurrentSource(int cn1, int cn2, int vn1, int vn2, double g) {
	stampMatrix(cn1, vn1, g);
	stampMatrix(cn2, vn2, g);
	stampMatrix(cn1, vn2, -g);
	stampMatrix(cn2, vn1, -g);
    }

    void stampCurrentSource(int n1, int n2, double i) {
	stampRightSide(n1, -i);
	stampRightSide(n2, i);
    }

    // stamp value x in row i, column j, meaning that a voltage change
    // of dv in node j will increase the current into node i by x dv.
    // (Unless i or j is a voltage source node.)
    void stampMatrix(int i, int j, double x) {
	if (i > 0 && j > 0) {
	    if (circuitNeedsMap) {
		i = circuitRowInfo[i-1].mapRow;
		RowInfo ri = circuitRowInfo[j-1];
		if (ri.type == RowInfo.ROW_CONST) {
		    //System.out.println("Stamping constant " + i + " " + j + " " + x);
		    circuitRightSide[i] -= x*ri.value;
		    return;
		}
		j = ri.mapCol;
		//System.out.println("stamping " + i + " " + j + " " + x);
	    } else {
		i--;
		j--;
	    }
	    circuitMatrix[i][j] += x;
	}
    }

    // stamp value x on the right side of row i, representing an
    // independent current source flowing into node i
    void stampRightSide(int i, double x) {
	if (i > 0) {
	    if (circuitNeedsMap) {
		i = circuitRowInfo[i-1].mapRow;
		//System.out.println("stamping " + i + " " + x);
	    } else
		i--;
	    circuitRightSide[i] += x;
	}
    }

    // indicate that the value on the right side of row i changes in doStep()
    void stampRightSide(int i) {
	//System.out.println("rschanges true " + (i-1));
	if (i > 0)
	    circuitRowInfo[i-1].rsChanges = true;
    }
    
    // indicate that the values on the left side of row i change in doStep()
    void stampNonLinear(int i) {
	if (i > 0)
	    circuitRowInfo[i-1].lsChanges = true;
    }

    double getIterCount() {
	if (speedBar.getValue() == 0)
	    return 0;
	//return (Math.exp((speedBar.getValue()-1)/24.) + .5);
	return .1*Math.exp((speedBar.getValue()-61)/24.);
    }
    
    boolean converged;
    int subIterations;
    void runCircuit() {
	if (circuitMatrix == null || elmList.size() == 0) {
	    circuitMatrix = null;
	    return;
	}
	int iter;
	//int maxIter = getIterCount();
	boolean debugprint = dumpMatrix;
	dumpMatrix = false;
	long steprate = (long) (160*getIterCount());
	long tm = System.currentTimeMillis();
	long lit = lastIterTime;
	if (1000 >= steprate*(tm-lastIterTime))
	    return;
	for (iter = 1; ; iter++) {
	    int i, j, k, subiter;
	    for (i = 0; i != elmList.size(); i++) {
		CircuitElm ce = getElm(i);
		ce.startIteration();
	    }
	    steps++;
	    final int subiterCount = 5000;
	    for (subiter = 0; subiter != subiterCount; subiter++) {
		converged = true;
		subIterations = subiter;
		for (i = 0; i != circuitMatrixSize; i++)
		    circuitRightSide[i] = origRightSide[i];
		if (circuitNonLinear) {
		    for (i = 0; i != circuitMatrixSize; i++)
			for (j = 0; j != circuitMatrixSize; j++)
			    circuitMatrix[i][j] = origMatrix[i][j];
		}
		for (i = 0; i != elmList.size(); i++) {
		    CircuitElm ce = getElm(i);
		    ce.doStep();
		}
		if (stopMessage != null)
		    return;
		boolean printit = debugprint;
		debugprint = false;
		for (j = 0; j != circuitMatrixSize; j++) {
		    for (i = 0; i != circuitMatrixSize; i++) {
			double x = circuitMatrix[i][j];
			if (Double.isNaN(x) || Double.isInfinite(x)) {
			    stop("nan/infinite matrix!", null);
			    return;
			}
		    }
		}
		if (printit) {
		    for (j = 0; j != circuitMatrixSize; j++) {
			for (i = 0; i != circuitMatrixSize; i++)
			    System.out.print(circuitMatrix[j][i] + ",");
			System.out.print("  " + circuitRightSide[j] + "\n");
		    }
		    System.out.print("\n");
		}
		if (circuitNonLinear) {
		    if (converged && subiter > 0)
			break;
		    if (!lu_factor(circuitMatrix, circuitMatrixSize,
				  circuitPermute)) {
			stop("Singular matrix!", null);
			return;
		    }
		}
		lu_solve(circuitMatrix, circuitMatrixSize, circuitPermute,
			 circuitRightSide);
		
		for (j = 0; j != circuitMatrixFullSize; j++) {
		    RowInfo ri = circuitRowInfo[j];
		    double res = 0;
		    if (ri.type == RowInfo.ROW_CONST)
			res = ri.value;
		    else
			res = circuitRightSide[ri.mapCol];
		    /*System.out.println(j + " " + res + " " +
		      ri.type + " " + ri.mapCol);*/
		    if (Double.isNaN(res)) {
			converged = false;
			//debugprint = true;
			break;
		    }
		    if (j < nodeList.size()-1) {
			CircuitNode cn = getCircuitNode(j+1);
			for (k = 0; k != cn.links.size(); k++) {
			    CircuitNodeLink cnl = (CircuitNodeLink)
				cn.links.elementAt(k);
			    cnl.elm.setNodeVoltage(cnl.num, res);
			}
		    } else {
			int ji = j-(nodeList.size()-1);
			//System.out.println("setting vsrc " + ji + " to " + res);
			voltageSources[ji].setCurrent(ji, res);
		    }
		}
		if (!circuitNonLinear)
		    break;
	    }
	    if (subiter > 5)
		System.out.print("converged after " + subiter + " iterations\n");
	    if (subiter == subiterCount) {
		stop("Convergence failed!", null);
		break;
	    }
	    t += timeStep;
	    for (i = 0; i != scopeCount; i++)
		scopes[i].timeStep();
	    tm = System.currentTimeMillis();
	    lit = tm;
	    if (iter*1000 >= steprate*(tm-lastIterTime) ||
		(tm-lastFrameTime > 500))
		break;
	}
	lastIterTime = lit;
	//System.out.println((System.currentTimeMillis()-lastFrameTime)/(double) iter);
    }

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

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

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

    void editFuncPoint(int x, int y) {
	// XXX
	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) {
	String ac = e.getActionCommand();
	if (e.getSource() == resetButton) {
	    int i;
	    
	    // on IE, drawImage() stops working inexplicably every once in
	    // a while.  Recreating it fixes the problem, so we do that here.
	    dbimage = main.createImage(winSize.width, winSize.height);
	    
	    for (i = 0; i != elmList.size(); i++)
		getElm(i).reset();
	    for (i = 0; i != scopeCount; i++)
		scopes[i].resetGraph();
	    analyzeFlag = true;
	    t = 0;
	    stoppedCheck.setState(false);
	    cv.repaint();
	}
	if (e.getSource() == dumpMatrixButton)
	    dumpMatrix = true;
	if (e.getSource() == exportItem)
	    doImport(false);
	if (e.getSource() == importItem)
	    doImport(true);
	if (e.getSource() == exitItem) {
	    applet.destroyFrame();
	    return;
	}
	if (ac.compareTo("stackAll") == 0)
	    stackAll();
	if (ac.compareTo("unstackAll") == 0)
	    unstackAll();
	if (e.getSource() == elmEditMenuItem)
	    doEdit();
	if (e.getSource() == elmDeleteMenuItem && menuElm != null)
	    deleteElm(menuElm);
	if (e.getSource() == elmScopeMenuItem && menuElm != null) {
	    int i;
	    for (i = 0; i != scopeCount; i++)
		if (scopes[i].elm == null)
		    break;
	    if (i == scopeCount) {
		if (scopeCount == scopes.length)
		    return;
		scopeCount++;
		scopes[i] = new Scope();
		scopes[i].position = i;
		handleResize();
	    }
	    scopes[i].setElm(menuElm);
	}
	if (menuScope != -1) {
	    if (ac.compareTo("remove") == 0)
		scopes[menuScope].setElm(null);
	    if (ac.compareTo("speed2") == 0)
		scopes[menuScope].speedUp();
	    if (ac.compareTo("speed1/2") == 0)
		scopes[menuScope].slowDown();
	    if (ac.compareTo("scale") == 0)
		scopes[menuScope].adjustScale(.5);
	    if (ac.compareTo("stack") == 0)
		stackScope(menuScope);
	    if (ac.compareTo("unstack") == 0)
		unstackScope(menuScope);
	    if (ac.compareTo("selecty") == 0)
		scopes[menuScope].selectY();
	    cv.repaint();
	}
	if (ac.indexOf("setup ") == 0)
	    readSetupFile(ac.substring(6),
			  ((MenuItem) e.getSource()).getLabel());
    }

    void stackScope(int s) {
	if (s == 0) {
	    if (scopeCount < 2)
		return;
	    s = 1;
	}
	if (scopes[s].position == scopes[s-1].position)
	    return;
	scopes[s].position = scopes[s-1].position;
	for (s++; s < scopeCount; s++)
	    scopes[s].position--;
    }
    
    void unstackScope(int s) {
	if (s == 0) {
	    if (scopeCount < 2)
		return;
	    s = 1;
	}
	if (scopes[s].position != scopes[s-1].position)
	    return;
	for (; s < scopeCount; s++)
	    scopes[s].position++;
    }

    void stackAll() {
	int i;
	for (i = 0; i != scopeCount; i++) {
	    scopes[i].position = 0;
	    scopes[i].showMax = false;
	}
    }

    void unstackAll() {
	int i;
	for (i = 0; i != scopeCount; i++) {
	    scopes[i].position = i;
	    scopes[i].showMax = true;
	}
    }
    
    void doEdit() {
	if (editDialog != null) {
	    requestFocus();
	    editDialog.setVisible(false);
	    editDialog = null;
	}
	editDialog = new EditDialog(menuElm, this);
	editDialog.show();
    }

    void doImport(boolean imp) {
	if (impDialog != null) {
	    requestFocus();
	    impDialog.setVisible(false);
	    impDialog = null;
	}
	String dump = "";
	if (!imp) {
	    int i;
	    int f = (dotsCheckItem.getState()) ? 1 : 0;
	    f |= (smallGridCheckItem.getState()) ? 2 : 0;
	    f |= (voltsCheckItem.getState()) ? 0 : 4;
	    f |= (powerCheckItem.getState()) ? 8 : 0;
	    f |= (showValuesCheckItem.getState()) ? 0 : 16;
	    dump = "$ " + f + " " +
		timeStep + " " + getIterCount() + " " +
		currentBar.getValue() + " " + voltageRange + " " +
		powerBar.getValue() + "\n";
	    for (i = 0; i != elmList.size(); i++)
		dump += getElm(i).dump() + "\n";
	    for (i = 0; i != scopeCount; i++) {
		String d = scopes[i].dump();
		if (d != null)
		    dump += d + "\n";
	    }
	    if (hintType != -1)
		dump += "h " + hintType + " " + hintItem1 + " " +
		    hintItem2 + "\n";
	}
	impDialog = new ImportDialog(this, dump);
	impDialog.show();
    }
    
    public void adjustmentValueChanged(AdjustmentEvent e) {
	System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
    }

    ByteArrayOutputStream readUrlData(URL url) throws java.io.IOException {
	Object o = url.getContent();
	FilterInputStream fis = (FilterInputStream) o;
	ByteArrayOutputStream ba = new ByteArrayOutputStream(fis.available());
	int blen = 1024;
	byte b[] = new byte[blen];
	while (true) {
	    int len = fis.read(b);
	    if (len <= 0)
		break;
	    ba.write(b, 0, len);
	}
	return ba;
    }
    
    void getSetupList(Menu menu, boolean retry) {
	System.out.println("getsetuplist " + retry);
	Menu stack[] = new Menu[6];
	int stackptr = 0;
	stack[stackptr++] = menu;
	try {
	    URL url = new URL(applet.getCodeBase() + "setuplist.txt");
	    ByteArrayOutputStream ba = readUrlData(url);
	    byte b[] = ba.toByteArray();
	    int len = ba.size();
	    int p;
	    if (len == 0 || b[0] != '#') {
		// got a redirect, try again
		getSetupList(menu, true);
		return;
	    }
	    for (p = 0; p < len; ) {
		int l;
		for (l = 0; l != len-p; l++)
		    if (b[l+p] == '\n') {
			l++;
			break;
		    }
		String line = new String(b, p, l-1);
		if (line.charAt(0) == '#')
		    ;
		else if (line.charAt(0) == '+') {
		    Menu n = new Menu(line.substring(1));
		    menu.add(n);
		    menu = stack[stackptr++] = n;
		} else if (line.charAt(0) == '-') {
		    menu = stack[--stackptr-1];
		} else {
		    int i = line.indexOf(' ');
		    if (i > 0)
			menu.add(getMenuItem(line.substring(i+1),
					     "setup " + line.substring(0, i)));
		}
		p += l;
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	    stop("Can't read setuplist.txt!", null);
	}
    }

    void readSetup(String text) {
	readSetup(text.getBytes(), text.length());
	titleLabel.setText("untitled");
    }
    
    void readSetupFile(String str, String title) {
	t = 0;
	System.out.println(str);
	try {
	    URL url = new URL(applet.getCodeBase() + "circuits/" + str);
	    ByteArrayOutputStream ba = readUrlData(url);
	    readSetup(ba.toByteArray(), ba.size());
	} catch (Exception e) {
	    e.printStackTrace();
	    stop("Unable to read " + str + "!", null);
	}
	titleLabel.setText(title);
    }

    void readSetup(byte b[], int len) {
	int i;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    ce.delete();
	}
	elmList.removeAllElements();
	hintType = -1;
	timeStep = 5e-6;
	dotsCheckItem.setState(true);
	smallGridCheckItem.setState(false);
	powerCheckItem.setState(false);
	voltsCheckItem.setState(true);
	showValuesCheckItem.setState(true);
	setGrid();
	speedBar.setValue(117); // 57
	currentBar.setValue(50);
	powerBar.setValue(50);
	voltageRange = 5;
	cv.repaint();
	int p;
	scopeCount = 0;
	for (p = 0; p < len; ) {
	    int l;
	    int linelen = 0;
	    for (l = 0; l != len-p; l++)
		if (b[l+p] == '\n' || b[l+p] == '\r') {
		    linelen = l++;
		    if (l+p < b.length && b[l+p] == '\n')
			l++;
		    break;
		}
	    String line = new String(b, p, linelen);
	    StringTokenizer st = new StringTokenizer(line);
	    while (st.hasMoreTokens()) {
		String type = st.nextToken();
		int tint = type.charAt(0);
		try {
		    if (tint == 'o') {
			Scope sc = new Scope();
			sc.position = scopeCount;
			sc.undump(st);
			scopes[scopeCount++] = sc;
			break;
		    }
		    if (tint == 'h') {
			readHint(st);
			break;
		    }
		    if (tint == '$') {
			readOptions(st);
			break;
		    }
		    if (tint >= '0' && tint <= '9')
			tint = new Integer(type).intValue();
		    int x1 = new Integer(st.nextToken()).intValue();
		    int y1 = new Integer(st.nextToken()).intValue();
		    int x2 = new Integer(st.nextToken()).intValue();
		    int y2 = new Integer(st.nextToken()).intValue();
		    int f  = new Integer(st.nextToken()).intValue();
		    CircuitElm ce = null;
		    Class cls = dumpTypes[tint];
		    if (cls == null) {
			System.out.println("unrecognized dump type: " + type);
			break;
		    }
		    // find element class
		    Class carr[] = new Class[7];
		    carr[0] = getClass();
		    carr[1] = carr[2] = carr[3] = carr[4] = carr[5] =
			int.class;
		    carr[6] = StringTokenizer.class;
		    Constructor cstr = null;
		    cstr = cls.getConstructor(carr);
		
		    // invoke constructor with starting coordinates
		    Object oarr[] = new Object[7];
		    oarr[0] = this;
		    oarr[1] = new Integer(x1);
		    oarr[2] = new Integer(y1);
		    oarr[3] = new Integer(x2);
		    oarr[4] = new Integer(y2);
		    oarr[5] = new Integer(f );
		    oarr[6] = st;
		    ce = (CircuitElm) cstr.newInstance(oarr);
		    ce.setPoints();
		    elmList.addElement(ce);
		} catch (java.lang.reflect.InvocationTargetException ee) {
		    ee.getTargetException().printStackTrace();
		    break;
		} catch (Exception ee) {
		    ee.printStackTrace();
		    break;
		}
		break;
	    }
	    p += l;
	    
	}
	enableItems();
	handleResize(); // for scopes
	analyzeCircuit();
    }

    void readHint(StringTokenizer st) {
	hintType  = new Integer(st.nextToken()).intValue();
	hintItem1 = new Integer(st.nextToken()).intValue();
	hintItem2 = new Integer(st.nextToken()).intValue();
    }

    void readOptions(StringTokenizer st) {
	int flags = new Integer(st.nextToken()).intValue();
	dotsCheckItem.setState((flags & 1) != 0);
	smallGridCheckItem.setState((flags & 2) != 0);
	voltsCheckItem.setState((flags & 4) == 0);
	powerCheckItem.setState((flags & 8) == 8);
	showValuesCheckItem.setState((flags & 16) == 0);
	timeStep = new Double (st.nextToken()).doubleValue();
	double sp = new Double(st.nextToken()).doubleValue();
	int sp2 = (int) (Math.log(10*sp)*24+61.5);
	//int sp2 = (int) (Math.log(sp)*24+1.5);
	speedBar  .setValue(sp2);
	currentBar.setValue(new Integer(st.nextToken()).intValue());
	voltageRange = new Double (st.nextToken()).doubleValue();
	try {
	    powerBar.setValue(new Integer(st.nextToken()).intValue());
	} catch (Exception e) {
	}
	setGrid();
    }
    
    int snapGrid(int x) {
	return (x+gridRound) & gridMask;
    }

    boolean doSwitch(int x, int y) {
	if (mouseElm == null || !(mouseElm instanceof SwitchElm))
	    return false;
	SwitchElm se = (SwitchElm) mouseElm;
	se.toggle();
	if (se.momentary)
	    heldSwitchElm = se;
	analyzeCircuit();
	return true;
    }

    void deleteElm(CircuitElm elm) {
	int e = locateElm(elm);
	if (e >= 0) {
	    elm.delete();
	    elmList.removeElementAt(e);
	    analyzeCircuit();
	}
    }

    int locateElm(CircuitElm elm) {
	int i;
	for (i = 0; i != elmList.size(); i++)
	    if (elm == elmList.elementAt(i))
		return i;
	return -1;
    }
    
    public void mouseDragged(MouseEvent e) {
	if (!circuitArea.contains(e.getX(), e.getY()))
	    return;
	if (dragElm != null)
	    dragElm.drag(e.getX(), e.getY());
	switch (mouseMode) {
	case MODE_DRAG_ALL:
	    dragAll(snapGrid(e.getX()), snapGrid(e.getY()));
	    break;
	case MODE_DRAG_ROW:
	    dragRow(snapGrid(e.getX()), snapGrid(e.getY()));
	    break;
	case MODE_DRAG_COLUMN:
	    dragColumn(snapGrid(e.getX()), snapGrid(e.getY()));
	    break;
	case MODE_DRAG_POST:
	    if (mouseElm != null)
		dragPost(snapGrid(e.getX()), snapGrid(e.getY()));
	    break;
	case MODE_DRAG_SELECTED:
	    if (mouseElm != null)
		dragItem(e.getX(), e.getY());
	    break;
	}
	dragging = true;
	if (mouseMode == MODE_DRAG_SELECTED && mouseElm instanceof TextElm) {
	    dragX = e.getX(); dragY = e.getY();
	} else {
	    dragX = snapGrid(e.getX()); dragY = snapGrid(e.getY());
	}
	cv.repaint(pause);
    }

    void dragAll(int x, int y) {
	int dx = x-dragX;
	int dy = y-dragY;
	if (dx == 0 && dy == 0)
	    return;
	int i;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    ce.move(dx, dy);
	}
	removeZeroLengthElements();
    }

    void dragRow(int x, int y) {
	int dy = y-dragY;
	if (dy == 0)
	    return;
	int i;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    if (ce.y  == dragY)
		ce.movePoint(0, 0, dy);
	    if (ce.y2 == dragY)
		ce.movePoint(1, 0, dy);
	}
	removeZeroLengthElements();
    }
    
    void dragColumn(int x, int y) {
	int dx = x-dragX;
	if (dx == 0)
	    return;
	int i;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    if (ce.x  == dragX)
		ce.movePoint(0, dx, 0);
	    if (ce.x2 == dragX)
		ce.movePoint(1, dx, 0);
	}
	removeZeroLengthElements();
    }

    void dragItem(int x, int y) {
	if (!(mouseElm instanceof TextElm)) {
	    x = snapGrid(x);
	    y = snapGrid(y);
	}
	int dx = x-dragX;
	int dy = y-dragY;
	if (dx == 0 && dy == 0)
	    return;
	mouseElm.movePoint(0, dx, dy);
	mouseElm.movePoint(1, dx, dy);
	analyzeCircuit();
    }

    void dragPost(int x, int y) {
	if (draggingPost == -1) {
	    draggingPost =
		(distanceSq(mouseElm.x , mouseElm.y , x, y) >
		 distanceSq(mouseElm.x2, mouseElm.y2, x, y)) ? 1 : 0;
	}
	int dx = x-dragX;
	int dy = y-dragY;
	if (dx == 0 && dy == 0)
	    return;
	mouseElm.movePoint(draggingPost, dx, dy);
	analyzeCircuit();
    }

    void removeZeroLengthElements() {
	int i;
	boolean changed = false;
	for (i = elmList.size()-1; i >= 0; i--) {
	    CircuitElm ce = getElm(i);
	    if (ce.x == ce.x2 && ce.y == ce.y2) {
		elmList.removeElementAt(i);
		changed = true;
	    }
	}
	analyzeCircuit();
    }

    public void mouseMoved(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
	    return;
	int x = e.getX();
	int y = e.getY();
	dragX = snapGrid(x); dragY = snapGrid(y);
	draggingPost = -1;
	int i;
	CircuitElm origMouse = mouseElm;
	mouseElm = null;
	mousePost = -1;
	plotXElm = plotYElm = null;
	int bestDist = 100000;
	int bestArea = 100000;
	for (i = 0; i != elmList.size(); i++) {
	    CircuitElm ce = getElm(i);
	    if (ce.boundingBox.contains(x, y)) {
		int j;
		int area = ce.boundingBox.width * ce.boundingBox.height;
		int jn = ce.getPostCount();
		if (jn > 2)
		    jn = 2;
		for (j = 0; j != jn; j++) {
		    Point pt = ce.getPost(j);
		    int dist = distanceSq(x, y, pt.x, pt.y);

		    // if multiple elements have overlapping bounding boxes,
		    // we prefer selecting elements that have posts close
		    // to the mouse pointer and that have a small bounding
		    // box area.
		    if (dist <= bestDist && area <= bestArea) {
			bestDist = dist;
			bestArea = area;
			mouseElm = ce;
		    }
		}
		if (ce.getPostCount() == 0)
		    mouseElm = ce;
	    }
	}
	scopeSelected = -1;
	if (mouseElm == null) {
	    for (i = 0; i != scopeCount; i++) {
		Scope s = scopes[i];
		if (s.rect.contains(x, y)) {
		    s.select();
		    scopeSelected = i;
		}
	    }
	    // the mouse pointer was not in any of the bounding boxes, but we
	    // might still be close to a post
	    for (i = 0; i != elmList.size(); i++) {
		CircuitElm ce = getElm(i);
		int j;
		int jn = ce.getPostCount();
		for (j = 0; j != jn; j++) {
		    Point pt = ce.getPost(j);
		    int dist = distanceSq(x, y, pt.x, pt.y);
		    if (distanceSq(pt.x, pt.y, x, y) < 26) {
			mouseElm = ce;
			mousePost = j;
			break;
		    }
		}
	    }
	} else {
	    mousePost = -1;
	    // look for post close to the mouse pointer
	    for (i = 0; i != mouseElm.getPostCount(); i++) {
		Point pt = mouseElm.getPost(i);
		if (distanceSq(pt.x, pt.y, x, y) < 26)
		    mousePost = i;
	    }
	}
	if (mouseElm != origMouse)
	    cv.repaint();
    }

    int distanceSq(int x1, int y1, int x2, int y2) {
	x2 -= x1;
	y2 -= y1;
	return x2*x2+y2*y2;
    }
    
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
	scopeSelected = -1;
	mouseElm = plotXElm = plotYElm = null;
	cv.repaint();
    }
    public void mousePressed(MouseEvent e) {
	if (e.isPopupTrigger()) {
	    doPopupMenu(e);
	    return;
	}
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	if (doSwitch(e.getX(), e.getY()))
	    return;
	dragging = true;
	if (mouseMode != MODE_ADD_ELM || addingClass == null)
	    return;
	
	int x0 = snapGrid(e.getX());
	int y0 = snapGrid(e.getY());
	if (!circuitArea.contains(x0, y0))
	    return;

	dragElm = constructElement(addingClass, x0, y0);
    }

    CircuitElm constructElement(Class c, int x0, int y0) {
	// find element class
	Class carr[] = new Class[3];
	carr[0] = getClass();
	carr[1] = carr[2] = int.class;
	Constructor cstr = null;
	try {
	    cstr = c.getConstructor(carr);
	} catch (Exception ee) {
	    ee.printStackTrace();
	    return null;
	}

	// invoke constructor with starting coordinates
	Object oarr[] = new Object[3];
	oarr[0] = this;
	oarr[1] = new Integer(x0);
	oarr[2] = new Integer(y0);
	try {
	    return (CircuitElm) cstr.newInstance(oarr);
	} catch (Exception ee) { ee.printStackTrace(); }
	return null;
    }
    
    void doPopupMenu(MouseEvent e) {
	menuElm = mouseElm;
	menuScope = -1;
	if (scopeSelected != -1) {
	    PopupMenu m = scopes[scopeSelected].getMenu();
	    menuScope = scopeSelected;
	    if (m != null)
		m.show(e.getComponent(), e.getX(), e.getY());
	} else if (mouseElm != null) {
	    elmEditMenuItem .setEnabled(mouseElm.getEditInfo(0) != null);
	    elmScopeMenuItem.setEnabled(mouseElm.canViewInScope());
	    elmMenu.show(e.getComponent(), e.getX(), e.getY());
	} else {
	    doMainMenuChecks(mainMenu);
	    mainMenu.show(e.getComponent(), e.getX(), e.getY());
	}
    }

    void doMainMenuChecks(Menu m) {
	int i;
	if (m == optionsMenu)
	    return;
	for (i = 0; i != m.getItemCount(); i++) {
	    MenuItem mc = m.getItem(i);
	    if (mc instanceof Menu)
		doMainMenuChecks((Menu) mc);
	    if (mc instanceof CheckboxMenuItem) {
		CheckboxMenuItem cmi = (CheckboxMenuItem) mc;
		cmi.setState(
		      mouseModeStr.compareTo(cmi.getActionCommand()) == 0);
	    }
	}
    }
    
    public void mouseReleased(MouseEvent e) {
	if (e.isPopupTrigger()) {
	    doPopupMenu(e);
	    return;
	}
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	dragging = false;
	boolean circuitChanged = false;
	if (heldSwitchElm != null) {
	    heldSwitchElm.mouseUp();
	    heldSwitchElm = null;
	    circuitChanged = true;
	}
	if (dragElm != null &&
	    !(dragElm.x == dragElm.x2 && dragElm.y == dragElm.y2)) {
	    elmList.addElement(dragElm);
	    dragElm = null;
	    circuitChanged = true;
	}
	if (circuitChanged)
	    analyzeCircuit();
	if (dragElm != null)
	    dragElm.delete();
	dragElm = null;
	cv.repaint();
    }

    void enableItems() {
	if (powerCheckItem.getState()) {
	    powerBar.enable();
	    powerLabel.enable();
	} else {
	    powerBar.disable();
	    powerLabel.disable();
	}
    }
    
    public void itemStateChanged(ItemEvent e) {
	cv.repaint(pause);
	Object mi = e.getItemSelectable();
	if (mi == stoppedCheck)
	    return;
	if (mi == smallGridCheckItem)
	    setGrid();
	if (mi == powerCheckItem) {
	    if (powerCheckItem.getState())
		voltsCheckItem.setState(false);
	    else
		voltsCheckItem.setState(true);
	}
	if (mi == voltsCheckItem && voltsCheckItem.getState())
	    powerCheckItem.setState(false);
	enableItems();
	if (menuScope != -1) {
	    Scope sc = scopes[menuScope];
	    sc.handleMenu(e, mi);
	}
	if (mi instanceof CheckboxMenuItem) {
	    MenuItem mmi = (MenuItem) mi;
	    mouseMode = MODE_ADD_ELM;
	    String s = mmi.getActionCommand();
	    if (s.length() > 0)
		mouseModeStr = s;
	    if (s.compareTo("DragAll") == 0)
		mouseMode = MODE_DRAG_ALL;
	    else if (s.compareTo("DragRow") == 0)
		mouseMode = MODE_DRAG_ROW;
	    else if (s.compareTo("DragColumn") == 0)
		mouseMode = MODE_DRAG_COLUMN;
	    else if (s.compareTo("DragSelected") == 0)
		mouseMode = MODE_DRAG_SELECTED;
	    else if (s.compareTo("DragPost") == 0)
		mouseMode = MODE_DRAG_POST;
	    else if (s.length() > 0) {
		try {
		    addingClass = Class.forName("CircuitFrame$" + s);
		} catch (Exception ee) {
		    ee.printStackTrace();
		}
	    }
	}
    }

    void setGrid() {
	gridSize = (smallGridCheckItem.getState()) ? 8 : 16;
	gridMask = ~(gridSize-1);
	gridRound = gridSize/2-1;
    }
    
    String getVoltageDText(double v) {
	return getUnitText(Math.abs(v), "V");
    }
    String getVoltageText(double v) {
	return getUnitText(v, "V");
    }
    String getUnitText(double v, String u) {
	double va = Math.abs(v);
	if (va < 1e-12)
	    return "0 " + 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) + " " + muString + 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;
	return showFormat.format(v*1e-9) + " G" + u;
    }
    String getShortUnitText(double v, String u) {
	double va = Math.abs(v);
	if (va < 1e-12)
	    return null;
	if (va < 1e-9)
	    return shortFormat.format(v*1e12) + "p" + u;
	if (va < 1e-6)
	    return shortFormat.format(v*1e9) + "n" + u;
	if (va < 1e-3)
	    return shortFormat.format(v*1e6) + muString + u;
	if (va < 1)
	    return shortFormat.format(v*1e3) + "m" + u;
	if (va < 1e3)
	    return shortFormat.format(v) + u;
	if (va < 1e6)
	    return shortFormat.format(v*1e-3) + "k" + u;
	if (va < 1e9)
	    return shortFormat.format(v*1e-6) + "M" + u;
	return shortFormat.format(v*1e-9) + "G" + u;
    }
    String getCurrentText(double i) {
	return getUnitText(i, "A");
    }
    String getCurrentDText(double i) {
	return getUnitText(Math.abs(i), "A");
    }

    abstract class CircuitElm {
	int x, y, x2, y2, flags, nodes[], voltSource;
	int dx, dy, dsign;
	double dn, dpx1, dpy1;
	Point point1, point2, lead1, lead2;
	double volts[];
	double current, curcount;
	Rectangle boundingBox;
	boolean noDiagonal;
	int getDumpType() { return 0; }
	Class getDumpClass() { return getClass(); }
	CircuitElm(int xx, int yy) {
	    x = x2 = xx;
	    y = y2 = yy;
	    allocNodes();
	    boundingBox = new Rectangle();
	}
	CircuitElm(int xa, int ya, int xb, int yb, int f) {
	    x = xa; y = ya; x2 = xb; y2 = yb; flags = f;
	    allocNodes();
	    boundingBox = new Rectangle();
	}
	void allocNodes() {
	    nodes = new int[getPostCount()+getInternalNodeCount()];
	    volts = new double[getPostCount()+getInternalNodeCount()];
	}
	String dump() {
	    int t = getDumpType();
	    return (t < 127 ? ((char)t)+" " : t+" ") + x + " " + y + " " +
		x2 + " " + y2 + " " + flags;
	}
	void reset() {
	    int i;
	    for (i = 0; i != getPostCount()+getInternalNodeCount(); i++)
		volts[i] = 0;
	    curcount = 0;
	}
	void draw(Graphics g) {}
	void setCurrent(int x, double c) { current = c; }
	double getCurrent() { return current; }
	void doStep() {}
	void delete() {}
	void startIteration() {}
	double getPostVoltage(int x) { return volts[x]; }
	void setNodeVoltage(int n, double c) {
	    volts[n] = c;
	    calculateCurrent();
	}
	void calculateCurrent() {}
	void setPoints() {
	    dx = x2-x; dy = y2-y;
	    dn = Math.sqrt(dx*dx+dy*dy);
	    dpx1 = dy/dn;
	    dpy1 = -dx/dn;
	    dsign = (dy == 0) ? sign(dx) : sign(dy);
	    point1 = new Point(x , y );
	    point2 = new Point(x2, y2);
	}
	void calcLeads(int len) {
	    if (dn < len || len == 0) {
		lead1 = point1;
		lead2 = point2;
		return;
	    }
	    lead1 = interpPoint(point1, point2, (dn-len)/(2*dn));
	    lead2 = interpPoint(point1, point2, (dn+len)/(2*dn));
	}
	Point interpPoint(Point a, Point b, double f) {
	    Point p = new Point();
	    interpPoint(a, b, p, f);
	    return p;
	}
	void interpPoint(Point a, Point b, Point c, double f) {
	    int xpd = b.x-a.x;
	    int ypd = b.y-a.y;
	    c.x = (int) (a.x*(1-f)+b.x*f+.48);
	    c.y = (int) (a.y*(1-f)+b.y*f+.48);
	}
	void interpPoint(Point a, Point b, Point c, double f, double g) {
	    int xpd = b.x-a.x;
	    int ypd = b.y-a.y;
	    int gx = b.y-a.y;
	    int gy = a.x-b.x;
	    g /= Math.sqrt(gx*gx+gy*gy);
	    c.x = (int) (a.x*(1-f)+b.x*f+g*gx+.48);
	    c.y = (int) (a.y*(1-f)+b.y*f+g*gy+.48);
	}
	Point interpPoint(Point a, Point b, double f, double g) {
	    Point p = new Point();
	    interpPoint(a, b, p, f, g);
	    return p;
	}
	void interpPoint2(Point a, Point b, Point c, Point d, double f, double g) {
	    int xpd = b.x-a.x;
	    int ypd = b.y-a.y;
	    int gx = b.y-a.y;
	    int gy = a.x-b.x;
	    g /= Math.sqrt(gx*gx+gy*gy);
	    c.x = (int) (a.x*(1-f)+b.x*f+g*gx+.48);
	    c.y = (int) (a.y*(1-f)+b.y*f+g*gy+.48);
	    d.x = (int) (a.x*(1-f)+b.x*f-g*gx+.48);
	    d.y = (int) (a.y*(1-f)+b.y*f-g*gy+.48);
	}
	void draw2Leads(Graphics g) {
	    // draw first lead
	    setVoltageColor(g, volts[0]);
	    drawThickLine(g, point1, lead1);

	    // draw second lead
	    setVoltageColor(g, volts[1]);
	    drawThickLine(g, lead2, point2);
	}
	Point [] newPointArray(int n) {
	    Point a[] = new Point[n];
	    while (n > 0)
		a[--n] = new Point();
	    return a;
	}
	
	void drawDots(Graphics g, Point pa, Point pb, double pos) {
	    if (stoppedCheck.getState() || pos == 0 || !dotsCheckItem.getState())
		return;
	    int dx = pb.x-pa.x;
	    int dy = pb.y-pa.y;
	    double dn = Math.sqrt(dx*dx+dy*dy);
	    g.setColor(Color.yellow);
	    int ds = 16;
	    pos %= ds;
	    if (pos < 0)
		pos += ds;
	    double di = 0;
	    for (di = pos; di < dn; di += ds) {
		int x0 = (int) (pa.x+di*dx/dn);
		int y0 = (int) (pa.y+di*dy/dn);
		g.fillRect(x0-1, y0-1, 4, 4);
	    }
	}

	Polygon calcArrow(Point a, Point b, double al, double aw) {
	    Polygon poly = new Polygon();
	    Point p1 = new Point();
	    Point p2 = new Point();
	    int adx = b.x-a.x;
	    int ady = b.y-a.y;
	    double l = Math.sqrt(adx*adx+ady*ady);
	    poly.addPoint(b.x, b.y);
	    interpPoint2(a, b, p1, p2, 1-al/l, aw);
	    poly.addPoint(p1.x, p1.y);
	    poly.addPoint(p2.x, p2.y);
	    return poly;
	}
	Polygon createPolygon(Point a, Point b, Point c) {
	    Polygon p = new Polygon();
	    p.addPoint(a.x, a.y);
	    p.addPoint(b.x, b.y);
	    p.addPoint(c.x, c.y);
	    return p;
	}
	Polygon createPolygon(Point a, Point b, Point c, Point d) {
	    Polygon p = new Polygon();
	    p.addPoint(a.x, a.y);
	    p.addPoint(b.x, b.y);
	    p.addPoint(c.x, c.y);
	    p.addPoint(d.x, d.y);
	    return p;
	}
	Polygon createPolygon(Point a[]) {
	    Polygon p = new Polygon();
	    int i;
	    for (i = 0; i != a.length; i++)
		p.addPoint(a[i].x, a[i].y);
	    return p;
	}
	void drag(int xx, int yy) {
	    xx = snapGrid(xx);
	    yy = snapGrid(yy);
	    if (noDiagonal) {
		if (abs(x-xx) < abs(y-yy))
		    xx = x;
		else
		    yy = y;
	    }
	    x2 = xx; y2 = yy;
	    setPoints();
	}
	void move(int dx, int dy) {
	    x += dx; y += dy; x2 += dx; y2 += dy;
	    setPoints();
	}
	void movePoint(int n, int dx, int dy) {
	    if (n == 0) {
		x += dx; y += dy;
	    } else {
		x2 += dx; y2 += dy;
	    }
	    setPoints();
	}
	void drawPosts(Graphics g) {
	    int i;
	    for (i = 0; i != getPostCount(); i++) {
		Point p = getPost(i);
		drawPost(g, p.x, p.y, nodes[i]);
	    }
	}
	void stamp() {}
	int getVoltageSourceCount() { return 0; }
	int getInternalNodeCount() { return 0; }
	void setNode(int p, int n) { nodes[p] = n; }
	void setVoltageSource(int n, int v) { voltSource = v; }
	int getVoltageSource() { return voltSource; }
	double getVoltageDiff() {
	    return volts[0] - volts[1];
	}
	boolean nonLinear() { return false; }
	int getPostCount() { return 2; }
	int getNode(int n) { return nodes[n]; }
	Point getPost(int n) {
	    return (n == 0) ? point1 : (n == 1) ? point2 : null;
	}
	void drawPost(Graphics g, int x0, int y0, int n) {
	    if (dragElm == null && mouseElm != this &&
		getCircuitNode(n).links.size() == 2)
		return;
	    if (mouseMode == MODE_DRAG_ROW || mouseMode == MODE_DRAG_COLUMN)
		return;
	    drawPost(g, x0, y0);
	}
	void drawPost(Graphics g, int x0, int y0) {
	    g.setColor(whiteColor);
	    g.fillOval(x0-3, y0-3, 7, 7);
	}
	void setBbox(int x1, int y1, int x2, int y2) {
	    if (x1 > x2) { int q = x1; x1 = x2; x2 = q; }
	    if (y1 > y2) { int q = y1; y1 = y2; y2 = q; }
	    boundingBox.setBounds(x1, y1, x2-x1+1, y2-y1+1);
	}
	void setBbox(Point p1, Point p2, double w) {
	    setBbox(p1.x, p1.y, p2.x, p2.y);
	    int gx = p2.y-p1.y;
	    int gy = p1.x-p2.x;
	    int dpx = (int) (dpx1*w);
	    int dpy = (int) (dpy1*w);
	    adjustBbox(p1.x+dpx, p1.y+dpy, p1.x-dpx, p1.y-dpy);
	}
	void adjustBbox(int x1, int y1, int x2, int y2) {
	    if (x1 > x2) { int q = x1; x1 = x2; x2 = q; }
	    if (y1 > y2) { int q = y1; y1 = y2; y2 = q; }
	    x1 = min(boundingBox.x, x1);
	    y1 = min(boundingBox.y, y1);
	    x2 = max(boundingBox.x+boundingBox.width-1,  x2);
	    y2 = max(boundingBox.y+boundingBox.height-1, y2);
	    boundingBox.setBounds(x1, y1, x2-x1, y2-y1);
	}
	boolean isCenteredText() { return false; }
	
	void drawCenteredText(Graphics g, String s, int x, int y, boolean cx) {
	    FontMetrics fm = g.getFontMetrics();
	    int w = fm.stringWidth(s);
	    if (cx)
		x -= w/2;
	    g.drawString(s, x, y+fm.getAscent()/2);
	    adjustBbox(x, y-fm.getAscent()/2,
		       x+w, y+fm.getAscent()/2+fm.getDescent());
	}
    
	void drawValues(Graphics g, String s, double hs) {
	    if (s == null)
		return;
	    g.setFont(unitsFont);
	    FontMetrics fm = g.getFontMetrics();
	    int w = fm.stringWidth(s);
	    g.setColor(whiteColor);
	    int ya = fm.getAscent()/2;
	    int xc, yc;
	    if (this instanceof RailElm || this instanceof SweepElm) {
		xc = x2;
		yc = y2;
	    } else {
		xc = (x2+x)/2;
		yc = (y2+y)/2;
	    }
	    int dpx = (int) (dpx1*hs);
	    int dpy = (int) (dpy1*hs);
	    if (dpx == 0) {
		g.drawString(s, xc-w/2, yc-abs(dpy)-2);
	    } else {
		int xx = xc+abs(dpx)+2;
		if (this instanceof VoltageElm || (x < x2 && y > y2))
		    xx = xc-(w+abs(dpx)+2);
		g.drawString(s, xx, yc+dpy+ya);
	    }
	}
	void drawCoil(Graphics g, int hs, Point p1, Point p2,
		      double v1, double v2) {
	    double len = Math.sqrt(distanceSq(p1.x, p1.y, p2.x, p2.y));
	    int segments = 30; // 10*(int) (len/10);
	    int i;
	    double segf = 1./segments;
	    
	    ps1.setLocation(p1);
	    for (i = 0; i != segments; i++) {
		double cx = (((i+1)*6.*segf) % 2)-1;
		double hsx = Math.sqrt(1-cx*cx);
		if (hsx < 0)
		    hsx = -hsx;
		interpPoint(p1, p2, ps2, i*segf, hsx*hs);
		double v = v1+(v2-v1)*i/segments;
		setVoltageColor(g, v);
		drawThickLine(g, ps1, ps2);
		ps1.setLocation(ps2);
	    }
	}
	void updateDotCount() {
	    curcount = updateDotCount(current, curcount);
	}
	double updateDotCount(double cur, double cc) {
	    if (stoppedCheck.getState())
		return cc;
	    double cadd = cur*currentMult;
	    /*if (cur != 0 && cadd <= .05 && cadd >= -.05)
	      cadd = (cadd < 0) ? -.05 : .05;*/
	    cadd %= 8;
	    /*if (cadd > 8)
		cadd = 8;
	    if (cadd < -8)
	    cadd = -8;*/
	    return cc + cadd;
	}
	void doDots(Graphics g) {
	    updateDotCount();
	    if (dragElm != this)
		drawDots(g, point1, point2, curcount);
	}
	void doAdjust() {}
	void setupAdjust() {}
	void getInfo(String arr[]) {
	}
	void getBasicInfo(String arr[]) {
	    arr[1] = "I = " + getCurrentDText(getCurrent());
	    arr[2] = "Vd = " + getVoltageDText(getVoltageDiff());
	}
	void setVoltageColor(Graphics g, double volts) {
	    if (this == mouseElm) {
		g.setColor(selectColor);
		return;
	    }
	    if (!voltsCheckItem.getState()) {
		if (!powerCheckItem.getState()) // && !conductanceCheckItem.getState())
		    g.setColor(whiteColor);
		return;
	    }
	    int c = (int) ((volts+voltageRange)*(colorScaleCount-1)/
			   (voltageRange*2));
	    if (c < 0)
		c = 0;
	    if (c >= colorScaleCount)
		c = colorScaleCount-1;
	    g.setColor(colorScale[c]);
	}
	void setPowerColor(Graphics g, boolean yellow) {
	    /*if (conductanceCheckItem.getState()) {
		setConductanceColor(g, current/getVoltageDiff());
		return;
		}*/
	    if (!powerCheckItem.getState())
		return;
	    setPowerColor(g, getPower());
	}
	void setPowerColor(Graphics g, double w0) {
	    w0 *= powerMult;
	    //System.out.println(w);
	    double w = (w0 < 0) ? -w0 : w0;
	    if (w > 1)
		w = 1;
	    int rg = 128+(int) (w*127);
	    int b  = (int) (128*(1-w));
	    /*if (yellow)
		g.setColor(new Color(rg, rg, b));
		else */
	    if (w0 > 0)
		g.setColor(new Color(rg, b, b));
	    else
		g.setColor(new Color(b, rg, b));
	}
	void setConductanceColor(Graphics g, double w0) {
	    w0 *= powerMult;
	    //System.out.println(w);
	    double w = (w0 < 0) ? -w0 : w0;
	    if (w > 1)
		w = 1;
	    int rg = (int) (w*255);
	    g.setColor(new Color(rg, rg, rg));
	}
	double getPower() { return getVoltageDiff()*current; }
	double getScopeValue(int x) {
	    return (x == 1) ? getPower() : getVoltageDiff();
	}
	String getScopeUnits(int x) {
	    return (x == 1) ? "W" : "V";
	}
	EditInfo getEditInfo(int n) { return null; }
	void setEditValue(int n, EditInfo ei) {}
	boolean getConnection(int n1, int n2) { return true; }
	boolean hasGroundConnection(int n1) { return false; }
	boolean isWire() { return false; }
	boolean canViewInScope() { return getPostCount() <= 2; }
	boolean comparePair(int x1, int x2, int y1, int y2) {
	    return ((x1 == y1 && x2 == y2) || (x1 == y2 && x2 == y1));
	}
    }
    
    class WireElm extends CircuitElm {
	public WireElm(int xx, int yy) { super(xx, yy); }
	public WireElm(int xa, int ya, int xb, int yb, int f,
		       StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	}
	static final int FLAG_SHOWCURRENT = 1;
	void draw(Graphics g) {
	    setVoltageColor(g, volts[0]);
	    drawThickLine(g, point1, point2);
	    doDots(g);
	    setBbox(point1, point2, 3);
	    if (mustShowCurrent()) {
	        String s = getShortUnitText(Math.abs(getCurrent()), "A");
	        drawValues(g, s, 4);
	    }
	    drawPosts(g);
	}
	void stamp() {
	    stampVoltageSource(nodes[0], nodes[1], voltSource, 0);
	}
	boolean mustShowCurrent() {
	    return (flags & FLAG_SHOWCURRENT) != 0;
	}
	int getVoltageSourceCount() { return 1; }
	void getInfo(String arr[]) {
	    arr[0] = "wire";
	    arr[1] = "I = " + getCurrentDText(getCurrent());
	    arr[2] = "V = " + getVoltageText(volts[0]);
	}
	int getDumpType() { return 'w'; }
	double getPower() { return 0; }
	double getVoltageDiff() { return volts[0]; }
	boolean isWire() { return true; }
	EditInfo getEditInfo(int n) {
	    if (n == 0) {
		EditInfo ei = new EditInfo("", 0, -1, -1);
		ei.checkbox = new Checkbox("Show Current", mustShowCurrent());
		return ei;
	    }
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    if (n == 0)
		flags = (ei.checkbox.getState()) ? FLAG_SHOWCURRENT : 0;
	}
    }

    void drawThickLine(Graphics g, int x, int y, int x2, int y2) {
	g.drawLine(x, y, x2, y2);
	g.drawLine(x+1, y, x2+1, y2);
	g.drawLine(x, y+1, x2, y2+1);
	g.drawLine(x+1, y+1, x2+1, y2+1);
    }

    void drawThickLine(Graphics g, Point pa, Point pb) {
	g.drawLine(pa.x, pa.y, pb.x, pb.y);
	g.drawLine(pa.x+1, pa.y, pb.x+1, pb.y);
	g.drawLine(pa.x, pa.y+1, pb.x, pb.y+1);
	g.drawLine(pa.x+1, pa.y+1, pb.x+1, pb.y+1);
    }

    void drawThickPolygon(Graphics g, int xs[], int ys[], int c) {
	int i;
	for (i = 0; i != c-1; i++)
	    drawThickLine(g, xs[i], ys[i], xs[i+1], ys[i+1]);
	drawThickLine(g, xs[i], ys[i], xs[0], ys[0]);
    }
    
    void drawThickPolygon(Graphics g, Polygon p) {
	drawThickPolygon(g, p.xpoints, p.ypoints, p.npoints);
    }
    
    void drawThickCircle(Graphics g, int cx, int cy, int ri) {
	int a;
	double m = pi/180;
	double r = ri*.98;
	for (a = 0; a != 360; a += 20) {
	    double ax = Math.cos(a*m)*r + cx;
	    double ay = Math.sin(a*m)*r + cy;
	    double bx = Math.cos((a+20)*m)*r + cx;
	    double by = Math.sin((a+20)*m)*r + cy;
	    drawThickLine(g, (int) ax, (int) ay, (int) bx, (int) by);
	}
    }
    
    class ResistorElm extends CircuitElm {
	double resistance;
	public ResistorElm(int xx, int yy) { super(xx, yy); resistance = 100; }
	public ResistorElm(int xa, int ya, int xb, int yb, int f,
		    StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    resistance = new Double(st.nextToken()).doubleValue();
	}
	int getDumpType() { return 'r'; }
	String dump() {
	    return super.dump() + " " + resistance;
	}

	Point ps3, ps4;
	void setPoints() {
	    super.setPoints();
	    calcLeads(32);
	    ps3 = new Point();
	    ps4 = new Point();
	}
	
	void draw(Graphics g) {
	    int segments = 16;
	    int i;
	    int ox = 0;
	    int hs = euroResistorCheckItem.getState() ? 6 : 8;
	    double v1 = volts[0];
	    double v2 = volts[1];
	    setBbox(point1, point2, hs);
	    draw2Leads(g);
	    setPowerColor(g, true);
	    double segf = 1./segments;
	    if (!euroResistorCheckItem.getState()) {
		// draw zigzag
		for (i = 0; i != segments; i++) {
		    int nx = 0;
		    switch (i & 3) {
		    case 0: nx = 1; break;
		    case 2: nx = -1; break;
		    default: nx = 0; break;
		    }
		    double v = v1+(v2-v1)*i/segments;
		    setVoltageColor(g, v);
		    interpPoint(lead1, lead2, ps1, i*segf, hs*ox);
		    interpPoint(lead1, lead2, ps2, (i+1)*segf, hs*nx);
		    drawThickLine(g, ps1, ps2);
		    ox = nx;
		}
	    } else {
		// draw rectangle
		setVoltageColor(g, v1);
		interpPoint2(lead1, lead2, ps1, ps2, 0, hs);
		drawThickLine(g, ps1, ps2);
		for (i = 0; i != segments; i++) {
		    double v = v1+(v2-v1)*i/segments;
		    setVoltageColor(g, v);
		    interpPoint2(lead1, lead2, ps1, ps2, i*segf, hs);
		    interpPoint2(lead1, lead2, ps3, ps4, (i+1)*segf, hs);
		    drawThickLine(g, ps1, ps3);
		    drawThickLine(g, ps2, ps4);
		}
		interpPoint2(lead1, lead2, ps1, ps2, 1, hs);
		drawThickLine(g, ps1, ps2);
	    }
	    if (showValuesCheckItem.getState()) {
		String s = getShortUnitText(resistance, "");
		drawValues(g, s, hs);
	    }
	    doDots(g);
	    drawPosts(g);
	}
    
	void calculateCurrent() {
	    current = (volts[0]-volts[1])/resistance;
	    //System.out.print(this + " res current set to " + current + "\n");
	}
	void stamp() {
	    stampResistor(nodes[0], nodes[1], resistance);
	}
	void getInfo(String arr[]) {
	    arr[0] = "resistor";
	    getBasicInfo(arr);
	    arr[3] = "R = " + getUnitText(resistance, ohmString);
	    arr[4] = "P = " + getUnitText(getPower(), "W");
	}
	EditInfo getEditInfo(int n) {
	    // ohmString doesn't work here on linux
	    if (n == 0)
		return new EditInfo("Resistance (ohms)", resistance, 0, 0);
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    resistance = ei.value;
	}
    }

    class CapacitorElm extends CircuitElm {
	double capacitance;
	double compResistance, voltdiff;
	Point plate1[], plate2[];
	public CapacitorElm(int xx, int yy) {
	    super(xx, yy);
	    capacitance = 1e-5;
	}
	public CapacitorElm(int xa, int ya, int xb, int yb, int f,
			    StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    capacitance = new Double(st.nextToken()).doubleValue();
	    voltdiff = new Double(st.nextToken()).doubleValue();
	}
	void setNodeVoltage(int n, double c) {
	    super.setNodeVoltage(n, c);
	    voltdiff = volts[0]-volts[1];
	}
	void reset() {
	    current = curcount = 0;
	    // put small charge on caps when reset to start oscillators
	    voltdiff = 1e-3;
	}
	int getDumpType() { return 'c'; }
	String dump() {
	    return super.dump() + " " + capacitance + " " + voltdiff;
	}
	void setPoints() {
	    super.setPoints();
	    double f = (dn/2-4)/dn;
	    // calc leads
	    lead1 = interpPoint(point1, point2, f);
	    lead2 = interpPoint(point1, point2, 1-f);
	    // calc plates
	    plate1 = newPointArray(2);
	    plate2 = newPointArray(2);
	    interpPoint2(point1, point2, plate1[0], plate1[1], f, 12);
	    interpPoint2(point1, point2, plate2[0], plate2[1], 1-f, 12);
	}
	
	void draw(Graphics g) {
	    int hs = 12;
	    setBbox(point1, point2, hs);
	    
	    // draw first lead and plate
	    setVoltageColor(g, volts[0]);
	    drawThickLine(g, point1, lead1);
	    setPowerColor(g, false);
	    drawThickLine(g, plate1[0], plate1[1]);
	    if (powerCheckItem.getState())
		g.setColor(Color.gray);

	    // draw second lead and plate
	    setVoltageColor(g, volts[1]);
	    drawThickLine(g, point2, lead2);
	    setPowerColor(g, false);
	    drawThickLine(g, plate2[0], plate2[1]);
	    
	    updateDotCount();
	    if (dragElm != this) {
		drawDots(g, point1, lead1, curcount);
		drawDots(g, point2, lead2, -curcount);
	    }
	    drawPosts(g);
	    if (showValuesCheckItem.getState()) {
		String s = getShortUnitText(capacitance, "F");
		drawValues(g, s, hs);
	    }
	}
	void stamp() {
	    // capacitor companion model using trapezoidal approximation
	    // (Thevenin equivalent) consists of a voltage source in
	    // series with a resistor
	    stampVoltageSource(nodes[0], nodes[2], voltSource);
	    compResistance = timeStep/(2*capacitance);
	    stampResistor(nodes[2], nodes[1], compResistance);
	}
	void startIteration() {
	    voltSourceValue = -voltdiff-current*compResistance;
	}
	double voltSourceValue;
	void doStep() {
	    updateVoltageSource(nodes[0], nodes[2], voltSource,
				voltSourceValue);
 	}
	int getVoltageSourceCount() { return 1; }
	int getInternalNodeCount() { return 1; }
	void getInfo(String arr[]) {
	    arr[0] = "capacitor";
	    getBasicInfo(arr);
	    arr[3] = "C = " + getUnitText(capacitance, "F");
	    arr[4] = "P = " + getUnitText(getPower(), "W");
	    //double v = getVoltageDiff();
	    //arr[4] = "U = " + getUnitText(.5*capacitance*v*v, "J");
	}
	EditInfo getEditInfo(int n) {
	    if (n == 0)
		return new EditInfo("Capacitance (uF)", capacitance*1e6, 0, 0);
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    capacitance = ei.value*1e-6;
	}
    }

    class InductorElm extends CircuitElm {
	double inductance;
	double compResistance;
	public InductorElm(int xx, int yy) { super(xx, yy); inductance = 1; }
	public InductorElm(int xa, int ya, int xb, int yb, int f,
		    StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    inductance = new Double(st.nextToken()).doubleValue();
	    current = new Double(st.nextToken()).doubleValue();
	}
	int getDumpType() { return 'l'; }
	String dump() {
	    return super.dump() + " " + inductance + " " + current;
	}
	void setPoints() {
	    super.setPoints();
	    calcLeads(32);
	}
	void draw(Graphics g) {
	    double v1 = volts[0];
	    double v2 = volts[1];
	    int i;
	    int hs = 8;
	    setBbox(point1, point2, hs);
	    draw2Leads(g);
	    setPowerColor(g, false);
	    drawCoil(g, 8, lead1, lead2, v1, v2);
	    if (showValuesCheckItem.getState()) {
		String s = getShortUnitText(inductance, "H");
		drawValues(g, s, hs);
	    }
	    doDots(g);
	    drawPosts(g);
	}
	void reset() { current = volts[0] = volts[1] = curcount = 0; }
	void stamp() {
	    // inductor companion model using trapezoidal approximation
	    // (Norton equivalent) consists of a current source in
	    // parallel with a resistor.
	    compResistance = 2*inductance/timeStep;
	    stampResistor(nodes[0], nodes[1], compResistance);
	    stampRightSide(nodes[0]);
	    stampRightSide(nodes[1]);
	}
	void startIteration() {
	    double voltdiff = volts[0]-volts[1];
	    curSourceValue = voltdiff/compResistance+current;
	    //System.out.println("ind " + this + " " + current + " " + voltdiff);
	}
	void calculateCurrent() {
	    double voltdiff = volts[0]-volts[1];
	    // we check compResistance because this might get called
	    // before stamp(), which sets compResistance, causing
	    // infinite current
	    if (compResistance > 0)
		current = voltdiff/compResistance + curSourceValue;
	}
	double curSourceValue;
	void doStep() {
	    stampCurrentSource(nodes[0], nodes[1], curSourceValue);
 	}
	void getInfo(String arr[]) {
	    arr[0] = "inductor";
	    getBasicInfo(arr);
	    arr[3] = "L = " + getUnitText(inductance, "H");
	    arr[4] = "P = " + getUnitText(getPower(), "W");
	}
	EditInfo getEditInfo(int n) {
	    if (n == 0)
		return new EditInfo("Inductance (H)", inductance, 0, 0);
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    inductance = ei.value;
	}
    }

    class DCVoltageElm extends VoltageElm {
	public DCVoltageElm(int xx, int yy) { super(xx, yy, WF_DC); }
	Class getDumpClass() { return VoltageElm.class; }
    }
    class ACVoltageElm extends VoltageElm {
	public ACVoltageElm(int xx, int yy) { super(xx, yy, WF_AC); }
	Class getDumpClass() { return VoltageElm.class; }
    }
    
    class VoltageElm extends CircuitElm {
	static final int FLAG_COS = 2;
	int waveform;
	static final int WF_DC = 0;
	static final int WF_AC = 1;
	static final int WF_SQUARE = 2;
	static final int WF_TRIANGLE = 3;
	static final int WF_SAWTOOTH = 4;
	static final int WF_PULSE = 5;
	static final int WF_VAR = 6;
	double frequency, maxVoltage, freqTimeZero, bias,
	    phaseShift, dutyCycle;
	VoltageElm(int xx, int yy, int wf) {
	    super(xx, yy);
	    waveform = wf;
	    maxVoltage = 5;
	    frequency = 40;
	    dutyCycle = .5;
	    reset();
	}
	public VoltageElm(int xa, int ya, int xb, int yb, int f,
		   StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    maxVoltage = 5;
	    frequency = 40;
	    waveform = WF_DC;
	    dutyCycle = .5;
	    try {
		waveform = new Integer(st.nextToken()).intValue();
		frequency = new Double(st.nextToken()).doubleValue();
		maxVoltage = new Double(st.nextToken()).doubleValue();
		bias = new Double(st.nextToken()).doubleValue();
		phaseShift = new Double(st.nextToken()).doubleValue();
		dutyCycle = new Double(st.nextToken()).doubleValue();
	    } catch (Exception e) {
	    }
	    if ((flags & FLAG_COS) != 0) {
		flags &= ~FLAG_COS;
		phaseShift = pi/2;
	    }
	    reset();
	}
	int getDumpType() { return 'v'; }
	String dump() {
	    return super.dump() + " " + waveform + " " + frequency + " " +
		maxVoltage + " " + bias + " " + phaseShift + " " +
		dutyCycle;
	}
	/*void setCurrent(double c) {
	    current = c;
	    System.out.print("v current set to " + c + "\n");
	    }*/

	void reset() {
	    freqTimeZero = 0;
	    curcount = 0;
	}
	double triangleFunc(double x) {
	    if (x < pi)
		return x*(2/pi)-1;
	    return 1-(x-pi)*(2/pi);
	}
	void stamp() {
	    if (waveform == WF_DC)
		stampVoltageSource(nodes[0], nodes[1], voltSource,
				   getVoltage());
	    else
		stampVoltageSource(nodes[0], nodes[1], voltSource);
	}
	void doStep() {
	    if (waveform != WF_DC)
		updateVoltageSource(nodes[0], nodes[1], voltSource,
				    getVoltage());
	}
	double getVoltage() {
	    double w = 2*pi*(t-freqTimeZero)*frequency + phaseShift;
	    switch (waveform) {
	    case WF_DC: return maxVoltage+bias;
	    case WF_AC: return Math.sin(w)*maxVoltage+bias;
	    case WF_SQUARE:
		return bias+((w % (2*pi) > (2*pi*dutyCycle)) ?
			      -maxVoltage : maxVoltage);
	    case WF_TRIANGLE:
		return bias+triangleFunc(w % (2*pi))*maxVoltage;
	    case WF_SAWTOOTH:
		return bias+(w % (2*pi))*(maxVoltage/pi)-maxVoltage;
	    case WF_PULSE:
		return ((w % (2*pi)) < 1) ? maxVoltage+bias : bias;
	    default: return 0;
	    }
	}
	final int circleSize = 17;
	void setPoints() {
	    super.setPoints();
	    calcLeads((waveform == WF_DC || waveform == WF_VAR) ? 8 : circleSize*2);
	}
	void draw(Graphics g) {
	    setBbox(x, y, x2, y2);
	    draw2Leads(g);
	    if (waveform == WF_DC) {
		setPowerColor(g, false);
		setVoltageColor(g, volts[0]);
		interpPoint2(lead1, lead2, ps1, ps2, 0, 10);
		drawThickLine(g, ps1, ps2);
		setVoltageColor(g, volts[1]);
		int hs = 16;
		setBbox(point1, point2, hs);
		interpPoint2(lead1, lead2, ps1, ps2, 1, hs);
		drawThickLine(g, ps1, ps2);
	    } else {
		setBbox(point1, point2, circleSize);
		interpPoint(lead1, lead2, ps1, .5);
		drawWaveform(g, ps1);
	    }
	    updateDotCount();
	    if (dragElm != this) {
		if (waveform == WF_DC)
		    drawDots(g, point1, point2, curcount);
		else {
		    drawDots(g, point1, lead1, curcount);
		    drawDots(g, point2, lead2, -curcount);
		}
	    }
	    drawPosts(g);
	}
	
	void drawWaveform(Graphics g, Point center) {
	    g.setColor(this == mouseElm ? selectColor : Color.gray);
	    setPowerColor(g, false);
	    int xc = center.x; int yc = center.y;
	    drawThickCircle(g, xc, yc, circleSize);
	    int wl = 8;
	    adjustBbox(xc-circleSize, yc-circleSize,
		       xc+circleSize, yc+circleSize);
	    int xc2;
	    switch (waveform) {
	    case WF_DC:
		{
		    break;
		}
	    case WF_SQUARE:
		xc2 = (int) (wl*2*dutyCycle-wl+xc);
		xc2 = max(xc-wl+3, min(xc+wl-3, xc2));
		drawThickLine(g, xc-wl, yc-wl, xc-wl, yc   );
		drawThickLine(g, xc-wl, yc-wl, xc2  , yc-wl);
		drawThickLine(g, xc2  , yc-wl, xc2  , yc+wl);
		drawThickLine(g, xc+wl, yc+wl, xc2  , yc+wl);
		drawThickLine(g, xc+wl, yc   , xc+wl, yc+wl);
		break;
	    case WF_PULSE:
		yc += wl/2;
		drawThickLine(g, xc-wl, yc-wl, xc-wl, yc   );
		drawThickLine(g, xc-wl, yc-wl, xc-wl/2, yc-wl);
		drawThickLine(g, xc-wl/2, yc-wl, xc-wl/2, yc);
		drawThickLine(g, xc-wl/2, yc, xc+wl, yc);
		break;
	    case WF_SAWTOOTH:
		drawThickLine(g, xc   , yc-wl, xc-wl, yc   );
		drawThickLine(g, xc   , yc-wl, xc   , yc+wl);
		drawThickLine(g, xc   , yc+wl, xc+wl, yc   );
		break;
	    case WF_TRIANGLE:
		{
		    int xl = 5;
		    drawThickLine(g, xc-xl*2, yc   , xc-xl, yc-wl);
		    drawThickLine(g, xc-xl, yc-wl, xc, yc);
		    drawThickLine(g, xc   , yc, xc+xl, yc+wl);
		    drawThickLine(g, xc+xl, yc+wl, xc+xl*2, yc);
		    break;
		}
	    case WF_AC:
		{
		    int i;
		    int xl = 10;
		    int ox = -1, oy = -1;
		    for (i = -xl; i <= xl; i++) {
			int yy = yc+(int) (.95*Math.sin(i*pi/xl)*wl);
			if (ox != -1)
			    drawThickLine(g, ox, oy, xc+i, yy);
			ox = xc+i; oy = yy;
		    }
		    break;
		}
	    }
	    if (showValuesCheckItem.getState()) {
		String s = getShortUnitText(frequency, "Hz");
		if (dx == 0 || dy == 0)
		    drawValues(g, s, circleSize);
	    }
	}
	
	int getVoltageSourceCount() {
	    return 1;
	}
	double getPower() { return -getVoltageDiff()*current; }
	double getVoltageDiff() { return volts[1] - volts[0]; }
	void getInfo(String arr[]) {
	    switch (waveform) {
	    case WF_DC: case WF_VAR:
		arr[0] = "voltage source"; break;
	    case WF_AC:       arr[0] = "A/C source"; break;
	    case WF_SQUARE:   arr[0] = "square wave gen"; break;
	    case WF_PULSE:    arr[0] = "pulse gen"; break;
	    case WF_SAWTOOTH: arr[0] = "sawtooth gen"; break;
	    case WF_TRIANGLE: arr[0] = "triangle gen"; break;
	    }
	    arr[1] = "I = " + getCurrentText(getCurrent());
	    arr[2] = ((this instanceof RailElm) ? "V = " : "Vd = ") +
		getVoltageText(getVoltageDiff());
	    if (waveform != WF_DC && waveform != WF_VAR) {
		arr[3] = "f = " + getUnitText(frequency, "Hz");
		arr[4] = "Vmax = " + getVoltageText(maxVoltage);
		int i = 5;
		if (bias != 0)
		    arr[i++] = "Voff = " + getVoltageText(bias);
		else if (frequency > 500)
		    arr[i++] = "wavelength = " +
			getUnitText(2.9979e8/frequency, "m");
		arr[i++] = "P = " + getUnitText(getPower(), "W");
	    }
	}
	EditInfo getEditInfo(int n) {
	    if (n == 0)
		return new EditInfo(waveform == WF_DC ? "Voltage" :
				    "Max Voltage", maxVoltage, -20, 20);
	    if (n == 1) {
		EditInfo ei =  new EditInfo("Waveform", waveform, -1, -1);
		ei.choice = new Choice();
		ei.choice.add("D/C");
		ei.choice.add("A/C");
		ei.choice.add("Square Wave");
		ei.choice.add("Triangle");
		ei.choice.add("Sawtooth");
		ei.choice.add("Pulse");
		ei.choice.select(waveform);
		return ei;
	    }
	    if (waveform == WF_DC)
		return null;
	    if (n == 2)
		return new EditInfo("Frequency (Hz)", frequency, 4, 500);
	    if (n == 3)
		return new EditInfo("DC Offset (V)", bias, -20, 20);
	    if (n == 4)
		return new EditInfo("Phase Offset (degrees)", phaseShift*180/pi,
				    -180, 180);
	    if (n == 5 && waveform == WF_SQUARE)
		return new EditInfo("Duty Cycle", dutyCycle*100, 0, 100);
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    if (n == 0)
		maxVoltage = ei.value;
	    if (n == 3)
		bias = ei.value;
	    if (n == 2) {
		// adjust time zero to maintain continuity ind the waveform
		// even though the frequency has changed.
		double oldfreq = frequency;
		frequency = ei.value;
		double maxfreq = 1/(8*timeStep);
		if (frequency > maxfreq)
		    frequency = maxfreq;
		double adj = frequency-oldfreq;
		freqTimeZero = t-oldfreq*(t-freqTimeZero)/frequency;
	    }
	    if (n == 1) {
		int ow = waveform;
		waveform = ei.choice.getSelectedIndex();
		if (waveform == WF_DC && ow != WF_DC) {
		    ei.newDialog = true;
		    bias = 0;
		} else if (waveform != WF_DC && ow == WF_DC) {
		    ei.newDialog = true;
		}
		if ((waveform == WF_SQUARE || ow == WF_SQUARE) &&
		    waveform != ow)
		    ei.newDialog = true;
		setPoints();
	    }
	    if (n == 4)
		phaseShift = ei.value*pi/180;
	    if (n == 5)
		dutyCycle = ei.value*.01;
	}
    }

    class CurrentElm extends CircuitElm {
	double currentValue;
	public CurrentElm(int xx, int yy) {
	    super(xx, yy);
	    currentValue = .01;
	}
	public CurrentElm(int xa, int ya, int xb, int yb, int f,
		   StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    try {
		currentValue = new Double(st.nextToken()).doubleValue();
	    } catch (Exception e) {
		currentValue = .01;
	    }
	}
	String dump() {
	    return super.dump() + " " + currentValue;
	}
	int getDumpType() { return 'i'; }
	
	Polygon arrow;
	Point ashaft1, ashaft2, center;
	void setPoints() {
	    super.setPoints();
	    calcLeads(26);
	    ashaft1 = interpPoint(lead1, lead2, .25);
	    ashaft2 = interpPoint(lead1, lead2, .6);
	    center = interpPoint(lead1, lead2, .5);
	    Point p2 = interpPoint(lead1, lead2, .75);
	    arrow = calcArrow(center, p2, 4, 4);
	}
	void draw(Graphics g) {
	    int cr = 12;
	    draw2Leads(g);
	    setVoltageColor(g, (volts[0]+volts[1])/2);
	    setPowerColor(g, false);
	    
	    drawThickCircle(g, center.x, center.y, cr);
	    drawThickLine(g, ashaft1, ashaft2);

	    g.fillPolygon(arrow);
	    setBbox(point1, point2, cr);
	    doDots(g);
	    if (showValuesCheckItem.getState()) {
		String s = getShortUnitText(currentValue, "A");
		if (dx == 0 || dy == 0)
		    drawValues(g, s, cr);
	    }
	    drawPosts(g);
	}
	void stamp() {
	    current = currentValue;
	    stampCurrentSource(nodes[0], nodes[1], current);
	}
	EditInfo getEditInfo(int n) {
	    if (n == 0)
		return new EditInfo("Current (A)", currentValue, 0, .1);
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    currentValue = ei.value;
	}
	void getInfo(String arr[]) {
	    arr[0] = "current source";
	    getBasicInfo(arr);
	}
	double getVoltageDiff() {
	    return volts[1] - volts[0];
	}
    }

    class PushSwitchElm extends SwitchElm {
	public PushSwitchElm(int xx, int yy) { super(xx, yy, true); }
	Class getDumpClass() { return SwitchElm.class; }
    }
    
    class SwitchElm extends CircuitElm {
	boolean momentary;
	// position 0 == closed, position 1 == open
	int position, posCount;
	public SwitchElm(int xx, int yy) {
	    super(xx, yy);
	    momentary = false;
	    position = 0;
	    posCount = 2;
	}
	SwitchElm(int xx, int yy, boolean mm) {
	    super(xx, yy);
	    position = (mm) ? 1 : 0;
	    momentary = mm;
	    posCount = 2;
	}
	public SwitchElm(int xa, int ya, int xb, int yb, int f,
			 StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    String str = st.nextToken();
	    if (str.compareTo("true") == 0)
		position = (this instanceof LogicInputElm) ? 0 : 1;
	    else if (str.compareTo("false") == 0)
		position = (this instanceof LogicInputElm) ? 1 : 0;
	    else
		position = new Integer(str).intValue();
	    momentary = new Boolean(st.nextToken()).booleanValue();
	    posCount = 2;
	}
	int getDumpType() { return 's'; }
	String dump() {
	    return super.dump() + " " + position + " " + momentary;
	}

	Point ps;
	void setPoints() {
	    super.setPoints();
	    calcLeads(32);
	    ps = new Point();
	}
	
	void draw(Graphics g) {
	    int openhs = 16;
	    int hs = (position == 1) ? openhs : 0;
	    setBbox(point1, point2, openhs);

	    draw2Leads(g);
	    
	    if (position == 0)
		doDots(g);
	    
	    if (mouseElm != this)
		g.setColor(whiteColor);
	    interpPoint(lead1, lead2, ps, 1, hs);
	    
	    drawThickLine(g, lead1, ps);
	    drawPosts(g);
	}
	void calculateCurrent() {
	    if (position == 1)
		current = 0;
	}
	void stamp() {
	    if (position == 0)
		stampVoltageSource(nodes[0], nodes[1], voltSource, 0);
	}
	int getVoltageSourceCount() {
	    return (position == 1) ? 0 : 1;
	}
	void mouseUp() {
	    if (momentary)
		toggle();
	}
	void toggle() {
	    position++;
	    if (position >= posCount)
		position = 0;
	}
	void getInfo(String arr[]) {
	    arr[0] = (momentary) ? "push switch (SPST)" : "switch (SPST)";
	    if (position == 1) {
		arr[1] = "open";
		arr[2] = "Vd = " + getVoltageDText(getVoltageDiff());
	    } else {
		arr[1] = "closed";
		arr[2] = "V = " + getVoltageText(volts[0]);
		arr[3] = "I = " + getCurrentDText(getCurrent());
	    }
	}
	boolean getConnection(int n1, int n2) { return position == 0; }
	boolean isWire() { return true; }
	EditInfo getEditInfo(int n) {
	    if (n == 0) {
		EditInfo ei = new EditInfo("", 0, -1, -1);
		ei.checkbox = new Checkbox("Momentary Switch", momentary);
		return ei;
	    }
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    if (n == 0)
		momentary = ei.checkbox.getState();
	}
    }

    // DPST switch
    class Switch2Elm extends SwitchElm {
	int link;
	
	public Switch2Elm(int xx, int yy) {
	    super(xx, yy, false);
	    noDiagonal = true;
	}
	Switch2Elm(int xx, int yy, boolean mm) {
	    super(xx, yy, mm);
	    noDiagonal = true;
	}
	public Switch2Elm(int xa, int ya, int xb, int yb, int f,
			  StringTokenizer st) {
	    super(xa, ya, xb, yb, f, st);
	    link = new Integer(st.nextToken()).intValue();
	    noDiagonal = true;
	}
	int getDumpType() { return 'S'; }
	String dump() {
	    return super.dump() + " " + position + " " + momentary + " " + link;
	}

	final int openhs = 16;
	Point swposts[], swpoles[];
	void setPoints() {
	    super.setPoints();
	    calcLeads(32);
	    swposts = newPointArray(2);
	    swpoles = newPointArray(2);
	    interpPoint2(lead1,  lead2,  swpoles[0], swpoles[1], 1, openhs);
	    interpPoint2(point1, point2, swposts[0], swposts[1], 1, openhs);
	}
	
	void draw(Graphics g) {
	    setBbox(point1, point2, openhs);

	    // draw first lead
	    setVoltageColor(g, volts[0]);
	    drawThickLine(g, point1, lead1);

	    // draw second lead
	    setVoltageColor(g, volts[1]);
	    drawThickLine(g, swpoles[0], swposts[0]);
	    
	    // draw third lead
	    setVoltageColor(g, volts[2]);
	    drawThickLine(g, swpoles[1], swposts[1]);

	    // draw switch
	    if (mouseElm != this)
		g.setColor(whiteColor);
	    drawThickLine(g, lead1, swpoles[position]);
	    
	    updateDotCount();
	    drawDots(g, point1, lead1, curcount);
	    drawDots(g, swpoles[position], swposts[position], curcount);
	    drawPosts(g);
	}
	Point getPost(int n) {
	    return (n == 0) ? point1 : swposts[n-1];
	}
	int getPostCount() { return 3; }
	void calculateCurrent() { }
	void stamp() {
	    stampVoltageSource(nodes[0], nodes[position+1], voltSource, 0);
	}
	int getVoltageSourceCount() {
	    return 1;
	}
	void toggle() {
	    super.toggle();
	    if (link != 0) {
		int i;
		for (i = 0; i != elmList.size(); i++) {
		    Object o = elmList.elementAt(i);
		    if (o instanceof Switch2Elm) {
			Switch2Elm s2 = (Switch2Elm) o;
			if (s2.link == link)
			    s2.position = position;
		    }
		}
	    }
	}
	boolean getConnection(int n1, int n2) {
	    return comparePair(n1, n2, 0, 1+position);
	}
	void getInfo(String arr[]) {
	    arr[0] = (link == 0) ? "switch (DPST)" : "switch (DPDT)";
	    arr[1] = "I = " + getCurrentDText(getCurrent());
	}
    }

    class DiodeElm extends CircuitElm {
	static final int FLAG_FWDROP = 1;
	public DiodeElm(int xx, int yy) {
	    super(xx, yy);
	    fwdrop = defaultdrop;
	    setup();
	}
	public DiodeElm(int xa, int ya, int xb, int yb, int f,
			StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    fwdrop = defaultdrop;
	    if ((f & FLAG_FWDROP) > 0) {
		try {
		    fwdrop = new Double(st.nextToken()).doubleValue();
		} catch (Exception e) {
		}
	    }
	    setup();
	}
	boolean nonLinear() { return true; }
	void setup() {
	    vdcoef = Math.log(1/leakage + 1)/fwdrop;
	    vt = 1/vdcoef;
	    vcrit = vt * Math.log(vt/(Math.sqrt(2)*leakage));
	}
	int getDumpType() { return 'd'; }
	String dump() {
	    flags |= FLAG_FWDROP;
	    return super.dump() + " " + fwdrop;
	}
	
	final int hs = 8;
	Polygon poly;
	Point cathode[];
	
	void setPoints() {
	    super.setPoints();
	    calcLeads(16);
	    cathode = newPointArray(2);
	    Point pa[] = newPointArray(2);
	    interpPoint2(lead1, lead2, pa[0], pa[1], 0, hs);
	    interpPoint2(lead1, lead2, cathode[0], cathode[1], 1, hs);
	    poly = createPolygon(pa[0], pa[1], lead2);
	}
	
	void draw(Graphics g) {
	    drawDiode(g);
	    doDots(g);
	    drawPosts(g);
	}
	
	void reset() {
	    lastvoltdiff = volts[0] = volts[1] = curcount = 0;
	}
	
	void drawDiode(Graphics g) {
	    setBbox(point1, point2, hs);

	    double v1 = volts[0];
	    double v2 = volts[1];

	    draw2Leads(g);

	    // draw arrow thingy
	    setPowerColor(g, true);
	    setVoltageColor(g, v1);
	    g.fillPolygon(poly);

	    // draw thing arrow is pointing to
	    setVoltageColor(g, v2);
	    drawThickLine(g, cathode[0], cathode[1]);
	}
	
	final double leakage = 1e-14; // was 1e-9;
	final double defaultdrop = .805904783;
	double vt, vdcoef, fwdrop;
	double lastvoltdiff;
	double vcrit;
	double limitStep(double vnew, double vold) {
	    double arg;
	    double oo = vnew;
	    
	    if (vnew > vcrit && Math.abs(vnew - vold) > (vt + vt)) {
		if(vold > 0) {
		    arg = 1 + (vnew - vold) / vt;
		    if(arg > 0) {
			vnew = vold + vt * Math.log(arg);
			double v0 = Math.log(1e-6/leakage)*vt;
			vnew = Math.max(v0, vnew);
			//System.out.println(oo + " " + vnew);
		    } else {
			vnew = vcrit;
		    }
		} else {
		    vnew = vt *Math.log(vnew/vt);
		}
		converged = false;
		//System.out.println(vnew + " " + oo + " " + vold);
	    }
	    return(vnew);
	}
	void stamp() {
	    stampNonLinear(nodes[0]);
	    stampNonLinear(nodes[1]);
	}
	void doStep() {
	    double voltdiff = volts[0] - volts[1];
	    // used to have .1 here, but needed .01 for peak detector
	    if (Math.abs(voltdiff-lastvoltdiff) > .01)
		converged = false;
	    voltdiff = limitStep(voltdiff, lastvoltdiff);
	    lastvoltdiff = voltdiff;
	    double eval = Math.exp(voltdiff*vdcoef);
	    // make diode linear with negative voltages; aids convergence
	    if (voltdiff < 0)
		eval = 1;
	    double geq = vdcoef*leakage*eval;
	    double nc = (eval-1)*leakage - geq*voltdiff;
	    stampConductance(nodes[0], nodes[1], geq);
	    stampCurrentSource(nodes[0], nodes[1], nc);
	}
	void calculateCurrent() {
	    double voltdiff = volts[0] - volts[1];
	    current = leakage*(Math.exp(voltdiff*vdcoef)-1);
	}
	void getInfo(String arr[]) {
	    arr[0] = "diode";
	    arr[1] = "I = " + getCurrentText(getCurrent());
	    arr[2] = "Vd = " + getVoltageText(getVoltageDiff());
	    arr[3] = "P = " + getUnitText(getPower(), "W");
	    arr[4] = "Vf = " + getVoltageText(fwdrop);
	}
	EditInfo getEditInfo(int n) {
	    if (n == 0)
		return new EditInfo("Fwd Voltage @ 1A", fwdrop, 10, 1000);
	    return null;
	} 
	void setEditValue(int n, EditInfo ei) {
	    fwdrop = ei.value;
	    setup();
	}
    }

    class LEDElm extends DiodeElm {
	double colorR, colorG, colorB;
	public LEDElm(int xx, int yy) {
	    super(xx, yy);
	    fwdrop = 2.1024259;
	    setup();
	    colorR = 1; colorG = colorB = 0;
	}
	public LEDElm(int xa, int ya, int xb, int yb, int f,
		      StringTokenizer st) {
	    super(xa, ya, xb, yb, f, st);
	    if ((f & FLAG_FWDROP) == 0)
		fwdrop = 2.1024259;
	    setup();
	    colorR = new Double(st.nextToken()).doubleValue();
	    colorG = new Double(st.nextToken()).doubleValue();
	    colorB = new Double(st.nextToken()).doubleValue();
	}
	int getDumpType() { return 162; }
	String dump() {
	    return super.dump() + " " + colorR + " " + colorG + " " + colorB;
	}

	Point ledLead1, ledLead2, ledCenter;
	void setPoints() {
	    super.setPoints();
	    int cr = 12;
	    ledLead1  = interpPoint(point1, point2, .5-cr/dn);
	    ledLead2  = interpPoint(point1, point2, .5+cr/dn);
	    ledCenter = interpPoint(point1, point2, .5);
	}
	
	void draw(Graphics g) {
	    if (this == mouseElm || this == dragElm) {
		super.draw(g);
		return;
	    }
	    setVoltageColor(g, volts[0]);
	    drawThickLine(g, point1, ledLead1);
	    setVoltageColor(g, volts[1]);
	    drawThickLine(g, ledLead2, point2);
	    
	    g.setColor(Color.gray);
	    int cr = 12;
	    drawThickCircle(g, ledCenter.x, ledCenter.y, cr);
	    cr -= 4;
	    double w = 255*current/.01;
	    if (w > 255)
		w = 255;
	    Color cc = new Color((int) (colorR*w), (int) (colorG*w),
				 (int) (colorB*w));
	    g.setColor(cc);
	    g.fillOval(ledCenter.x-cr, ledCenter.y-cr, cr*2, cr*2);
	    setBbox(point1, point2, cr);
	    updateDotCount();
	    drawDots(g, point1, ledLead1, curcount);
	    drawDots(g, point2, ledLead2, -curcount);
	    drawPosts(g);
	}

	void getInfo(String arr[]) {
	    super.getInfo(arr);
	    arr[0] = "LED";
	}
    }

    class NTransistorElm extends TransistorElm {
	public NTransistorElm(int xx, int yy) { super(xx, yy, false); }
	Class getDumpClass() { return TransistorElm.class; }
    }
    class PTransistorElm extends TransistorElm {
	public PTransistorElm(int xx, int yy) { super(xx, yy, true); }
	Class getDumpClass() { return TransistorElm.class; }
    }
    
    class TransistorElm extends CircuitElm {
	int pnp;
	double beta;
	double fgain;
	final int FLAG_FLIP = 1;
	TransistorElm(int xx, int yy, boolean pnpflag) {
	    super(xx, yy);
	    pnp = (pnpflag) ? -1 : 1;
	    beta = 100;
	    setup();
	}
	public TransistorElm(int xa, int ya, int xb, int yb, int f,
		      StringTokenizer st) {
	    super(xa, ya, xb, yb, f);
	    pnp = new Integer(st.nextToken()).intValue();
	    beta = 100;
	    try {
		lastvbe = new Double(st.nextToken()).doubleValue();
		lastvbc = new Double(st.nextToken()).doubleValue();
		volts[0] = 0;
		volts[1] = -lastvbe;
		volts[2] = -lastvbc;
		beta = new Double(st.nextToken()).doubleValue();
	    } catch (Exception e) {
	    }
	    setup();
	}
	void setup() {
	    vcrit = vt * Math.log(vt/(Math.sqrt(2)*leakage));
	    fgain = beta/(beta+1);
	    noDiagonal = true;
	}
	boolean nonLinear() { return true; }
	void reset() {
	    volts[0] = volts[1] = volts[2] = 0;
	    lastvbc = lastvbe = curcount = 0;
	}
	int getDumpType() { return 't'; }
	String dump() {
	    return super.dump() + " " + pnp + " " + (volts[0]-volts[1]) + " " +
		(volts[0]-volts[2]) + " " + beta;
	}
	double ic, ie, ib, curcount_c, curcount_e, curcount_b;
	Polygon rectPoly, arrowPoly;
	
	void draw(Graphics g) {
	    setBbox(point1, point2, 16);
	    setPowerColor(g, true);
	    // draw collector
	    setVoltageColor(g, volts[1]);
	    drawThickLine(g, coll[0], coll[1]);
	    // draw emitter
	    setVoltageColor(g, volts[2]);
	    drawThickLine(g, emit[0], emit[1]);
	    // draw arrow
	    g.setColor(lightGrayColor);
	    g.fillPolygon(arrowPoly);
	    // draw base
	    setVoltageColor(g, volts[0]);
	    if (powerCheckItem.getState())
		g.setColor(Color.gray);
	    drawThickLine(g, point1, base);
	    // draw dots
	    curcount_b = updateDotCount(-ib, curcount_b);
	    drawDots(g, base, point1, curcount_b);
	    curcount_c = updateDotCount(-ic, curcount_c);
	    drawDots(g, coll[1], coll[0], curcount_c);
	    curcount_e = updateDotCount(-ie, curcount_e);
	    drawDots(g, emit[1], emit[0], curcount_e);
	    // draw base rectangle
	    setVoltageColor(g, volts[0]);
	    setPowerColor(g, true);
	    g.fillPolygon(rectPoly);

	    if ((mouseElm == this || dragElm == this) && dy == 0) {
		g.setColor(Color.white);
		g.setFont(unitsFont);
		int ds = sign(dx);
		g.drawString("B", base.x-10*ds, base.y-5);
		g.drawString("C", coll[0].x-3+9*ds, coll[0].y+4); // x+6 if ds=1, -12 if -1
		g.drawString("E", emit[0].x-3+9*ds, emit[0].y+4);
	    }
	    drawPosts(g);
	}
	Point getPost(int n) {
	    return (n == 0) ? point1 : (n == 1) ? coll[0] : emit[0];
	}
	
	int getPostCount() { return 3; }
	double getPower() {
	    return (volts[0]-volts[2])*ib + (volts[1]-volts[2])*ic;
	}

	Point rect[], coll[], emit[], base;
	void setPoints() {
	    super.setPoints();
	    int hs = 16;
	    if ((flags & FLAG_FLIP) != 0)
		dsign = -dsign;
	    int hs2 = hs*dsign*pnp;
	    // calc collector, emitter posts
	    coll = newPointArray(2);
	    emit = newPointArray(2);
	    interpPoint2(point1, point2, coll[0], emit[0], 1, hs2);
	    // calc rectangle edges
	    rect = newPointArray(4);
	    interpPoint2(point1, point2, rect[0], rect[1], 1-16/dn, hs);
	    interpPoint2(point1, point2, rect[2], rect[3], 1-13/dn, hs);
	    // calc points where collector/emitter leads contact rectangle
	    interpPoint2(point1, point2, coll[1], emit[1], 1-13/dn, 6*dsign*pnp);
	    // calc point where base lead contacts rectangle
	    base = new Point();
	    interpPoint (point1, point2, base, 1-16/dn);

	    // rectangle
	    rectPoly = createPolygon(rect[0], rect[2], rect[3], rect[1]);

	    // arrow
	    if (pnp == 1)
		arrowPoly = calcArrow(emit[1], emit[0], 8, 4);
	    else {
		Point pt = interpPoint(point1, point2, 1-11/dn, -5*dsign*pnp);
		arrowPoly = calcArrow(emit[0], pt, 8, 4);
	    }
	}
	
	static final double leakage = 1e-13; // 1e-6;
	static final double vt = .025;
	static final double vdcoef = 1/vt;
	static final double rgain = .5;
	double vcrit;
	double lastvbc, lastvbe;
	double limitStep(double vnew, double vold) {
	    double arg;
	    double oo = vnew;
	    
	    if (vnew > vcrit && Math.abs(vnew - vold) > (vt + vt)) {
		if(vold > 0) {
		    arg = 1 + (vnew - vold) / vt;
		    if(arg > 0) {
			vnew = vold + vt * Math.log(arg);
		    } else {
			vnew = vcrit;
		    }
		} else {
		    vnew = vt *Math.log(vnew/vt);
		}
		converged = false;
		//System.out.println(vnew + " " + oo + " " + vold);
	    }
	    return(vnew);
	}
	void stamp() {
	    stampNonLinear(nodes[0]);
	    stampNonLinear(nodes[1]);
	    stampNonLinear(nodes[2]);
	}
	void doStep() {
	    double vbc = volts[0]-volts[1]; // typically negative
	    double vbe = volts[0]-volts[2]; // typically positive
	    if (Math.abs(vbc-lastvbc) > .01 || // .01
		Math.abs(vbe-lastvbe) > .01)
		converged = false;
	    //if (subIterations > 300 && subIterations < 330)
	    //System.out.print("T " + vbc + " " + vbe + "\n");
	    vbc = pnp*limitStep(pnp*vbc, pnp*lastvbc);
	    vbe = pnp*limitStep(pnp*vbe, pnp*lastvbe);
	    lastvbc = vbc;
	    lastvbe = vbe;
	    double pcoef = vdcoef*pnp;
	    double expbc = Math.exp(vbc*pcoef);
	    /*if (expbc > 1e13 || Double.isInfinite(expbc))
	      expbc = 1e13;*/
	    double expbe = Math.exp(vbe*pcoef);
	    if (expbe < 1)
		expbe = 1;
	    /*if (expbe > 1e13 || Double.isInfinite(expbe))
	      expbe = 1e13;*/
	    ie = pnp*leakage*(-(expbe-1)+rgain*(expbc-1));
	    ic = pnp*leakage*(fgain*(expbe-1)-(expbc-1));
	    ib = -(ie+ic);
	    //System.out.println("gain " + ic/ib);
	    //System.out.print("T " + vbc + " " + vbe + " " + ie + " " + ic + "\n");
	    double gee = -leakage*vdcoef*expbe;
	    double gec = rgain*leakage*vdcoef*expbc;
	    double gce = -gee*fgain;
	    double gcc = -gec*(1/rgain);
	    /*System.out.print("gee = " + gee + "\n");
	    System.out.print("gec = " + gec + "\n");
	    System.out.print("gce = " + gce + "\n");
	    System.out.print("gcc = " + gcc + "\n");
	    System.out.print("gce+gcc = " + (gce+gcc) + "\n");
	    System.out.print("gee+gec = " + (gee+gec) + "\n");*/
	    
	    // stamps from page 302 of Pillage.  Node 0 is the base,
	    // node 1 the collector, node 2 the emitter
	    stampMatrix(nodes[0], nodes[0], -gee-gec-gce-gcc);
	    stampMatrix(nodes[0], nodes[1], gec+gcc);
	    stampMatrix(nodes[0], nodes[2], gee+gce);
	    stampMatrix(nodes[1], nodes[0], gce+gcc);
	    stampMatrix(nodes[1], nodes[1], -gcc);
	    stampMatrix(nodes[1], nodes[2], -gce);
	    stampMatrix(nodes[2], nodes[0], gee+gec);
	    stampMatrix(nodes[2], nodes[1], -gec);
	    stampMatrix(nodes[2], nodes[2], -gee);

	    // we are solving for v(k+1), not delta v, so we use formula
	    // 10.5.13, multiplying J by v(k)
	    stampRightSide(nodes[0], -ib - (gec+gcc)*vbc - (gee+gce)*vbe);
	    stampRightSide(nodes[1], -ic + gce*vbe + gcc*vbc);
	    stampRightSide(nodes[2], -ie + gee*vbe + gec*vbc);
	}
	void getInfo(String arr[]) {
	    arr[0] = "transistor (" + ((pnp == -1) ? "PNP)" : "NPN)") + " beta=" +
		showFormat.format(beta);
	    double vbc = volts[0]-volts[1];
	    double vbe = volts[0]-volts[2];
	    double vce = volts[1]-volts[2];
	    if (vbc*pnp > .2)
		arr[1] = vbe*pnp > .2 ? "saturation" : "reverse active";
	    else
		arr[1] = vbe*pnp > .2 ? "fwd active" : "cutoff";
	    arr[2] = "Ic = " + getCurrentText(ic);
	    arr[3] = "Ib = " + getCurrentText(ib);
	    arr[4] = "Vbe = " + getVoltageText(vbe);
	    arr[5] = "Vbc = " + getVoltageText(vbc);
	    arr[6] = "Vce = " + getVoltageText(vce);
	}
	double getScopeValue(int x) {
	    switch (x) {
	    case SCOPEVAL_IB: return ib;
	    case SCOPEVAL_IC: return ic;
	    case SCOPEVAL_IE: return ie;
	    case SCOPEVAL_VBE: return volts[0]-volts[2];
	    case SCOPEVAL_VBC: return volts[0]-volts[1];
	    case SCOPEVAL_VCE: return volts[1]-volts[2];
	    }
	    return 0;
	}
	String getScopeUnits(int x) {
	    switch (x) {
	    case SCOPEVAL_IB: case SCOPEVAL_IC:
	    case SCOPEVAL_IE: return "A";
	    default: return "V";
	    }
	}
	EditInfo getEditInfo(int n) {
	    if (n == 0)
		return new EditInfo("Beta/hFE", beta, 10, 1000);
	    return null;
	}
	void setEditValue(int n, EditInfo ei) {
	    beta = ei.value;
	    setup();
	}
	boolean canViewInScope() { return true; }
    }

    class Scope {
	final int FLAG_YELM = 32;
	double minV[], maxV[], minMaxV;
	double minI[], maxI[], minMaxI;
	int scopePointCount = 128;
	int ptr, ctr, speed, position;
	int value;
	String text;
	Rectangle rect;
	boolean showI, showV, showMax, showFreq, lockScale, plot2d, plotXY;
	CircuitElm elm, xElm, yElm;
	MemoryImageSource imageSource;
	Image image;
	int pixels[];
	int draw_ox, draw_oy;
	float dpixels[];
	Scope() {
	    rect = new Rectangle();
	    reset();
	}
	void showCurrent(boolean b) { showI = b; value = 0; }
	void showVoltage(boolean b) { showV = b; value = 0; }
	void showMax    (boolean b) { showMax = b; }
	void showFreq   (boolean b) { showFreq = b; }
	void setLockScale  (boolean b) { lockScale = b; }
	void resetGraph() {
	    scopePointCount = 1;
	    while (scopePointCount < rect.width)
		scopePointCount *= 2;
	    minV = new double[scopePointCount];
	    maxV = new double[scopePointCount];
	    minI = new double[scopePointCount];
	    maxI = new double[scopePointCount];
	    ptr = ctr = 0;
	    allocImage();
	}
	boolean active() { return elm != null; }
	void reset() {
	    resetGraph();
	    minMaxV = 5;
	    minMaxI = .1;
	    speed = 64;
	    showI = showV = showMax = true;
	    showFreq = lockScale = false;
	    // no showI for Output
	    if (elm != null && (elm instanceof OutputElm ||
				elm instanceof LogicOutputElm ||
				elm instanceof ProbeElm))
		showI = false;
	    value = 0;
	    if (elm instanceof TransistorElm)
		value = SCOPEVAL_VCE;
	}
	void setRect(Rectangle r) {
	    rect = r;
	    resetGraph();
	}
	int getWidth() { return rect.width; }
	int rightEdge() { return rect.x+rect.width; }
	
	void setElm(CircuitElm ce) {
	    elm = ce;
	    reset();
	}
	
	void timeStep() {
	    if (elm == null)
		return;
	    double v = elm.getScopeValue(value);
	    if (v < minV[ptr])
		minV[ptr] = v;
	    if (v > maxV[ptr])
		maxV[ptr] = v;
	    double i = 0;
	    if (value == 0) {
		i = elm.getCurrent();
		if (i < minI[ptr])
		    minI[ptr] = i;
		if (i > maxI[ptr])
		    maxI[ptr] = i;
	    }

	    if (plot2d && dpixels != null) {
		boolean newscale = false;
		while (v > minMaxV || v < -minMaxV) {
		    minMaxV *= 2;
		    newscale = true;
		}
		double yval = i;
		if (plotXY)
		    yval = (yElm == null) ? 0 : yElm.getVoltageDiff();
		while (yval > minMaxI || yval < -minMaxI) {
		    minMaxI *= 2;
		    newscale = true;
		}
		if (newscale)
		    clear2dView();
		double xa = v/minMaxV;
		double ya = yval/minMaxI;
		int x = (int) (rect.width *(1+xa)*.499);
		int y = (int) (rect.height*(1-ya)*.499);
		drawTo(x, y);
	    } else {
		ctr++;
		if (ctr >= speed) {
		    ptr = (ptr+1) & (scopePointCount-1);
		    minV[ptr] = maxV[ptr] = v;
		    minI[ptr] = maxI[ptr] = i;
		    ctr = 0;
		}
	    }
	}

	void drawTo(int x2, int y2) {
	    if (draw_ox == -1) {
		draw_ox = x2;
		draw_oy = y2;
	    }
	    // need to draw a line from x1,y1 to x2,y2
	    if (draw_ox == x2 && draw_oy == y2) {
		dpixels[x2+rect.width*y2] = 1;
	    } else if (abs(y2-draw_oy) > abs(x2-draw_ox)) {
		// y difference is greater, so we step along y's
		// from min to max y and calculate x for each step
		double sgn = sign(y2-draw_oy);
		int x, y;
		for (y = draw_oy; y != y2+sgn; y += sgn) {
		    x = draw_ox+(x2-draw_ox)*(y-draw_oy)/(y2-draw_oy);
		    dpixels[x+rect.width*y] = 1;
		}
	    } else {
		// x difference is greater, so we step along x's
		// from min to max x and calculate y for each step
		double sgn = sign(x2-draw_ox);
		int x, y;
		for (x = draw_ox; x != x2+sgn; x += sgn) {
		    y = draw_oy+(y2-draw_oy)*(x-draw_ox)/(x2-draw_ox);
		    dpixels[x+rect.width*y] = 1;
		}
	    }
	    draw_ox = x2;
	    draw_oy = y2;
	}
	
	void clear2dView() {
	    int i;
	    for (i = 0; i != dpixels.length; i++)
		dpixels[i] = 0;
	    draw_ox = draw_oy = -1;
	}
	
	void adjustScale(double x) {
	    minMaxV *= x;
	    minMaxI *= x;
	}

	void draw2d(Graphics g) {
	    int i;
	    if (pixels == null || dpixels == null)
		return;
	    int col = (printableCheckItem.getState()) ? 0xFFFFFFFF : 0;
	    for (i = 0; i != pixels.length; i++)
		pixels[i] = col;
	    for (i = 0; i != rect.width; i++)
		pixels[i+rect.width*(rect.height/2)] = 0xFF00FF00;
	    int ycol = (plotXY) ? 0