import java.awt.*; import java.util.Vector; /** * The DrawPanel object is Kali's actual drawing area. It receives * and handles the mouse events related to drawing, maintains the list * of line segments that have been drawn (and their colors), keeps * track of whether we're drawing a temporary segment (the segment * that follows the mouse during drawing), and if so, its coordinates. *

* The screen is drawn in Kali whenever the system calls the * DrawPanel's paint() method; this method goes through the list of * current segments, and calls the Panorama's drawSegement() method * for each one. */ class DrawPanel extends Panel { /** * The lists of segments and their colors */ Vector segments = new Vector(); Vector colors = new Vector(); /** * The endpoints of the temporary line segment, in internal * coordinates. */ DVector v1, v2; /** * Are we drawing a temporary segment? */ boolean drawing = false; /** * Max amount of time, in milliseconds, between the clicks * of a double-click. (Two clicks within this amount of time * are considered to be a double-click.) A double-click * ends the current polyline. */ long doubleClickTime = 400; /** * Are we doing double-buffering? (In general we should, * for flicker-free drawing, but this flag is here so we * can turn it off during testing/debugging if we want). */ boolean doublebuffered = true; /** * ids of the KaliCanvas and Panorama objects that this * DrawPanel relates to. */ KaliCanvas kaliCanvas; Panorama panorama; /** * lastMouseTime records the time at which the last MOUSE_DOWN * event happened; it's used in detecting double clicks. */ long lastMouseTime = 0; /** * lastMouseX and lastMouseY record the (raw) screen coordinates * at which the last MOUSE_DOWN occurred. */ int lastMouseX = -1000; int lastMouseY = -1000; /** * closeMouseDistanceSquared is the square of the distance, in * pixels, which is considered "close" for mouse clicks. */ int closeMouseDistanceSquared = 36; /** * Pointer to the offscreen image used for double-buffering. */ static Image offscreen; /** * Create a new DrawPanel object. * @param panorama The Panorama object that this DrawPanel * relates to. * @param kaliCanvas The KaliCanvas object that this DrawPanel * relates to. */ public DrawPanel(Panorama panorama, KaliCanvas kaliCanvas) { setBackground(Color.white); setForeground(Color.black); this.panorama = panorama; this.kaliCanvas = kaliCanvas; } private double screenDistanceSquared(int x1, int y1, int x2, int y2) { int dx = x1 - x2; int dy = y1 - y2; return dx*dx + dy*dy; } /** * Erase the screen and clear the list of segments */ public void clear() { segments.removeAllElements(); colors.removeAllElements(); drawing = false; repaint(); } /** * Handle mouse events */ public boolean handleEvent(Event e) { boolean doubleClicked; boolean mouseClose; switch (e.id) { case Event.MOUSE_DOWN: doubleClicked = (e.when - lastMouseTime < doubleClickTime); mouseClose = (screenDistanceSquared(e.x,e.y, lastMouseX, lastMouseY) <= closeMouseDistanceSquared); lastMouseX = e.x; lastMouseY = e.y; lastMouseTime = e.when; if (drawing) { // we drop the line (quit drawing) if either the MOUSE_DOWN event // happened within doubleClickTime of the previous one, or if // it's considered "close" to the previous one. if (doubleClicked || mouseClose) { drawing = false; repaint(); } else { colors.addElement(getForeground()); segments.addElement(new Segment(v1.copy(), kaliCanvas.rawScreenToInternal(e.x, e.y))); repaint(); v1 = kaliCanvas.rawScreenToInternal(e.x,e.y); } } else { // Don't start a line with a double-click if (!doubleClicked) { v1 = kaliCanvas.rawScreenToInternal(e.x,e.y); drawing = true; } } return true; case Event.MOUSE_DRAG: case Event.MOUSE_MOVE: if (drawing) { v2 = kaliCanvas.rawScreenToInternal(e.x,e.y); repaint(); } return true; case Event.WINDOW_DESTROY: System.exit(0); return true; default: return false; } } /** * Paint the panel, by looping through all the segements * and calling the panorama object's drawSegment() method * for each one. */ public void paint(Graphics g) { int np = segments.size(); if ( (np > 0) || drawing ) { Color foreground = getForeground(); kaliCanvas.setGraphics(g, size().width, size().height); panorama.prepareToDraw(); for (int i=0; i < np; i++) { Segment s = (Segment)segments.elementAt(i); Color c = (Color)colors.elementAt(i); panorama.drawSegment(s, c); } if (drawing) { Segment s = new Segment(v1, v2); panorama.drawSegment(s, foreground); } } } /** * Update the screen, with doublebuffering. */ public void update(Graphics screeng){ // This update method should be pluggable into anything that's a Component // (e.g. a subclass of Panel.) // We assume two instance variables declared above: // Image offscreen; // Scratch space // boolean doublebuffered; // If true, do double-buffering // With luck this may be all you need. // [Given to mbp by asr on Tue Aug 20 15:52:17 1996; originally by slevy?] Rectangle area = screeng.getClipRect(); Graphics g; if (doublebuffered) { /* If we're double-buffering, we need an (off-screen) image to * draw into. Keep one lying around in "offscreen". * If that hasn't yet been initialized, or if our size has * changed since then, then create a new one. */ if(offscreen == null || offscreen.getWidth(null) != area.width || offscreen.getHeight(null) != area.height) { offscreen = createImage(area.width, area.height); } /* We'll render into the off-screen image's graphics area. * Start by clearing it to the background color. */ g = offscreen.getGraphics(); g.clipRect(area.x, area.y, area.width, area.height); } else { /* Otherwise, we're single-buffered; render directly to the screen. */ g = screeng; } g.setColor(getBackground()); g.clearRect(0, 0, area.width, area.height); g.setColor(getForeground()); paint(g); /* * If we were double-buffering, finish by copying the image we just * drew into the relevant piece of the screen. */ if (doublebuffered) { screeng.drawImage(offscreen, area.x, area.y, area.width, area.height, this); } } }