/* Lab.java */ /* Part of the www.MyPhysicsLab.com physics simulation applet. Copyright (c) 2001 Erik Neumann This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact Erik Neumann at erikn@MyPhysicsLab.com or 610 N. 65th St. Seattle WA 98103 */ /* Order to compile java files: 1. Simulation.java = base classes, elements 2. sim1.java = particular simulations 3. Lab.java = user interface stuff */ import java.applet.*; import java.net.URL; import java.awt.*; import java.awt.event.*; import java.util.Enumeration; import java.util.Vector; import java.util.StringTokenizer; import java.text.NumberFormat; import java.io.*; import java.util.Iterator; ///////////////////////////////////////////////////////////////////////////// // CControl class // user interface control /* a control is hooked up to a canvas(eg. sim or graph) when it is constructed. control gets parameter names & values from canvas at update time. control area of screen is set... how? */ class CControl extends CCanvas { /* line_height is a cludge... here's why: I want to know how many lines the control needs. But a font only tells its dimensions when we have a graphics device. But that happens long after the layout is set up. We can perhaps ask to re-layout if we discover the line_height is different? */ public int line_height = 10; // WARNING: only accurate after draw() private int columns = 1; // number of columns private CCanvas canvas = null; // the canvas being controlled private Font myFont = null; private FontMetrics myFM = null; private int ascent = 20; private int descent = 10; private int leading = 5; private NumberFormat nf = null; private int selected = -1; // index of selected control private String keybuf = ""; // typing input buffer private ControlTimer myTimer = null; //------------------- private classes ---------------------------------------- /* If user doesn't hit 'enter' key after a few seconds, then we do it for them in this thread. Whenever a field is being editted, create a ControlTimer object. Whenever a key is pressed, tell ControlTimer to hit 'enter' key in 5 seconds. */ /* ControlTimer Thread class Make an instance, start it running with a long sleep time (eg. 60 seconds) the renew method should interrupt the thread and set a flag telling it to renew. the cancel method should interrupt and set flag to cancel. On interrupt, check the flag and do one or the other. */ private class ControlTimer extends Thread { public int action = 1; // 0 means continue, 1 means terminate public long delay = 50000; ControlTimer() { super("ControlTimer Thread"); setDaemon(true); // so that this thread doesn't prevent us from quitting the app } public void run() { //System.out.println("ControlTimer starting"); do { try { { //System.out.println("ControlTimer going to sleep for "+delay); sleep(delay); // hit 'enter' for the lazy slow user. //System.out.println("ControlTimer is checking key buf"); CControl.this.checkKeyBuf(); action = 1; } } catch(InterruptedException e) { // when we are interrupted, we just go back to sleep (if action == 0) //System.out.println("ControlTimer interrupted, action == "+action); } } while (action != 1); //System.out.println("ControlTimer is quitting"); } } //----------------- CControl Class ---------------------------------------- CControl(Applet app, CCanvas cnvs, int cols) // constructor { super(app); name = "control"; columns = cols; canvas = cnvs; // assume a column is 10 wide // CoordMap inputs are direction, x1, x2, y1, y2, align_x, align_y map = new CoordMap(CoordMap.INCREASE_DOWN, 0, 10*cols, 0, 10, CoordMap.ALIGN_MIDDLE, CoordMap.ALIGN_MIDDLE); map.setFillScreen(true); } public boolean acceptsKeyEvents() { return true; } // returns needed height of control in terms of pixels. // NOTE WARNING: line_height is only accurate after draw() has been called public int height() { // add 3 for extra space below bottom line return 3 + line_height* (int)Math.ceil((double)canvas.params.length/(double)columns); } public void setFont(Graphics g) { if (myFont != null) return; myFont = new Font("Serif", Font.PLAIN, 14); myFM = g.getFontMetrics(myFont); ascent = myFM.getAscent(); descent = myFM.getDescent(); leading = myFM.getLeading(); nf = NumberFormat.getNumberInstance(); nf.setMaximumFractionDigits(5); // could include code here to redo layout if new line_height is bigger than previous if (line_height != ascent+descent+leading) { line_height = ascent+descent+leading; ((Lab)my_applet).redoLayout(); System.out.println("control line height changed "+line_height); } } public void draw(Graphics g) { int i; super.draw(g); setFont(g); g.setFont(myFont); // draw name of each parameter in current column, until full g.setColor(Color.black); if (canvas == null) return; if (canvas.params == null) return; int column = 0; // for each parameter int x = map.screen_left + 10; int y = map.screen_top + ascent + leading; for (i=0; i map.screen_top + map.screen_height) // column full { x += map.screen_width/columns; y = map.screen_top + ascent + leading; } // draw the text & value of parameter if (selected == i) { g.setColor(Color.red); if (keybuf.length() > 0) g.drawString(canvas.params[i] + ": " + keybuf, x, y); else g.drawString(canvas.params[i] + ": " + nf.format(canvas.get(i)), x, y); } else { g.setColor(Color.black); g.drawString(canvas.params[i] + ": " + nf.format(canvas.get(i)), x, y); } // draw a box around selected guy if (selected == i) { g.setColor(Color.red); g.drawRect(x-8, y-ascent-leading/2, map.screen_width/columns-3, ascent+descent+leading/2); } // advance to next line y += ascent + descent + leading; } g.setColor(Color.green); for (i=0; i= canvas.params.length) { checkKeyBuf(); selected = -1; // nothing selected } // click on a new or different cell else if (click_on != selected) { checkKeyBuf(); selected = click_on; if (myTimer == null) { myTimer = new ControlTimer(); myTimer.start(); } } else // if we click on the currently selected cell, just renew the timer. { renewTimer(); } repaint(); // if canvas has a pointer to Lab, then could repaint here!!! } // keyPressed is where we can capture control keys like backspace & enter public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_BACK_SPACE) // backspace { if (keybuf.length() >=1) { keybuf = keybuf.substring(0, keybuf.length()-1); repaint(); } renewTimer(); } else if (keyCode == KeyEvent.VK_ENTER) // enter key checkKeyBuf(); } // keyTyped indicates a key has been pressed & released... only for keys that have a unicode // character representation (so no control keys like enter, backspace, cursor) public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); if (selected < 0) return; // ignore if no one selected if (((c>='0')&&(c<='9')) || (c=='.') || (c=='-')) { // append the character to the keybuf keybuf = keybuf + c; repaint(); renewTimer(); } } // updates the variable from what was typed in to keybuf // and deselects the parameter control. void checkKeyBuf() { if ((selected >= 0) && (keybuf.length() > 0)) { // turn the keybuf into a double try { double dd = (new Double(keybuf)).doubleValue(); // might want to put a min/max check here... canvas.set(selected, dd); } catch (NumberFormatException e) { } } // set so that nobody is selected keybuf = ""; selected = -1; repaint(); killTimer(); // destroy any outstanding timer & task } } // end CControl class ///////////////////////////////////////////////////////////////////////////// // LabTimer class class LabTimer extends Thread { private Vector myCanvas; private long delay = 33; private boolean firstTime = true; private boolean suspendRequested = false; LabTimer(long dly) { super("LabTimer Thread"); myCanvas = new Vector(10); delay = dly; } public void run() { try { while (!interrupted()) // loop until interrupted { checkSuspended(); // repaint all canvas's that want repainting... int i; int n = myCanvas.size(); for (i=0; i10) size = 1; } graph1.setDrawMode(mode, size); } public void setGraph1AutoScale(String s) { System.out.println("setGraph1AutoScale "+s); if (s == null) return; if (s.length() == 0) return; graph1.setAutoScale("true".equalsIgnoreCase(s)); } public void setSim1Param(int num, String s) { System.out.println("setSim1Param "+num+" "+s); if (s == null) return; if (s.length() == 0) return; double val = (new Double(s)).doubleValue(); // could get an exception!! sim1.set(num, val); ctrl1.repaint(); // ensure that params are updated } public void setSim1Var(int num, String s) { System.out.println("setSim1Var "+num+" "+s); if (s == null) return; if (s.length() == 0) return; double val = (new Double(s)).doubleValue(); // could get an exception!! sim1.setVar(num, val); } public void makeControls() { if (ctrl1_cols <= 0) // to hide it, set to zero columns { // hide the existing control if (ctrl1 != null) { cnvs.removeElement(ctrl1); ctrl1 = null; } } else { // what if sim1 is null? ctrl1 = new CControl(Lab.this, sim1, ctrl1_cols); } } public void setControl1Columns(String name) { System.out.println("setControl1Columns "+name); if (name == null) return; if (name.length() == 0) return; int crtl1_cols = Integer.parseInt(name); makeControls(); } public void layoutCanvas() { Dimension d = Lab.this.getSize(); System.out.println("layoutCanvas "+layout_num); cnvs.removeAllElements(); switch (layout_num) { case 0: { // sim1 gets entire screen if (sim1 != null) { sim1.setScreen(0, 0, d.width, d.height); cnvs.addElement(sim1); } break; } case 1: { // sim1 gets right half, graph1 gets left half if (sim1 != null) { sim1.setScreen(d.width/2, 0, d.width/2, d.height); cnvs.addElement(sim1); } if (graph1 != null) { graph1.setScreen(0, 0, d.width/2, d.height); cnvs.addElement(graph1); } else System.out.println("layout: graph1 is null"); break; } case 2: { // ctrl1 at bottom, sim1 gets right half, //graph1 gets left half int h_ctrl = 0; // give the controls what they need if (ctrl1 != null) { h_ctrl = ctrl1.height(); ctrl1.setScreen(0, d.height - h_ctrl, d.width, h_ctrl); cnvs.addElement(ctrl1); } // divide upper evenly if (sim1 != null) { sim1.setScreen(d.width/2, 0, d.width/2, d.height - h_ctrl); cnvs.addElement(sim1); } if (graph1 != null) { graph1.setScreen(0, 0, d.width/2, d.height - h_ctrl); cnvs.addElement(graph1); } break; } case 3: { // sim1 gets top, ctrl1 gets bottom int h_ctrl = 0; // give the controls bottom that they need if (ctrl1 != null) { h_ctrl = ctrl1.height(); ctrl1.setScreen(0, d.height - h_ctrl, d.width, h_ctrl); cnvs.addElement(ctrl1); } // upper goes to sim1 if (sim1 != null) { sim1.setScreen(0, 0, d.width, d.height - h_ctrl); cnvs.addElement(sim1); } break; } } // repaint each canvas int i; int n = Lab.this.cnvs.size(); for (i=0; i