// QuantumStates.java (C) 2002 by Paul Falstad, www.falstad.com import java.awt.*; import java.applet.Applet; import java.util.Vector; import java.util.Random; import java.awt.image.MemoryImageSource; import java.lang.Math; import java.awt.event.*; class QuantumStatesCanvas extends Canvas { QuantumStatesFrame pg; QuantumStatesCanvas(QuantumStatesFrame p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300,400); } public void update(Graphics g) { pg.updateQuantumStates(g); } public void paint(Graphics g) { pg.updateQuantumStates(g); } }; class QuantumStatesLayout implements LayoutManager { public QuantumStatesLayout() {} public void addLayoutComponent(String name, Component c) {} public void removeLayoutComponent(Component c) {} public Dimension preferredLayoutSize(Container target) { return new Dimension(500, 500); } public Dimension minimumLayoutSize(Container target) { return new Dimension(100,100); } public void layoutContainer(Container target) { int barwidth = 0; int i; for (i = 1; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (d.width > barwidth) barwidth = d.width; } } Insets insets = target.insets(); int targetw = target.size().width - insets.left - insets.right; int cw = targetw-barwidth; int targeth = target.size().height - (insets.top+insets.bottom); target.getComponent(0).move(insets.left, insets.top); target.getComponent(0).resize(cw, targeth); cw += insets.left; int h = insets.top; for (i = 1; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (m instanceof Scrollbar || m instanceof DecentScrollbar) d.width = barwidth; if (m instanceof Label) { h += d.height/5; d.width = barwidth; } m.move(cw, h); m.resize(d.width, d.height); h += d.height; } } } }; public class QuantumStates extends Applet { QuantumStatesFrame qf; void destroyFrame() { if (qf != null) qf.dispose(); qf = null; } public void init() { qf = new QuantumStatesFrame(this); qf.init(); } public void destroy() { if (qf != null) qf.dispose(); qf = null; } }; class QuantumStatesFrame extends Frame implements ComponentListener, ActionListener, MouseMotionListener, MouseListener, ItemListener, DecentScrollbarListener { Thread engine = null; Dimension winSize; Image dbimage; Random random; int stateCount; int elevelCount; int maxStateCount = 300; int sampleCount = 80; int pSampleCount; double modes[][]; public static final double epsilon = .00001; public static final double epsilon2 = .003; public static final double baseEnergy = 1.01; public String getAppletInfo() { return "QuantumStates by Paul Falstad"; } Button blankButton; Button normalizeButton; Button maximizeButton; Button rescaleButton; Checkbox stoppedCheck; CheckboxMenuItem xCheckItem; CheckboxMenuItem pCheckItem; CheckboxMenuItem parityCheckItem; CheckboxMenuItem currentCheckItem; CheckboxMenuItem expectCheckItem; CheckboxMenuItem uncertaintyCheckItem; CheckboxMenuItem probCheckItem; CheckboxMenuItem probPhaseCheckItem; CheckboxMenuItem reImCheckItem; CheckboxMenuItem magPhaseCheckItem; CheckboxMenuItem alwaysNormItem; CheckboxMenuItem alwaysMaxItem; Menu waveFunctionMenu; MenuItem measureEItem; MenuItem measureXItem; MenuItem exitItem; Choice mouseChooser; Choice setupChooser; Vector setupList; Setup setup; DecentScrollbar forceBar, speedBar, resBar; DecentScrollbar massBar, aux1Bar, aux2Bar; Label aux1Label, aux2Label; View viewPotential, viewX, viewP, viewParity, viewStates, viewCurrent; View viewList[]; int viewCount; double magcoef[]; double phasecoef[]; double phasecoefcos[]; double phasecoefsin[]; double phasecoefadj[]; double elevels[]; double dispmax[]; static final double pi = 3.14159265358979323846; double step; double func[]; double funci[]; double pdata[], pdatar[], pdatai[], currentData[], parityData[]; double pot[]; int selectedCoef; int selectedPaneHandle; double selectedPState; static final int SEL_NONE = 0; static final int SEL_POTENTIAL = 1; static final int SEL_X = 2; static final int SEL_P = 3; static final int SEL_STATES = 4; static final int SEL_HANDLE = 5; static final int MOUSE_EIGEN = 0; static final int MOUSE_EDIT = 1; static final int MOUSE_GAUSS = 2; int selection; int dragX, dragY; int xpoints[], ypoints[]; boolean dragging; boolean startup; boolean statesChanged, adjustingStates, adjustingWaveFunc; boolean setupModified; double t; int pause; static final int phaseColorCount = 480; Color phaseColors[]; int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } QuantumStatesCanvas cv; QuantumStates applet; QuantumStatesFrame(QuantumStates a) { super("1-d Quantum States Applet"); applet = a; } public void init() { startup = true; xpoints = new int[5]; ypoints = new int[5]; setupList = new Vector(); Setup s = new InfiniteWellSetup(); while (s != null) { setupList.addElement(s); s = s.createNext(); } selectedCoef = -1; setLayout(new QuantumStatesLayout()); cv = new QuantumStatesCanvas(this); cv.addComponentListener(this); cv.addMouseMotionListener(this); cv.addMouseListener(this); add(cv); MenuBar mb = new MenuBar(); Menu m = new Menu("File"); mb.add(m); m.add(exitItem = getMenuItem("Exit")); m = new Menu("View"); mb.add(m); m.add(xCheckItem = getCheckItem("Position")); xCheckItem.setState(true); m.add(pCheckItem = getCheckItem("Momentum")); pCheckItem.setState(true); m.add(parityCheckItem = getCheckItem("Parity")); m.add(currentCheckItem = getCheckItem("Probability Current")); m.addSeparator(); m.add(expectCheckItem = getCheckItem("Expectation Values")); expectCheckItem.setState(true); m.add(uncertaintyCheckItem = getCheckItem("Uncertainties")); Menu m2 = waveFunctionMenu = new Menu("Wave Function"); m.add(m2); m2.add(probCheckItem = getCheckItem("Probability")); m2.add(probPhaseCheckItem = getCheckItem("Probability + Phase")); probPhaseCheckItem.setState(true); m2.add(reImCheckItem = getCheckItem("Real + Imaginary Parts")); m2.add(magPhaseCheckItem = getCheckItem("Magnitude + Phase")); m = new Menu("Measure"); mb.add(m); m.add(measureEItem = getMenuItem("Measure Energy")); m.add(measureXItem = getMenuItem("Measure Position")); setMenuBar(mb); m = new Menu("Options"); mb.add(m); m.add(alwaysNormItem = getCheckItem("Always Normalize")); m.add(alwaysMaxItem = getCheckItem("Always Maximize")); setMenuBar(mb); setupChooser = new Choice(); int i; for (i = 0; i != setupList.size(); i++) setupChooser.add("Setup: " + ((Setup) setupList.elementAt(i)).getName()); setup = (Setup) setupList.elementAt(0); setupChooser.addItemListener(this); add(setupChooser); mouseChooser = new Choice(); mouseChooser.add("Mouse = Set Eigenstate"); mouseChooser.add("Mouse = Edit Function"); mouseChooser.add("Mouse = Create Gaussian"); mouseChooser.addItemListener(this); add(mouseChooser); add(blankButton = new Button("Clear")); blankButton.addActionListener(this); add(normalizeButton = new Button("Normalize")); normalizeButton.addActionListener(this); add(maximizeButton = new Button("Maximize")); maximizeButton.addActionListener(this); add(rescaleButton = new Button("Rescale Graphs")); rescaleButton.addActionListener(this); stoppedCheck = new Checkbox("Stopped"); stoppedCheck.addItemListener(this); add(stoppedCheck); add(new Label("Simulation Speed", Label.CENTER)); add(speedBar = new DecentScrollbar(this, 122, 1, 300)); add(new Label("Resolution", Label.CENTER)); add(resBar = new DecentScrollbar(this, 128, sampleCount, maxStateCount)); add(new Label("Particle Mass", Label.CENTER)); add(massBar = new DecentScrollbar(this, 16, 1, 100)); add(aux1Label = new Label("Aux 1", Label.CENTER)); add(aux1Bar = new DecentScrollbar(this, 50, 1, 100)); add(aux2Label = new Label("Aux 2", Label.CENTER)); add(aux2Bar = new DecentScrollbar(this, 50, 1, 100)); try { String param = applet.getParameter("PAUSE"); if (param != null) pause = Integer.parseInt(param); } catch (Exception e) { } magcoef = new double[maxStateCount]; phasecoef = new double[maxStateCount]; phasecoefcos = new double[maxStateCount]; phasecoefsin = new double[maxStateCount]; phasecoefadj = new double[maxStateCount]; dispmax = new double[maxStateCount]; setResolution(); phaseColors = new Color[phaseColorCount+1]; for (i = 0; i != phaseColorCount; i++) { int pm = phaseColorCount/6; int a1 = i % pm; int a2 = a1*255/pm; int a3 = 255-a2; Color c = null; switch (i/pm) { case 0: c = new Color(255, a2, 0); break; case 1: c = new Color(a3, 255, 0); break; case 2: c = new Color(0, 255, a2); break; case 3: c = new Color(0, a3, 255); break; case 4: c = new Color(a2, 0, 255); break; case 5: c = new Color(255, 0, a3); break; } phaseColors[i] = c; } phaseColors[phaseColorCount] = phaseColors[0]; random = new Random(); reinit(); cv.setBackground(Color.black); cv.setForeground(Color.lightGray); resize(500, 500); handleResize(); show(); } MenuItem getMenuItem(String s) { MenuItem mi = new MenuItem(s); mi.addActionListener(this); return mi; } CheckboxMenuItem getCheckItem(String s) { CheckboxMenuItem mi = new CheckboxMenuItem(s); mi.addItemListener(this); return mi; } void reinit() { doSetup(); } void handleResize() { Dimension d = winSize = cv.getSize(); if (winSize.width == 0) return; int potsize = (viewPotential == null) ? 0 : viewPotential.height; int statesize = (viewStates == null) ? 50 : viewStates.height; viewX = viewP = viewParity = viewCurrent = null; viewList = new View[10]; viewList[0] = viewPotential = new View(); int i = 1; if (xCheckItem.getState()) viewList[i++] = viewX = new View(); if (pCheckItem.getState()) viewList[i++] = viewP = new View(); if (parityCheckItem.getState()) viewList[i++] = viewParity = new View(); if (currentCheckItem.getState()) viewList[i++] = viewCurrent = new View(); viewList[i++] = viewStates = new View(); viewCount = i; int sizenum = viewCount; int toth = winSize.height; // preserve size of potential and state panes if possible if (potsize > 0) { sizenum--; toth -= potsize; } if (statesize > 0) { sizenum--; toth -= statesize; } int cury = 0; for (i = 0; i != viewCount; i++) { View v = viewList[i]; int h = toth/sizenum; if (v == viewPotential && potsize > 0) h = potsize; else if (v == viewStates && statesize > 0) h = statesize; v.x = 0; v.width = winSize.width; v.y = cury; v.height = h; cury += h; } setGraphLines(); dbimage = createImage(d.width, d.height); } void setGraphLines() { int i; for (i = 0; i != viewCount; i++) { View v = viewList[i]; v.mid_y = v.y + v.height/2; v.ymult = .90*v.height/2; v.lower_y = (int) (v.mid_y+v.ymult); v.ymult2 = v.ymult*2; } } void doBlank() { int x; for (x = 0; x != sampleCount; x++) func[x] = funci[x] = 0; for (x = 0; x != stateCount; x++) magcoef[x] = 0; } void normalize() { double norm = 0; int i; for (i = 0; i != stateCount; i++) norm += magcoef[i]*magcoef[i]; if (norm == 0) return; double normmult = 1/java.lang.Math.sqrt(norm); for (i = 0; i != stateCount; i++) magcoef[i] *= normmult; cv.repaint(pause); } void maximize() { int i; double maxm = 0; for (i = 0; i != stateCount; i++) if (magcoef[i] > maxm) maxm = magcoef[i]; if (maxm == 0) return; for (i = 0; i != stateCount; i++) magcoef[i] *= 1/maxm; cv.repaint(pause); } void rescaleGraphs() { int i; for (i = 0; i != viewCount; i++) viewList[i].scale = 0; } void transform() { int x, y; t = 0; for (y = 0; y != stateCount; y++) { double a = 0; double b = 0; for (x = 1; x != sampleCount; x++) { a += modes[y][x]*func[x]; b += modes[y][x]*funci[x]; } if (a < epsilon && a > -epsilon) a = 0; if (b < epsilon && b > -epsilon) b = 0; double r = java.lang.Math.sqrt(a*a+b*b); magcoef[y] = r; double ph2 = java.lang.Math.atan2(b, a); phasecoefadj[y] = ph2; phasecoef[y] = ph2; } if (alwaysNormItem.getState()) normalize(); if (alwaysMaxItem.getState()) maximize(); } 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(); } public void updateQuantumStates(Graphics realg) { Graphics g = dbimage.getGraphics(); if (winSize == null || winSize.width == 0) return; boolean allQuiet = true; if (!stoppedCheck.getState() && !dragging && !adjustingStates) { int val = speedBar.getValue(); double tadd = java.lang.Math.exp(val/20.)*(.1/5); // add random crap into the time to avoid aliasing tadd *= 1 + getrand(300)*(.00191171); t += tadd; allQuiet = false; } Color gray1 = new Color(76, 76, 76); Color gray2 = new Color(127, 127, 127); g.setColor(cv.getBackground()); g.fillRect(0, 0, winSize.width, winSize.height); g.setColor(cv.getForeground()); int i; int ox = -1, oy = -1; for (i = 1; i != viewCount; i++) { g.setColor(i == selectedPaneHandle ? Color.yellow : Color.gray); g.drawLine(0, viewList[i].y, winSize.width, viewList[i].y); } if (statesChanged) { cv.setCursor(Cursor.getPredefinedCursor(WAIT_CURSOR)); if (adjustingStates) genModes(false); else { realg.setColor(cv.getBackground()); FontMetrics fm = realg.getFontMetrics(); String cs = "Calculating..."; realg.fillRect(0, winSize.height-30, 20+fm.stringWidth(cs), 30); realg.setColor(Color.white); realg.drawString(cs, 10, winSize.height-10); genModes(true); transform(); } cv.setCursor(null); statesChanged = false; if (startup) { magcoef[0] = magcoef[1] = 1; startup = false; } } int mid_y = viewPotential.mid_y; double ymult = viewPotential.ymult; viewPotential.scale = 1; g.setColor(gray2); g.drawLine(winSize.width/2, mid_y-(int) ymult, winSize.width/2, mid_y+(int) ymult); ox = -1; int j; double norm = 0; if (!adjustingStates) { for (j = 0; j != stateCount; j++) { if (magcoef[j] < epsilon && magcoef[j] > -epsilon) { magcoef[j] = phasecoef[j] = phasecoefadj[j] = 0; continue; } phasecoef[j] = (-(elevels[j]+baseEnergy)*t+phasecoefadj[j]) % (2*pi); if (phasecoef[j] > pi) phasecoef[j] -= 2*pi; else if (phasecoef[j] < -pi) phasecoef[j] += 2*pi; phasecoefcos[j] = java.lang.Math.cos(phasecoef[j]); phasecoefsin[j] = java.lang.Math.sin(phasecoef[j]); norm += magcoef[j]*magcoef[j]; } } double normmult2 = 1/norm; double normmult = java.lang.Math.sqrt(normmult2); if (norm == 0) normmult = normmult2 = 0; g.setColor(Color.gray); for (i = 0; i != elevelCount; i++) { if (i == stateCount) g.setColor(Color.darkGray); double dy = elevels[i]; int y = mid_y - (int) (ymult * dy); g.drawLine(0, y, winSize.width, y); } g.setColor(Color.white); for (i = 0; i != sampleCount; i++) { int x = winSize.width * i / sampleCount; double dy = pot[i]; int y = mid_y - (int) (ymult * dy); if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } // calculate expectation value of E if (!adjustingStates && norm != 0 && (expectCheckItem.getState() || uncertaintyCheckItem.getState())) { double expecte = 0; double expecte2 = 0; for (i = 0; i != stateCount; i++) { double prob = magcoef[i]*magcoef[i]*normmult2; expecte += prob*elevels[i]; expecte2 += prob*elevels[i]*elevels[i]; } double uncert = java.lang.Math.sqrt(expecte2-expecte*expecte); if (uncertaintyCheckItem.getState()) { g.setColor(Color.blue); int y = mid_y - (int) (ymult * (expecte+uncert)); g.drawLine(0, y, winSize.width, y); y = mid_y - (int) (ymult * (expecte-uncert)); if (expecte-uncert >= -1) g.drawLine(0, y, winSize.width, y); } if (expectCheckItem.getState()) { int y = mid_y - (int) (ymult * expecte); g.setColor(Color.red); g.drawLine(0, y, winSize.width, y); } } if (selectedCoef != -1 && !dragging) { g.setColor(Color.yellow); int y = mid_y - (int) (ymult * elevels[selectedCoef]); g.drawLine(0, y, winSize.width, y); } ox = -1; g.setColor(Color.white); double maxf = 0; double expectx = 0; if (!adjustingStates && !adjustingWaveFunc) { // calculate wave function for (i = 0; i != sampleCount; i++) { int x = winSize.width * i / sampleCount; double dr = 0, di = 0; for (j = 0; j != stateCount; j++) { dr += magcoef[j] * modes[j][i] * phasecoefcos[j]; di += magcoef[j] * modes[j][i] * phasecoefsin[j]; } dr *= normmult; di *= normmult; func[i] = dr; funci[i] = di; double dy = dr*dr+di*di; expectx += dy*x; if (dy > maxf) maxf = dy; } } else { for (i = 0; i != sampleCount; i++) { int x = winSize.width * i / sampleCount; double dr = func[i], di = funci[i]; double dy = dr*dr+di*di; expectx += dy*x; if (dy > maxf) maxf = dy; } } if (viewX != null) { // draw X representation mid_y = viewX.mid_y; ymult = viewX.ymult; drawFunction(g, viewX, func, funci, sampleCount, 0); if (selectedCoef != -1 && !dragging) { g.setColor(Color.yellow); ox = -1; for (i = 0; i != sampleCount; i++) { int x = winSize.width * i / sampleCount; double dy = modes[selectedCoef][i]/dispmax[selectedCoef]; int y = mid_y - (int) (ymult * dy); if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } if (selectedPState != 0) { g.setColor(Color.yellow); ox = -1; int s2 = sampleCount*2; for (i = 0; i != s2; i++) { int x = winSize.width * i / s2; double dy = java.lang.Math.cos(selectedPState* (i-sampleCount)*.5); int y = mid_y - (int) (ymult * dy); if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } } if (viewP != null) { // draw P representation for (i = 0; i != pSampleCount*2; i++) pdata[i] = 0; for (i = 0; i != sampleCount; i++) { int ii = (i <= sampleCount/2) ? (sampleCount/2-i)*2 : (pSampleCount-(i-sampleCount/2))*2; pdata[ii] = func[sampleCount-1-i]; pdata[ii+1] = funci[sampleCount-1-i]; } four1(pdata, pSampleCount, 1); double pnorm = 1/java.lang.Math.sqrt(pSampleCount); for (i = 0; i != pSampleCount; i++) { int ii = (i <= pSampleCount/2) ? (pSampleCount/2-i)*2 : (pSampleCount-(i-pSampleCount/2))*2; pdatar[i] = pdata[ii]*pnorm; pdatai[i] = pdata[ii+1]*pnorm; } int offset = pSampleCount/4; drawFunction(g, viewP, pdatar, pdatai, pSampleCount/2, offset); } if (viewParity != null) { // draw parity graph double pplus = 0, pminus = 0; for (i = 0; i != sampleCount; i++) { double a1 = func[i]; double a2 = funci[i]; double b1 = func[sampleCount-1-i]; double b2 = funci[sampleCount-1-i]; double c1 = (a1+b1)*(a1+b1)+(a2+b2)*(a2+b2); double c2 = (a1-b1)*(a1-b1)+(a2-b2)*(a2-b2); pplus += c1; pminus += c2; } parityData[90] = java.lang.Math.sqrt(pplus)/2; parityData[10] = java.lang.Math.sqrt(pminus)/2; drawFunction(g, viewParity, parityData, null, 100, 0); } if (viewCurrent != null) { // draw probability current for (i = 0; i != sampleCount-1; i++) { double a1 = func[i+1]-func[i]; double a2 = funci[i+1]-funci[i]; currentData[i] = func[i]*a2 - funci[i]*a1; } drawFunction(g, viewCurrent, currentData, null, sampleCount, 0); } if (!adjustingStates) { // draw state phasors stateColSize = 20; int buffer = 5; for (i = 19; i >= 8; i--) { int ss = winSize.width/i; int h = ss*((stateCount+i-1)/i); if (h <= viewStates.height-buffer) stateColSize = i; } stateSize = winSize.width/stateColSize; int ss2 = stateSize/2; for (i = 0; i != stateCount; i++) { int x = stateSize*(i % stateColSize)+ss2; int y = stateSize*(i / stateColSize)+ss2 + viewStates.y + buffer; g.setColor(i == selectedCoef ? Color.yellow : (magcoef[i] == 0) ? gray2 : Color.white); g.drawOval(x-ss2, y-ss2, stateSize, stateSize); int xa = (int) (magcoef[i]*phasecoefcos[i]*ss2); int ya = (int) (-magcoef[i]*phasecoefsin[i]*ss2); g.drawLine(x, y, x+xa, y+ya); g.fillOval(x+xa-1, y+ya-1, 3, 3); } } realg.drawImage(dbimage, 0, 0, this); if (!stoppedCheck.getState() && !allQuiet) cv.repaint(pause); } int stateColSize, stateSize; void drawFunction(Graphics g, View view, double fr[], double fi[], int count, int offset) { int i; double expectx = 0; double expectx2 = 0; double maxsq = 0; double tot = 0; int zero = winSize.width/2; for (i = 0; i != count; i++) { int x = winSize.width * i / (count-1); int ii = i+offset; double dr = fr[ii]; double di = (fi == null) ? 0 : fi[ii]; double dy = dr*dr+di*di; if (dy > maxsq) maxsq = dy; int dev = x-zero; expectx += dy*dev; expectx2 += dy*dev*dev; tot += dy; } expectx /= tot; expectx2 /= tot; double maxnm = java.lang.Math.sqrt(maxsq); double uncert = java.lang.Math.sqrt(expectx2-expectx*expectx); int ox = -1, oy = 0; double bestscale = 0; if (fi != null && (probCheckItem.getState() || probPhaseCheckItem.getState())) bestscale = 1/maxsq; else bestscale = 1/maxnm; if (!adjustingWaveFunc) { // adjust scale view.scale *= 1.001; if (view.scale > bestscale || view.scale == 0) view.scale = bestscale; if (view.scale > 1e8) view.scale = 1e8; } g.setColor(Color.gray); /* double scaler = 1e-10; while (scaler*view.scale*view.ymult*600 < view.height) scaler *= 10; System.out.print(scaler + "\n"); for (i = 0; i != 20; i++) { int y = (int) (view.ymult * i * view.scale * scaler * 10); System.out.print(i + " " + y + "\n"); if (y > view.height/2) break; g.drawLine(winSize.width-5, view.mid_y - y, winSize.width, view.mid_y - y); } */ if ((probCheckItem.getState() || probPhaseCheckItem.getState() || magPhaseCheckItem.getState()) && fi != null) { // draw probability or magnitude g.setColor(Color.white); double mult = view.ymult2*view.scale; for (i = 0; i != count; i++) { int x = winSize.width * i / (count-1); double dy = 0; int ii = i+offset; if (!magPhaseCheckItem.getState()) dy = (fr[ii]*fr[ii]+fi[ii]*fi[ii]); else dy = java.lang.Math.sqrt(fr[ii]*fr[ii]+fi[ii]*fi[ii]); if (!probCheckItem.getState()) { double ang = java.lang.Math.atan2(fi[ii], fr[ii]); g.setColor(phaseColors[(int)((ang+pi)*phaseColorCount/(2*pi+.2))]); } int y = view.lower_y - (int) (mult * dy); if (y < view.y) y = view.y; if (ox != -1) { xpoints[0] = ox; ypoints[0] = view.lower_y+1; xpoints[1] = ox; ypoints[1] = oy; xpoints[2] = x; ypoints[2] = y; xpoints[3] = x; ypoints[3] = view.lower_y+1; g.fillPolygon(xpoints, ypoints, 4); } ox = x; oy = y; } } else { int mid_y = view.mid_y; double mult = view.ymult*view.scale; if (fi != null) { g.setColor(Color.blue); for (i = 0; i != count; i++) { int x = winSize.width * i / (count-1); int ii = i+offset; int y = mid_y - (int) (mult * fi[ii]); if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } g.setColor(Color.white); ox = -1; for (i = 0; i != count; i++) { int x = winSize.width * i / (count-1); int ii = i+offset; int y = mid_y - (int) (mult * fr[ii]); if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } if (maxsq > 0 && fi != null) { expectx += zero; if (uncertaintyCheckItem.getState()) { g.setColor(Color.blue); g.drawLine((int) (expectx-uncert), view.y, (int) (expectx-uncert), view.y+view.height); g.drawLine((int) (expectx+uncert), view.y, (int) (expectx+uncert), view.y+view.height); } if (expectCheckItem.getState()) { g.setColor(Color.red); g.drawLine((int) expectx, view.y, (int) expectx, view.y+view.height); } } } void edit(MouseEvent e) { if (selection == SEL_NONE) return; int x = e.getX(); int y = e.getY(); switch (selection) { case SEL_HANDLE: editHandle(y); break; case SEL_STATES: editMag(x, y); break; default: editFunc(x, y); break; } } void editHandle(int y) { int dy = y-viewList[selectedPaneHandle].y; View upper = viewList[selectedPaneHandle-1]; View lower = viewList[selectedPaneHandle]; int minheight = 10; if (upper.height+dy < minheight || lower.height-dy < minheight) return; upper.height += dy; lower.height -= dy; lower.y += dy; setGraphLines(); cv.repaint(pause); } void editMag(int x, int y) { if (selectedCoef == -1) return; int ss2 = stateSize/2; int x0 = stateSize*(selectedCoef % stateColSize)+ss2; int y0 = stateSize*(selectedCoef / stateColSize)+ss2 + viewStates.y; x -= x0; y -= y0; double mag = java.lang.Math.sqrt(x*x+y*y)/ss2; double ang = java.lang.Math.atan2(-y, x); double ang0 = (-(elevels[selectedCoef]+baseEnergy)*t) % (2*pi); if (mag > 10) mag = 0; if (mag > 1) mag = 1; magcoef[selectedCoef] = mag; phasecoefadj[selectedCoef] = (ang-ang0) % (2*pi); if (phasecoefadj[selectedCoef] > pi) phasecoefadj[selectedCoef] -= 2*pi; if (alwaysNormItem.getState()) normalize(); cv.repaint(pause); } void editFunc(int x, int y) { if (mouseChooser.getSelectedIndex() == MOUSE_EIGEN) { if (selection == SEL_X) { editXState(x, y); return; } if (selection == SEL_P) { editPState(x, y); return; } if (selection == SEL_POTENTIAL) { findStateByEnergy(y); enterSelectedState(); } return; } if (mouseChooser.getSelectedIndex() == MOUSE_GAUSS) { if (selection == SEL_X) editXGauss(x, y); if (selection == SEL_P) editPGauss(x, y); return; } if (selection == SEL_P) return; if (dragX == x) { editFuncPoint(x, y); dragY = y; } else { // need to draw a line from old x,y to new x,y and // call editFuncPoint for each point on that line. yuck. int x1 = (x < dragX) ? x : dragX; int y1 = (x < dragX) ? y : dragY; int x2 = (x > dragX) ? x : dragX; int y2 = (x > dragX) ? y : dragY; dragX = x; dragY = y; for (x = x1; x <= x2; x++) { y = y1+(y2-y1)*(x-x1)/(x2-x1); editFuncPoint(x, y); } } if (adjustingWaveFunc) { transform(); if (alwaysNormItem.getState()) normalize(); else maximize(); } } void editXGauss(int x, int y) { int i; int xi = x * sampleCount / winSize.width; double mult = java.lang.Math.exp(-(y-viewX.mid_y)*.03-4); for (i = 0; i != sampleCount; i++) { int ii = i-xi; func[i] = java.lang.Math.exp(-ii*ii*mult); funci[i] = 0; } transform(); if (alwaysNormItem.getState()) normalize(); else maximize(); rescaleGraphs(); } void editPGauss(int x, int y) { int i; int xi = x * sampleCount / winSize.width; double mult = java.lang.Math.exp(-(y-viewP.mid_y)*.03-4); double p = getPState(x); int s2 = sampleCount/2; // rather than draw a gaussian and then transform it back to X, // we just draw a function on the X graph that looks like a gaussian // on the P graph. for (i = 0; i != sampleCount; i++) { int ii = i-s2; double n = java.lang.Math.exp(-ii*ii*mult); func[i] = java.lang.Math.cos(p*ii)*n; funci[i] = java.lang.Math.sin(p*ii)*n; } selectedPState = 0; transform(); if (alwaysNormItem.getState()) normalize(); else maximize(); rescaleGraphs(); } void editFuncPoint(int x, int y) { View v = (selection == SEL_X) ? viewX : viewPotential; int lox = x * sampleCount / winSize.width; int hix = ((x+1) * sampleCount-1) / winSize.width; double val = (v.mid_y - y) / v.ymult; double val2 = (v.lower_y - y) / v.ymult2; if (val > 1) val = 1; if (val < -1) val = -1; if (val2 > 1) val2 = 1; if (val2 < 0) val2 = 0; val /= v.scale; val2 /= v.scale; if (lox < 1) lox = 1; if (hix >= sampleCount-1) hix = sampleCount-2; for (; lox <= hix; lox++) { if (selection == SEL_POTENTIAL) { pot[lox] = val; setupModified = true; adjustingStates = statesChanged = true; } else { if (probCheckItem.getState() || probPhaseCheckItem.getState()) { double valnew = java.lang.Math.sqrt(val2); double n = java.lang.Math.sqrt(func[lox]*func[lox] + funci[lox]*funci[lox]); if (n == 0) { func[lox] = 1; n = 1; } // edit magnitude, preserving phase func[lox] = valnew*func[lox]/n; funci[lox] = valnew*funci[lox]/n; } else if (magPhaseCheckItem.getState()) { double n = java.lang.Math.sqrt(func[lox]*func[lox] + funci[lox]*funci[lox]); if (n == 0) { func[lox] = 1; n = 1; } func[lox] = val2*func[lox]/n; funci[lox] = val2*funci[lox]/n; } else { func[lox] = val; } adjustingWaveFunc = true; } } cv.repaint(pause); } void editXState(int x, int y) { int ax = x * sampleCount / winSize.width; if (ax < 1 || ax >= sampleCount) return; int i; for (i = 0; i != sampleCount; i++) func[i] = funci[i] = 0; func[ax] = 1; transform(); rescaleGraphs(); if (!alwaysNormItem.getState()) maximize(); cv.repaint(pause); } double getPState(int x) { double p = (x * pSampleCount/2 / winSize.width) - pSampleCount/4; return p*pi/(pSampleCount/2); } void editPState(int x, int y) { double p = getPState(x); int i; int s2 = sampleCount/2; for (i = 0; i != sampleCount; i++) { func[i] = java.lang.Math.cos(p*(i-s2)); funci[i] = java.lang.Math.sin(p*(i-s2)); } transform(); rescaleGraphs(); if (!alwaysNormItem.getState()) maximize(); selectedPState = 0; cv.repaint(pause); } public void componentHidden(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e) { cv.repaint(pause); } public void componentResized(ComponentEvent e) { handleResize(); cv.repaint(pause); } public void actionPerformed(ActionEvent e) { if (e.getSource() == exitItem) { applet.destroyFrame(); return; } if (e.getSource() == measureEItem) measureE(); if (e.getSource() == measureXItem) measureX(); if (e.getSource() == blankButton) { doBlank(); cv.repaint(); } if (e.getSource() == normalizeButton) normalize(); if (e.getSource() == maximizeButton) maximize(); if (e.getSource() == rescaleButton) rescaleGraphs(); } public void scrollbarValueChanged(DecentScrollbar ds) { System.out.print(ds.getValue() + "\n"); if (ds == massBar) { statesChanged = adjustingStates = true; cv.repaint(pause); } if (ds == aux1Bar || ds == aux2Bar) { adjustingStates = true; setup.drawPotential(); statesChanged = true; cv.repaint(pause); } if (ds == resBar) adjustingStates = true; } public void scrollbarFinished(DecentScrollbar ds) { if (ds == resBar) { adjustingStates = false; setResolution(); reinit(); cv.repaint(pause); } if (ds == massBar || ds == aux1Bar || ds == aux2Bar) { adjustingStates = false; statesChanged = true; cv.repaint(pause); } } public boolean handleEvent(Event ev) { if (ev.id == Event.WINDOW_DESTROY) { applet.destroyFrame(); return true; } return super.handleEvent(ev); } void setResolution() { sampleCount = resBar.getValue(); sampleCount++; func = new double[sampleCount]; funci = new double[sampleCount]; pot = new double[sampleCount]; statesChanged = true; int ps = 8*sampleCount; pSampleCount = 1; while (pSampleCount < ps) pSampleCount *= 2; pdata = new double[pSampleCount*2]; pdatar = new double[pSampleCount]; pdatai = new double[pSampleCount]; parityData = new double[100]; currentData = new double[sampleCount]; } void genModes(boolean getStates) { statesChanged = false; int n = sampleCount; double d[] = new double[n+1]; double e[] = new double[n+1]; double z[][] = new double[n+1][n+1]; int i, j; double m1 = 1/(massBar.getValue()*.02); double maxpot = -20; // express schroedinger's equation as a matrix equation // using finite differencing for (i = 1; i <= n; i++) { if (i < n) e[i] = -m1; d[i] = 2*m1+pot[i-1]; if (pot[i-1] > maxpot) maxpot = pot[i-1]; for (j = 1; j <= n; j++) z[i][j] = 0; z[i][i] = 1; } tqli(d, e, n, getStates ? z : null); //System.out.print("done with tqli\n"); elevels = new double[n]; elevelCount = n; // now get the eigenvalues and sort them for (i = 0; i != n; i++) { //System.out.print("d " + i + " " + d[i+1] + "\n"); if (i < n-1 && d[i] == d[i+1]) System.out.print("degeneracy! " + i + "\n"); elevels[i] = d[i+1]; } int si, sj; // sort the elevels for (si = 1; si < n; si++) { double v = elevels[si]; sj = si; while (elevels[sj-1] > v) { elevels[sj] = elevels[sj-1]; sj--; if (sj <= 0) break; } elevels[sj] = v; } while (maxpot > 0 && elevels[elevelCount-1] > maxpot) elevelCount--; stateCount = elevelCount; int maxs = sampleCount*3/8; if (stateCount > maxs && getStates) stateCount = maxs; if (!getStates) return; // get the eigenstates modes = new double[stateCount][sampleCount]; for (i = 0; i != stateCount; i++) { for (j = 0; j != n; j++) if (elevels[i] == d[j+1]) break; if (j == n) { System.out.print("can't find elevels! " + i + " " + elevels[i] + "\n"); continue; } d[j+1] = -1; int k; dispmax[i] = 0; for (k = 0; k != n; k++) { modes[i][k] = z[k+1][j+1]; if (modes[i][k] > dispmax[i]) dispmax[i] = modes[i][k]; else if (-modes[i][k] > dispmax[i]) dispmax[i] = -modes[i][k]; } } // The energy levels calculated above for a harmonic oscillator are // not evenly spaced because of numerical error, so we force // them to be evenly spaced in order to get coherent states to work if (!setupModified) setup.fudgeLevels(); } public void mouseDragged(MouseEvent e) { dragging = true; edit(e); } public void mouseMoved(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) return; int x = e.getX(); int y = e.getY(); dragX = x; dragY = y; int oldCoef = selectedCoef; int oldSelection = selection; selectedCoef = -1; selectedPState = 0; selectedPaneHandle = -1; selection = 0; int i; for (i = 1; i != viewCount; i++) { int dy = y-viewList[i].y; if (dy >= -3 && dy <= 3) { selectedPaneHandle = i; selection = SEL_HANDLE; } } Cursor cs = null; if (selection == SEL_HANDLE) cs = Cursor.getPredefinedCursor(N_RESIZE_CURSOR); else if (viewX != null && viewX.contains(x, y)) selection = SEL_X; else if (viewP != null && viewP.contains(x, y)) { selection = SEL_P; selectedPState = getPState(x); cv.repaint(pause); } else if (viewPotential.contains(x, y)) { selection = SEL_POTENTIAL; if (mouseChooser.getSelectedIndex() == MOUSE_EIGEN) findStateByEnergy(y); } else if (viewStates.contains(x, y)) { int xi = x/stateSize; int yi = (y-viewStates.y)/stateSize; selectedCoef = xi+yi*stateColSize; if (selectedCoef >= stateCount) selectedCoef = -1; if (selectedCoef != -1) selection = SEL_STATES; } cv.setCursor(cs); if (selection != oldSelection || selectedCoef != oldCoef) cv.repaint(pause); } void findStateByEnergy(int y) { int i; double dy = (viewPotential.mid_y-y)/viewPotential.ymult; double dist = 100; for (i = 0; i != stateCount; i++) { double d = java.lang.Math.abs(elevels[i]-dy); if (d < dist) { dist = d; selectedCoef = i; } } } public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2 && selectedCoef != -1) enterSelectedState(); } void enterSelectedState() { int i; for (i = 0; i != stateCount; i++) if (selectedCoef != i) magcoef[i] = 0; magcoef[selectedCoef] = 1; cv.repaint(pause); rescaleGraphs(); } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { if (!dragging) { if (selectedCoef != -1) { selectedCoef = -1; cv.repaint(pause); } if (selectedPState != 0) { selectedPState = 0; cv.repaint(pause); } } } public void mousePressed(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; dragging = true; edit(e); } public void mouseReleased(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; if (mouseChooser.getSelectedIndex() == MOUSE_EDIT && selection == SEL_POTENTIAL) { adjustingStates = false; statesChanged = true; } if (selection == SEL_STATES && alwaysMaxItem.getState()) maximize(); dragging = adjustingWaveFunc = false; cv.repaint(pause); } public void itemStateChanged(ItemEvent e) { if (e.getItemSelectable() == stoppedCheck) { cv.repaint(pause); return; } if (e.getItemSelectable() == setupChooser) doSetup(); if (e.getItemSelectable() == xCheckItem || e.getItemSelectable() == pCheckItem || e.getItemSelectable() == parityCheckItem || e.getItemSelectable() == currentCheckItem) { handleResize(); cv.repaint(pause); } if (e.getItemSelectable() == alwaysNormItem && alwaysNormItem.getState()) { normalize(); alwaysMaxItem.setState(false); cv.repaint(pause); } if (e.getItemSelectable() == alwaysMaxItem && alwaysMaxItem.getState()) { maximize(); alwaysNormItem.setState(false); cv.repaint(pause); } int i; for (i = 0; i != waveFunctionMenu.countItems(); i++) if (e.getItemSelectable() == waveFunctionMenu.getItem(i)) { int j; ((CheckboxMenuItem) waveFunctionMenu.getItem(i)) .setState(true); for (j = 0; j != waveFunctionMenu.countItems(); j++) if (i != j) ((CheckboxMenuItem) waveFunctionMenu.getItem(j)) .setState(false); rescaleGraphs(); } } void doSetup() { doBlank(); int i; for (i = 0; i != sampleCount; i++) func[i] = funci[i] = pot[i] = 0; setup = (Setup) setupList.elementAt(setupChooser.getSelectedIndex()); aux1Bar.setValue(100); aux2Bar.setValue(100); setup.select(); setup.drawPotential(); setupModified = false; statesChanged = true; if (setup.getAuxBarCount() == 2) { aux2Label.show(); aux2Bar.show(); } else { aux2Label.hide(); aux2Bar.hide(); } validate(); } void measureE() { normalize(); double n = random.nextDouble(); int i; for (i = 0; i != stateCount; i++) { double m = magcoef[i]*magcoef[i]; n -= m; if (n < 0) break; } if (i == stateCount) return; int pick = i; for (i = 0; i != stateCount; i++) magcoef[i] = 0; magcoef[pick] = 1; rescaleGraphs(); } void measureX() { int i; double n = random.nextDouble(); for (i = 0; i != sampleCount; i++) { double m = func[i]*func[i] + funci[i]*funci[i]; n -= m; if (n < 0) break; } if (i == sampleCount) return; int pick = i; for (i = 0; i != sampleCount; i++) func[i] = funci[i] = 0; func[pick] = 1; transform(); rescaleGraphs(); normalize(); } // this is from Numerical Recipes in C. It finds the eigenvalues // and eigenvectors of an nxn tridiagonal symmetric matrix specified // by d[] and e[]. void tqli(double d[], double e[], int n, double z[][]) { int m,l,iter,i,k; double s,r,p,g,f,dd,c,b; //for (i=2;i<=n;i++) e[i-1]=e[i]; e[n]=0.0; for (l=1;l<=n;l++) { iter=0; do { for (m=l;m<=n-1;m++) { dd=java.lang.Math.abs(d[m])+java.lang.Math.abs(d[m+1]); if ((double)(java.lang.Math.abs(e[m])+dd) == dd) break; } if (m != l) { if (iter++ == 30) System.out.print("Too many iterations in tqli\n"); g=(d[l+1]-d[l])/(2.0*e[l]); r=pythag(g,1.0); g=d[m]-d[l]+e[l]/(g+SIGN(r,g)); s=c=1.0; p=0.0; for (i=m-1;i>=l;i--) { f=s*e[i]; b=c*e[i]; e[i+1]=(r=pythag(f,g)); if (r == 0.0) { d[i+1] -= p; e[m]=0.0; break; } s=f/r; c=g/r; g=d[i+1]-p; r=(d[i]-g)*s+2.0*c*b; d[i+1]=g+(p=s*r); g=c*r-b; if (z != null) { for (k=1;k<=n;k++) { f=z[k][i+1]; z[k][i+1]=s*z[k][i]+c*f; z[k][i]=c*z[k][i]-s*f; } } } if (r == 0.0 && i >= l) continue; d[l] -= p; e[l]=g; e[m]=0.0; } } while (m != l); } } double SQR(double a) { return a*a; } double pythag(double a, double b) { double absa,absb; absa=java.lang.Math.abs(a); absb=java.lang.Math.abs(b); if (absa > absb) return absa*java.lang.Math.sqrt(1.0+SQR(absb/absa)); else return (absb == 0.0 ? 0.0 : absb*java.lang.Math.sqrt(1.0+SQR(absa/absb))); } double SIGN(double a, double b) { return b >= 0 ? java.lang.Math.abs(a) : -java.lang.Math.abs(a); } // fourier transform void four1(double data[],int nn,int isign) { int n,mmax,m,j,istep,i; double wtemp,wr,wpr,wpi,wi,theta; double tempr,tempi; n=nn << 1; j=1; for (i=1;i i) { tempr = data[j-1]; data[j-1] = data[i-1]; data[i-1] = tempr; tempr = data[j]; data[j] = data[i]; data[i] = tempr; } m=n >> 1; while (m >= 2 && j > m) { j -= m; m >>= 1; } j += m; } mmax=2; while (n > mmax) { istep=2*mmax; theta=6.28318530717959/(isign*mmax); wtemp=java.lang.Math.sin(0.5*theta); wpr = -2.0*wtemp*wtemp; wpi=java.lang.Math.sin(theta); wr=1.0; wi=0.0; for (m=1;m= count) ? 1 : floor; } } } Setup createNext() { return new DeltaArraySetup(); } } class DeltaArraySetup extends Setup { String getName() { return "Delta Fn Array"; } void select() { aux1Label.setText("Well Count"); aux1Bar.setValue(10); aux2Label.setText("Well Separation"); aux2Bar.setValue(30); } void drawPotential() { int i; int maxWells = 30; int width = aux2Bar.getValue()/5+2; int count = aux1Bar.getValue()*(maxWells)/101+1; int offset = (sampleCount-(count-1)*width+1)/2; for (i = 1; i != sampleCount; i++) { if (i < offset) pot[i] = 1; else { int ii = i-offset; int j = (ii % width); pot[i] = 1; if (j == 0 && count-- > 0) pot[i] = -1; } } for (i = 0; i != width; i++) pot[i] = pot[sampleCount-1-i] = 1; } Setup createNext() { return new HarmonicOscillatorSetup(); } } class HarmonicOscillatorSetup extends Setup { String getName() { return "Harmonic Oscillator"; } void select() { aux1Label.setText("Spring Constant"); aux2Label.setText("Offset"); aux2Bar.setValue(50); } void drawPotential() { int i; double width = (aux1Bar.getValue())*(sampleCount/2)/110.; int offset = (50-aux2Bar.getValue())*(sampleCount/2)/100; double a = 2/(width*width); for (i = 0; i != sampleCount; i++) { int ii = offset+i-sampleCount/2; pot[i] = a*ii*ii-1; } pot[0] = pot[sampleCount-1] = 50; } void fudgeLevels() { int i; if (stateCount < 10) return; double avg = 0; for (i = 0; i != 10; i++) avg += elevels[i+1]-elevels[i]; avg /= 10; for (i = 1; i != stateCount; i++) { // if the levels are way off then leave them alone. this // can happen if the offset is too far to the left, for // example. if ((elevels[i]-elevels[i-1])/avg > 2) break; elevels[i] = elevels[i-1]+avg; } } Setup createNext() { return new InfiniteWellFieldSetup(); } } class InfiniteWellFieldSetup extends Setup { String getName() { return "Infinite Well + Field"; } void select() { aux1Label.setText("Well Width"); aux2Label.setText("Field Strength"); } void drawPotential() { int i; int width = (100-aux1Bar.getValue())*(sampleCount/2)/110+1; for (i = 0; i != sampleCount; i++) pot[i] = 50; double field = -(aux2Bar.getValue()-50)/(50.*sampleCount/2); for (i = width; i <= sampleCount-1-width; i++) pot[i] = (i-sampleCount/2)*field; } Setup createNext() { return new CoulombSetup(); } } class CoulombSetup extends Setup { String getName() { return "Coulomb"; } void select() { aux1Label.setText("Charge"); aux1Bar.setValue(8); } void drawPotential() { int i; double width = (aux1Bar.getValue())*(sampleCount/2)/110.; for (i = 0; i != sampleCount; i++) { int ii = i-sampleCount/2; if (ii < 0) ii = -ii; double v = 1-width/ii; if (v < -1) v = -1; pot[i] = v; } pot[0] = pot[sampleCount-1] = 50; } int getAuxBarCount() { return 1; } Setup createNext() { return null; } } class View extends Rectangle { int mid_y, lower_y; double ymult, ymult2, scale; } }; interface DecentScrollbarListener { abstract void scrollbarValueChanged(DecentScrollbar ds); abstract void scrollbarFinished(DecentScrollbar dc); } // this is a scrollbar that notifies us when the user is _done_ fiddling // with the value. class DecentScrollbar extends Canvas implements MouseListener, MouseMotionListener { int value, lo, hi; DecentScrollbarListener listener; DecentScrollbar(DecentScrollbarListener parent, int start, int lo_, int hi_) { value = start; lo = lo_; hi = hi_; listener = parent; addMouseListener(this); addMouseMotionListener(this); gray1 = new Color(104, 104, 104); gray2 = new Color(168, 168, 168); gray3 = new Color(192, 192, 192); gray4 = new Color(224, 224, 224); } Color gray1, gray2, gray3, gray4; public Dimension getPreferredSize() { return new Dimension(20,20); } static final int tw = 8; boolean dragging; int thumbpos, dragoffset; public void paint(Graphics g) { Dimension size = getSize(); int w = size.width; int h = size.height; int x = thumbpos = (value-lo)*(w-2-tw)/(hi-lo)+1; g.setColor(gray2); g.fillRect(0, 0, w, h); g.setColor(gray3); g.fillRect(x, 2, tw, h-4); g.setColor(gray4); g.drawLine(0, h-1, w, h-1); g.drawLine(w-1, 1, w-1, h-1); g.drawLine(x, 1, x+tw-1, 1); g.drawLine(x, 1, x, h-2); g.setColor(gray1); g.drawLine(0, 0, w-1, 0); g.drawLine(0, 0, 0, h-1); g.drawLine(x+tw-1, 2, x+tw-1, h-2); g.drawLine(x+1, h-2, x+tw-1, h-2); } int getValue() { return value; } boolean setValue(int v) { if (v < lo) v = lo; if (v > hi) v = hi; if (value == v) return false; value = v; repaint(); return true; } public void mousePressed(MouseEvent e) { if (thumbpos <= e.getX() && thumbpos+tw >= e.getX()) { dragging = true; dragoffset = e.getX()-thumbpos; } } public void mouseReleased(MouseEvent e) { if (dragging) listener.scrollbarFinished(this); dragging = false; } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseDragged(MouseEvent e) { if (!dragging) return; int x = e.getX()-dragoffset; Dimension size = getSize(); int v = (x-1)*(hi-lo)/(size.width-2-tw)+lo; if (setValue(v)) listener.scrollbarValueChanged(this); } public void mouseMoved(MouseEvent e) { } };