/* * org.openmicroscopy.shoola.agents.imviewer.browser.BirdEyeView * *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 University of Dundee. All rights reserved. * * * 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.agents.imviewer.browser; //Java imports import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.util.Arrays; import javax.swing.JPanel; //Third-party libraries //Application-internal dependencies import org.openmicroscopy.shoola.agents.imviewer.util.ImagePaintingFactory; import org.openmicroscopy.shoola.util.ui.UIUtilities; /** * Bird eye view using <code>JPanel</code>. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * <small> * (<b>Internal version:</b> $Revision: $Date: $) * </small> * @since 3.0-Beta4 */ class BirdEyeViewComponent extends JPanel implements MouseListener, MouseMotionListener { /** Property indicating to render a region. */ static final String DISPLAY_REGION_PROPERTY = "displayRegion"; /** Property indicating to the component is collapsed or expanded.*/ static final String FULL_DISPLAY_PROPERTY = "fullDisplay"; /** The width of the border. */ static final int BORDER = 2; /** The width of the border x 5. */ static final int BORDER_5 = 5*BORDER; /** The default fill color. */ private static final Color FILL_COLOR = Color.LIGHT_GRAY; /** The default stroke color. */ private static final Color STROKE_COLOR = Color.BLACK; /** The default color for the border. */ private static final Color BORDER_COLOR = Color.LIGHT_GRAY;//Color.WHITE; /** The default selection color. */ private static final Color SELECTION_COLOR = new Color(255, 0, 0, 100); /** The default selection color. */ private static final Color SELECTION_COLOR_BORDER = Color.red; /** The default selection color. */ private static final Color SELECTION_BLUE_COLOR = new Color(0, 0, 255, 100); /** The default selection color. */ private static final Color SELECTION_BLUE_COLOR_BORDER = Color.blue; /** The processing image. */ private BufferedImage pImage; /** Color of the selection rectangle. */ private Color color; /** Color of the border of the selection rectangle. */ private Color colorBorder; /** The width of the rectangle. */ private int w = 30; //to change /** The width of the rectangle. */ private int h = 20; //to change /** The X-coordinate of the top-left corner. */ private int bx; /** The Y-coordinate of the top-left corner. */ private int by; /** The X-coordinate of the top-left corner. */ private int ax; /** The Y-coordinate of the top-left corner. */ private int ay; /** Flag indicating the mouse is over the image. */ private boolean release; /** Flag indicating the mouse is over the image. */ private boolean bover; /** Flag indicating the mouse is locked. */ private boolean locked = false; /** The X-coordinate of the top-left corner. */ private int px; /** The Y-coordinate of the top-left corner. */ private int py; /** * The difference of <code>bx</code>and the X-coordinate of the mouse * clicked. */ private int bdifx = 0; /** * The difference of <code>by</code>and the Y-coordinate of the mouse * clicked. */ private int bdify = 0; /** Flag indicating to display the full image or only the arrow. */ private boolean fullDisplay; /** The length of the side of the arrow. */ private int v = 4; /** The X-coordinate of the arrow. */ private int xArrow = 2; /** The Y-coordinate of the arrow. */ private int yArrow = 2; /** The area covered by the image. */ private Rectangle imageRectangle; /** The area covered by the cross. */ private Rectangle cross; /** The width of the canvas. */ private int canvasWidth; /** The height of the canvas. */ private int canvasHeight; /** The location of the mouse.*/ private int mouseX, mouseY; /** One of the constants defined by this class.*/ private int locationIndex; /** Indicates if the mouse pressed occurred on the cross or not.*/ private boolean inCross = false; /** * Returns <code>true</code> if the specified coordinates are contained * in the selection, <code>false</code> otherwise. * * @param x The X-coordinate of the mouse pressed. * @param y The Y-coordinate of the mouse pressed. * @return See above. */ private boolean inSelection(int x, int y) { if (x < bx || x > (bx+w)) return false; if (y < by || y > (by+h)) return false; return true; } /** * Returns <code>true</code> if the region is the full size of the image * <code>false</code> otherwise. * * @param r The region to handle. * @return See above. */ private boolean isSameSelection(Rectangle r) { if (r.width == imageRectangle.width && r.height == imageRectangle.height) return true; return (r.x == px && r.y == py); } /** Sets the location of the cross.*/ private void setCrossLocation() { if (!fullDisplay) { cross.x = 0; cross.y = 0; return; } switch (locationIndex) { case ImageCanvas.BOTTOM_RIGHT: cross.x = canvasWidth-cross.width; cross.y = canvasHeight-cross.height; break; case ImageCanvas.TOP_LEFT: cross.x = 0; cross.y = 0; } } /** * Returns <code>true</code> if the rectangle is in the image, * <code>false</code> otherwise. * * @return See above. */ private boolean inImage() { boolean b = true; if (bx < imageRectangle.x) { bx = imageRectangle.x; b = false; } if (by < imageRectangle.y) { by = imageRectangle.y; b = false; } if (!b) return b; if (bx+w > imageRectangle.width) { bx = imageRectangle.width-w; b = false; } if (by+h > imageRectangle.height) { by = imageRectangle.height-h; b = false; } return b; } /** * Sets the cursor depending on the specific location. * * @param x The X-coordinate of the mouse pressed. * @param y The Y-coordinate of the mouse pressed. */ private void setCursor(int x, int y) { boolean b = inSelection(x, y); if (b) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { setCursor(Cursor.getDefaultCursor()); } } /** * Creates a new instance. * * @param locationIndex One of the location constants defined by this class. */ BirdEyeViewComponent(int locationIndex) { fullDisplay = true; pImage = null; release = true; switch (locationIndex) { case ImageCanvas.TOP_LEFT: case ImageCanvas.BOTTOM_RIGHT: this.locationIndex = locationIndex; break; default: this.locationIndex = ImageCanvas.TOP_LEFT; } cross = new Rectangle(0, 0, BORDER_5, BORDER_5); } /** Creates a new instance. */ BirdEyeViewComponent() { this(ImageCanvas.TOP_LEFT); } /** * Returns the region used to select the part of the image to view. * * @return See above. */ Rectangle getSelectionRegion() { return new Rectangle(bx, by, w, h); } /** * Sets the location of the selection rectangle. * * @param x The X-coordinate of the location. * @param y The Y-coordinate of the location. * @param w The width of the selection region. * @param h The width of the selection region. */ void setSelection(int x, int y, int w, int h) { bx = x; by = y; if (w > imageRectangle.width) w = imageRectangle.width; if (h > imageRectangle.height) h = imageRectangle.height; this.w = w; this.h = h; if (bx < 0) { this.w += x; bx = 0; } else if (bx+w > imageRectangle.width) { this.w = imageRectangle.width-bx; } if (by < 0) { this.h += y; by = 0; } else if (by+h > imageRectangle.height) { this.h = imageRectangle.height-by; } repaint(); } /** * Sets the size of the canvas. * * @param w The width of the canvas. * @param h The height of the canvas. */ void setCanvasSize(int w, int h) { setSize(w, h); canvasWidth = w; canvasHeight = h; setCrossLocation(); } /** * Adds an image to the display. * * @param image The image to display. */ void setImage(BufferedImage image) { pImage = image; if (image != null) { long count = 0; long totalRed = 0, totalGreen = 0, totalBlue = 0; Color c; //determine the color of the lens for (int i = 0 ; i < image.getWidth() ; i += 10) { for (int j = 0 ; j < image.getHeight() ; j += 10) { count++; c = new Color(image.getRGB(i, j)); totalRed += c.getRed(); totalGreen += c.getGreen(); totalBlue += c.getBlue(); } } c = new Color((int) (totalRed/count), (int) (totalGreen/count), (int) (totalBlue/count)); int result = UIUtilities.getColorRange(c); if (result == UIUtilities.RED_COLOR) setSelectionColor(SELECTION_BLUE_COLOR); else setSelectionColor(SELECTION_COLOR); setCanvasSize(image.getWidth(), image.getHeight()); imageRectangle = new Rectangle(0, 0, pImage.getWidth(), pImage.getHeight()); if (w == 0) w = image.getWidth(); if (h == 0) h = image.getHeight(); } repaint(); } /** * Sets the selection color. * * @param color The value to set. */ void setSelectionColor(Color color) { if (color != null) { this.color = color; if (color.equals(SELECTION_COLOR)) colorBorder = SELECTION_COLOR_BORDER; else colorBorder = SELECTION_BLUE_COLOR_BORDER; } } /** * Returns the location of the bird eye view. * * @return See above. */ int getLocationIndex() { return locationIndex; } /** * Sets the component up. * * @param x The default value for the X-coordinate of the top left corner. * @param y The default value for the Y-coordinate of the top left corner. */ void setup(int x, int y) { if (x < 0) x = 0; if (y < 0) y = 0; setSelectionColor(SELECTION_COLOR); bx = x; by = x; fullDisplay = true; installListeners(true); } /** * Adds or removes the listeners depending on the specified components. * * @param add Pass <code>true</code> to attach the listeners, * <code>false</code> otherwise. */ void installListeners(boolean add) { if (add) { // prevent adding the listeners multiple times if (!Arrays.asList(getMouseListeners()).contains(this)) { addMouseListener(this); addMouseMotionListener(this); } } else { removeMouseListener(this); removeMouseMotionListener(this); } } /** * Returns the size of the image displayed in the component. * * @return See above. */ Dimension getImageSize() { if (pImage == null) return null; return new Dimension(pImage.getWidth(), pImage.getHeight()); } /** * Overridden to paint the image. * @see javax.swing.JComponent#paintComponent(Graphics) */ public void paintComponent(Graphics g) { super.paintComponent(g); if (pImage == null) return; Graphics2D g2D = (Graphics2D) g; ImagePaintingFactory.setGraphicRenderingSettings(g2D); setCrossLocation(); if (!fullDisplay) { g2D.setColor(FILL_COLOR); g2D.fillRect(cross.x, cross.y, cross.width, cross.height); g2D.setColor(STROKE_COLOR); switch (locationIndex) { case ImageCanvas.BOTTOM_RIGHT: g2D.drawLine(xArrow, yArrow, BORDER_5, BORDER_5); g2D.drawLine(xArrow, yArrow, xArrow+v, yArrow); g2D.drawLine(xArrow, yArrow, xArrow, yArrow+v); break; case ImageCanvas.TOP_LEFT: default: g2D.drawLine(xArrow, yArrow, BORDER_5-xArrow, BORDER_5-yArrow); g2D.drawLine(BORDER_5-xArrow, BORDER_5-yArrow, BORDER_5-xArrow, BORDER_5-yArrow-v); g2D.drawLine(BORDER_5-xArrow, BORDER_5-yArrow, BORDER_5-xArrow-v, BORDER_5-yArrow); } return; } if (imageRectangle == null) { g2D.setColor(BORDER_COLOR); } setSize(canvasWidth, canvasHeight); g2D.drawImage(pImage, null, 0, 0); g2D.setColor(FILL_COLOR); g2D.fillRect(cross.x, cross.y, cross.width, cross.height); g2D.setColor(STROKE_COLOR); switch (locationIndex) { case ImageCanvas.BOTTOM_RIGHT: g2D.drawLine(canvasWidth-BORDER_5+xArrow, canvasHeight-BORDER_5+yArrow, canvasWidth-xArrow, canvasHeight-yArrow); g2D.drawLine(canvasWidth-xArrow-v, canvasHeight-yArrow, canvasWidth-xArrow, canvasHeight-yArrow); g2D.drawLine(canvasWidth-xArrow, canvasHeight-yArrow-v, canvasWidth-xArrow, canvasHeight-yArrow); break; case ImageCanvas.TOP_LEFT: default: g2D.drawLine(xArrow, yArrow, xArrow+v, yArrow); g2D.drawLine(xArrow, yArrow, xArrow, yArrow+v); g2D.drawLine(xArrow, yArrow, BORDER_5, BORDER_5); } g2D.setColor(color); g2D.fillRect(bx, by, w, h); if (colorBorder != null) { g2D.setColor(colorBorder); g2D.drawRect(bx, by, w, h); if (!release) g2D.drawRect(ax, ay, w, h); } g2D.setColor(BORDER_COLOR); g2D.drawRect(0, 0, canvasWidth, canvasHeight); } /** * Depending on mouse click location, shows or hide the bird eye view. * @see MouseListener#mouseReleased(MouseEvent) */ public void mousePressed(MouseEvent e) { px = ax; py = ay; mouseX = e.getX(); mouseY = e.getY(); inCross = false; if (cross.contains(mouseX, mouseY)) { inCross = true; boolean old = fullDisplay; fullDisplay = !fullDisplay; if (!fullDisplay) setSize(cross.width, cross.height); else setSize(canvasWidth, canvasHeight); firePropertyChange(FULL_DISPLAY_PROPERTY, old, fullDisplay); return; } if (!inSelection(mouseX, mouseY)) { bx = mouseX-w/2; if (bx < 0) bx = 0; by = mouseY-h/2; if (by < 0) by = 0; } if (bx < 0) bx = 0; if (by < 0) by = 0; fullDisplay = true; locked = bover; bdifx = mouseX-bx; bdify = mouseY-by; ax = bx; ay = by; release = false; setCursor(mouseX, mouseY); } /** * Fires a property to display the selection. * @see MouseListener#mouseReleased(MouseEvent) */ public void mouseReleased(MouseEvent e) { if (fullDisplay && !inCross) { locked = false; Rectangle r = getSelectionRegion(); if (!isSameSelection(r)) firePropertyChange(DISPLAY_REGION_PROPERTY, null, r); } mouseX = e.getX(); mouseY = e.getY(); bdifx = mouseX-bx; bdify = mouseY-by; ax = bx; ay = by; release = true; locked = false; } /** * Sets the location of the mouse when dragging the selection. * @see MouseMotionListener#mouseDragged(MouseEvent) */ public void mouseDragged(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); if (!inImage()) locked = false; if (locked) { bx = mouseX-bdifx; by = mouseY-bdify; } locked = true; if (bx <= 0) bx = 1; if (by <= 0) by = 1; if (bx+w >= pImage.getWidth()) bx = pImage.getWidth()-w-1; if (by+h >= pImage.getHeight()) by = pImage.getHeight()-h-1; repaint(); } /** * Sets the cursor depending on action. * @see MouseMotionListener#mouseMoved(MouseEvent) */ public void mouseMoved(MouseEvent e) { setCursor(e.getX(), e.getY()); } /** * Required by the {@link MouseListener} I/F but no-operation implementation * in our case. * @see MouseListener#mouseClicked(MouseEvent) */ public void mouseClicked(MouseEvent e) {} /** * Required by the {@link MouseListener} I/F but no-operation implementation * in our case. * @see MouseListener#mouseEntered(MouseEvent) */ public void mouseEntered(MouseEvent e) {} /** * Required by the {@link MouseListener} I/F but no-operation implementation * in our case. * @see MouseListener#mouseExited(MouseEvent) */ public void mouseExited(MouseEvent e) {} }