import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.util.*; import java.io.*; import java.applet.*; import java.net.*; public class Tour extends Applet { TourMap map; // a map of nodes and views. TourMap.Navigator navi; // a navigator for the map. //ImageArea mainImage; // the main image //ImageArea previewImage; // the preview image. TextArea commentBox; // box to dsplay comments in. Controller control; // the panel which has the movement controls on it. LoresBtn loresBtn; // the button to toggle picture resolution. Checkbox resolutionCheck; // checkbox to set image display resolution. MediaTracker mTrack; // a media tracker for the images. Image testImg; byte previewDir; // the direction the preview window is pointing. Image mainIm; // the main image Image previewIm; // the preview image. public static int imageSizes[][] = { {150,102} , {460,314}, {460,314} }; public static int CON_WIDTH=148; // width of the controller image. public static int CON_HEIGHT=131; public static int APPLET_WIDTH=650; public static int APPLET_HEIGHT=400; public static int LORES_WIDTH=71; public static int LORES_HEIGHT=28; public static int BACK_COLOR=0xffcc99; public void init() { // read in the map, and set up the display area. mTrack = new MediaTracker(this); InputStream is=null; URL codeURL = getCodeBase(); URL dataURL; URLConnection URLcon; try { StringBuffer fileStr = new StringBuffer(codeURL.getFile()); fileStr.append("Tour.dat"); System.out.println(fileStr); dataURL = new URL(codeURL.getProtocol(), codeURL.getHost(), fileStr.toString()); is = dataURL.openStream(); } catch (Exception e) { System.out.println(e); System.exit(1); } map = new TourMap(); map.readFile(is); navi = map.GetNavigator(); // set up the display. commentBox=new TextArea("",3,40,TextArea.SCROLLBARS_VERTICAL_ONLY); control=new Controller(getCodeBase(),490,150,this); loresBtn=new LoresBtn(getCodeBase(),490,300, this); BorderLayout bl = new BorderLayout(); setLayout(bl); //Panel ctrlPanel= new Panel(); //ctrlPanel.setLayout(new BorderLayout()); //ctrlPanel.add(control, BorderLayout.SOUTH); //add(ctrlPanel, BorderLayout.EAST); add(commentBox, BorderLayout.SOUTH); addMouseListener(new MouseHandler()); addMouseMotionListener(new MouseMotionHandler()); previewDir=(byte)8; // no preview to start with. updateComment(); // update the comment field. loadImage(navi.getView((byte)0), navi.getNode(), navi.getViewNo(), 2); waitForImage(navi.getNode(), navi.getViewNo(), 2); mainIm=navi.getView((byte)0).images[2]; control.setButtons(navi,false); }; public void move(byte dir) { // move in a given direction. If dir = 0 then walk along the current view. // if dir>0 then turn to face direction dir. control.setButtons(navi,true); // show the wait indicator. control.setEnable(false); if (dir == 0) { navi.walk((byte)0); } else { navi.turn(dir); }; setMainView(); setPreview((byte)8); updateComment(); doPaint(); }; public void setMainView() { // set the main view image from the current navigator. int loadSize=2; // preferred size to load main image. int showSize; // size to show the image at. if (loresBtn.getState()) loadSize=1; // load low-res image. // check whether an image needs to be loaded: boolean loadIm; TourView mainView=navi.getView((byte)0); if (mainView.images[2] != null) { loadIm=false; // there's a big picture loaded, so no need to load another. showSize=2; // show the big picture. } else { showSize=loadSize; loadIm=(mainView.images[loadSize]==null); }; // load an image if necessary. if (loadIm) { showStatus("Loading main image - please wait..."); doPaint(); //display the wait indicator. // now load the image. loadImage(mainView, navi.getNode(), navi.getViewNo(), loadSize); waitForImage(navi.getNode(), navi.getViewNo(), loadSize); showStatus(""); }; mainIm = mainView.images[showSize]; control.setButtons(navi,false); control.setEnable(true); }; public void setPreview(byte dir) { // set the direction of the preview window. 8= no preview. previewDir=(byte)8; if ((dir == 8) | (navi.canView(dir))) { previewDir=dir; if (dir !=8) { if (navi.getView((byte)dir).images[0] == null) { showStatus("Loading preview image - please wait ..."); loadImage(navi.getView((byte)dir), navi.getNode(), (byte)((navi.getViewNo() + dir ) % 8 ), 0); waitForImage(navi.getNode(),(byte)((navi.getViewNo() + dir) % 8), 0); showStatus(""); }; previewIm = navi.getView(previewDir).images[0]; }; doPaint(); }; } public void updateComment() { // update the comment field to show details of the current view. TourView curView=navi.getView((byte)0); // this is the current view. String comment; comment = curView.comment; commentBox.setText(comment); }; public void loadImage(TourView view, TourNode node, byte dir, int size) { String iName; // name of the image file. if (view.images[size] == null) { int nodeNo = map.findNodeNo(node); iName="n" + nodeNo + "v" + (int)dir + "s" + size + ".jpg"; try { URL imURL = new URL(getCodeBase().getProtocol(), getCodeBase().getHost(), getCodeBase().getFile() + "Images/"); System.out.println("URL is:" + imURL); view.images[size]=getImage(imURL,iName).getScaledInstance( imageSizes[size][0], imageSizes[size][1], Image.SCALE_SMOOTH); mTrack.addImage(view.images[size], (nodeNo * 100) + (dir * 10) + size ); } catch (Exception e) { System.out.println(e); System.exit(1); }; System.out.println("Image " + iName + "Loaded"); }; }; public void waitForImage(TourNode node, byte dir, int size) { try { mTrack.waitForID((map.findNodeNo(node) * 100) + (dir * 10) + size); } catch (Exception e) { System.out.println(e); System.exit(1); }; }; public void doPaint() { paint(getGraphics()); } public void paint(Graphics g) { // paint the applet: draw the main and preview images straight into the applet // window. Image buffIm = createImage(APPLET_WIDTH,APPLET_HEIGHT); // image for double buffer Graphics buffGraph = buffIm.getGraphics(); buffGraph.setColor(new Color(BACK_COLOR)); buffGraph.fillRect(0,0,APPLET_WIDTH,APPLET_HEIGHT); buffGraph.setColor(new Color(0,0,0)); buffGraph.drawImage(mainIm,0,10,this); buffGraph.drawRect(0,10,imageSizes[2][0],imageSizes[2][1]); if (previewDir != (byte)8) { buffGraph.drawImage(previewIm,490,10,this); buffGraph.drawRect(490,10,imageSizes[0][0],imageSizes[0][1]); }; control.paint(buffGraph); // paint the controller. loresBtn.paint(buffGraph); // paint the low res button; g.drawImage(buffIm,0,0,this); }; public class MouseHandler extends MouseAdapter { public void mouseClicked(MouseEvent me) { System.out.println("Mouse Event at: " + me.getX() + ", " + me.getY()); control.mouseClicked(me); loresBtn.mouseClicked(me); }; }; public class MouseMotionHandler extends MouseMotionAdapter { public void mouseMoved(MouseEvent me) { control.mouseMoved(me); } }; public class LoresBtn { // display a togglable button to switch between high and low resolution pictures. Image[] btnIm; // array of button face images. // [0] is unselected (high res) // [1] is selected (low res). int[][] btnPixels; // pixel array for button face image. Image[] btnMaskIm; // mask (alpha channel) images for the buttons. int[][] btnMaskPixels; // pixel array for mask. Image curBtnIm; // current button face to display. boolean pressed; // is the button pressed? Tour tour; // the tour that this button belongs to. int x,y; // the location of the button on the screen. MediaTracker mTrack; public LoresBtn(URL baseURL, int xPos, int yPos, Tour t) { // load and initialise the pictures, set button state etc. tour=t; x=xPos; y=yPos; mTrack=new MediaTracker(tour); btnIm=new Image[2]; btnPixels=new int[2][]; btnMaskIm=new Image[2]; btnMaskPixels=new int[2][]; // first load all the images... for (int i=0; i<2; i++) { try { btnIm[i]=getImage(baseURL,"lores" +i+ ".jpg"); mTrack.addImage(btnIm[i],1); btnMaskIm[i]=getImage(baseURL,"loresm" + i + ".jpg"); mTrack.addImage(btnMaskIm[i],1); } catch (Exception e) { System.out.println("Error: " + e); }; }; try { mTrack.waitForID(1); } catch (Exception e) { System.out.println("Error: " + e); }; System.out.println("Loaded low-res button images."); // now grab the image pixels into arrays. for (int i=0; i<2; i++) { btnPixels[i]=new int[LORES_WIDTH * LORES_HEIGHT]; PixelGrabber pg=new PixelGrabber(btnIm[i],0,0,LORES_WIDTH,LORES_HEIGHT, btnPixels[i],0,LORES_WIDTH); btnMaskPixels[i]=new int[LORES_WIDTH * LORES_HEIGHT]; PixelGrabber pg2=new PixelGrabber(btnMaskIm[i],0,0,LORES_WIDTH,LORES_HEIGHT, btnMaskPixels[i],0,LORES_WIDTH); try { pg.grabPixels(); pg2.grabPixels(); } catch (Exception e) { System.out.println("Error: " + e); }; }; // now merge the alpha channel info from the mask images into the face images. for (int i=0; i<2; i++) { for (int j=0; j < LORES_WIDTH * LORES_HEIGHT; j++) { btnPixels[i][j]=(btnPixels[i][j] & 0xffffff ) + ((btnMaskPixels[i][j] & 0xff) << 24 ); }; btnIm[i]=createImage(new MemoryImageSource(LORES_WIDTH,LORES_HEIGHT, btnPixels[i],0,LORES_WIDTH)); }; curBtnIm=btnIm[0]; pressed=false; }; public void paint(Graphics g) { // paint the button. g.drawImage(curBtnIm,x,y,tour); }; public void mouseClicked(MouseEvent me) { // first check if the click was near the button. if ((me.getX() < x) | (me.getY() < y) | (me.getX() >= x + LORES_WIDTH) | (me.getY() >= y + LORES_HEIGHT) ) return; // now use the mask image to work out whether it hit the spot. int mousePix=btnMaskPixels[0][(me.getX() - x) + (me.getY() - y) * LORES_WIDTH]; int blue=mousePix & 0xff; if (blue < 5) return; // didn't touch the button. // now change the button face and button state. if (pressed) { pressed = false; curBtnIm = btnIm[0]; // switch to high res picture. tour.control.setButtons(tour.navi,true); // show the wait iindicator. tour.control.setEnable(false); tour.setMainView(); } else { pressed = true; curBtnIm = btnIm[1]; }; tour.doPaint(); }; public boolean getState() { return pressed; }; }; // end of class LoresBtn public class Controller { // display a navigator object on the screen, and interpret mouse clicks over // the object Image[] conIm; // array of controller base images. // [0] is just the central bit, normal state (orange). // [1] is the central bit in image waiting state (blue). // [2] is with the buttons, can't walk state (blue). // [3] is with the buttons, can walk state (orange). Image selIm; // an image whose pixel values describe how to render a controller // image from the base images above. // this is also used to interpret mouse clicks onto a button. // the blue channel holds values from 0 to 180 in steps of 20, or 255 // - if the value is 0, then you are in the unchanged part of the image // if the value is 20-160, then you are in the area of button no. // (val/20)-1. i.e. val=60 means button no.2 // if the value is 180, then you are in the area of the wait indicator. Image renIm; // the current rendered controller image. int[][] conPixels; // arrays to hold the pixels for the 4 controller images. int[] selPixels; // array of selecter pixels MediaTracker mTrack; int x,y; // x and y coords to draw the controller at. Tour tour; // the tour of which this is the controller. boolean enabled; // is the controller enabled? int lastButtonHovered; // last button the mouse was hovered over. public Controller(URL baseURL, int xPos, int yPos, Tour t) { // initialise the controller - load button images // baseURL = URL to load images from; Xpos,yPos = where to draw the controller. x=xPos; y=yPos; tour=t; enabled=true; System.out.println(); System.out.println("Initialising controller"); mTrack=new MediaTracker(tour); conIm=new Image[4]; conPixels=new int[4][]; Image[] maskIm; // array of controller mask images maskIm=new Image[4]; int[][] maskPixels; // array to store mask pixels. maskPixels=new int[4][]; for (int i= 0; i<4 ; i++) { try { conIm[i]=getImage(baseURL,"control" + i + ".jpg"); mTrack.addImage(conIm[i],1); maskIm[i]=getImage(baseURL,"conmask"+ i + ".jpg"); mTrack.addImage(maskIm[i],1); } catch (Exception e) { System.out.println("Error: " + e); }; }; System.out.println("Loaded controller and mask images."); try { selIm=getImage(baseURL,"selecter.gif"); mTrack.addImage(selIm,1); mTrack.waitForID(1); } catch (Exception e) { System.out.println("Error: " + e); }; System.out.println("Loaded selecter image."); // now grab the pixels from each of the images into an array. for (int i=0; i<4; i++) { conPixels[i]=new int[CON_WIDTH * CON_HEIGHT]; PixelGrabber pg=new PixelGrabber(conIm[i],0,0,CON_WIDTH,CON_HEIGHT, conPixels[i],0,CON_WIDTH); maskPixels[i]=new int[CON_WIDTH * CON_HEIGHT]; PixelGrabber pg2=new PixelGrabber(maskIm[i],0,0,CON_WIDTH,CON_HEIGHT, maskPixels[i],0,CON_WIDTH); try { pg.grabPixels(); pg2.grabPixels(); } catch (Exception e) { System.out.println("Error: " + e); }; } System.out.println("Grabbed pixels from controller and mask images."); selPixels=new int[CON_WIDTH * CON_HEIGHT]; PixelGrabber pg=new PixelGrabber(selIm,0,0,CON_WIDTH,CON_HEIGHT, selPixels,0,CON_WIDTH); try { pg.grabPixels(); } catch (Exception e) { System.out.println("Error: " +e); }; System.out.println("Grabbed pixels from selecter image."); // now merge the alpha channel info from the mask images into the base images for (int i=0; i<4; i++) { for (int j=0; j< CON_WIDTH * CON_HEIGHT; j++) { conPixels[i][j]=(conPixels[i][j] & 0xffffff ) + ((maskPixels[i][j] & 0xff) << 24 ); }; }; System.out.println("Merged alpha channel from masks into base images."); }; public void setButtons(TourMap.Navigator navi, boolean waiting) { // set up the buttons for a particular node. // first, create an array which says which image to take pixels from, // according to which button area we are in. int[] selArray=new int[10]; selArray[0]=0; // always draw the central ring from image 1. for (int i=0; i<8; i++) { // now set the array for each of the buttons. if (navi.canView((byte)i)){ if (navi.canWalk((byte)i)) { selArray[i+1]=3; // paint from the 'can walk' picture. } else { selArray[i+1]=2; // paint from the 'can't walk' picture. }; } else { selArray[i+1]=0; // paint from the no buttons picture. }; }; selArray[9]= (waiting) ? 1 : 0; // are we waiting for something? System.out.print("SelArray is: "); for (int i=0; i<10; i++) { System.out.print(selArray[i]+", "); }; System.out.println(); // now scan the selecter pixels, and substitute pixels from one of the // four base images according to the selecter array. int sel; int blue; int renPixel; int[] renPixels=new int[CON_WIDTH * CON_HEIGHT]; for (int i=0; i < CON_WIDTH * CON_HEIGHT; i++) { blue=selPixels[i] & 0xff; // the blue channel of the selecter. if (blue==255) { renPixel=0; //transparent pixel } else { renPixel=conPixels[selArray[(int)blue/20]][i]; }; // System.out.print(selPixels[i]+","); // if (i%20==0) System.out.println(); renPixels[i]=renPixel ; // add in the alpha channel. }; renIm=createImage(new MemoryImageSource(CON_WIDTH,CON_HEIGHT, renPixels,0,CON_WIDTH)); }; public void paint(Graphics g) { // paint the controller. g.drawImage(renIm,x,y,tour); } public void mouseClicked(MouseEvent me) { // the mouse has been clicked. // first check if the click was near the controller. if (enabled == false) return; if ((me.getX() < x) | (me.getY() < y) | (me.getX() >= x + CON_WIDTH) | (me.getY() >= y + CON_HEIGHT) ) return; // now use the selecter image to work out which button was clicked. int selPix=selPixels[(me.getX() - x) + (me.getY() - y) * CON_WIDTH]; int blue=selPix & 0xff; if ((blue < 20) | (blue > 160)) return; // not on a button. int button=(blue/20)-1; // now move the tour to a new node. tour.move((byte)button); }; public void mouseMoved(MouseEvent me) { // the mouse was moved. find out where. int button; if ((enabled == false) | ((me.getX() < x) | (me.getY() < y) | (me.getX() >= x + CON_WIDTH) | (me.getY() >= y + CON_HEIGHT) )) { button=8; } else { // now use the selecter image to work out which button was clicked. int selPix=selPixels[(me.getX() - x) + (me.getY() - y) * CON_WIDTH]; int blue=selPix & 0xff; if ((blue < 20) | (blue > 160)) { button=8; } else { button=(blue/20)-1; }; }; // now show the preview image. if (button != lastButtonHovered) { enabled=false; tour.setPreview((byte)button); enabled=true; }; lastButtonHovered=button; } public void setEnable(boolean en) { enabled=en; }; }; // end of class Tour.Controller }; // end of class Tour. class TourView { // this class defines a single view from a TourNode. public Image[] images; // large and small images for this view public String comment; // text comment to be shown when looking at this view public TourNode walkTo; // the node you can walk to along this view, or null. public byte walkDir; // the direction you end up facing when walking to // the above node. public TourView(TourNode walk, byte dir, String com) { // construct with a given walkto, dirn, and comment. images=new Image[3]; walkTo=walk; walkDir=dir; comment=com; }; }; class TourNode { // this class represents a point on the TourMap, from which you can see // up to 8 views, and walk to other TourNodes. TourView[] views; // the views from here. (numbered from 0 to 7 // clockwise.) public TourNode() { views= new TourView[8]; }; public TourView getView(byte dir) { // return the view in a given direction. return views[dir]; }; public void setView(TourView view, byte dir) { // set the view in a given dirn. views[dir]=view; }; }; class TourMap { // this class holds the map of a whole tour, consisting of a number of // linked TourNode s. int numNodes; // number of nodes in the tour. TourNode[] nodeList; // all the TourNode objects in the tour. TourNode firstNode; // the first node in the tour. byte firstDir; // initial view direction. void readFile(InputStream is) { // read in a data file with a list of map nodes. String line; TourNode curNode=null; byte curDir=0; TourView curView=null; String walkStr; TourNode walkNode=null; byte walkDir=(byte)255; String comment; String nodeComment=""; boolean extComment=false; // an extended comment over more than one line. try { BufferedReader br = new BufferedReader(new InputStreamReader(is)); // convert input stream to a buffered reader. numNodes=0;firstNode=null;firstDir=-1; while ((line = br.readLine()) != null) { // read in the global parameters. if (line.startsWith("startMap")) break; // end of global parameters. if (line.startsWith("numNodes")) { numNodes=Integer.parseInt(line.substring(9)); nodeList=new TourNode[numNodes+1]; // set up node array. for (int i=1; i<=numNodes; i++) // fill array with empty TourNodes. nodeList[i]=new TourNode(); }; if (line.startsWith("firstNode")) firstNode=nodeList[Integer.parseInt(line.substring(10))]; if (line.startsWith("firstView")) firstDir=(byte)Integer.parseInt(line.substring(10)); }; System.out.println("Read global parameters:"); System.out.println("numNodes = " + numNodes); System.out.println("firstNode is " + firstNode); System.out.println("firstView is " + firstDir); // having read the global parameters, now read in the node data. while ((line = br.readLine()) != null) { if (line.startsWith("n")) { int mark1=line.indexOf(';',0); System.out.println(); System.out.println("Reading node " + line.substring(1,mark1)); curNode=nodeList[Integer.parseInt(line.substring(1,mark1))]; nodeComment=line.substring(mark1+1); while (nodeComment.endsWith("/")) { // read in an extended comment. nodeComment=nodeComment.substring(0,nodeComment.length()-1); if ((line= br.readLine()) !=null) { nodeComment+=line; }; }; } else { if (line.startsWith(" v")) { int mark1=line.indexOf(';',0); // find the first semicolon. int mark2=line.indexOf(';',mark1+1); // find the 2nd semicolon. curDir=(byte)Integer.parseInt(line.substring(2,mark1)); walkStr=line.substring(mark1+1,mark2); if (walkStr.equals("x")) { // you can't walk along this view walkNode=null; walkDir=(byte)255; } else { // you can, so get the target node and view. int mark3=walkStr.indexOf('v'); walkNode=nodeList[Integer.parseInt(walkStr.substring(1,mark3))]; walkDir=(byte)Integer.parseInt(walkStr.substring(mark3+1)); }; comment=line.substring(mark2+1); while (comment.endsWith("/")) { // read in an extended comment. comment=comment.substring(0,comment.length()-1); if ((line= br.readLine()) !=null) { comment+=line; }; }; if (comment.length() > 0 ) comment+=System.getProperty("line.separator"); comment+=nodeComment; while ((mark1=comment.indexOf("\\")) != -1) { // convert '\'s to newlines. comment=comment.substring(0,mark1-1) + System.getProperty("line.separator") + comment.substring(mark1+1,comment.length()); }; curView=new TourView(walkNode,walkDir,comment); curNode.setView(curView,curDir); System.out.println("Read in view no. " + curDir + " as " + curView.toString()); }; }; }; } catch (IOException ioe) { System.out.println(ioe); }; }; int findNodeNo(TourNode node) { // find the node number of a given node. int nodeNo=-1; for (int i = 1; i<= numNodes; i++) { if (nodeList[i]==node) nodeNo=i; }; return nodeNo; }; public Navigator GetNavigator() { return new Navigator(); }; class Navigator { // this class is used to navigate a TourMap - i.e. move from node // to node and view to view. TourNode curNode; // the current node. byte curDir; // current view direction on this node. public Navigator() { curNode = firstNode; curDir = firstDir; }; public TourNode getNode() { return curNode; }; public int getNodeNo() { return findNodeNo(curNode); // this is a method of the enclosing class TourMap. }; public byte getViewNo() { return curDir; }; public boolean canWalk(byte dir) { // can you walk this way? N.B. that dir is relative to the // current direction. 0 is ahead, 2 is right, 4 is backwards // 6 is left etc. if (getView(dir) == null) { return false; } else { return (getView(dir).walkTo != null); }; }; public boolean canView(byte dir) { // can you view this direction? if (dir > 7) return false; return (getView(dir) != null); }; public void turn(byte dir) { // turn to face the given direction if (canView(dir)) { curDir+=dir; curDir%=8; }; }; public void walk(byte dir) { // walk in the given direction. if (canWalk(dir)) { TourNode newNode=getView(dir).walkTo; curDir=getView(dir).walkDir; curNode=newNode; }; }; public TourView getView(byte dir) { // get the view in the given dirn. return curNode.getView((byte)((int)(curDir + dir) % 8)); }; }; // end of class Navigator };