import javax.swing.JScrollPane; import javax.swing.JButton; import javax.swing.SwingUtilities; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.net.MalformedURLException; import java.awt.Image; import java.awt.Dimension; import java.awt.Color; import java.awt.MediaTracker; import java.util.StringTokenizer; /* * ZazaMap * * Copyright (C) 2001 Brian Rudy (brudyNO@SPAMpraecogito.com) * * 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. * ** Info ** * Authored in NetBeans 3.2 IDE * * Grab images of current map, and robot. Scale to * dimensions of the applet panel. Get setup info * from position server CGI (goal points). Get * robot's position and orientation from the position * server and draw (client-pull). Scale all graphics based * on zoomlevel. * * Todo- Update frequency parameter loading, exception handling!, * server-side status reports, client-side controls? * * ** Revision History ** * 0.61 3-15-2002 * Fixed bug preventing screen redraws on image update. * * 0.60 1-13-2002 * Much needed code cleanup of map rendering routines. Proper threading of * image downloads. * * 0.52 11-2-2001 * Minor UI tweaks. Still needs some work. * * 0.51 9-16-2001 * Tracking-related bug seems to have dissapeared after adding some semaphores * during screen redraws after changing zoom level. A minor bug during * auto-tracking leaves remnants of the last screen around the borders of the * animationPane after a zoom level change. * * 0.50 9-14-2001 * Added work-arounds for buffer overflow when tracking and user attempts to * scroll (remove scroll bars when tracking, should fix this), and when * changing zoom level (tracking temporarily disabled during resize). * Still has a bug related to auto-tracking. * * 0.49 9-13-2001 * Scrollbars now fully functional. Toolbar graphics enhancements. * Zoom level now fully functional. Added robot auto-tracking via Track button. * * 0.48 9-12-2001 * Basic scrollbar functionality. Updated button logic on toolbar. * Basic zoom level support (needs work). * * 0.47 8-23-2001 * Added toolbar with start/stop for position retrieval * and stubs for magnification level. Added stubs for scrollable window. * * 0.46 8-15-2001 * Added basic goal point voting. * * 0.45 8-2-2001 * Added basic target info window spawning. * * 0.44 7-31-2001 * Added stub for goal point selection. * * 0.43 7-30-2001 * Added basic goal plotting and labeling (currently disabled * for clarity). Fixed goal point parsing and array population. * Added getPos support. * * 0.42 7-28-2001 * Fixed exception handling in NetIO routines. Extended parsing. * Fixed robotImage scaling. * * 0.41 7-27-2001 * Added basic setup data retrieval and preliminary parsing. * * 0.4 7-25-2001 * Added basic NetIO framework * * 0.3 7-23-2001 * Basic image rendering using Java2D extensions for * image manipulation (rotation) * * Known Bugs- Java2D imagebuffer data not transparent when * pasted into 'g' canvas. Robot image has square borders * as a result */ /* * Moves a foreground image in front of a background image. */ public class ZazaMap2 extends javax.swing.JApplet implements java.awt.event.ActionListener { boolean frozen = false; javax.swing.Timer timer; ZazaMap2.AnimationPane animationPane; JScrollPane pictureScrollPane; //Image original; boolean isInitialized = false; // The first array must be initialized String[][] goals = new String[20][]; int numGoals; int PosX = 0; int lastPosX = 0; int PosY = 0; int lastPosY = 0; int PosTheta; // In degrees int lastPosTheta = 0; int goalSize = 10; URL targetURL; int goalSelected = 0; boolean asApplet = false; boolean posEnabled = true; private Dimension size; // indicates size taken up by graphics int zoomlevel = 1; int lastzoomlevel = 0; boolean trackEnabled = false; boolean stopTracking = false; boolean retrack = false; // These are only used when run as an application static String fgFile = "http://zazaconsole.exhibits.thetech.org/~brudy/zaza/java/images/robot.gif"; static String bgFile = "http://zazaconsole.exhibits.thetech.org/~brudy/zaza/java/images/map-ex.gif"; //Invoked only when run as an applet. public void init() { asApplet = true; //Get the images. Image bgImage = getMapImage(getParameter("MAPIMAGE")); Image fgImage = getMapImage(getParameter("ROBOTIMAGE")); buildUI(getContentPane(), bgImage, fgImage); // Get setup info from server setup(); } public Image getMapImage(String name) { Image img = getImage(getCodeBase(), name); try { MediaTracker tracker = new MediaTracker(this); tracker.addImage(img, 0); tracker.waitForID(0); } catch (Exception e) {} return img; } public void buildUI(java.awt.Container container, Image bgImage, Image fgImage) { //int fps = 10; int fps = 1; //How many milliseconds between frames? int delay = (fps > 0) ? (1000 / fps) : 100; //Set up a timer that calls this object's action handler. timer = new javax.swing.Timer(delay, this); timer.setInitialDelay(0); timer.setCoalesce(true); size = new Dimension(0,0); animationPane = new ZazaMap2.AnimationPane(bgImage, fgImage); // Set up the scroll pane. pictureScrollPane = new JScrollPane(animationPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); pictureScrollPane.setViewportBorder( javax.swing.BorderFactory.createLineBorder(Color.black)); if (!asApplet) { pictureScrollPane.setPreferredSize(new Dimension(320, 240)); } pictureScrollPane.setVisible(true); container.setLayout(new java.awt.BorderLayout()); container.add("North", new ZazaMap2.DemoControls(animationPane)); container.add("Center", pictureScrollPane); animationPane.addMouseListener(new java.awt.event.MouseAdapter() { public void mousePressed(java.awt.event.MouseEvent e) { boolean clickedOnGoal = goalClick(e.getX(), e.getY(), goalSize); // on mouseUp event over goal, do something if (clickedOnGoal) { System.out.println("You selected goal number " + String.valueOf(goalSelected) + " which is " + goals[goalSelected][2]); // Cast your vote sendGoal(goalSelected); if (asApplet) { try { URL infoURL = new URL(goals[goalSelected][3]); getAppletContext().showDocument(infoURL, "infowindow"); } catch (MalformedURLException er) { } catch (IOException er) { } } } else { System.out.println("You clicked X=" + String.valueOf(e.getX()) + ", Y=" + String.valueOf(e.getY()) + " but there is nothing here."); } } }); } public boolean goalClick(int xposition, int yposition, int diameter) { if (isInitialized){ for(int i = 0; i < numGoals; i++){ if (((Integer.valueOf(goals[i][0]).intValue()*zoomlevel)-goalSize/2 <= xposition) && (((Integer.valueOf(goals[i][0]).intValue()*zoomlevel)-goalSize/2)+diameter >= xposition)){ if (((Integer.valueOf(goals[i][1]).intValue()*zoomlevel)-goalSize/2 <= yposition) && (((Integer.valueOf(goals[i][1]).intValue()*zoomlevel)-goalSize/2)+diameter >= yposition)){ goalSelected = i; return true; } } } } return false; } public void fixScrollbars() { // this is needed to work around a threading bug that causes a buffer // overflow when using auto-tracking. stopTracking = true; animationPane.scrollRectToVisible(new java.awt.Rectangle(0, 0, animationPane.background.getWidth(this)*zoomlevel, animationPane.background.getHeight(this)*zoomlevel)); animationPane.setPreferredSize(new Dimension(animationPane.background.getWidth(this)*zoomlevel, animationPane.background.getHeight(this)*zoomlevel)); animationPane.revalidate(); //animationPane.repaint(); stopTracking = false; } //Invoked by a browser only. public void start() { startAnimation(); } //Invoked by a browser only. public void stop() { stopAnimation(); } //Can be invoked from any thread. public synchronized void startAnimation() { if (frozen) { //Do nothing. The user has requested that we //stop changing the image. } else { //Start animating! if (!timer.isRunning()) { timer.start(); } fixScrollbars(); } } //Can be invoked from any thread. public synchronized void stopAnimation() { //Stop the animating thread. if (timer.isRunning()) { timer.stop(); } } // This is called when the timer expires public void actionPerformed(java.awt.event.ActionEvent e) { if (posEnabled) { // Get updated position info from posServer getPos(); } //Display it. animationPane.repaint(); } // The following should be in it's own class String posServer = "http://zazaconsole.exhibits.thetech.org/cgi-bin/posServer"; // Get setup info from posServer (goals) public void setup() { try { System.out.println("Getting setup info..."); String SetupStr= posServer + "?setup=yes"; System.out.println("I am getting " + SetupStr); URL url = new URL(SetupStr); URLConnection connection = url.openConnection(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine; StringTokenizer dataIn; String delimiter = ","; int tokenIndex; int index = 0; while ((inputLine = in.readLine()) != null) { tokenIndex = 0; // print it to the console System.out.println("Setup data: " + inputLine); // Parse with stringTokenizer dataIn = new StringTokenizer(inputLine, delimiter, true); goals[index] = new String[4]; //create sub-array // For goal parsing. Add x,y,name to goal array // x goals[index][0] = dataIn.nextToken(); // delimiter dataIn.nextToken(); // y goals[index][1] = dataIn.nextToken(); // delimiter dataIn.nextToken(); // name goals[index][2] = dataIn.nextToken(); // delimiter dataIn.nextToken(); // URL goals[index][3] = dataIn.nextToken(); // EOL delimiters dataIn.nextToken(); dataIn.nextToken(); index++; } numGoals = index; in.close(); isInitialized = true; } catch (MalformedURLException e) { } catch (IOException e) { } } // Get position info (x,y,theta) public void getPos() { try { System.out.println("Getting position info..."); String posReqStr= posServer + "?getpos=yes"; URL url = new URL(posReqStr); URLConnection connection = url.openConnection(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine; StringTokenizer dataIn; String delimiter = ","; while ((inputLine = in.readLine()) != null) { // print it to the console //System.out.println("Position data: " + inputLine); // Parse with stringTokenizer dataIn = new StringTokenizer(inputLine, delimiter, true); // x PosX = Integer.valueOf(dataIn.nextToken()).intValue(); // delimiter dataIn.nextToken(); // y PosY = Integer.valueOf(dataIn.nextToken()).intValue(); // delimiter dataIn.nextToken(); // name PosTheta = Integer.valueOf(dataIn.nextToken()).intValue(); // EOL delimiters dataIn.nextToken(); dataIn.nextToken(); } in.close(); //System.out.println("Current position x=" + String.valueOf(PosX) + ", y=" + String.valueOf(PosY) + ", theta=" + String.valueOf(PosTheta)); } catch (MalformedURLException e) { } catch (IOException e) { } } //Get status info (mode change, remote exceptions) public void getInfo() { try { System.out.println("Getting status info..."); String posReqStr= posServer + "?getinfo"; URL url = new URL(posReqStr); URLConnection connection = url.openConnection(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { // print it to the console // Gotta parse this somehow System.out.println("Status info: " + inputLine); } in.close(); } catch (MalformedURLException e) { } catch (IOException e) { } } // Send goal vote (server-side tally/arbitration) public void sendGoal(int goalNum) { try { System.out.println("Sending goal number " + String.valueOf(goalNum)); String posReqStr = posServer + "?sendgoal=" + String.valueOf(goalNum); URL url = new URL(posReqStr); URLConnection connection = url.openConnection(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { // print it to the console // Gotta parse this somehow System.out.println("Goal request reply: " + inputLine); } in.close(); } catch (MalformedURLException e) { } catch (IOException e) { } } class AnimationPane extends javax.swing.JPanel { public Image background, foreground; private java.awt.image.BufferedImage bimg; public int xPos, yPos; private int maxUnitIncrement = 10; public AnimationPane(Image background, Image foreground) { this.background = background; this.foreground = foreground; //this.setOpaque(false); } public java.awt.Graphics2D createGraphics2D(int w, int h) { java.awt.Graphics2D g2 = null; if (bimg == null || bimg.getWidth() != w || bimg.getHeight() != h) { bimg = (java.awt.image.BufferedImage) createImage(w, h); System.out.print("Resizing canvas.\n"); retrack = true; } else { retrack = false; } g2 = bimg.createGraphics(); //g2.setBackground(getBackground()); g2.setBackground(Color.white); //g2.setRenderingHint(java.awt.RenderingHints.KEY_RENDERING, // java.awt.RenderingHints.VALUE_RENDER_QUALITY); g2.clearRect(0, 0, w, h); return g2; } public void drawMap(int bgWidth, int bgHeight, java.awt.Graphics2D g2) { int imageWidth, imageHeight; final int fgWidth = 5*zoomlevel; //int fgHeight = foreground.getHeight(this); //int fgHeight = 20; final int fgHeight = 5*zoomlevel; Color goalColor = Color.blue; Color posColor = Color.red; Color goalTextColor = Color.orange; java.awt.image.BufferedImage bimgrob = (java.awt.image.BufferedImage) createImage(fgWidth, fgHeight); java.awt.Graphics2D grob = bimgrob.createGraphics(); grob.setBackground(Color.white); grob.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON); grob.setRenderingHint(java.awt.RenderingHints.KEY_RENDERING, java.awt.RenderingHints.VALUE_RENDER_QUALITY); //If we have a valid width and height for the //background image, draw it. imageWidth = background.getWidth(this); imageHeight = background.getHeight(this); if ((imageWidth > 0) && (imageHeight > 0)) { g2.drawImage(background,0,0, imageWidth*zoomlevel,imageHeight*zoomlevel, this); } // Draw in goal points if (isInitialized) { for (int i = 0; i < numGoals; i++) { int goalx = Integer.valueOf(goals[i][0]).intValue(); int goaly = Integer.valueOf(goals[i][1]).intValue(); //System.out.println("Plotting a goal at x=" + goals[i][0] //+ " y=" + goals[i][1] + " name=" + goals[i][2]); g2.setColor(goalColor); g2.fillOval((goalx*zoomlevel) - goalSize/2, (goaly*zoomlevel) - goalSize/2, goalSize, goalSize); // This works, but clutters the canvas g2.setColor(goalTextColor); g2.drawString(goals[i][2], goalx*zoomlevel, (goaly*zoomlevel)-4); } } //If we have a valid width and height for the //foreground image, draw it. //imageWidth = foreground.getWidth(this); //imageHeight = foreground.getHeight(this); imageWidth = fgWidth; imageHeight = fgWidth; if ((imageWidth > 0) && (imageHeight > 0)) { grob.clearRect(0, 0, bimgrob.getWidth(this), bimgrob.getHeight(this)); grob.rotate(Math.toRadians(-PosTheta), bimgrob.getWidth(this)/2, bimgrob.getHeight(this)/2); grob.translate(bimgrob.getWidth(this)/2-imageWidth/2,bimgrob.getHeight(this)/2-imageHeight/2); grob.drawImage(foreground,0,0,imageWidth,imageHeight,this); //angdeg += 2; //if (angdeg == 360) { // angdeg = 0; //} grob.dispose(); g2.drawImage(bimgrob, (PosX*zoomlevel) - imageWidth/2, (background.getHeight(this) - PosY)*zoomlevel - imageHeight/2,imageWidth,imageHeight, this); // Don't scroll unless we need to if (trackEnabled && (!stopTracking) && (!retrack)) { int animx = pictureScrollPane.getWidth(); int animy = pictureScrollPane.getHeight(); int winstartx = (PosX*zoomlevel) - animx/2; if (winstartx < 0) { winstartx = 0; } int winstarty = (background.getHeight(this) - PosY)*zoomlevel - animy/2; if (winstarty < 0) { winstarty = 0; } //robot position text g2.setColor(posColor); g2.drawString("Theta =" + String.valueOf(PosTheta), winstartx+3, winstarty+animy-6); g2.drawString("Robot x=" + String.valueOf(PosX), winstartx+3, winstarty+animy-16); g2.drawString("Robot y=" + String.valueOf(PosY), winstartx+3, winstarty+animy-26); //System.out.print("Width=" + String.valueOf(animx) + ", Height=" + String.valueOf(animy) + ", Startx=" // + String.valueOf(winstartx) + ", Starty=" + String.valueOf(winstarty) + "."); animationPane.scrollRectToVisible(new java.awt.Rectangle(winstartx, winstarty, animx-20, animy-20)); //pictureScrollPane.scrollRectToVisible(new java.awt.Rectangle(winstartx, winstarty, animx, animy)); //pictureScrollPane.repaint(); //animationPane.revalidate(); lastPosX = PosX; lastPosY = PosY; } else { //robot position text g2.setColor(posColor); g2.drawString("Theta =" + String.valueOf(PosTheta), 2, background.getHeight(this)-4); g2.drawString("Robot x=" + String.valueOf(PosX), 2, background.getHeight(this)-14); g2.drawString("Robot y=" + String.valueOf(PosY), 2, background.getHeight(this)-24); } } } //Draw the current frame of animation. public void paintComponent(java.awt.Graphics g) { int imageWidth, imageHeight; //super.paintComponent(g); //paint any space not covered by the background image imageWidth = background.getWidth(this)*zoomlevel; imageHeight = background.getHeight(this)*zoomlevel; if ((imageWidth > 0) && (imageHeight > 0)) { if ((retrack) || (PosX != lastPosX) || (PosY != lastPosY) || (PosTheta != lastPosTheta) || (bimg == null)) { java.awt.Graphics2D g2 = createGraphics2D(imageWidth, imageHeight); drawMap(imageWidth, imageHeight, g2); g2.dispose(); // This may not be required g.clearRect(0, 0, imageWidth, imageHeight); g.drawImage(bimg, 0, 0, this); bimg.flush(); } } else { System.out.println("Either imagewidth or imageheight are invalid!"); } } } /** * The DemoControls class adds buttons for position updating, * and zoom level. */ class DemoControls extends javax.swing.JPanel implements java.awt.event.ActionListener { ZazaMap2.AnimationPane demo; javax.swing.JToolBar toolbar; protected JButton b1, b2, b3, b4, b5; public DemoControls(ZazaMap2.AnimationPane demo) { javax.swing.ImageIcon icon; javax.swing.JLabel iconlabel; this.demo = demo; //setBackground(Color.gray); setBackground(Color.black); add(toolbar = new javax.swing.JToolBar()); toolbar.setFloatable(false); //toolbar.setFloatable(true); toolbar.setBackground(Color.black); try { icon = new javax.swing.ImageIcon(new URL("http://zazaconsole.exhibits.thetech.org/~brudy/zaza/java/images/map-title.jpg")); iconlabel = new javax.swing.JLabel(icon); toolbar.add(iconlabel); } catch (MalformedURLException e) { } catch (IOException e) { } toolbar.addSeparator(); b1 = new JButton("Pos"); b1.setToolTipText("Download robot position data"); b1.setBackground(Color.green); b1.setSelected(true); b1.addActionListener(this); toolbar.add(b1); b5 = new JButton("Track"); b5.setToolTipText("Track the robot's position in the window"); b5.setBackground(Color.lightGray); b5.setSelected(false); b5.addActionListener(this); toolbar.add(b5); toolbar.addSeparator(); b2 = new JButton("1x"); b2.setToolTipText("Zoom 1x"); b2.setBackground(Color.green); b2.setSelected(true); b2.addActionListener(this); toolbar.add(b2); b3 = new JButton("2x"); b3.setToolTipText("Zoom 2x"); b3.setBackground(Color.lightGray); b3.setSelected(false); b3.addActionListener(this); toolbar.add(b3); b4 = new JButton("5x"); b4.setToolTipText("Zoom 5x"); b4.setBackground(Color.lightGray); b4.setSelected(false); b4.addActionListener(this); toolbar.add(b4); //toolbar.addSeparator(); //JTextField textField = new JTextField("Zaza!"); //toolbar.add(textField); } public void actionPerformed(java.awt.event.ActionEvent e) { JButton b = (JButton) e.getSource(); b.setSelected(!b.isSelected()); b.setBackground(b.isSelected() ? Color.green : Color.lightGray); if (b.getText().equals("Pos")) { posEnabled = b.isSelected(); } else if (b.getText().equals("Track")) { if (b.isSelected()) { pictureScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); pictureScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); } else { pictureScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); pictureScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); } trackEnabled = b.isSelected(); } else if (b.getText().equals("1x")) { zoomlevel = 1; b3.setSelected(false); b3.setBackground(Color.lightGray); b4.setSelected(false); b4.setBackground(Color.lightGray); } else if (b.getText().equals("2x")) { zoomlevel = 2; b2.setSelected(false); b2.setBackground(Color.lightGray); b4.setSelected(false); b4.setBackground(Color.lightGray); } else if (b.getText().equals("5x")) { zoomlevel = 5; b2.setSelected(false); b2.setBackground(Color.lightGray); b3.setSelected(false); b3.setBackground(Color.lightGray); } // Might want to do something different here ie. re-center on current position fixScrollbars(); } } // End DemoControls class //Invoked only when run as an application (which should only happen during testing) public static void main(String[] args) { //Image bgImage = java.awt.Toolkit.getDefaultToolkit().getImage(ZazaMap2.bgFile); //Image fgImage = java.awt.Toolkit.getDefaultToolkit().getImage(ZazaMap2.fgFile); try { URL bgURL = new URL(ZazaMap2.bgFile); URL fgURL = new URL(ZazaMap2.fgFile); Image bgImage = java.awt.Toolkit.getDefaultToolkit().getImage(bgURL); Image fgImage = java.awt.Toolkit.getDefaultToolkit().getImage(fgURL); javax.swing.JFrame f = new javax.swing.JFrame("ZazaMap2"); final ZazaMap2 controller = new ZazaMap2(); controller.buildUI(f.getContentPane(), bgImage, fgImage); f.addWindowListener(new java.awt.event.WindowAdapter() { public void windowIconified(java.awt.event.WindowEvent e) { controller.stopAnimation(); } public void windowDeiconified(java.awt.event.WindowEvent e) { controller.startAnimation(); } public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); //f.setSize(new java.awt.Dimension(320, 240)); f.pack(); f.setVisible(true); //controller.fixScrollbars(); // Get setup info from server controller.setup(); controller.startAnimation(); } catch (MalformedURLException e) { } catch (IOException e) { } } }