org.jdesktop.swingx.JXImageView Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swingx-all Show documentation
Show all versions of swingx-all Show documentation
Fork of the inactive swingx-all library
/*
* $Id: JXImageView.java 4248 2012-11-13 18:12:08Z kschaefe $
*
* 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 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;
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 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.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.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.Level;
import java.util.logging.Logger;
/**
* 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 ErrorListener} added by the
* {@link #addErrorListener}()
method.
*
* @author Joshua Marinacci [email protected]
*/
@JavaBean
public class JXImageView extends JXPanel {
private static final 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 final 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 final 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 IOException thrown if the image cannot be loaded
*/
public void setImage(URL url) throws IOException {
setImageURL(url);
}
/**
* Set the current image to an image pointed to by this File.
*
* @param file a File pointing to an image
* @throws 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) {
LOG.log(Level.WARNING, "Cannot create DnD handler", ex);
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);
}
}
}
};
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);
}
}
}
};
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 final JXImageView panel;
private Point prev = null;
private Point start = null;
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()) {
if (curr.distance(start) > 5) {
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 {
private DataFlavor urlFlavor;
DnDHandler() throws ClassNotFoundException {
urlFlavor = new DataFlavor("application/x-java-url;class=java.net.URL");
}
@Override
public int getSourceActions(JComponent c) {
return COPY;
}
@Override
protected void exportDone(JComponent source, Transferable data, int action) {
}
@Override
public boolean canImport(JComponent c, DataFlavor[] flavors) {
for (DataFlavor flavor : flavors) {
if (DataFlavor.javaFileListFlavor.equals(flavor)) {
return true;
}
if (DataFlavor.imageFlavor.equals(flavor)) {
return true;
}
if (urlFlavor.match(flavor)) {
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);
if (files.size() > 0) {
File file = files.get(0);
setImageString(file.toURI().toURL().toString());
return true;
}
}
Object obj = t.getTransferData(urlFlavor);
if (obj instanceof URL) {
setImageString(obj.toString());
}
return true;
} catch (Exception ex) {
LOG.log(Level.SEVERE, ex.getMessage(), ex);
fireError(ex);
}
}
return false;
}
}
private static class ImageTransferable implements Transferable {
private final Image img;
private List files;
private final String exportName;
private final String exportFormat;
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 {
if (flavor == DataFlavor.imageFlavor) {
return img;
}
if (flavor == DataFlavor.javaFileListFlavor) {
if (files == null) {
files = new ArrayList<>();
File file = File.createTempFile(exportName, "." + exportFormat);
ImageIO.write(GraphicsUtilities.convertToBufferedImage(img), exportFormat, file);
files.add(file);
}
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 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);
}
}