package edu.hws.eck.mdb; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A MandelbrotPanel contains a MandelbrotDisplay and a status bar. The display * computes and displays a visualization of the Mandelbrot Set. The status bar is * a JLabel that is used to display information that the user might be interested * in. A mouse listener is installed on the display that enables the user to * zoom in and out on the image. Nothing is done to stop the user from zooming * in beyond the limited accuracy of numbers of type double -- when this happens, * the picture will first become "blocky" and with even further zooms will become * meaningless. * *

Mouse actions on the display: *

*/ public class MandelbrotPanel extends JPanel { private MandelbrotDisplay display; private JLabel statusBar; /** * Create a panel containing a MandelbrotDisplay and the label that * is used as a status bar. Also add a mouse listener of type * MouseHandler (defined in this class). A ComponentListener * is added that will respond to a change in the size of the * display by reporting the new size in the status bar. */ public MandelbrotPanel() { setLayout(new BorderLayout()); display = new MandelbrotDisplay(); statusBar = new JLabel(I18n.tr("Idle")); add(display,BorderLayout.CENTER); add(statusBar,BorderLayout.SOUTH); MouseHandler mouser = new MouseHandler(); display.addMouseListener(mouser); display.addMouseMotionListener(mouser); display.addComponentListener( new ComponentAdapter() { public void componentResized(ComponentEvent evt) { // Called when the display changes size; report the new // size in the status bar. Note that this is also // called when the display gets its initial size, so // when the program opens, the image size will be shown // in the status bar. String w = "" + display.getWidth(); String h = "" + display.getHeight(); statusBar.setText(I18n.tr("status.imageSize",w,h)); } }); } /** * Returns the MandelbrotDisplay that is contained in this panel. */ public MandelbrotDisplay getDisplay() { return display; } /** * Zoom in on or out from a point in the image. * @param x x-coordinate of the point at the center of the zoom * @param y y-coordinate of the point at the center of the zoom * @param factor magnification or shrinking factor. If factor is * greater than 1, zoom out. If factor is less than 1, zoom in. * For example, factor=0.5 shrinks the x,y ranges in the image * to half their previous size. * @param movePointToCenter if true, then the image point at pixel * position (x,y) is moved to the center pixel of the image after * the zoom; if false, the point is not moved so that the pixel * at (x,y) represents the same point after the zoom as before * and all the other points move towards or away from that one. */ public void zoom(int x, int y, double factor, boolean movePointToCenter) { double xmin = display.getXmin(); double xmax = display.getXmax(); double ymin = display.getYmin(); double ymax = display.getYmax(); double newWidth = factor*(xmax-xmin); double newHeight = factor*(ymax-ymin); double centerX = xmin + ((double)x)/display.getWidth()*(xmax-xmin); double centerY = ymax - ((double)y)/display.getHeight()*(ymax-ymin); if (movePointToCenter) { display.setLimits(centerX-newWidth/2,centerX+newWidth/2, centerY-newHeight/2,centerY+newHeight/2); } else { double newXmin = centerX - newWidth*(centerX-xmin)/(xmax-xmin); double newYmin = centerY - newHeight*(centerY-ymin)/(ymax-ymin); display.setLimits(newXmin,newXmin+newWidth,newYmin,newYmin+newHeight); } } /** * Display the coordinates of the image point that corresponds * to pixel coordinates (x,y). */ private void doShowCoordsInStatusBar(int x, int y) { double xmin = display.getXmin(); double xmax = display.getXmax(); double ymin = display.getYmin(); double ymax = display.getYmax(); double width = display.getWidth(); double height = display.getHeight(); double xCoord = xmin + x/width*(xmax-xmin); double yCoord = ymax - y/height*(ymax-ymin); // The next 10 lines try to avoid more digits after the decimal // points than makes sense. If it succeeds the coordinates // that are shown should differ only in their last few digits. double diff = xmax - xmin; int scale = 4; if (diff > 0) { while (diff < 1) { scale++; diff *= 10; } } String xStr = String.format("%1." + scale + "f", xCoord); String yStr = String.format("%1." + scale + "f", yCoord); statusBar.setText(I18n.tr("status.mouseCoords",xStr,yStr)); } /** * Defines the listener that responds to user mouse actions on the display. * Note that the (x,y) coordinates for the events refer to the display, since * the listeners are registered to respond to events on the display, not on * this panel. Mouse drags and clicks are used for zooming the image. Dragging * and mouse motion also show the current mouse coordinates in the status bar. */ private class MouseHandler implements MouseListener, MouseMotionListener { int startX, startY; // Location of mousePressed event. boolean dragging; // True if a drag operation is in progress. boolean zoomOut; // True if the action will be a zoom out rather than // a zoom in. This is set to true if the shift key // or meta key is down for the mousePressed action. // (Also true if the right-mouse button is used.) boolean moved; // During a drag operation, this becomes true if // the mouse actually moves at least a few pixels. // If so, the mouse action is interpreted as a // click rather than a drag. boolean movePointToCenter; // True if the click point for a click // operation should be moved to the center // of the image; false if it should not // be moved. This is set in the mousePressed // routine to be true if the alt/option key // is down (or the middle mouse button is used). public void mousePressed(MouseEvent evt) { doShowCoordsInStatusBar(startX,startY); dragging = false; if (display.getStatus() == MandelbrotDisplay.STATUS_OUT_OF_MEMORY) { // If there is not enough memory to have an image in the display, // don't try to zoom it! return; } startX = evt.getX(); startY = evt.getY(); zoomOut = evt.isShiftDown() || evt.isMetaDown(); dragging = true; moved = false; movePointToCenter = evt.isAltDown(); } public void mouseReleased(MouseEvent evt) { if (!dragging) return; if (moved) // If moved is true, this is a drag operation, otherwise, a click. display.applyZoom(zoomOut); // zoom into or out of zoom rect that user has drawn else if (zoomOut) zoom(startX,startY,2,movePointToCenter); // zoom out from point else zoom(startX,startY,0.5,movePointToCenter); // zoom in on point dragging = false; } public void mouseDragged(MouseEvent evt) { int x = evt.getX(); int y = evt.getY(); doShowCoordsInStatusBar(x,y); if (!dragging) return; int width = Math.abs(x-startX); int height = Math.abs(y-startY); if (Math.abs(width) < 3 || Math.abs(height) < 3) { // Too close to start point to have a zoom box. if (!display.drawZoomBox(null)) { // display.drawZoomBox() returns false if there is // some error, such as not having an image, that // prevents having a zoom box. This is unlikely to // happen, but if it does, we should cancel the zoom. dragging = false; } return; } moved = true; // Mouse has moved more than 2 pixels away from start position. // During a draw operation, a zoom box is drawn with one corner at the // mouse's starting position. When the mouse is released, the image // is zoomed out form this box or into this box. // The next 6 lines adjust the shape of the zoom box so that it matches // the shape of the window. This is so that zooming will use the same // magnification factor in both directions. double aspect = (double)width/height; double imageAspect = (double)display.getWidth()/display.getHeight(); if (aspect < imageAspect) width = (int)(width*imageAspect/aspect+0.49); else if (aspect > imageAspect) height = (int)(height*aspect/imageAspect+0.49); // The next 9 lines compute the upper left corner of the rectangle, // so that it has one corner at the start position of the mouse; // width and height represent the size of the zoom rect. int x1,y1; if (x < startX) x1 = startX - width; else x1 = startX; if (y < startY) y1 = startY - height; else y1 = startY; Rectangle rect = new Rectangle(x1,y1,width,height); if (!display.drawZoomBox(rect)) dragging = false; } public void mouseMoved(MouseEvent evt) { doShowCoordsInStatusBar(evt.getX(),evt.getY()); } public void mouseExited(MouseEvent evt) { // When mouse moves out of the display, get rid of the coordinate // display in the status bar. statusBar.setText("Idle"); } public void mouseEntered(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } } // end nested class MouseHandler } // end class MandelbrotPanel