/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.tools.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Arrays; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; /** * <p><code>NavigableImagePanel</code> is a lightweight container displaying an image that can be zoomed in and out and * panned with ease and simplicity, using an adaptive rendering for high quality display and satisfactory performance. * </p> * * <h3>Image</h3> * * <p>An image is loaded either via a constructor:</p> * * <pre> NavigableImagePanel panel = new NavigableImagePanel(image); * </pre> * * <p>or using a setter:</p> * * <pre> NavigableImagePanel panel = new NavigableImagePanel(); panel.setImage(image); * </pre> * * <p>When an image is set, it is initially painted centered in the component, at the largest possible size, fully * visible, with its aspect ratio is preserved. This is defined as 100% of the image size and its corresponding zoom * level is 1.0.</p> * * <h3>Zooming</h3> * * <p>Zooming can be controlled interactively, using either the mouse scroll wheel (default) or the mouse two buttons, * or programmatically, allowing the programmer to implement other custom zooming methods. If the mouse does not have a * scroll wheel, set the zooming device to mouse buttons:</p> * * <pre> panel.setZoomDevice(ZoomDevice.MOUSE_BUTTON); * </pre> * * <p>The left mouse button works as a toggle switch between zooming in and zooming out modes, and the right button * zooms an image by one increment (default is 20%). You can change the zoom increment value by:</p> * * <pre> panel.setZoomIncrement(newZoomIncrement); * </pre> * * <p>If you intend to provide programmatic zoom control, set the zoom device to none to disable both the mouse wheel * and buttons for zooming purposes:</p> * * <pre> panel.setZoomDevice(ZoomDevice.NONE); * </pre> * * <p>and use <code>setZoom()</code> to change the zoom level.</p> * * <p>Zooming is always around the point the mouse pointer is currently at, so that this point (called a zooming center) * remains stationary ensuring that the area of an image we are zooming into does not disappear off the screen. The * zooming center stays at the same location on the screen and all other points move radially away from it (when zooming * in), or towards it (when zooming out). For programmatically controlled zooming the zooming center is either specified * when <code>setZoom()</code> is called:</p> * * <pre> panel.setZoom(newZoomLevel, newZoomingCenter); * </pre> * * <p>or assumed to be the point of an image which is the closest to the center of the panel, if no zooming center is * specified:</p> * * <pre> panel.setZoom(newZoomLevel); * </pre> * * <p>There are no lower or upper zoom level limits.</p> * * <h3>Navigation</h3> * * <p><code>NavigableImagePanel</code> does not use scroll bars for navigation, but relies on a navigation image located * in the upper left corner of the panel. The navigation image is a small replica of the image displayed in the panel. * When you click on any point of the navigation image that part of the image is displayed in the panel, centered. The * navigation image can also be zoomed in the same way as the main image.</p> * * <p>In order to adjust the position of an image in the panel, it can be dragged with the mouse, using the left button. * </p> * * <p>For programmatic image navigation, disable the navigation image:</p> * * <pre> panel.setNavigationImageEnabled(false) * </pre> * * <p>and use <code>getImageOrigin()</code> and <code>setImageOrigin()</code> to move the image around the panel.</p> * * <h3>Rendering</h3> * * <p><code>NavigableImagePanel</code> uses the Nearest Neighbor interpolation for image rendering (default in Java). * When the scaled image becomes larger than the original image, the Bilinear interpolation is applied, but only to the * part of the image which is displayed in the panel. This interpolation change threshold can be controlled by adjusting * the value of <code>HIGH_QUALITY_RENDERING_SCALE_THRESHOLD</code>.</p> * * @version $Revision$, $Date$ */ public class NavigableImagePanel extends JPanel { //~ Static fields/initializers --------------------------------------------- /** * . * * <p>Identifies a change to the zoom level.</p> */ public static final String ZOOM_LEVEL_CHANGED_PROPERTY = "zoomLevel"; /** * . * * <p>Identifies a change to the zoom increment.</p> */ public static final String ZOOM_INCREMENT_CHANGED_PROPERTY = "zoomIncrement"; /** * . * * <p>Identifies that the image in the panel has changed.</p> */ public static final String IMAGE_CHANGED_PROPERTY = "image"; private static final double SCREEN_NAV_IMAGE_FACTOR = 0.15; // 15% of panel's width private static final double NAV_IMAGE_FACTOR = 0.3; // 30% of panel's width private static final double HIGH_QUALITY_RENDERING_SCALE_THRESHOLD = 1.0; private static final Object INTERPOLATION_TYPE = RenderingHints.VALUE_INTERPOLATION_BILINEAR; //~ Instance fields -------------------------------------------------------- private double zoomIncrement = 0.2; private double zoomFactor = 1.0 + zoomIncrement; private double navZoomFactor = 1.0 + zoomIncrement; private BufferedImage image; private BufferedImage navigationImage; private int navImageWidth; private int navImageHeight; private double initialScale = 0.0; private double scale = 0.0; private double navScale = 0.0; private int originX = 0; private int originY = 0; private Point mousePosition; private Dimension previousPanelSize; private boolean navigationImageEnabled = true; private boolean highQualityRenderingEnabled = true; private WheelZoomDevice wheelZoomDevice = null; private ButtonZoomDevice buttonZoomDevice = null; //~ Constructors ----------------------------------------------------------- /** * <p>Creates a new navigable image panel with no default image and the mouse scroll wheel as the zooming * device.</p> */ public NavigableImagePanel() { setOpaque(false); addComponentListener(new ComponentAdapter() { @Override public void componentResized(final ComponentEvent e) { if (scale > 0.0) { if (isFullImageInPanel()) { centerImage(); } else if (isImageEdgeInPanel()) { scaleOrigin(); } if (isNavigationImageEnabled()) { createNavigationImage(); } repaint(); } previousPanelSize = getSize(); } }); addMouseListener(new MouseAdapter() { @Override public void mousePressed(final MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { if (isInNavigationImage(e.getPoint())) { final Point p = e.getPoint(); displayImageAt(p); } } } }); addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(final MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e) && !isInNavigationImage(e.getPoint())) { final Point p = e.getPoint(); moveImage(p); } } @Override public void mouseMoved(final MouseEvent e) { // we need the mouse position so that after zooming // that position of the image is maintained mousePosition = e.getPoint(); } }); setZoomDevice(ZoomDevice.MOUSE_WHEEL); } /** * <p>Creates a new navigable image panel with the specified image and the mouse scroll wheel as the zooming * device.</p> * * @param image DOCUMENT ME! * * @throws IOException DOCUMENT ME! */ public NavigableImagePanel(final BufferedImage image) throws IOException { this(); setImage(image); } //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! */ private void addWheelZoomDevice() { if (wheelZoomDevice == null) { wheelZoomDevice = new WheelZoomDevice(); addMouseWheelListener(wheelZoomDevice); } } /** * DOCUMENT ME! */ private void addButtonZoomDevice() { if (buttonZoomDevice == null) { buttonZoomDevice = new ButtonZoomDevice(); addMouseListener(buttonZoomDevice); } } /** * DOCUMENT ME! */ private void removeWheelZoomDevice() { if (wheelZoomDevice != null) { removeMouseWheelListener(wheelZoomDevice); wheelZoomDevice = null; } } /** * DOCUMENT ME! */ private void removeButtonZoomDevice() { if (buttonZoomDevice != null) { removeMouseListener(buttonZoomDevice); buttonZoomDevice = null; } } /** * <p>Sets a new zoom device.</p> * * @param newZoomDevice specifies the type of a new zoom device. */ public void setZoomDevice(final ZoomDevice newZoomDevice) { if (newZoomDevice == ZoomDevice.NONE) { removeWheelZoomDevice(); removeButtonZoomDevice(); } else if (newZoomDevice == ZoomDevice.MOUSE_BUTTON) { removeWheelZoomDevice(); addButtonZoomDevice(); } else if (newZoomDevice == ZoomDevice.MOUSE_WHEEL) { removeButtonZoomDevice(); addWheelZoomDevice(); } } /** * <p>Gets the current zoom device.</p> * * @return DOCUMENT ME! */ public ZoomDevice getZoomDevice() { if (buttonZoomDevice != null) { return ZoomDevice.MOUSE_BUTTON; } else if (wheelZoomDevice != null) { return ZoomDevice.MOUSE_WHEEL; } else { return ZoomDevice.NONE; } } /** * Called from paintComponent() when a new image is set. */ private void initializeParams() { final double xScale = (double)getWidth() / image.getWidth(); final double yScale = (double)getHeight() / image.getHeight(); initialScale = Math.min(xScale, yScale); scale = initialScale; // An image is initially centered centerImage(); // if (isNavigationImageEnabled()) { createNavigationImage(); // } } /** * Centers the current image in the panel. */ private void centerImage() { originX = (int)(getWidth() - getScreenImageWidth()) / 2; originY = (int)(getHeight() - getScreenImageHeight()) / 2; } /** * Creates and renders the navigation image in the upper let corner of the panel. */ private void createNavigationImage() { // We keep the original navigation image larger than initially // displayed to allow for zooming into it without pixellation effect. navImageWidth = (int)(getWidth() * NAV_IMAGE_FACTOR); navImageHeight = navImageWidth * image.getHeight() / image.getWidth(); final int scrNavImageWidth = (int)(getWidth() * SCREEN_NAV_IMAGE_FACTOR); final int scrNavImageHeight = scrNavImageWidth * image.getHeight() / image.getWidth(); navScale = (double)scrNavImageWidth / navImageWidth; navigationImage = new BufferedImage(navImageWidth, navImageHeight, image.getType()); final Graphics g = navigationImage.getGraphics(); g.drawImage(image, 0, 0, navImageWidth, navImageHeight, null); } /** * <p>Sets an image for display in the panel.</p> * * @param image an image to be set in the panel */ public void setImage(final BufferedImage image) { final BufferedImage oldImage = this.image; this.image = image; // Reset scale so that initializeParameters() is called in paintComponent() // for the new image. scale = 0.0; firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image); repaint(); } /** * <p>Tests whether an image uses the standard RGB color space.</p> * * @param bImage DOCUMENT ME! * * @return DOCUMENT ME! */ public static boolean isStandardRGBImage(final BufferedImage bImage) { return bImage.getColorModel().getColorSpace().isCS_sRGB(); } /** * Converts this panel's coordinates into the original image coordinates. * * @param p DOCUMENT ME! * * @return DOCUMENT ME! */ private Coords panelToImageCoords(final Point p) { return new Coords((p.x - originX) / scale, (p.y - originY) / scale); } /** * Converts the original image coordinates into this panel's coordinates. * * @param p DOCUMENT ME! * * @return DOCUMENT ME! */ private Coords imageToPanelCoords(final Coords p) { return new Coords((p.x * scale) + originX, (p.y * scale) + originY); } /** * Converts the navigation image coordinates into the zoomed image coordinates. * * @param p DOCUMENT ME! * * @return DOCUMENT ME! */ private Point navToZoomedImageCoords(final Point p) { final int x = p.x * getScreenImageWidth() / getScreenNavImageWidth(); final int y = p.y * getScreenImageHeight() / getScreenNavImageHeight(); return new Point(x, y); } /** * The user clicked within the navigation image and this part of the image is displayed in the panel. The clicked * point of the image is centered in the panel. * * @param p DOCUMENT ME! */ private void displayImageAt(final Point p) { final Point scrImagePoint = navToZoomedImageCoords(p); originX = -(scrImagePoint.x - (getWidth() / 2)); originY = -(scrImagePoint.y - (getHeight() / 2)); repaint(); } /** * Tests whether a given point in the panel falls within the image boundaries. * * @param p DOCUMENT ME! * * @return DOCUMENT ME! */ private boolean isInImage(final Point p) { final Coords coords = panelToImageCoords(p); final int x = coords.getIntX(); final int y = coords.getIntY(); return ((x >= 0) && (x < image.getWidth()) && (y >= 0) && (y < image.getHeight())); } /** * Tests whether a given point in the panel falls within the navigation image boundaries. * * @param p DOCUMENT ME! * * @return DOCUMENT ME! */ private boolean isInNavigationImage(final Point p) { return (isNavigationImageEnabled() && (p.x < getScreenNavImageWidth()) && (p.y < getScreenNavImageHeight())); } /** * Used when the image is resized. * * @return DOCUMENT ME! */ private boolean isImageEdgeInPanel() { if (previousPanelSize == null) { return false; } return (((originX > 0) && (originX < previousPanelSize.width)) || ((originY > 0) && (originY < previousPanelSize.height))); } /** * Tests whether the image is displayed in its entirety in the panel. * * @return DOCUMENT ME! */ private boolean isFullImageInPanel() { return ((originX >= 0) && ((originX + getScreenImageWidth()) < getWidth()) && (originY >= 0) && ((originY + getScreenImageHeight()) < getHeight())); } /** * <p>Indicates whether the high quality rendering feature is enabled.</p> * * @return true if high quality rendering is enabled, false otherwise. */ public boolean isHighQualityRenderingEnabled() { return highQualityRenderingEnabled; } /** * <p>Enables/disables high quality rendering.</p> * * @param enabled enables/disables high quality rendering */ public void setHighQualityRenderingEnabled(final boolean enabled) { highQualityRenderingEnabled = enabled; } /** * High quality rendering kicks in when when a scaled image is larger than the original image. In other words, when * image decimation stops and interpolation starts. * * @return DOCUMENT ME! */ private boolean isHighQualityRendering() { return (highQualityRenderingEnabled && (scale > HIGH_QUALITY_RENDERING_SCALE_THRESHOLD)); } /** * <p>Indicates whether navigation image is enabled.</p> * * @return true when navigation image is enabled, false otherwise. */ public boolean isNavigationImageEnabled() { return navigationImageEnabled; } /** * <p>Enables/disables navigation with the navigation image.</p> * * <p>Navigation image should be disabled when custom, programmatic navigation is implemented.</p> * * @param enabled true when navigation image is enabled, false otherwise. */ public void setNavigationImageEnabled(final boolean enabled) { navigationImageEnabled = enabled; repaint(); } /** * Used when the panel is resized. */ private void scaleOrigin() { originX = originX * getWidth() / previousPanelSize.width; originY = originY * getHeight() / previousPanelSize.height; repaint(); } /** * Converts the specified zoom level to scale. * * @param zoom DOCUMENT ME! * * @return DOCUMENT ME! */ private double zoomToScale(final double zoom) { return initialScale * zoom; } /** * <p>Gets the current zoom level.</p> * * @return the current zoom level */ public double getZoom() { return scale / initialScale; } /** * <p>Sets the zoom level used to display the image.</p> * * <p>This method is used in programmatic zooming. The zooming center is the point of the image closest to the * center of the panel. After a new zoom level is set the image is repainted.</p> * * @param newZoom the zoom level used to display this panel's image. */ public void setZoom(final double newZoom) { final Point zoomingCenter = new Point(getWidth() / 2, getHeight() / 2); setZoom(newZoom, zoomingCenter); } /** * <p>Sets the zoom level used to display the image, and the zooming center, around which zooming is done.</p> * * <p>This method is used in programmatic zooming. After a new zoom level is set the image is repainted.</p> * * @param newZoom the zoom level used to display this panel's image. * @param zoomingCenter DOCUMENT ME! */ public void setZoom(final double newZoom, final Point zoomingCenter) { final Coords imageP = panelToImageCoords(zoomingCenter); if (imageP.x < 0.0) { imageP.x = 0.0; } if (imageP.y < 0.0) { imageP.y = 0.0; } if (imageP.x >= image.getWidth()) { imageP.x = image.getWidth() - 1.0; } if (imageP.y >= image.getHeight()) { imageP.y = image.getHeight() - 1.0; } final Coords correctedP = imageToPanelCoords(imageP); final double oldZoom = getZoom(); scale = zoomToScale(newZoom); final Coords panelP = imageToPanelCoords(imageP); originX += (correctedP.getIntX() - (int)panelP.x); originY += (correctedP.getIntY() - (int)panelP.y); firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom), new Double(getZoom())); repaint(); } /** * <p>Gets the current zoom increment.</p> * * @return the current zoom increment */ public double getZoomIncrement() { return zoomIncrement; } /** * <p>Sets a new zoom increment value.</p> * * @param newZoomIncrement new zoom increment value */ public void setZoomIncrement(final double newZoomIncrement) { final double oldZoomIncrement = zoomIncrement; zoomIncrement = newZoomIncrement; firePropertyChange(ZOOM_INCREMENT_CHANGED_PROPERTY, new Double(oldZoomIncrement), new Double(zoomIncrement)); } /** * Zooms an image in the panel by repainting it at the new zoom level. The current mouse position is the zooming * center. */ private void zoomImage() { final Coords imageP = panelToImageCoords(mousePosition); final double oldZoom = getZoom(); scale *= zoomFactor; final Coords panelP = imageToPanelCoords(imageP); originX += (mousePosition.x - (int)panelP.x); originY += (mousePosition.y - (int)panelP.y); firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom), new Double(getZoom())); repaint(); } /** * Zooms the navigation image. */ private void zoomNavigationImage() { navScale *= navZoomFactor; repaint(); } /** * <p>Gets the image origin.</p> * * <p>Image origin is defined as the upper, left corner of the image in the panel's coordinate system.</p> * * @return the point of the upper, left corner of the image in the panel's coordinates system. */ public Point getImageOrigin() { return new Point(originX, originY); } /** * <p>Sets the image origin.</p> * * <p>Image origin is defined as the upper, left corner of the image in the panel's coordinate system. After a new * origin is set, the image is repainted. This method is used for programmatic image navigation.</p> * * @param x the x coordinate of the new image origin * @param y the y coordinate of the new image origin */ public void setImageOrigin(final int x, final int y) { setImageOrigin(new Point(x, y)); } /** * <p>Sets the image origin.</p> * * <p>Image origin is defined as the upper, left corner of the image in the panel's coordinate system. After a new * origin is set, the image is repainted. This method is used for programmatic image navigation.</p> * * @param newOrigin the value of a new image origin */ public void setImageOrigin(final Point newOrigin) { originX = newOrigin.x; originY = newOrigin.y; repaint(); } /** * Moves te image (by dragging with the mouse) to a new mouse position p. * * @param p DOCUMENT ME! */ private void moveImage(final Point p) { final int xDelta = p.x - mousePosition.x; final int yDelta = p.y - mousePosition.y; originX += xDelta; originY += yDelta; mousePosition = p; repaint(); } /** * Gets the bounds of the image area currently displayed in the panel (in image coordinates). * * @return DOCUMENT ME! */ private Rectangle getImageClipBounds() { final Coords startCoords = panelToImageCoords(new Point(0, 0)); final Coords endCoords = panelToImageCoords(new Point(getWidth() - 1, getHeight() - 1)); final int panelX1 = startCoords.getIntX(); final int panelY1 = startCoords.getIntY(); final int panelX2 = endCoords.getIntX(); final int panelY2 = endCoords.getIntY(); // No intersection? if ((panelX1 >= image.getWidth()) || (panelX2 < 0) || (panelY1 >= image.getHeight()) || (panelY2 < 0)) { return null; } final int x1 = (panelX1 < 0) ? 0 : panelX1; final int y1 = (panelY1 < 0) ? 0 : panelY1; final int x2 = (panelX2 >= image.getWidth()) ? (image.getWidth() - 1) : panelX2; final int y2 = (panelY2 >= image.getHeight()) ? (image.getHeight() - 1) : panelY2; return new Rectangle(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } /** * Paints the panel and its image at the current zoom level, location, and interpolation method dependent on the * image scale. * * @param g the <code>Graphics</code> context for painting */ @Override protected void paintComponent(final Graphics g) { super.paintComponent(g); // Paints the background if (image == null) { return; } if (scale == 0.0) { initializeParams(); } if (isHighQualityRendering()) { final Rectangle rect = getImageClipBounds(); if ((rect == null) || (rect.width == 0) || (rect.height == 0)) { // no part of image is displayed in the // panel return; } final BufferedImage subimage = image.getSubimage(rect.x, rect.y, rect.width, rect.height); final Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, INTERPOLATION_TYPE); g2.drawImage( subimage, Math.max(0, originX), Math.max(0, originY), Math.min((int)(subimage.getWidth() * scale), getWidth()), Math.min((int)(subimage.getHeight() * scale), getHeight()), null); } else { g.drawImage(image, originX, originY, getScreenImageWidth(), getScreenImageHeight(), null); } // Draw navigation image if (isNavigationImageEnabled()) { g.drawImage(navigationImage, 0, 0, getScreenNavImageWidth(), getScreenNavImageHeight(), null); final Color backup = g.getColor(); g.setColor(Color.BLACK); g.drawRect(0, 0, getScreenNavImageWidth(), getScreenNavImageHeight()); g.setColor(backup); drawZoomAreaOutline(g); } } /** * Paints a white outline over the navigation image indicating the area of the image currently displayed in the * panel. * * @param g DOCUMENT ME! */ private void drawZoomAreaOutline(final Graphics g) { if (isFullImageInPanel()) { return; } final int x = -originX * getScreenNavImageWidth() / getScreenImageWidth(); final int y = -originY * getScreenNavImageHeight() / getScreenImageHeight(); final int width = getWidth() * getScreenNavImageWidth() / getScreenImageWidth(); final int height = getHeight() * getScreenNavImageHeight() / getScreenImageHeight(); g.setColor(Color.RED); g.drawRect(x, y, width, height); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ private int getScreenImageWidth() { return (int)(scale * image.getWidth()); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ private int getScreenImageHeight() { return (int)(scale * image.getHeight()); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ private int getScreenNavImageWidth() { return (int)(navScale * navImageWidth); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ private int getScreenNavImageHeight() { return (int)(navScale * navImageHeight); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ private static String[] getImageFormatExtensions() { final String[] names = ImageIO.getReaderFormatNames(); for (int i = 0; i < names.length; i++) { names[i] = names[i].toLowerCase(); } Arrays.sort(names); return names; } /** * DOCUMENT ME! * * @param name DOCUMENT ME! * * @return DOCUMENT ME! */ private static boolean endsWithImageFormatExtension(final String name) { final int dotIndex = name.lastIndexOf("."); if (dotIndex == -1) { return false; } final String extension = name.substring(dotIndex + 1).toLowerCase(); return (Arrays.binarySearch(getImageFormatExtensions(), extension) >= 0); } /** * DOCUMENT ME! * * @param args DOCUMENT ME! */ public static void main(final String[] args) { if (args.length == 0) { System.out.println("Usage: java NavigableImagePanel imageFilename"); System.exit(1); } final String filename = args[0]; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { final JFrame frame = new JFrame("Navigable Image Panel"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final NavigableImagePanel panel = new NavigableImagePanel(); try { final BufferedImage image = ImageIO.read(new File(filename)); panel.setImage(image); } catch (IOException e) { JOptionPane.showMessageDialog(null, e.getMessage(), "", JOptionPane.ERROR_MESSAGE); System.exit(1); } frame.getContentPane().add(panel, BorderLayout.CENTER); final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); final Rectangle bounds = ge.getMaximumWindowBounds(); frame.setSize(new Dimension(bounds.width, bounds.height)); frame.setVisible(true); } }); } //~ Inner Classes ---------------------------------------------------------- /** * <p>Defines zoom devices.</p> * * @version $Revision$, $Date$ */ public static class ZoomDevice { //~ Static fields/initializers ----------------------------------------- /** * <p>Identifies that the panel does not implement zooming, but the component using the panel does (programmatic * zooming method).</p> */ public static final ZoomDevice NONE = new ZoomDevice("none"); /** * . * * <p>Identifies the left and right mouse buttons as the zooming device.</p> */ public static final ZoomDevice MOUSE_BUTTON = new ZoomDevice("mouseButton"); /** * . * * <p>Identifies the mouse scroll wheel as the zooming device.</p> */ public static final ZoomDevice MOUSE_WHEEL = new ZoomDevice("mouseWheel"); //~ Instance fields ---------------------------------------------------- private String zoomDevice; //~ Constructors ------------------------------------------------------- /** * Creates a new ZoomDevice object. * * @param zoomDevice DOCUMENT ME! */ private ZoomDevice(final String zoomDevice) { this.zoomDevice = zoomDevice; } //~ Methods ------------------------------------------------------------ @Override public String toString() { return zoomDevice; } } /** * This class is required for high precision image coordinates translation. * * @version $Revision$, $Date$ */ private class Coords { //~ Instance fields ---------------------------------------------------- public double x; public double y; //~ Constructors ------------------------------------------------------- /** * Creates a new Coords object. * * @param x DOCUMENT ME! * @param y DOCUMENT ME! */ public Coords(final double x, final double y) { this.x = x; this.y = y; } //~ Methods ------------------------------------------------------------ /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public int getIntX() { return (int)Math.round(x); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public int getIntY() { return (int)Math.round(y); } @Override public String toString() { return "[Coords: x=" + x + ",y=" + y + "]"; } } /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ private class WheelZoomDevice implements MouseWheelListener { //~ Methods ------------------------------------------------------------ @Override public void mouseWheelMoved(final MouseWheelEvent e) { final Point p = e.getPoint(); final boolean zoomIn = (e.getWheelRotation() < 0); if (isInNavigationImage(p)) { if (zoomIn) { navZoomFactor = 1.0 + zoomIncrement; } else { navZoomFactor = 1.0 - zoomIncrement; } zoomNavigationImage(); } else if (isInImage(p)) { if (zoomIn) { zoomFactor = 1.0 + zoomIncrement; } else { zoomFactor = 1.0 - zoomIncrement; } zoomImage(); } } } /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ private class ButtonZoomDevice extends MouseAdapter { //~ Methods ------------------------------------------------------------ @Override public void mouseClicked(final MouseEvent e) { final Point p = e.getPoint(); if (SwingUtilities.isRightMouseButton(e)) { if (isInNavigationImage(p)) { navZoomFactor = 1.0 - zoomIncrement; zoomNavigationImage(); } else if (isInImage(p)) { zoomFactor = 1.0 - zoomIncrement; zoomImage(); } } else { if (isInNavigationImage(p)) { navZoomFactor = 1.0 + zoomIncrement; zoomNavigationImage(); } else if (isInImage(p)) { zoomFactor = 1.0 + zoomIncrement; zoomImage(); } } } } }