001    /*
002     * $Id: JXImageView.java 3289 2009-03-10 14:43:54Z kschaefe $
003     *
004     * Copyright 2006 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 org.jdesktop.swingx.color.ColorUtil;
025    import org.jdesktop.swingx.error.ErrorListener;
026    import org.jdesktop.swingx.error.ErrorSupport;
027    import org.jdesktop.swingx.graphics.GraphicsUtilities;
028    
029    import javax.imageio.ImageIO;
030    import javax.swing.*;
031    import javax.swing.event.MouseInputAdapter;
032    import java.awt.*;
033    import java.awt.datatransfer.DataFlavor;
034    import java.awt.datatransfer.Transferable;
035    import java.awt.datatransfer.UnsupportedFlavorException;
036    import java.awt.event.ActionEvent;
037    import java.awt.event.InputEvent;
038    import java.awt.event.MouseEvent;
039    import java.awt.geom.AffineTransform;
040    import java.awt.geom.Point2D;
041    import java.awt.image.AffineTransformOp;
042    import java.awt.image.BufferedImage;
043    import java.awt.image.BufferedImageOp;
044    import java.io.File;
045    import java.io.IOException;
046    import java.net.URL;
047    import java.util.ArrayList;
048    import java.util.List;
049    import java.util.logging.Logger;
050    
051    /**
052     * <p>A panel which shows an image centered. The user can drag an image into the 
053     * panel from other applications and move the image around within the view.
054     * The JXImageView has built in actions for scaling, rotating, opening a new 
055     * image, and saving. These actions can be obtained using the relevant get*Action()
056     * methods.
057     *</p> 
058     * 
059     * <p>TODO: has dashed rect and text indicating you should drag there.</p>
060     * 
061     * 
062     * <p>If the user drags more than one photo at a time into the JXImageView only
063     * the first photo will be loaded and shown. Any errors generated internally, 
064     * such as dragging in a list of files which are not images, will be reported 
065     * to any attached {@link org.jdesktop.swingx.error.ErrorListener} added by the
066     * <CODE>{@link #addErrorListener}()</CODE> method.</p>
067     *
068     * @author Joshua Marinacci joshua.marinacci@sun.com
069     */
070    public class JXImageView extends JXPanel {
071        
072        private Logger log = Logger.getLogger(JXImageView.class.getName());
073        /* ======= instance variables ========= */
074        // the image this view will show
075        private Image image;
076        // the url of the image, if available
077        private URL imageURL;
078        
079        // support for error listeners
080        private ErrorSupport errorSupport = new ErrorSupport(this);
081        
082        // location to draw image. if null then draw in the center
083        private Point2D imageLocation;
084        // the background paint 
085        private Paint checkerPaint;
086        // the scale for drawing the image
087        private double scale = 1.0;
088        // controls whether the user can move images around
089        private boolean editable = true;
090        // the handler for moving the image around within the panel
091        private MoveHandler moveHandler = new MoveHandler(this);
092        // controls the drag part of drag and drop
093        private boolean dragEnabled = false;
094        // controls the filename of the dropped file
095        private String exportName = "UntitledImage";
096        // controls the format and filename extension of the dropped file
097        private String exportFormat = "png";
098        
099        /** Creates a new instance of JXImageView */
100        public JXImageView() {
101            checkerPaint = ColorUtil.getCheckerPaint(Color.white,new Color(250,250,250),50);
102            setEditable(true);
103        }
104        
105       
106    
107        /* ========= properties ========= */
108        /**
109         * Gets the current image location. This location can be changed programmatically 
110         * or by the user dragging the image within the JXImageView.
111         * @return the current image location
112         */
113        public Point2D getImageLocation() {
114            return imageLocation;
115        }
116    
117        /**
118         * Set the current image location.
119         * @param imageLocation The new image location.
120         */
121        public void setImageLocation(Point2D imageLocation) {
122            Point2D old = getImageLocation();
123            this.imageLocation = imageLocation;
124            firePropertyChange("imageLocation", old, getImageLocation());
125            repaint();
126        }
127        
128        /**
129         * Gets the currently set image, or null if no image is set.
130         * @return the currently set image, or null if no image is set.
131         */
132        public Image getImage() {
133            return image;
134        }
135    
136        /**
137         * Sets the current image. Can set null if there should be no image show.
138         * @param image the new image to set, or null.
139         */
140        public void setImage(Image image) {
141            Image oldImage = getImage();
142            this.image = image;
143            setImageLocation(null);
144            setScale(1.0);
145            firePropertyChange("image",oldImage,image);
146            repaint();
147        }
148        
149        /**
150         * Set the current image to an image pointed to by this URL.
151         * @param url a URL pointing to an image, or null
152         * @throws java.io.IOException thrown if the image cannot be loaded
153         */
154        public void setImage(URL url) throws IOException {
155            setImageURL(url);
156            //setImage(ImageIO.read(url));
157        }
158        
159        /**
160         * Set the current image to an image pointed to by this File.
161         * @param file a File pointing to an image
162         * @throws java.io.IOException thrown if the image cannot be loaded
163         */
164        public void setImage(File file) throws IOException {
165            setImageURL(file.toURI().toURL());
166        }
167        
168        /**
169         * Gets the current image scale . When the scale is set to 1.0 
170         * then one image pixel = one screen pixel. When scale < 1.0 the draw image
171         * will be smaller than it's real size. When scale > 1.0 the drawn image will
172         * be larger than it's real size. 1.0 is the default value.
173         * @return the current image scale
174         */
175        public double getScale() {
176            return scale;
177        }
178    
179        /**
180         * Sets the current image scale . When the scale is set to 1.0 
181         * then one image pixel = one screen pixel. When scale < 1.0 the draw image
182         * will be smaller than it's real size. When scale > 1.0 the drawn image will
183         * be larger than it's real size. 1.0 is the default value.
184         * @param scale the new image scale
185         */
186        public void setScale(double scale) {
187            double oldScale = this.scale;
188            this.scale = scale;
189            this.firePropertyChange("scale",oldScale,scale);
190            repaint();
191        }
192    
193        /**
194         * Returns whether or not the user can drag images.
195         * @return whether or not the user can drag images
196         */
197        public boolean isEditable() {
198            return editable;
199        }
200    
201        /**
202         * Sets whether or not the user can drag images. When set to true the user can
203         * drag the photo around with their mouse. Also the cursor will be set to the
204         * 'hand' cursor. When set to false the user cannot drag photos around
205         * and the cursor will be set to the default.
206         * @param editable whether or not the user can drag images
207         */
208        public void setEditable(boolean editable) {
209            boolean old = isEditable();
210            this.editable = editable;
211            if(editable) {
212                addMouseMotionListener(moveHandler);
213                addMouseListener(moveHandler);
214                this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
215                try {
216                    this.setTransferHandler(new DnDHandler());
217                } catch (ClassNotFoundException ex) {
218                    ex.printStackTrace();
219                    fireError(ex);
220                }
221            } else {
222                removeMouseMotionListener(moveHandler);
223                removeMouseListener(moveHandler);
224                this.setCursor(Cursor.getDefaultCursor());
225                setTransferHandler(null);
226            }
227            firePropertyChange("editable", old, isEditable());
228        }
229        
230        /**
231         * Sets the <CODE>dragEnabled</CODE> property, which determines whether or not 
232         * the user can drag images out of the image view and into other components or 
233         * application. Note: <B>setting
234         * this to true will disable the ability to move the image around within the
235         * well.</B>, though it will not change the <b>editable</b> property directly.
236         * @param dragEnabled the value to set the dragEnabled property to.
237         */
238        public void setDragEnabled(boolean dragEnabled) {
239            boolean old = isDragEnabled();
240            this.dragEnabled = dragEnabled;
241            firePropertyChange("dragEnabled", old, isDragEnabled());
242        }
243    
244        /**
245         * Gets the current value of the <CODE>dragEnabled</CODE> property.
246         * @return the current value of the <CODE>dragEnabled</CODE> property
247         */
248        public boolean isDragEnabled() {
249            return dragEnabled;
250        }
251        
252        /**
253         * Adds an ErrorListener to the list of listeners to be notified
254         * of ErrorEvents
255         * @param el an ErrorListener to add
256         */
257        public void addErrorListener(ErrorListener el) {
258            errorSupport.addErrorListener(el);
259        }
260        
261        /**
262         * Remove an ErrorListener from the list of listeners to be notified of ErrorEvents.
263         * @param el an ErrorListener to remove
264         */
265        public void removeErrorListener(ErrorListener el) {
266            errorSupport.removeErrorListener(el);
267        }
268        
269        /**
270         * Send a new ErrorEvent to all registered ErrorListeners
271         * @param throwable the Error or Exception which was thrown
272         */
273        protected void fireError(Throwable throwable) {
274            errorSupport.fireErrorEvent(throwable);
275        }
276    
277        
278        private static FileDialog getSafeFileDialog(Component comp) {
279            Window win = SwingUtilities.windowForComponent(comp);
280            if(win instanceof Dialog) {
281                return new FileDialog((Dialog)win);
282            }
283            if(win instanceof Frame) {
284                return new FileDialog((Frame)win);
285            }
286            return null;
287        }
288                
289        // an action which will open a file chooser and load the selected image
290        // if any.
291        /**
292         * Returns an Action which will open a file chooser, ask the user for an image file
293         * then load the image into the view. If the load fails an error will be fired
294         * to all registered ErrorListeners
295         * @return the action
296         * @see ErrorListener
297         * @deprecated see SwingX issue 990
298         */
299        @Deprecated
300        public Action getOpenAction() {
301            Action action = new AbstractAction() {
302                public void actionPerformed(ActionEvent actionEvent) {
303                    FileDialog fd = getSafeFileDialog(JXImageView.this);
304                    fd.setMode(FileDialog.LOAD);
305                    fd.setVisible(true);
306                    if(fd.getFile() != null) {
307                        try {
308                            setImage(new File(fd.getDirectory(),fd.getFile()));
309                        } catch (IOException ex) {
310                            fireError(ex);
311                        }
312                    }
313                    /*
314                    JFileChooser chooser = new JFileChooser();
315                    chooser.showOpenDialog(JXImageView.this);
316                    File file = chooser.getSelectedFile();
317                    if(file != null) {
318                        try {
319                            setImage(file);
320                        } catch (IOException ex) {
321                            log.fine(ex.getMessage());
322                            ex.printStackTrace();
323                            fireError(ex);
324                        }
325                    }
326                     */
327                }
328            };
329            action.putValue(Action.NAME,"Open");
330            return action;
331        }
332        
333        // an action that will open a file chooser then save the current image to
334        // the selected file, if any.
335        /**
336         * Returns an Action which will open a file chooser, ask the user for an image file
337         * then save the image from the view. If the save fails an error will be fired
338         * to all registered ErrorListeners
339         * @return an Action
340         * @deprecated see SwingX issue 990
341         */
342        @Deprecated
343        public Action getSaveAction() {
344            Action action = new AbstractAction() {
345                public void actionPerformed(ActionEvent evt) {
346                    Image img = getImage();
347                    BufferedImage dst = new BufferedImage(
348                                img.getWidth(null),
349                                img.getHeight(null), 
350                                BufferedImage.TYPE_INT_ARGB);
351                    Graphics2D g = (Graphics2D)dst.getGraphics();
352                    
353                    try {
354                        // smooth scaling
355                        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
356                                RenderingHints.VALUE_INTERPOLATION_BICUBIC);
357                        g.drawImage(img, 0, 0, null);
358                    } finally {
359                        g.dispose();
360                    }
361                    FileDialog fd = new FileDialog((Frame)SwingUtilities.windowForComponent(JXImageView.this));
362                    fd.setMode(FileDialog.SAVE);
363                    fd.setVisible(true);
364                    if(fd.getFile() != null) {
365                        try {
366                            ImageIO.write(dst,"png",new File(fd.getDirectory(),fd.getFile()));
367                        } catch (IOException ex) {
368                            fireError(ex);
369                        }
370                    }
371                    /*
372                    JFileChooser chooser = new JFileChooser();
373                    chooser.showSaveDialog(JXImageView.this);
374                    File file = chooser.getSelectedFile();
375                    if(file != null) {
376                        try {
377                            ImageIO.write(dst,"png",file);
378                        } catch (IOException ex) {
379                            log.fine(ex.getMessage());
380                            ex.printStackTrace();
381                            fireError(ex);
382                        }
383                    }
384                     */
385                }
386            };
387    
388            action.putValue(Action.NAME,"Save");
389            return action;
390        }
391        
392        /**
393         * Get an action which will rotate the currently selected image clockwise.
394         * @return an action
395         * @deprecated see SwingX issue 990
396         */
397        @Deprecated
398        public Action getRotateClockwiseAction() {
399            Action action = new AbstractAction() {
400                public void actionPerformed(ActionEvent evt) {
401                    Image img = getImage();
402                    BufferedImage src = new BufferedImage(
403                                img.getWidth(null),
404                                img.getHeight(null), 
405                                BufferedImage.TYPE_INT_ARGB);
406                    BufferedImage dst = new BufferedImage(
407                                img.getHeight(null), 
408                                img.getWidth(null),
409                                BufferedImage.TYPE_INT_ARGB);
410                    Graphics2D g = (Graphics2D)src.getGraphics();
411                    
412                    try {
413                        // smooth scaling
414                        g.drawImage(img, 0, 0, null);
415                    } finally {
416                        g.dispose();
417                    }
418                    
419                    AffineTransform trans = AffineTransform.getRotateInstance(Math.PI/2,0,0);
420                    trans.translate(0,-src.getHeight());
421                    BufferedImageOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
422                    op.filter(src,dst);
423                    setImage(dst);
424                }
425            };
426            action.putValue(Action.NAME,"Rotate Clockwise");
427            return action;        
428        }
429        
430        /**
431         * Gets an action which will rotate the current image counter clockwise.
432         * @return an Action
433         * @deprecated see SwingX issue 990
434         */
435        @Deprecated
436        public Action getRotateCounterClockwiseAction() {
437            Action action = new AbstractAction() {
438                public void actionPerformed(ActionEvent evt) {
439                    Image img = getImage();
440                    BufferedImage src = new BufferedImage(
441                                img.getWidth(null),
442                                img.getHeight(null), 
443                                BufferedImage.TYPE_INT_ARGB);
444                    BufferedImage dst = new BufferedImage(
445                                img.getHeight(null), 
446                                img.getWidth(null),
447                                BufferedImage.TYPE_INT_ARGB);
448                    Graphics2D g = (Graphics2D)src.getGraphics();
449                    
450                    try {
451                        // smooth scaling
452                        g.drawImage(img, 0, 0, null);
453                    } finally {
454                        g.dispose();
455                    }
456                    AffineTransform trans = AffineTransform.getRotateInstance(-Math.PI/2,0,0);
457                    trans.translate(-src.getWidth(),0);
458                    BufferedImageOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
459                    op.filter(src,dst);
460                    setImage(dst);
461                }
462            };
463            action.putValue(Action.NAME, "Rotate CounterClockwise");
464            return action;        
465        }
466           
467        /**
468         * Gets an action which will zoom the current image out by a factor of 2.
469         * @return an action
470         * @deprecated see SwingX issue 990
471         */
472        @Deprecated
473        public Action getZoomOutAction() {
474            Action action = new AbstractAction() {
475                public void actionPerformed(ActionEvent actionEvent) {
476                    setScale(getScale()*0.5);
477                }
478            };
479            action.putValue(Action.NAME,"Zoom Out");
480            return action;
481        }
482        
483        /**
484         * Gets an action which will zoom the current image in by a factor of 2
485         * @return an action
486         * @deprecated see SwingX issue 990
487         */
488        @Deprecated
489        public Action getZoomInAction() {
490            Action action = new AbstractAction() {
491                public void actionPerformed(ActionEvent actionEvent) {
492                    setScale(getScale()*2);
493                }
494            };
495            action.putValue(Action.NAME,"Zoom In");
496            return action;
497        }
498        /* === overriden methods === */
499        
500        /**
501         * Implementation detail.
502         * @param g 
503         */
504        protected void paintComponent(Graphics g) {
505            ((Graphics2D)g).setPaint(checkerPaint);
506            //g.setColor(getBackground());
507            g.fillRect(0,0,getWidth(),getHeight());
508            if(getImage() != null) {
509                Point2D center = new Point2D.Double(getWidth()/2,getHeight()/2);
510                if(getImageLocation() != null) {
511                    center = getImageLocation();
512                }
513                Point2D loc = new Point2D.Double();
514                double width = getImage().getWidth(null)*getScale();
515                double height = getImage().getHeight(null)*getScale();
516                loc.setLocation(center.getX()-width/2, center.getY()-height/2);
517                g.drawImage(getImage(), (int)loc.getX(), (int)loc.getY(),
518                        (int)width,(int)height,
519                        null);
520            }
521        }
522    
523        
524        /* === Internal helper classes === */
525    
526        private class MoveHandler extends MouseInputAdapter {
527            private JXImageView panel;
528            private Point prev = null;
529            private Point start = null;
530            public MoveHandler(JXImageView panel) {
531                this.panel = panel;
532            }
533    
534            public void mousePressed(MouseEvent evt) {
535                prev = evt.getPoint();
536                start = prev;
537            }
538    
539            public void mouseDragged(MouseEvent evt) {
540                Point curr = evt.getPoint();
541                
542                if(isDragEnabled()) {
543                    //log.fine("testing drag enabled: " + curr + " " + start);
544                    //log.fine("distance = " + curr.distance(start));
545                    if(curr.distance(start) > 5) {
546                        JXImageView.this.log.fine("starting the drag: ");
547                        panel.getTransferHandler().exportAsDrag((JComponent)evt.getSource(),evt,TransferHandler.COPY);
548                        return;
549                    }
550                }
551                
552                int offx = curr.x - prev.x;
553                int offy = curr.y - prev.y;
554                Point2D offset = getImageLocation();
555                if (offset == null) {
556                    if (image != null) {
557                        offset = new Point2D.Double(getWidth() / 2, getHeight() / 2);
558                    } else {
559                        offset = new Point2D.Double(0, 0);
560                    }
561                }
562                offset = new Point2D.Double(offset.getX() + offx, offset.getY() + offy);
563                setImageLocation(offset);
564                prev = curr;
565                repaint();
566            }
567    
568            public void mouseReleased(MouseEvent evt) {
569                prev = null;
570            }
571        }
572    
573        private class DnDHandler extends TransferHandler {
574            DataFlavor urlFlavor;
575            
576            public DnDHandler() throws ClassNotFoundException {
577                 urlFlavor = new DataFlavor("application/x-java-url;class=java.net.URL");
578            }
579            
580            public void exportAsDrag(JComponent c, InputEvent evt, int action) {
581                //log.fine("exportting as drag");
582                super.exportAsDrag(c,evt,action);
583            }
584            public int getSourceActions(JComponent c) {
585                //log.fine("get source actions: " + c);
586                return COPY;
587            }
588            protected void exportDone(JComponent source, Transferable data, int action) {
589                //log.fine("exportDone: " + source + " " + data + " " +action);
590            }
591    
592            public boolean canImport(JComponent c, DataFlavor[] flavors) {
593                //log.fine("canImport:" + c);
594                for (int i = 0; i < flavors.length; i++) {
595                    //log.fine("testing: "+flavors[i]);
596                    if (DataFlavor.javaFileListFlavor.equals(flavors[i])) {
597                        return true;
598                    }
599                    if (DataFlavor.imageFlavor.equals(flavors[i])) {
600                        return true;
601                    }
602                    if (urlFlavor.match(flavors[i])) {
603                        return true;
604                    }
605                    
606                }
607                return false;
608            }
609    
610            protected Transferable createTransferable(JComponent c) {
611                JXImageView view = (JXImageView)c;
612                return new ImageTransferable(view.getImage(),
613                        view.getExportName(), view.getExportFormat());
614            }
615            
616            @SuppressWarnings("unchecked")
617            public boolean importData(JComponent comp, Transferable t) {
618                if (canImport(comp, t.getTransferDataFlavors())) {
619                    try {
620                        if(t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
621                            List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
622                            //log.fine("doing file list flavor");
623                            if (files.size() > 0) {
624                                File file = files.get(0);
625                                //log.fine("readingt hte image: " + file.getCanonicalPath());
626                                /*Iterator it = ImageIO.getImageReaders(new FileInputStream(file));
627                                while(it.hasNext()) {
628                                    log.fine("can read: " + it.next());
629                                }*/
630                                setImageString(file.toURI().toURL().toString());
631                                //BufferedImage img = ImageIO.read(file.toURI().toURL());
632                                //setImage(img);
633                                return true;
634                            }
635                        }
636                        //log.fine("doing a uri list");
637                        Object obj = t.getTransferData(urlFlavor);
638                        //log.fine("obj = " + obj + " " + obj.getClass().getPackage() + " "
639                        //        + obj.getClass().getName());
640                        if(obj instanceof URL) {
641                            setImageString(((URL)obj).toString());
642                        }
643                        return true;
644                    } catch (Exception ex) {
645                        log .severe(ex.getMessage());
646                        ex.printStackTrace();
647                        fireError(ex);
648                    }
649                }
650                return false;
651            }
652    
653        }
654    
655        
656        private class ImageTransferable implements Transferable {
657            private Image img;
658            private List<File> files;
659            private String exportName, exportFormat;
660            public ImageTransferable(Image img, String exportName, String exportFormat) {
661                this.img = img;
662                this.exportName = exportName;
663                this.exportFormat = exportFormat;
664            }
665    
666            public DataFlavor[] getTransferDataFlavors() {
667                return new DataFlavor[] { DataFlavor.imageFlavor,
668                    DataFlavor.javaFileListFlavor };
669            }
670    
671            public boolean isDataFlavorSupported(DataFlavor flavor) {
672                if(flavor == DataFlavor.imageFlavor) {
673                    return true;
674                }
675                return flavor == DataFlavor.javaFileListFlavor;
676            }
677    
678            public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
679                //log.fine("doing get trans data: " + flavor);
680                if(flavor == DataFlavor.imageFlavor) {
681                    return img;
682                }
683                if(flavor == DataFlavor.javaFileListFlavor) {
684                    if(files == null) {
685                        files = new ArrayList<File>();
686                        File file = File.createTempFile(exportName,"."+exportFormat);
687                        //log.fine("writing to: " + file);
688                        ImageIO.write(GraphicsUtilities.convertToBufferedImage(img),exportFormat,file);
689                        files.add(file);
690                    }
691                    //log.fine("returning: " + files);
692                    return files;
693                }
694                return null;
695            }
696        }
697    
698        public String getExportName() {
699            return exportName;
700        }
701    
702        public void setExportName(String exportName) {
703            String old = getExportName();
704            this.exportName = exportName;
705            firePropertyChange("exportName", old, getExportName());
706        }
707    
708        public String getExportFormat() {
709            return exportFormat;
710        }
711    
712        public void setExportFormat(String exportFormat) {
713            String old = getExportFormat();
714            this.exportFormat = exportFormat;
715            firePropertyChange("exportFormat", old, getExportFormat());
716        }
717    
718        public URL getImageURL() {
719            return imageURL;
720        }
721    
722        public void setImageURL(URL imageURL) throws IOException {
723            URL old = getImageURL();
724            this.imageURL = imageURL;
725            firePropertyChange("imageURL", old, getImageURL());
726            setImage(ImageIO.read(getImageURL()));
727        }
728        
729        /** Returns the current image's URL (if available) as a string.
730         * If the image has no URL, or if there is no image, then this
731         * method will return null.
732         * @return the url of the image as a string
733         */
734        public String getImageString() {
735            if(getImageURL() == null) {
736                return null;
737            }
738            return getImageURL().toString();
739        }
740        
741        /** Sets the current image using a string. This string <b>must</b>
742         * contain a valid URL.
743         * @param url string of a URL
744         * @throws java.io.IOException thrown if the URL does not parse
745         */
746        public void setImageString(String url) throws IOException {
747            String old = getImageString();
748            setImageURL(new URL(url));
749            firePropertyChange("imageString", old, url);
750        }
751        
752    }