001    /*
002     * $Id: JXImagePanel.java,v 1.13 2006/04/26 17:10:52 joshy 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    import java.awt.Cursor;
024    import java.awt.Dimension;
025    import java.awt.Graphics;
026    import java.awt.Graphics2D;
027    import java.awt.Image;
028    import java.awt.Rectangle;
029    import java.awt.event.MouseAdapter;
030    import java.awt.event.MouseEvent;
031    import java.io.File;
032    import java.net.URL;
033    import java.util.logging.Level;
034    import java.util.logging.Logger;
035    import javax.imageio.ImageIO;
036    
037    import javax.swing.ImageIcon;
038    import javax.swing.JFileChooser;
039    import javax.swing.JLabel;
040    import javax.swing.SwingUtilities;
041    
042    
043    /**
044     * <p>A panel that draws an image. The standard (and currently only supported)
045     * mode is to draw the specified image starting at position 0,0 in the
046     * panel. The component&amp;s preferred size is based on the image, unless
047     * explicitly set by the user.</p>
048     *
049     * <p>In the future, the JXImagePanel will also support tiling of images,
050     * scaling, resizing, cropping, segways etc.</p>
051     *
052     * <p>This component also supports allowing the user to set the image. If the
053     * <code>JXImagePanel</code> is editable, then when the user clicks on the
054     * <code>JXImagePanel</code> a FileChooser is shown allowing the user to pick
055     * some other image to use within the <code>JXImagePanel</code>.</p>
056     *
057     * <p>Images to be displayed can be set based on URL, Image, etc.
058     *
059     * @author rbair
060     */
061    public class JXImagePanel extends JXPanel {
062        public static enum Style {CENTERED, TILED, SCALED};
063        private static final Logger LOG = Logger.getLogger(JXImagePanel.class
064                .getName());
065        /**
066         * Text informing the user that clicking on this component will allow them to set the image
067         */
068        private static final String TEXT = "<html><i><b>Click here<br>to set the image</b></i></html>";
069        /**
070         * The image to draw
071         */
072        private Image img;
073        /**
074         * If true, then the image can be changed. Perhaps a better name is
075         * &quot;readOnly&quot;, but editable was chosen to be more consistent
076         * with other Swing components.
077         */
078        private boolean editable = false;
079        /**
080         * The mouse handler that is used if the component is editable
081         */
082        private MouseHandler mhandler = new MouseHandler();
083        /**
084         * If not null, then the user has explicitly set the preferred size of
085         * this component, and this should be honored
086         */
087        private Dimension preferredSize;
088        /**
089         * Specifies how to draw the image, i.e. what kind of Style to use
090         * when drawing
091         */
092        private Style style = Style.CENTERED;
093        
094        public JXImagePanel() {
095        }
096        
097        public JXImagePanel(URL imageUrl) {
098            try {
099                setImage(ImageIO.read(imageUrl));
100            } catch (Exception e) {
101                //TODO need convert to something meaningful
102                LOG.log(Level.WARNING, "", e);
103            }
104        }
105        
106        /**
107         * Sets the image to use for the background of this panel. This image is
108         * painted whether the panel is opaque or translucent.
109         * @param image if null, clears the image. Otherwise, this will set the
110         * image to be painted. If the preferred size has not been explicitly set,
111         * then the image dimensions will alter the preferred size of the panel.
112         */
113        public void setImage(Image image) {
114            if (image != img) {
115                Image oldImage = img;
116                img = image;
117                firePropertyChange("image", oldImage, img);
118                invalidate();
119                repaint();
120            }
121        }
122        
123        /**
124         * @return the image used for painting the background of this panel
125         */
126        public Image getImage() {
127            return img;
128        }
129        
130        /**
131         * @param editable
132         */
133        public void setEditable(boolean editable) {
134            if (editable != this.editable) {
135                //if it was editable, remove the mouse handler
136                if (this.editable) {
137                    removeMouseListener(mhandler);
138                }
139                this.editable = editable;
140                //if it is now editable, add the mouse handler
141                if (this.editable) {
142                    addMouseListener(new MouseHandler());
143                }
144                setToolTipText(editable ? TEXT : "");
145                firePropertyChange("editable", !editable, editable);
146                repaint();
147            }
148        }
149        
150        /**
151         * @return whether the image for this panel can be changed or not via
152         * the UI. setImage may still be called, even if <code>isEditable</code>
153         * returns false.
154         */
155        public boolean isEditable() {
156            return editable;
157        }
158        
159        /**
160         * Sets what style to use when painting the image
161         *
162         * @param s
163         */
164        public void setStyle(Style s) {
165            if (style != s) {
166                Style oldStyle = style;
167                style = s;
168                firePropertyChange("style", oldStyle, s);
169                repaint();
170            }
171        }
172        
173        /**
174         * @return the Style used for drawing the image (CENTERED, TILED, etc).
175         */
176        public Style getStyle() {
177            return style;
178        }
179        
180        public void setPreferredSize(Dimension pref) {
181            preferredSize = pref;
182            super.setPreferredSize(pref);
183        }
184        
185        public Dimension getPreferredSize() {
186            if (preferredSize == null && img != null) {
187                //it has not been explicitly set, so return the width/height of the image
188                int width = img.getWidth(null);
189                int height = img.getHeight(null);
190                if (width == -1 || height == -1) {
191                    return super.getPreferredSize();
192                }
193                return new Dimension(width, height);
194            } else {
195                return super.getPreferredSize();
196            }
197        }
198        
199        /**
200         * Overriden to paint the image on the panel
201         */
202        protected void paintComponent(Graphics g) {
203            super.paintComponent(g);
204    //        Insets insets = getInsets();
205    //        g.fillRect(insets.left, insets.top, getWidth() - insets.right - insets.left, getHeight() - insets.bottom - insets.top);
206            Graphics2D g2 = (Graphics2D)g;
207            if (img != null) {
208                int imgWidth = img.getWidth(null);
209                int imgHeight = img.getHeight(null);
210                if (imgWidth == -1 || imgHeight == -1) {
211                    //image hasn't completed loading, return
212                    return;
213                }
214                
215                switch (style) {
216                    case CENTERED:
217                        Rectangle clipRect = g2.getClipBounds();
218                        int imageX = (getWidth() - imgWidth) / 2;
219                        int imageY = (getHeight() - imgHeight) / 2;
220                        Rectangle r = SwingUtilities.computeIntersection(imageX, imageY, imgWidth, imgHeight, clipRect);
221                        if (r.x == 0 && r.y == 0 && (r.width == 0 || r.height == 0)) {
222                            return;
223                        }
224                        //I have my new clipping rectangle "r" in clipRect space.
225                        //It is therefore the new clipRect.
226                        clipRect = r;
227                        //since I have the intersection, all I need to do is adjust the
228                        //x & y values for the image
229                        int txClipX = clipRect.x - imageX;
230                        int txClipY = clipRect.y - imageY;
231                        int txClipW = clipRect.width;
232                        int txClipH = clipRect.height;
233                        
234                        g2.drawImage(img, clipRect.x, clipRect.y, clipRect.x + clipRect.width, clipRect.y + clipRect.height,
235                                txClipX, txClipY, txClipX + txClipW, txClipY + txClipH, null);
236                        break;
237                    case TILED:
238                    case SCALED:
239                        g2.drawImage(img, 0, 0, getWidth(), getHeight(), null);
240                        break;
241                    default:
242                        LOG.fine("unimplemented");
243                        g2.drawImage(img, 0, 0, this);
244                        break;
245                }
246            }
247        }
248        
249        /**
250         * Handles click events on the component
251         */
252        private class MouseHandler extends MouseAdapter {
253            private Cursor oldCursor;
254            private JFileChooser chooser;
255            
256            public void mouseClicked(MouseEvent evt) {
257                if (chooser == null) {
258                    chooser = new JFileChooser();
259                }
260                int retVal = chooser.showOpenDialog(JXImagePanel.this);
261                if (retVal == JFileChooser.APPROVE_OPTION) {
262                    File file = chooser.getSelectedFile();
263                    try {
264                        setImage(new ImageIcon(file.toURI().toURL()).getImage());
265                    } catch (Exception ex) {
266                    }
267                }
268            }
269            
270            public void mouseEntered(MouseEvent evt) {
271                if(evt.getSource() instanceof JLabel) {
272                    JLabel label = (JLabel)evt.getSource();
273                    if (oldCursor == null) {
274                        oldCursor = label.getCursor();
275                        label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
276                    }
277                }
278            }
279            
280            public void mouseExited(MouseEvent evt) {
281                JLabel label = (JLabel)evt.getSource();
282                if (oldCursor != null) {
283                    label.setCursor(oldCursor);
284                    oldCursor = null;
285                }
286            }
287        }
288    }
289