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
}
}