import java.applet.*; import java.awt.*; import java.awt.image.*; import java.net.*; import java.util.*; // Freedom VR 2.0 // (C) 1997 Paul A. Houle (houle@msc.cornell.edu) // http://www.msc.cornell.edu/~houle/vr/freedom/ // 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. // (or see http://www.gnu.org/copyleft/gpl.html) public class fvr2 extends Applet implements Runnable { static final boolean debug = false; // if false, debug code is omitted // by javac since this variable is // final. boolean debugLoad = false; // issue debug messages for image // loading. Generates a lot of // data! boolean debugEvents = false; // debug event handling boolean debugPaint = false; // debug painting AMDProgressBarEmbed progbar; // progress bar Image frame[][]; // VR model frames int status[][]; // status of frames boolean fatal; // true if a fatal error happened boolean ready; // true if ready to spin boolean stopped; // true if we've been stopped boolean dragged; // true if we're dragging boolean isMouseIn; // true if mouse is in applet Image dBuff; // the double buffer int direction = 1; // 1 for cw photos, -1 for ccw int upsideDown = 1; // make -1 to flip upside down Thread me; // the active thread int loadFrameI,loadFrameJ; // frame being loaded at the moment int FrameI, FrameJ; // frame being displayed at the moment int oldx,oldy; // x,y coordinates at drag start int zoneI,zoneJ; // current dragPixel zone int mouseX, mouseY; // x,y coordinates of mouse int dragPixels = 10; // drag sensitivity, pixels per frame int progX, progY; // progress bar X and Y int marginX,marginY; // X and Y margins boolean circleN,circleM; // do we circle in horizontal and // vertical directions? int N, M; // numbers of frames // N=horizontal // M=vertical int initialI, initialJ; // initial frame to display String modelBase; // URL base for loading frames String extension; // extension of image files String statusText; // loaded status Text String currentStatus; // I remember currentStatus static int myCursor = Frame.MOVE_CURSOR; // the type of cursor I use Hashtable plugins; // list of loaded plug-ins Vector hotspots; // Vector of hotspots fvr2Hotspot overHotspot; // The hotspot mouse is over int recall_x, recall_y; // used to remember where mouse was // when 'x' is hit. /** * * Applet init method. Do all of the initialization which can be * done quickly; everything except for image loading. Image loading * will be done in a separate thread so the AWT event handler won't * be hung up when we load images * */ public void init() { fatal = false; ready = false; // if neither nFrames or mFrames is specified, we'll // use this variable to trigger an error message boolean frames_specified = false; // nFrames number of frames in horizontal direction String s = getParameter("nFrames"); if(s == null) { N = 1; } else try { N = Integer.parseInt(s); frames_specified = true; } catch(NumberFormatException nfe) { System.err.println("fvr2: nFrames not a number"); fatal = true; repaint(); return; } // mFrames number of frames in vertical direction s = getParameter("mFrames"); if(s == null) { M = 1; } else try { M = Integer.parseInt(s); frames_specified = true; } catch(NumberFormatException nfe) { System.err.println("fvr2: mFrames not a number"); fatal = true; repaint(); return; } if(frames_specified == false) { System.err.println("fvr2: must specify nFrames or mFrames."); fatal = true; repaint(); return; } // progX -- position progress bar in X coordinate progX = 20; s = getParameter("progX"); if(s != null) try { progX = Integer.parseInt(s); } catch(NumberFormatException nfe) { System.err.println("fvr2: progX not a number"); fatal = true; repaint(); return; } // progY -- position progress bar in Y coordinate progY = 20; s = getParameter("progY"); if(s != null) try { progY = Integer.parseInt(s); } catch(NumberFormatException nfe) { System.err.println("fvr2: progY not a number"); fatal = true; repaint(); return; } // marginX -- position of VR image X coordinate marginX = 0; marginY = 0; s = getParameter("marginX"); if(s != null) try { marginX = Integer.parseInt(s); } catch(NumberFormatException nfe) { System.err.println("fvr2: marginX not a number"); fatal = true; repaint(); return; } // marginY -- position of VR image Y coordinate s = getParameter("marginY"); if(s != null) try { marginY = Integer.parseInt(s); } catch(NumberFormatException nfe) { System.err.println("fvr2: marginY not a number"); fatal = true; repaint(); return; } // modelBase -- where to find VR image files s = getParameter("modelBase"); if(s == null) { s = "model/"; } modelBase = s; // imgExt -- what the filenames end in extension = ".jpg"; s = getParameter("imgExt"); if(s != null) extension = s; // noCircleN, noCircleM -- for making non 360 // degree models which don't close in a circle circleN = true; if(getParameter("noCircleN") != null) circleN = false; circleM = true; if(getParameter("noCircleM") != null) circleM = false; // ccw -- for a model where photos go in counter // clockwise order if(getParameter("ccw") != null) direction = -1; // upsideDown -- for a model where the photos are // reversed in the vertical direction if(getParameter("upsideDown") != null) upsideDown = -1; // initialI, initialJ -- the first frame to display // default is 1 initialI = 1; s = getParameter("initialI"); if(s != null) initialI = Integer.parseInt(s); initialJ = 1; s = getParameter("initialJ"); if(s != null) initialJ = Integer.parseInt(s); // bgColor -- set background color Color bgColor = getColorParameter("bgColor"); if(bgColor != null) setBackground(bgColor); // configure status line text statusText = "Freedom VR ready"; if(getParameter("statusText") != null) statusText = getParameter("statusText"); // load plug-ins plugins = new Hashtable(); s = getParameter("plugins"); if(s != null) { for(StringTokenizer st = new StringTokenizer(s);st.hasMoreElements();) { Class pluginClass = null; String pluginName = (String)st.nextElement(); try { pluginClass = Class.forName(pluginName); } catch(Exception e) { fatal = true; System.out.println("fvr2 fatal error: couldn't load plugin " + pluginName); break; } plugins.put(pluginName,pluginClass); } } // initialize arrays frame = new Image[M][N]; status = new int[M][N]; // set up progress bar progbar = new AMDProgressBarEmbed(this); progbar.reshape(progX,progY,150,25); progbar.setText("Loading VR object"); progbar.setPercent(0); // set up a wait cursor while we load setCursor(Frame.WAIT_CURSOR); FrameI = initialI; FrameJ = initialJ; // initialize Hotspot system hotspots = new Vector(); overHotspot = null; isMouseIn = false; System.out.println(getAppletInfo()); } // code stolen from "Java in a Nutshell" by D. Flanagan // parse a hexadecimal color #RRGGBB Color getColorParameter(String name) { String value = getParameter(name); int intvalue; try { intvalue = Integer.parseInt(value,16); } catch(NumberFormatException e) { return null; } return new Color(intvalue); } /** * @return applet info string **/ public String getAppletInfo() { return "Freedom VR 2.0 (http://www.honeylocust.com/vr/)"; } /** * The browser calls start() after calling * init(); in Freedom VR, this method starts a new * thread whose job is to load images. The thread dies after the images * are loaded, and for the rest of it's life, FVR is "animated" by * the AWT event handling thread. Execution picks up in the run() * method after the thread starts. **/ public void start() { me = new Thread(this); me.start(); } /** * * The browser calls stop() when an applet is being * stopped. The stop could be temporary, for instance, the applet * might be stopped temporarily when the user visits another page, * and restarted when the user hits the back button and comes back * to the page with the applet. In our case, we shut down the image * loading thread is shut down if it's running. Nothing else needs to * be done, since no event handlers are going to be called when the * applet is stopped so the applet happily hibernates. * *

