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 }