org.jdesktop.swingx.JXImageView Maven / Gradle / Ivy
/*
* $Id: JXImageView.java 4153 2012-02-02 14:17:49Z kleopatra $
*
* Copyright 2006 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.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Window;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.MouseInputAdapter;
import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.error.ErrorListener;
import org.jdesktop.swingx.error.ErrorSupport;
import org.jdesktop.swingx.painter.MattePainter;
import org.jdesktop.swingx.util.GraphicsUtilities;
import org.jdesktop.swingx.util.PaintUtils;
/**
* A panel which shows an image centered. The user can drag an image into the
* panel from other applications and move the image around within the view.
* The JXImageView has built in actions for scaling, rotating, opening a new
* image, and saving. These actions can be obtained using the relevant get*Action()
* methods.
*
*
* TODO: has dashed rect and text indicating you should drag there.
*
*
* If the user drags more than one photo at a time into the JXImageView only
* the first photo will be loaded and shown. Any errors generated internally,
* such as dragging in a list of files which are not images, will be reported
* to any attached {@link org.jdesktop.swingx.error.ErrorListener} added by the
* {@link #addErrorListener}()
method.
*
* @author Joshua Marinacci [email protected]
*/
@JavaBean
public class JXImageView extends JXPanel {
private Logger log = Logger.getLogger(JXImageView.class.getName());
/* ======= instance variables ========= */
// the image this view will show
private Image image;
// the url of the image, if available
private URL imageURL;
// support for error listeners
private ErrorSupport errorSupport = new ErrorSupport(this);
// location to draw image. if null then draw in the center
private Point2D imageLocation;
// the scale for drawing the image
private double scale = 1.0;
// controls whether the user can move images around
private boolean editable = true;
// the handler for moving the image around within the panel
private MoveHandler moveHandler = new MoveHandler(this);
// controls the drag part of drag and drop
private boolean dragEnabled = false;
// controls the filename of the dropped file
private String exportName = "UntitledImage";
// controls the format and filename extension of the dropped file
private String exportFormat = "png";
/** Creates a new instance of JXImageView */
public JXImageView() {
// fix for: java.net/jira/browse/SWINGX-1479
setBackgroundPainter(new MattePainter(PaintUtils.getCheckerPaint(Color.white,new Color(250,250,250),50)));
setEditable(true);
}
/* ========= properties ========= */
/**
* Gets the current image location. This location can be changed programmatically
* or by the user dragging the image within the JXImageView.
* @return the current image location
*/
public Point2D getImageLocation() {
return imageLocation;
}
/**
* Set the current image location.
* @param imageLocation The new image location.
*/
public void setImageLocation(Point2D imageLocation) {
Point2D old = getImageLocation();
this.imageLocation = imageLocation;
firePropertyChange("imageLocation", old, getImageLocation());
repaint();
}
/**
* Gets the currently set image, or null if no image is set.
* @return the currently set image, or null if no image is set.
*/
public Image getImage() {
return image;
}
/**
* Sets the current image. Can set null if there should be no image show.
* @param image the new image to set, or null.
*/
public void setImage(Image image) {
Image oldImage = getImage();
this.image = image;
setImageLocation(null);
setScale(1.0);
firePropertyChange("image",oldImage,image);
repaint();
}
/**
* Set the current image to an image pointed to by this URL.
* @param url a URL pointing to an image, or null
* @throws java.io.IOException thrown if the image cannot be loaded
*/
public void setImage(URL url) throws IOException {
setImageURL(url);
//setImage(ImageIO.read(url));
}
/**
* Set the current image to an image pointed to by this File.
* @param file a File pointing to an image
* @throws java.io.IOException thrown if the image cannot be loaded
*/
public void setImage(File file) throws IOException {
setImageURL(file.toURI().toURL());
}
/**
* Gets the current image scale . When the scale is set to 1.0
* then one image pixel = one screen pixel. When scale < 1.0 the draw image
* will be smaller than it's real size. When scale > 1.0 the drawn image will
* be larger than it's real size. 1.0 is the default value.
* @return the current image scale
*/
public double getScale() {
return scale;
}
/**
* Sets the current image scale . When the scale is set to 1.0
* then one image pixel = one screen pixel. When scale < 1.0 the draw image
* will be smaller than it's real size. When scale > 1.0 the drawn image will
* be larger than it's real size. 1.0 is the default value.
* @param scale the new image scale
*/
public void setScale(double scale) {
double oldScale = this.scale;
this.scale = scale;
this.firePropertyChange("scale",oldScale,scale);
repaint();
}
/**
* Returns whether or not the user can drag images.
* @return whether or not the user can drag images
*/
public boolean isEditable() {
return editable;
}
/**
* Sets whether or not the user can drag images. When set to true the user can
* drag the photo around with their mouse. Also the cursor will be set to the
* 'hand' cursor. When set to false the user cannot drag photos around
* and the cursor will be set to the default.
* @param editable whether or not the user can drag images
*/
public void setEditable(boolean editable) {
boolean old = isEditable();
this.editable = editable;
if(editable) {
addMouseMotionListener(moveHandler);
addMouseListener(moveHandler);
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
try {
this.setTransferHandler(new DnDHandler());
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
fireError(ex);
}
} else {
removeMouseMotionListener(moveHandler);
removeMouseListener(moveHandler);
this.setCursor(Cursor.getDefaultCursor());
setTransferHandler(null);
}
firePropertyChange("editable", old, isEditable());
}
/**
* Sets the dragEnabled
property, which determines whether or not
* the user can drag images out of the image view and into other components or
* application. Note: setting
* this to true will disable the ability to move the image around within the
* well., though it will not change the editable property directly.
* @param dragEnabled the value to set the dragEnabled property to.
*/
public void setDragEnabled(boolean dragEnabled) {
boolean old = isDragEnabled();
this.dragEnabled = dragEnabled;
firePropertyChange("dragEnabled", old, isDragEnabled());
}
/**
* Gets the current value of the dragEnabled
property.
* @return the current value of the dragEnabled
property
*/
public boolean isDragEnabled() {
return dragEnabled;
}
/**
* Adds an ErrorListener to the list of listeners to be notified
* of ErrorEvents
* @param el an ErrorListener to add
*/
public void addErrorListener(ErrorListener el) {
errorSupport.addErrorListener(el);
}
/**
* Remove an ErrorListener from the list of listeners to be notified of ErrorEvents.
* @param el an ErrorListener to remove
*/
public void removeErrorListener(ErrorListener el) {
errorSupport.removeErrorListener(el);
}
/**
* Send a new ErrorEvent to all registered ErrorListeners
* @param throwable the Error or Exception which was thrown
*/
protected void fireError(Throwable throwable) {
errorSupport.fireErrorEvent(throwable);
}
private static FileDialog getSafeFileDialog(Component comp) {
Window win = SwingUtilities.windowForComponent(comp);
if(win instanceof Dialog) {
return new FileDialog((Dialog)win);
}
if(win instanceof Frame) {
return new FileDialog((Frame)win);
}
return null;
}
// an action which will open a file chooser and load the selected image
// if any.
/**
* Returns an Action which will open a file chooser, ask the user for an image file
* then load the image into the view. If the load fails an error will be fired
* to all registered ErrorListeners
* @return the action
* @see ErrorListener
* @deprecated see SwingX issue 990
*/
@Deprecated
public Action getOpenAction() {
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
FileDialog fd = getSafeFileDialog(JXImageView.this);
fd.setMode(FileDialog.LOAD);
fd.setVisible(true);
if(fd.getFile() != null) {
try {
setImage(new File(fd.getDirectory(),fd.getFile()));
} catch (IOException ex) {
fireError(ex);
}
}
/*
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(JXImageView.this);
File file = chooser.getSelectedFile();
if(file != null) {
try {
setImage(file);
} catch (IOException ex) {
log.fine(ex.getMessage());
ex.printStackTrace();
fireError(ex);
}
}
*/
}
};
action.putValue(Action.NAME,"Open");
return action;
}
// an action that will open a file chooser then save the current image to
// the selected file, if any.
/**
* Returns an Action which will open a file chooser, ask the user for an image file
* then save the image from the view. If the save fails an error will be fired
* to all registered ErrorListeners
* @return an Action
* @deprecated see SwingX issue 990
*/
@Deprecated
public Action getSaveAction() {
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent evt) {
Image img = getImage();
BufferedImage dst = new BufferedImage(
img.getWidth(null),
img.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D)dst.getGraphics();
try {
// smooth scaling
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(img, 0, 0, null);
} finally {
g.dispose();
}
FileDialog fd = new FileDialog((Frame)SwingUtilities.windowForComponent(JXImageView.this));
fd.setMode(FileDialog.SAVE);
fd.setVisible(true);
if(fd.getFile() != null) {
try {
ImageIO.write(dst,"png",new File(fd.getDirectory(),fd.getFile()));
} catch (IOException ex) {
fireError(ex);
}
}
/*
JFileChooser chooser = new JFileChooser();
chooser.showSaveDialog(JXImageView.this);
File file = chooser.getSelectedFile();
if(file != null) {
try {
ImageIO.write(dst,"png",file);
} catch (IOException ex) {
log.fine(ex.getMessage());
ex.printStackTrace();
fireError(ex);
}
}
*/
}
};
action.putValue(Action.NAME,"Save");
return action;
}
/**
* Get an action which will rotate the currently selected image clockwise.
* @return an action
* @deprecated see SwingX issue 990
*/
@Deprecated
public Action getRotateClockwiseAction() {
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent evt) {
Image img = getImage();
BufferedImage src = new BufferedImage(
img.getWidth(null),
img.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
BufferedImage dst = new BufferedImage(
img.getHeight(null),
img.getWidth(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D)src.getGraphics();
try {
// smooth scaling
g.drawImage(img, 0, 0, null);
} finally {
g.dispose();
}
AffineTransform trans = AffineTransform.getRotateInstance(Math.PI/2,0,0);
trans.translate(0,-src.getHeight());
BufferedImageOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
op.filter(src,dst);
setImage(dst);
}
};
action.putValue(Action.NAME,"Rotate Clockwise");
return action;
}
/**
* Gets an action which will rotate the current image counter clockwise.
* @return an Action
* @deprecated see SwingX issue 990
*/
@Deprecated
public Action getRotateCounterClockwiseAction() {
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent evt) {
Image img = getImage();
BufferedImage src = new BufferedImage(
img.getWidth(null),
img.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
BufferedImage dst = new BufferedImage(
img.getHeight(null),
img.getWidth(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D)src.getGraphics();
try {
// smooth scaling
g.drawImage(img, 0, 0, null);
} finally {
g.dispose();
}
AffineTransform trans = AffineTransform.getRotateInstance(-Math.PI/2,0,0);
trans.translate(-src.getWidth(),0);
BufferedImageOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
op.filter(src,dst);
setImage(dst);
}
};
action.putValue(Action.NAME, "Rotate CounterClockwise");
return action;
}
/**
* Gets an action which will zoom the current image out by a factor of 2.
* @return an action
* @deprecated see SwingX issue 990
*/
@Deprecated
public Action getZoomOutAction() {
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
setScale(getScale()*0.5);
}
};
action.putValue(Action.NAME,"Zoom Out");
return action;
}
/**
* Gets an action which will zoom the current image in by a factor of 2
* @return an action
* @deprecated see SwingX issue 990
*/
@Deprecated
public Action getZoomInAction() {
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
setScale(getScale()*2);
}
};
action.putValue(Action.NAME,"Zoom In");
return action;
}
/* === overriden methods === */
/**
* Implementation detail.
* @param g
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(getImage() != null) {
Point2D center = new Point2D.Double(getWidth()/2,getHeight()/2);
if(getImageLocation() != null) {
center = getImageLocation();
}
Point2D loc = new Point2D.Double();
double width = getImage().getWidth(null)*getScale();
double height = getImage().getHeight(null)*getScale();
loc.setLocation(center.getX()-width/2, center.getY()-height/2);
g.drawImage(getImage(), (int)loc.getX(), (int)loc.getY(),
(int)width,(int)height,
null);
}
}
/* === Internal helper classes === */
private class MoveHandler extends MouseInputAdapter {
private JXImageView panel;
private Point prev = null;
private Point start = null;
public MoveHandler(JXImageView panel) {
this.panel = panel;
}
@Override
public void mousePressed(MouseEvent evt) {
prev = evt.getPoint();
start = prev;
}
@Override
public void mouseDragged(MouseEvent evt) {
Point curr = evt.getPoint();
if(isDragEnabled()) {
//log.fine("testing drag enabled: " + curr + " " + start);
//log.fine("distance = " + curr.distance(start));
if(curr.distance(start) > 5) {
JXImageView.this.log.fine("starting the drag: ");
panel.getTransferHandler().exportAsDrag((JComponent)evt.getSource(),evt,TransferHandler.COPY);
return;
}
}
int offx = curr.x - prev.x;
int offy = curr.y - prev.y;
Point2D offset = getImageLocation();
if (offset == null) {
if (image != null) {
offset = new Point2D.Double(getWidth() / 2, getHeight() / 2);
} else {
offset = new Point2D.Double(0, 0);
}
}
offset = new Point2D.Double(offset.getX() + offx, offset.getY() + offy);
setImageLocation(offset);
prev = curr;
repaint();
}
@Override
public void mouseReleased(MouseEvent evt) {
prev = null;
}
}
private class DnDHandler extends TransferHandler {
DataFlavor urlFlavor;
public DnDHandler() throws ClassNotFoundException {
urlFlavor = new DataFlavor("application/x-java-url;class=java.net.URL");
}
@Override
public void exportAsDrag(JComponent c, InputEvent evt, int action) {
//log.fine("exportting as drag");
super.exportAsDrag(c,evt,action);
}
@Override
public int getSourceActions(JComponent c) {
//log.fine("get source actions: " + c);
return COPY;
}
@Override
protected void exportDone(JComponent source, Transferable data, int action) {
//log.fine("exportDone: " + source + " " + data + " " +action);
}
@Override
public boolean canImport(JComponent c, DataFlavor[] flavors) {
//log.fine("canImport:" + c);
for (int i = 0; i < flavors.length; i++) {
//log.fine("testing: "+flavors[i]);
if (DataFlavor.javaFileListFlavor.equals(flavors[i])) {
return true;
}
if (DataFlavor.imageFlavor.equals(flavors[i])) {
return true;
}
if (urlFlavor.match(flavors[i])) {
return true;
}
}
return false;
}
@Override
protected Transferable createTransferable(JComponent c) {
JXImageView view = (JXImageView)c;
return new ImageTransferable(view.getImage(),
view.getExportName(), view.getExportFormat());
}
@Override
@SuppressWarnings("unchecked")
public boolean importData(JComponent comp, Transferable t) {
if (canImport(comp, t.getTransferDataFlavors())) {
try {
if(t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor);
//log.fine("doing file list flavor");
if (files.size() > 0) {
File file = files.get(0);
//log.fine("readingt hte image: " + file.getCanonicalPath());
/*Iterator it = ImageIO.getImageReaders(new FileInputStream(file));
while(it.hasNext()) {
log.fine("can read: " + it.next());
}*/
setImageString(file.toURI().toURL().toString());
//BufferedImage img = ImageIO.read(file.toURI().toURL());
//setImage(img);
return true;
}
}
//log.fine("doing a uri list");
Object obj = t.getTransferData(urlFlavor);
//log.fine("obj = " + obj + " " + obj.getClass().getPackage() + " "
// + obj.getClass().getName());
if(obj instanceof URL) {
setImageString(((URL)obj).toString());
}
return true;
} catch (Exception ex) {
log .severe(ex.getMessage());
ex.printStackTrace();
fireError(ex);
}
}
return false;
}
}
private class ImageTransferable implements Transferable {
private Image img;
private List files;
private String exportName, exportFormat;
public ImageTransferable(Image img, String exportName, String exportFormat) {
this.img = img;
this.exportName = exportName;
this.exportFormat = exportFormat;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { DataFlavor.imageFlavor,
DataFlavor.javaFileListFlavor };
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
if(flavor == DataFlavor.imageFlavor) {
return true;
}
return flavor == DataFlavor.javaFileListFlavor;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
//log.fine("doing get trans data: " + flavor);
if(flavor == DataFlavor.imageFlavor) {
return img;
}
if(flavor == DataFlavor.javaFileListFlavor) {
if(files == null) {
files = new ArrayList();
File file = File.createTempFile(exportName,"."+exportFormat);
//log.fine("writing to: " + file);
ImageIO.write(GraphicsUtilities.convertToBufferedImage(img),exportFormat,file);
files.add(file);
}
//log.fine("returning: " + files);
return files;
}
return null;
}
}
public String getExportName() {
return exportName;
}
public void setExportName(String exportName) {
String old = getExportName();
this.exportName = exportName;
firePropertyChange("exportName", old, getExportName());
}
public String getExportFormat() {
return exportFormat;
}
public void setExportFormat(String exportFormat) {
String old = getExportFormat();
this.exportFormat = exportFormat;
firePropertyChange("exportFormat", old, getExportFormat());
}
public URL getImageURL() {
return imageURL;
}
public void setImageURL(URL imageURL) throws IOException {
URL old = getImageURL();
this.imageURL = imageURL;
firePropertyChange("imageURL", old, getImageURL());
setImage(ImageIO.read(getImageURL()));
}
/** Returns the current image's URL (if available) as a string.
* If the image has no URL, or if there is no image, then this
* method will return null.
* @return the url of the image as a string
*/
public String getImageString() {
if(getImageURL() == null) {
return null;
}
return getImageURL().toString();
}
/** Sets the current image using a string. This string must
* contain a valid URL.
* @param url string of a URL
* @throws java.io.IOException thrown if the URL does not parse
*/
public void setImageString(String url) throws IOException {
String old = getImageString();
setImageURL(new URL(url));
firePropertyChange("imageString", old, url);
}
}