* Note that applets are responsible for stopping their own threads and * will eat up CPU time and hurt performance on the client machine unless * they stop themselves when requested in either the stop() * or destroy() methods. * **/ public void stop() { me.stop(); me = null; } /** * * When destroy() is called, it's all over. Time to die. * The applet will never get called back again. Freedom VR trashes * any image objects it owns to free up memory. destroy() * typically gets called a while after stop() does, * when the browser decides that the chance of the user going back to * the applet page is small enough that it isn't worth holding onto the * applet's code and data. * */ public void destroy() { System.out.println("Freedom VR destroying self."); // trash any image objects we own for(int i = 0;i < M;i++) for(int j = 0;j < N;j++) if(frame[i][j] != null) { frame[i][j].flush(); frame[i][j]=null; }; System.gc(); } /** * * Ths method implements ImageObserver. Java has a rather * complex system for loading images -- a method call which displays an * image returns immediately and causes another thread to display the * image. This sounds complicated and it is, but it has the advantage * that an applet isn't going to be hung up waiting for images to load. *

* The job of this method in Freedom VR is to control image loading: * it finds out when an image is completely loaded and then notifies * the image loading thread that it can go load the next image. * * @param img The image that's notifying us * @param infoflags the status of the image * @param x an x coordinate * @param y a y coordinate * @param width image width * @param height image height * @return false if we'll need more updates in the future, * true if we've been told everything we need to know. **/ synchronized public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { // make sure locks are released so thread can stop in an // orderly manner if(!isActive()) { notify(); return false; } ; if(debug && debugLoad) System.out.println("Got into image update, status=" + infoflags); if(img != frame[loadFrameI - 1][loadFrameJ - 1]) { System.err.println("fvr2: image update out of sequence."); fatal = true; notify(); return false; } ; status[loadFrameI - 1][loadFrameJ - 1] = infoflags; notify(); return true; } /** * * Map frame number to the name of the frame; return the relative * URL for frame i,j given i,j * * @param i vertical frame number * @param j horizontal frame number * @returns string with relative URL for frame i,j */ String frameName(int i, int j) { // if M==1 the movie is a horizontal strip and names are // just modelBase+i+extension, if N==1 the movie is a vertical // strip and names are still just modelBase+j+extension, if // both are nonzero then names are // modelBase+i+"_"+j+extension if(M == 1) { return modelBase + j + extension; } else if(N == 1) { return modelBase + i + extension; } return modelBase + i + '_' + j + extension; } /** * * Given the i and j number of the frame, return the number giving * the serial order in which it will be loaded. Used for the progress * bar. * * @param i vertical frame number * @param j horizontal frame number * @return serial number of frame * **/ int frameNumber(int i, int j) { int number=((i - 1) * N + j); if (number<(initialI-1)*N+initialJ) number++; return number; } /** * * Load the frame indexed by i and j * * @param i vertical frame number * @param j horizontal frame number * @return true if everything worked, false * if it didn't. * **/ boolean loadFrame(int i, int j) { // if the image is already loaded, we're done! if(status[i - 1][j - 1] == ImageObserver.ALLBITS) return true; try { URL url = new URL(getDocumentBase(),frameName(i,j)); if(debug) System.out.println("URL = " + url.toString()); frame[i - 1][j - 1] = getImage(url); status[i - 1][j - 1] = 0; } catch(MalformedURLException mue) { fatal = true; System.err.println("fvr2: URL " + frameName(i,j) + " ill-formed"); repaint(); return false; } loadFrameI = i; loadFrameJ = j; // make sure we die in an orderly manner when the // applet (and thread) is stopped if(!isActive()) return false; if(debug && debugLoad) System.out.println("fvr2: preparing image " + i + "," + j); synchronized(this) { if(!prepareImage(frame[i - 1][j - 1],this)) { do { try { if(debug && debugLoad) System.out.println("fvr2: wait() on image " + i + "," + j); wait(); if(debug && debugLoad) System.out.println("fvr2: got out of wait."); } catch(InterruptedException e) { // The really weird thing is that neither JDK 1.0.2 nor // JDK 1.1 ever throw an InterruptedException! } if(!isActive()) return false; if((status[loadFrameI - 1][loadFrameJ - 1] & ImageObserver.ABORT) != 0) { fatal = true; repaint(); return false; } if(((status[loadFrameI - 1][loadFrameJ - 1] & ImageObserver.SOMEBITS) != 0) && (i == 0)) synchronousRepaint(); } while((status[loadFrameI - 1][loadFrameJ - 1] & ImageObserver.ALLBITS) == 0); } else if(debug && debugLoad) System.out.println("Image " + i + "," + j + " already ready!"); // the image decoders seem to leave a lot of garbage behind // in memory when they are done. If we clean up after loading // a large image, reliability on MacOS JVMs // improves. I think this is also good for performance // on Unix JVMs; another advantage of loading images only one // at a time. System.gc(); // clean up after image loader status[i - 1][j - 1] = ImageObserver.ALLBITS; } return true; } /** * The job of the run method is to run in a thread which loads the * images. * *

* Loading images one at a time saves a lot of grief in early Java * implementations -- since image loading requires bursts of CPU activity * as well as use of the network, in theory one can improve performance * by loading more than one image at once. In practice many * implementations of Java somehow trip up on themselves when loading * images and you're really best off loading one image at a time. Mac * JVMs can hang up entirely when abused, while Netscape on AIX loads * images incredibly slowly when more than one is being loaded at a * time. The following code has been tested through several revisions * and seems to give consistently good to excellent performance on * all platforms, unlike MediaTracker(). Prehaps I * should make it reusable, but Freedom VR is deliberately designed to * have a minimum number of classes so it loads lightning fast. * **/ public void run() { // the repaint thread appears to run at priority 4 in all // implementations of Java. Setting this thread to priority 3 // ensures that the repaint thread can override this thread and // repaint the screen at any time on all platforms // we've tested on (Unix, Mac and Windows) me.setPriority(3); // Image loading eats memory, collect garbage to make sure we // get as much as we can. System.gc(); setDefaultStatus("Freedom VR: Loading poster frame"); loadFrame(initialI,initialJ); for(int i = 1;i <= M;i++) { for(int j = 1;j <= N;j++) { setDefaultStatus("Freedom VR: Loading frame " + frameNumber(i,j) + " of " + (N * M)); if(!loadFrame(i,j)) return; progbar.setPercent(frameNumber(i,j) * 1.0 / (N * M)); } } if(debug) System.out.println("fvr2: Image loading complete."); ready = true; setDefaultStatus(statusText); setCursor(myCursor); repaint(); } /** * The following group of methods are event handlers and override * methods in java.awt.Component of which java.applet.Applet * is a subclass. Freedom VR 2.0 uses the JDK 1.0.2 event model, * but may be upgraded to JDK 1.1 in the future when most users are using * JDK 1.1 AWT compatible browsers. *

* I get called when the mouse button goes down over the applet. This * warns me that the user may be about to drag the object. * * @param evt Event object * @param x x coordinate of mouse * @param y y coordinate of mouse * @return true if event has been handled, false * if it must be passed further up the hierarchy. **/ public boolean mouseDown(Event evt, int x, int y) { if(overHotspot != null) overHotspot.mouseDown(); mouseX = x; // continuously remember mouse location mouseY = y; oldx = x; oldy = y; zoneI = 0; zoneJ = 0; return true; } /** * I get called when the mouse is dragged (moved with the button down) * over the applet. This code is responsible for rotating objects. * * @param evt Event object * @param x x coordinate of mouse * @param y y coordinate of mouse * @return true if event has been handled, false * if it must be passed further up the hierarchy. **/ public boolean mouseDrag(Event evt, int x, int y) { mouseX = x; // continuously remember mouse location mouseY = y; if(debug && debugEvents) System.out.println("Mouse drag at " + x + "," + y); if(overHotspot != null) return super.mouseDrag(evt,x,y); dragged = true; int newzoneI = (y - oldy) / dragPixels; int newzoneJ = (x - oldx) / dragPixels; if(newzoneJ != zoneJ && N != 1) { panRight(newzoneJ - zoneJ); zoneJ = newzoneJ; } if(newzoneI != zoneI && M != 1) { tiltDown(newzoneI - zoneI); zoneI = newzoneI; } return true; } /** * This method is called when the mouse is moved, that is, when the * button is up. This method alerts Hotspots when the mouse is moved * in or out of them. * * @param evt Event object * @param x x coordinate of mouse * @param y y coordinate of mouse * @return true if event has been handled, false * if it must be passed further up the hierarchy. **/ public boolean mouseMove(Event evt, int x, int y) { mouseX = x; // keep track of mouse coordinates mouseY = y; if(overHotspot == null) { fvr2Hotspot hs = searchHotspots(FrameI,FrameJ,x,y); if(hs == null) return true; overHotspot = hs; hs.mouseOver(); } else { fvr2Hotspot hs = overHotspot; if(!hs.isMouseOver(FrameI,FrameJ,x,y)) { overHotspot = null; hs.mouseOut(); setCursor(myCursor); showStatus(statusText); } } return true; } /** * This method is called when the mouse button is released. This * method does two things. It drops us out of drag mode if we're * dragging, and it also triggers the Hotspot if the user let the button * go over a Hotspot. * * @param evt Event object * @param x x coordinate of mouse * @param y y coordinate of mouse * @return true if event has been handled, false * if it must be passed further up the hierarchy. **/ public boolean mouseUp(Event evt, int x, int y) { mouseX = x; // keep track of mouse coordinates mouseY = y; if(debug && debugEvents) { System.out.println("fvr2: mouseUp event received"); System.out.println("overHotspot = " + overHotspot); } if(dragged) { dragged = false; paintHotspots(getGraphics()); return true; } fvr2Hotspot hs = overHotspot; if(hs != null) hs.mouseUp(); return true; } /** * if the mouse enters the applet, we show our status line, * tell ourselves the mouse is in, request the focus so we can get * keyboard input, and repaint. * * @param evt Event object * @param x x coordinate of mouse * @param y y coordinate of mouse * @return true if event has been handled, false * if it must be passed further up the hierarchy. **/ public boolean mouseEnter(Event evt, int x, int y) { isMouseIn = true; showStatus(currentStatus); requestFocus(); repaint(); return true; } /** * if the mouse leaves the applet, we deactivate any hotspots and * tell ourselves the mouse is out and repaint. * * @param evt Event object * @param x x coordinate of mouse * @param y y coordinate of mouse * @return true if event has been handled, false * if it must be passed further up the hierarchy. **/ public boolean mouseExit(Event evt, int x, int y) { isMouseIn = false; if(overHotspot != null) { overHotspot.mouseOut(); overHotspot = null; } repaint(); return true; } /** * * And if somebody hits a key we display frame number and cursor x,y * just a little bit of GUI for authoring ;-) *

* Also, per request, added keyboard navigation, you can spin the * object with the arrow keys * * @param evt Event object * @param key the integer value of the key that was hit * */ public boolean keyUp(Event evt, int key) { if(debug && debugEvents) System.out.println("fvr2: Captured key up event, key=" + key); if(key == Event.LEFT) { panLeft(1); return true; } if(key == Event.RIGHT) { panRight(1); return true; } if(key == Event.DOWN) { tiltDown(1); return true; } if(key == Event.UP) { tiltUp(1); return true; } // if the user hits the 'x' button we display the frame // numbers and the (x,y) coordinates to the screen if(key == (int)'X' || key == (int)'x') { recall_x = mouseX; recall_y = mouseY; System.out.println("Frame [" + FrameI + "," + FrameJ + "] + (x,y)=(" + recall_x + "," + recall_y + ")"); return true; } // if the user hits the 's' button, we display the size // of the rectangle between the place where 'x' was last hit // and where the cursor is. if(key == (int)'s' || key == (int)'S') { System.out.println("(width,height)=(" + (mouseX - recall_x) + "," + (mouseY - recall_y) + ")"); return true; } if(key == (int)'\n') { if(overHotspot != null) overHotspot.mouseUp(); } return true; } /** * The following group of methods are used for moving the view. This * may be of interest to Javascript programmers who would like to script * Freedom VR, so they've been made public; the terms pan and tilt * come from cinematography and are probably technically right only for * the panorama case. * * Pan the view to the right. * * @param steps the number of frames to pan * @return true if pan succeeded, false if * it didn't. * */ public boolean panRight(int steps) { steps *= direction; FrameJ += steps; if(!circleN) { if(FrameJ < 1) { FrameJ = 1; repaint(); return false; } if(FrameJ >= N) { FrameJ = N; repaint(); return false; } } while(FrameJ < 1) FrameJ += N; FrameJ = ((FrameJ - 1) % N) + 1; repaint(); return true; } /** * * Pan view to the left. * * @param steps the number of frames to pan * @return true if pan succeeded, false if * it didn't. * */ public boolean panLeft(int steps) { return panRight(-steps); } /** * * Tilt view down. * * @param steps the number of frames to tilt * @return true if tilt succeeded, false if * it didn't. * */ public boolean tiltDown(int steps) { steps *= upsideDown; FrameI += steps; if(!circleM) { if(FrameI < 1) { FrameI = 1; repaint(); return false; } ; if(FrameI >= M) { FrameI = M; repaint(); return false; } ; } ; while(FrameI < 1) FrameI += M; FrameI = ((FrameI - 1) % M) + 1; repaint(); return true; } /** * * Tilt view up. * * @param steps the number of frames to tilt * @return true if tilt succeeded, false if * it didn't. * */ public boolean tiltUp(int steps) { return tiltDown(-steps); } /** * The next bunch of methods are preoccupied with graphical display * * * update() is overridden to prevent flicker; by default * update() fills in the background color before calling * paint * * @param g the graphics context from the AWT * */ public void update(Graphics g) { paint(g); } /** * * The paint() method. Draws the appropriate frame, * superimposing progress bar if necessary, double buffering if * prudent. * * @param g the graphics context from the AWT * */ public void paint(Graphics g) { if(debug && debugPaint) System.out.println("paint called!"); // if we've had a fatal error, we display a red screen. Error // reporting for image loading appears to work poorly and many // error conditions (such as OutOfMemory) while loading never // notify us. if(fatal) { g.setColor(Color.red); g.fillRect(0,0,size().width,size().height); return; } // avoid null pointer errors if(frame == null) return; if(frame[FrameI - 1][FrameJ - 1] == null) return; if(status[FrameI - 1][FrameJ - 1] == 0) return; // draw background -- draw only parts that do not overlap // image to increase speed and eliminate flicker without // the (very heavy on some platforms, especially MacOS) // speed penalty of double buffering. (DB is cheap on Unix, // expensive on Windows, and unacceptable slow on MacOS) // we ~still~ however, use double buffering when !ready // so the progress bar doesn't flicker. Performance isn't // so critical here, we think Graphics g1 = g; if(!ready) { if(dBuff == null) dBuff = createImage(size().width,size().height); g1 = dBuff.getGraphics(); g1.setColor(getBackground()); g1.fillRect(0,0,size().width,size().height); } else { dBuff = null; // throw it out when we're done with it if(marginX != 0) g1.clearRect(0,0,marginX,size().height); int imgWidth = frame[FrameI - 1][FrameJ - 1].getWidth(null); if(marginX + imgWidth < size().width) g1.clearRect(marginX + imgWidth,0,size().width - marginX - imgWidth,size().height); if(marginY != 0) g1.clearRect(0,0,size().width,marginY); int imgHeight = frame[FrameI - 1][FrameJ - 1].getHeight(null); if(marginY + imgHeight < size().width) g1.clearRect(0,marginY + imgHeight,size().width,size().height - marginY - imgHeight); } // draw image g1.drawImage(frame[FrameI - 1][FrameJ - 1],marginX,marginY,null); // if we're still loading paint progress bar, using // double-buffering to eliminate flicker. if(!ready) { progbar.paint(g1); g.drawImage(dBuff,0,0,null); } paintHotspots(g); } /** * * This method loops through the hotspots and gets the ones that are * visible to paint themselves. Called by paint() * * @param g graphics context from the AWT * **/ public void paintHotspots(Graphics g) { if(ready && !dragged && isMouseIn) for(Enumeration e = hotspots.elements();e.hasMoreElements();) { fvr2Hotspot hs = (fvr2Hotspot)e.nextElement(); if(hs.isInFrame(FrameI,FrameJ)) { hs.paint(g); } } } /** * * When this method is called, the applet does a synchronous Repaint; it * repaints itself immediately and does not return until the repaint is * completed, in contrast to the behavior of repaint(). * repaint() is best used in event handlers (unless you only * need to make small changes in the graphics, such as when you're * drawing rubber band lines) while synchronousRepaint is * best used in a thread which is doing other things. (Such as * loading images or computation) * */ public void synchronousRepaint() { paint(getGraphics()); } /** * This bit of code can set the type of cursour in Netscape. It * doesn't work in appletviewer, but the try/catch prevents this * from crashing the applet. * * @param type cursor type, defined in java.awt.Frame * */ void setCursor(int type) { try { ((Frame)getParent()).setCursor(type); } catch(Exception e) { } } /** * This is a control binding for javascript: it's job is to make new * Hotspot objects and interconnect them with the applet * * @param name, Hotspot name * @param frame_i, Hotspot vertical frame # * @param frame_j, Hotspot horizontal frame # * @param x Hotspot x coordinate * @param y Hotspot y coordinate * @param height Hotspot height * @param width Hotspot width * */ public fvr2Hotspot newHotspot(String kind) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class hsClass = (Class)plugins.get(kind); if(hsClass == null) { fatal = true; System.out.println("Hotspot plugin " + kind + " not loaded"); repaint(); return null; } fvr2Hotspot spot = (fvr2Hotspot)hsClass.newInstance(); spot.attachApplet(this); hotspots.addElement(spot); return spot; } // set the status that's displayed when the mouse moves over the // applet. void setDefaultStatus(String s) { currentStatus = s; showStatus(s); } /** * * Search all the Hotspots for the first (topmost) one that is at the * coordinates "frame_i", "frame_j", "x" and "y" * * @param frame_i vertical frame index * @param frame_j horizontal frame index * @param x x coordinate of mouse * @param y y coordinate of mouse * @return the Hotspot if there is one, return null if there isn't * */ fvr2Hotspot searchHotspots(int frame_i, int frame_j, int x, int y) { // java.util.Vector uses synchronize which improves // reliability but practically slows things down. Bracketing // all of our accesses to hotspot within a synchronized // statement can reduce synchronization overhead if a // JVM is smart enough to take advantage of it. synchronized(hotspots) { for(int i = 0;i < hotspots.size();i++) { fvr2Hotspot hs = (fvr2Hotspot)hotspots.elementAt(i); if(hs.isMouseOver(FrameI,FrameJ,x,y)) return hs; } } return null; // return null if we're not over one } }