001: /*
002: * $Id: JXImagePanel.java,v 1.14 2006/07/11 17:11:35 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package org.jdesktop.swingx;
023:
024: import java.awt.Cursor;
025: import java.awt.Dimension;
026: import java.awt.Graphics;
027: import java.awt.Graphics2D;
028: import java.awt.Image;
029: import java.awt.Insets;
030: import java.awt.Rectangle;
031: import java.awt.event.MouseAdapter;
032: import java.awt.event.MouseEvent;
033: import java.io.File;
034: import java.net.URL;
035: import java.util.logging.Level;
036: import java.util.logging.Logger;
037: import javax.imageio.ImageIO;
038:
039: import javax.swing.ImageIcon;
040: import javax.swing.JFileChooser;
041: import javax.swing.JLabel;
042: import javax.swing.SwingUtilities;
043:
044: /**
045: * <p>A panel that draws an image. The standard (and currently only supported)
046: * mode is to draw the specified image starting at position 0,0 in the
047: * panel. The component&s preferred size is based on the image, unless
048: * explicitly set by the user.</p>
049: *
050: * <p>In the future, the JXImagePanel will also support tiling of images,
051: * scaling, resizing, cropping, segways etc.</p>
052: *
053: * <p>This component also supports allowing the user to set the image. If the
054: * <code>JXImagePanel</code> is editable, then when the user clicks on the
055: * <code>JXImagePanel</code> a FileChooser is shown allowing the user to pick
056: * some other image to use within the <code>JXImagePanel</code>.</p>
057: *
058: * <p>Images to be displayed can be set based on URL, Image, etc.
059: *
060: * @author rbair
061: */
062: public class JXImagePanel extends JXPanel {
063: public static enum Style {
064: CENTERED, TILED, SCALED, SCALED_KEEP_ASPECT_RATIO
065: };
066:
067: private static final Logger LOG = Logger
068: .getLogger(JXImagePanel.class.getName());
069: /**
070: * Text informing the user that clicking on this component will allow them to set the image
071: */
072: private static final String TEXT = "<html><i><b>Click here<br>to set the image</b></i></html>";
073: /**
074: * The image to draw
075: */
076: private Image img;
077: /**
078: * If true, then the image can be changed. Perhaps a better name is
079: * "readOnly", but editable was chosen to be more consistent
080: * with other Swing components.
081: */
082: private boolean editable = false;
083: /**
084: * The mouse handler that is used if the component is editable
085: */
086: private MouseHandler mhandler = new MouseHandler();
087: /**
088: * If not null, then the user has explicitly set the preferred size of
089: * this component, and this should be honored
090: */
091: private Dimension preferredSize;
092: /**
093: * Specifies how to draw the image, i.e. what kind of Style to use
094: * when drawing
095: */
096: private Style style = Style.CENTERED;
097:
098: public JXImagePanel() {
099: }
100:
101: public JXImagePanel(URL imageUrl) {
102: try {
103: setImage(ImageIO.read(imageUrl));
104: } catch (Exception e) {
105: //TODO need convert to something meaningful
106: LOG.log(Level.WARNING, "", e);
107: }
108: }
109:
110: /**
111: * Sets the image to use for the background of this panel. This image is
112: * painted whether the panel is opaque or translucent.
113: * @param image if null, clears the image. Otherwise, this will set the
114: * image to be painted. If the preferred size has not been explicitly set,
115: * then the image dimensions will alter the preferred size of the panel.
116: */
117: public void setImage(Image image) {
118: if (image != img) {
119: Image oldImage = img;
120: img = image;
121: firePropertyChange("image", oldImage, img);
122: invalidate();
123: repaint();
124: }
125: }
126:
127: /**
128: * @return the image used for painting the background of this panel
129: */
130: public Image getImage() {
131: return img;
132: }
133:
134: /**
135: * @param editable
136: */
137: public void setEditable(boolean editable) {
138: if (editable != this .editable) {
139: //if it was editable, remove the mouse handler
140: if (this .editable) {
141: removeMouseListener(mhandler);
142: }
143: this .editable = editable;
144: //if it is now editable, add the mouse handler
145: if (this .editable) {
146: addMouseListener(new MouseHandler());
147: }
148: setToolTipText(editable ? TEXT : "");
149: firePropertyChange("editable", !editable, editable);
150: repaint();
151: }
152: }
153:
154: /**
155: * @return whether the image for this panel can be changed or not via
156: * the UI. setImage may still be called, even if <code>isEditable</code>
157: * returns false.
158: */
159: public boolean isEditable() {
160: return editable;
161: }
162:
163: /**
164: * Sets what style to use when painting the image
165: *
166: * @param s
167: */
168: public void setStyle(Style s) {
169: if (style != s) {
170: Style oldStyle = style;
171: style = s;
172: firePropertyChange("style", oldStyle, s);
173: repaint();
174: }
175: }
176:
177: /**
178: * @return the Style used for drawing the image (CENTERED, TILED, etc).
179: */
180: public Style getStyle() {
181: return style;
182: }
183:
184: public void setPreferredSize(Dimension pref) {
185: preferredSize = pref;
186: super .setPreferredSize(pref);
187: }
188:
189: public Dimension getPreferredSize() {
190: if (preferredSize == null && img != null) {
191: //it has not been explicitly set, so return the width/height of the image
192: int width = img.getWidth(null);
193: int height = img.getHeight(null);
194: if (width == -1 || height == -1) {
195: return super .getPreferredSize();
196: }
197: return new Dimension(width, height);
198: } else {
199: return super .getPreferredSize();
200: }
201: }
202:
203: /**
204: * Overriden to paint the image on the panel
205: */
206: protected void paintComponent(Graphics g) {
207: super .paintComponent(g);
208: Graphics2D g2 = (Graphics2D) g;
209: if (img != null) {
210: final int imgWidth = img.getWidth(null);
211: final int imgHeight = img.getHeight(null);
212: if (imgWidth == -1 || imgHeight == -1) {
213: //image hasn't completed loading, return
214: return;
215: }
216:
217: Insets insets = getInsets();
218: final int pw = getWidth() - insets.left - insets.right;
219: final int ph = getHeight() - insets.top - insets.bottom;
220:
221: switch (style) {
222: case CENTERED:
223: Rectangle clipRect = g2.getClipBounds();
224: int imageX = (pw - imgWidth) / 2 + insets.left;
225: int imageY = (ph - imgHeight) / 2 + insets.top;
226: Rectangle r = SwingUtilities.computeIntersection(
227: imageX, imageY, imgWidth, imgHeight, clipRect);
228: if (r.x == 0 && r.y == 0
229: && (r.width == 0 || r.height == 0)) {
230: return;
231: }
232: //I have my new clipping rectangle "r" in clipRect space.
233: //It is therefore the new clipRect.
234: clipRect = r;
235: //since I have the intersection, all I need to do is adjust the
236: //x & y values for the image
237: int txClipX = clipRect.x - imageX;
238: int txClipY = clipRect.y - imageY;
239: int txClipW = clipRect.width;
240: int txClipH = clipRect.height;
241:
242: g2.drawImage(img, clipRect.x, clipRect.y, clipRect.x
243: + clipRect.width, clipRect.y + clipRect.height,
244: txClipX, txClipY, txClipX + txClipW, txClipY
245: + txClipH, null);
246: break;
247: case TILED:
248: case SCALED:
249: g2
250: .drawImage(img, insets.left, insets.top, pw,
251: ph, null);
252: break;
253: case SCALED_KEEP_ASPECT_RATIO:
254: int w;
255: int h;
256: if ((imgWidth - pw) > (imgHeight - ph)) {
257: w = pw;
258: final float ratio = ((float) w)
259: / ((float) imgWidth);
260: h = (int) (imgHeight * ratio);
261: } else {
262: h = ph;
263: final float ratio = ((float) h)
264: / ((float) imgHeight);
265: w = (int) (imgWidth * ratio);
266: }
267: final int x = (pw - w) / 2 + insets.left;
268: final int y = (ph - h) / 2 + insets.top;
269: g2.drawImage(img, x, y, w, h, null);
270: break;
271: default:
272: LOG.fine("unimplemented");
273: g2.drawImage(img, insets.left, insets.top, this );
274: break;
275: }
276: }
277: }
278:
279: /**
280: * Handles click events on the component
281: */
282: private class MouseHandler extends MouseAdapter {
283: private Cursor oldCursor;
284: private JFileChooser chooser;
285:
286: public void mouseClicked(MouseEvent evt) {
287: if (chooser == null) {
288: chooser = new JFileChooser();
289: }
290: int retVal = chooser.showOpenDialog(JXImagePanel.this );
291: if (retVal == JFileChooser.APPROVE_OPTION) {
292: File file = chooser.getSelectedFile();
293: try {
294: setImage(new ImageIcon(file.toURI().toURL())
295: .getImage());
296: } catch (Exception ex) {
297: }
298: }
299: }
300:
301: public void mouseEntered(MouseEvent evt) {
302: if (evt.getSource() instanceof JLabel) {
303: JLabel label = (JLabel) evt.getSource();
304: if (oldCursor == null) {
305: oldCursor = label.getCursor();
306: label.setCursor(Cursor
307: .getPredefinedCursor(Cursor.HAND_CURSOR));
308: }
309: }
310: }
311:
312: public void mouseExited(MouseEvent evt) {
313: JLabel label = (JLabel) evt.getSource();
314: if (oldCursor != null) {
315: label.setCursor(oldCursor);
316: oldCursor = null;
317: }
318: }
319: }
320: }
|