org.jdesktop.swingx.JXImagePanel Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id: JXImagePanel.java 4017 2011-05-10 21:00:48Z kschaefe $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
/**
*
* A panel that draws an image. The standard mode is to draw the specified image
* centered and unscaled. The component&s preferred size is based on the
* image, unless explicitly set by the user.
*
*
* Images to be displayed can be set based on URL, Image, etc. This is
* accomplished by passing in an image loader.
*
*
* public class URLImageLoader extends Callable<Image> {
* private URL url;
*
* public URLImageLoader(URL url) {
* url.getClass(); //null check
* this.url = url;
* }
*
* public Image call() throws Exception {
* return ImageIO.read(url);
* }
* }
*
* imagePanel.setImageLoader(new URLImageLoader(url));
*
*
*
*
* This component also supports allowing the user to set the image. If the
* JXImagePanel
is editable, then when the user clicks on the
* JXImagePanel
a FileChooser is shown allowing the user to pick
* some other image to use within the JXImagePanel
.
*
*
* TODO In the future, the JXImagePanel will also support tiling of images,
* scaling, resizing, cropping, segues etc.
*
*
* TODO other than the image loading this component can be replicated by a
* JXPanel with the appropriate Painter. What's the point?
*
*
* @author rbair
* @deprecated (pre-1.6.2) use a JXPanel with an ImagePainter; see Issue 988
*/
//moved to package-private instead of deleting; needed by JXLoginPane
@Deprecated
class JXImagePanel extends JXPanel {
public static enum Style {
CENTERED, TILED, SCALED, SCALED_KEEP_ASPECT_RATIO
}
private static final Logger LOG = Logger.getLogger(JXImagePanel.class.getName());
/**
* Text informing the user that clicking on this component will allow them
* to set the image
*/
private static final String TEXT = "Click here
to set the image";
/**
* The image to draw
*/
private SoftReference img = new SoftReference(null);
/**
* If true, then the image can be changed. Perhaps a better name is
* "readOnly", but editable was chosen to be more consistent with
* other Swing components.
*/
private boolean editable = false;
/**
* The mouse handler that is used if the component is editable
*/
private MouseHandler mhandler = new MouseHandler();
/**
* Specifies how to draw the image, i.e. what kind of Style to use when
* drawing
*/
private Style style = Style.CENTERED;
private Image defaultImage;
private Callable imageLoader;
private static final ExecutorService service = Executors.newFixedThreadPool(5);
public JXImagePanel() {
}
//TODO remove this constructor; no where else can a URL be used in this class
public JXImagePanel(URL imageUrl) {
try {
setImage(ImageIO.read(imageUrl));
} catch (Exception e) {
// TODO need convert to something meaningful
LOG.log(Level.WARNING, "", e);
}
}
/**
* Sets the image to use for the background of this panel. This image is
* painted whether the panel is opaque or translucent.
*
* @param image if null, clears the image. Otherwise, this will set the
* image to be painted. If the preferred size has not been explicitly
* set, then the image dimensions will alter the preferred size of
* the panel.
*/
public void setImage(Image image) {
if (image != img.get()) {
Image oldImage = img.get();
img = new SoftReference(image);
firePropertyChange("image", oldImage, img);
invalidate();
repaint();
}
}
/**
* @return the image used for painting the background of this panel
*/
public Image getImage() {
Image image = img.get();
//TODO perhaps we should have a default image loader?
if (image == null && imageLoader != null) {
try {
image = imageLoader.call();
img = new SoftReference(image);
} catch (Exception e) {
LOG.log(Level.WARNING, "", e);
}
}
return image;
}
/**
* @param editable
*/
public void setEditable(boolean editable) {
if (editable != this.editable) {
// if it was editable, remove the mouse handler
if (this.editable) {
removeMouseListener(mhandler);
}
this.editable = editable;
// if it is now editable, add the mouse handler
if (this.editable) {
addMouseListener(mhandler);
}
setToolTipText(editable ? TEXT : "");
firePropertyChange("editable", !editable, editable);
repaint();
}
}
/**
* @return whether the image for this panel can be changed or not via the
* UI. setImage may still be called, even if isEditable
* returns false.
*/
public boolean isEditable() {
return editable;
}
/**
* Sets what style to use when painting the image
*
* @param s
*/
public void setStyle(Style s) {
if (style != s) {
Style oldStyle = style;
style = s;
firePropertyChange("style", oldStyle, s);
repaint();
}
}
/**
* @return the Style used for drawing the image (CENTERED, TILED, etc).
*/
public Style getStyle() {
return style;
}
/**
* {@inheritDoc}
* The old property value in PCE fired by this method might not be always correct!
*/
@Override
public Dimension getPreferredSize() {
if (!isPreferredSizeSet() && img != null) {
Image img = this.img.get();
// was img GCed in the mean time?
if (img != null) {
// it has not been explicitly set, so return the width/height of
// the image
int width = img.getWidth(null);
int height = img.getHeight(null);
if (width == -1 || height == -1) {
return super.getPreferredSize();
}
Insets insets = getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;
return new Dimension(width, height);
}
}
return super.getPreferredSize();
}
/**
* Overridden to paint the image on the panel
*
* @param g
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Image img = this.img.get();
if (img == null && imageLoader != null) {
// schedule for loading (will repaint itself once loaded)
// have to use new future task every time as it holds strong
// reference to the object it retrieved and doesn't allow to reset
// it.
service.execute(new FutureTask(imageLoader) {
@Override
protected void done() {
super.done();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
JXImagePanel.this.setImage(get());
} catch (InterruptedException e) {
// ignore - canceled image load
} catch (ExecutionException e) {
LOG.log(Level.WARNING, "", e);
}
}
});
}
});
img = defaultImage;
}
if (img != null) {
final int imgWidth = img.getWidth(null);
final int imgHeight = img.getHeight(null);
if (imgWidth == -1 || imgHeight == -1) {
// image hasn't completed loading, return
return;
}
Insets insets = getInsets();
final int pw = getWidth() - insets.left - insets.right;
final int ph = getHeight() - insets.top - insets.bottom;
switch (style) {
case CENTERED:
Rectangle clipRect = g2.getClipBounds();
int imageX = (pw - imgWidth) / 2 + insets.left;
int imageY = (ph - imgHeight) / 2 + insets.top;
Rectangle r = SwingUtilities.computeIntersection(imageX, imageY, imgWidth, imgHeight, clipRect);
if (r.x == 0 && r.y == 0 && (r.width == 0 || r.height == 0)) {
return;
}
// I have my new clipping rectangle "r" in clipRect space.
// It is therefore the new clipRect.
clipRect = r;
// since I have the intersection, all I need to do is adjust the
// x & y values for the image
int txClipX = clipRect.x - imageX;
int txClipY = clipRect.y - imageY;
int txClipW = clipRect.width;
int txClipH = clipRect.height;
g2.drawImage(img, clipRect.x, clipRect.y, clipRect.x + clipRect.width, clipRect.y + clipRect.height, txClipX, txClipY, txClipX + txClipW, txClipY + txClipH, null);
break;
case TILED:
g2.translate(insets.left, insets.top);
Rectangle clip = g2.getClipBounds();
g2.setClip(0, 0, pw, ph);
int totalH = 0;
while (totalH < ph) {
int totalW = 0;
while (totalW < pw) {
g2.drawImage(img, totalW, totalH, null);
totalW += img.getWidth(null);
}
totalH += img.getHeight(null);
}
g2.setClip(clip);
g2.translate(-insets.left, -insets.top);
break;
case SCALED:
g2.drawImage(img, insets.left, insets.top, pw, ph, null);
break;
case SCALED_KEEP_ASPECT_RATIO:
int w = pw;
int h = ph;
final float ratioW = ((float) w) / ((float) imgWidth);
final float ratioH = ((float) h) / ((float) imgHeight);
if (ratioW < ratioH) {
h = (int) (imgHeight * ratioW);
} else {
w = (int) (imgWidth * ratioH);
}
final int x = (pw - w) / 2 + insets.left;
final int y = (ph - h) / 2 + insets.top;
g2.drawImage(img, x, y, w, h, null);
break;
default:
LOG.fine("unimplemented");
g2.drawImage(img, insets.left, insets.top, this);
break;
}
}
}
/**
* Handles click events on the component
*/
private class MouseHandler extends MouseAdapter {
private Cursor oldCursor;
private JFileChooser chooser;
@Override
public void mouseClicked(MouseEvent evt) {
if (chooser == null) {
chooser = new JFileChooser();
}
int retVal = chooser.showOpenDialog(JXImagePanel.this);
if (retVal == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
try {
setImage(new ImageIcon(file.toURI().toURL()).getImage());
} catch (Exception ex) {
}
}
}
@Override
public void mouseEntered(MouseEvent evt) {
if (oldCursor == null) {
oldCursor = getCursor();
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
@Override
public void mouseExited(MouseEvent evt) {
if (oldCursor != null) {
setCursor(oldCursor);
oldCursor = null;
}
}
}
public void setDefaultImage(Image def) {
this.defaultImage = def;
}
public void setImageLoader(Callable loadImage) {
this.imageLoader = loadImage;
}
}