1 /*
   2  * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  26 
  27 import java.awt.*;
  28 import java.awt.image.*;
  29 import java.beans.ConstructorProperties;
  30 import java.beans.Transient;
  31 import java.net.URL;
  32 
  33 import java.io.Serializable;
  34 import java.io.ObjectOutputStream;
  35 import java.io.ObjectInputStream;
  36 import java.io.IOException;
  37 
  38 import java.util.Locale;
  39 import javax.accessibility.*;
  40 
  41 import sun.awt.AppContext;
  42 import java.lang.reflect.Field;
  43 import java.security.*;
  44 
  45 /**
  46  * An implementation of the Icon interface that paints Icons
  47  * from Images. Images that are created from a URL, filename or byte array
  48  * are preloaded using MediaTracker to monitor the loaded state
  49  * of the image.
  50  *
  51  * <p>
  52  * For further information and examples of using image icons, see
  53  * <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/icon.html">How to Use Icons</a>
  54  * in <em>The Java Tutorial.</em>
  55  *
  56  * <p>
  57  * <strong>Warning:</strong>
  58  * Serialized objects of this class will not be compatible with
  59  * future Swing releases. The current serialization support is
  60  * appropriate for short term storage or RMI between applications running
  61  * the same version of Swing.  As of 1.4, support for long term storage
  62  * of all JavaBeans<sup><font size="-2">TM</font></sup>
  63  * has been added to the <code>java.beans</code> package.
  64  * Please see {@link java.beans.XMLEncoder}.
  65  *
  66  * @author Jeff Dinkins
  67  * @author Lynn Monsanto
  68  */
  69 public class ImageIcon implements Icon, Serializable, Accessible {
  70     /* Keep references to the filename and location so that
  71      * alternate persistence schemes have the option to archive
  72      * images symbolically rather than including the image data
  73      * in the archive.
  74      */
  75     transient private String filename;
  76     transient private URL location;
  77 
  78     transient Image image;
  79     transient int loadStatus = 0;
  80     ImageObserver imageObserver;
  81     String description = null;
  82 
  83     // Fields for twisted backward compatibility only. DO NOT USE.
  84     protected final static Component component;
  85     protected final static MediaTracker tracker;
  86 
  87     static {
  88         component = AccessController.doPrivileged(new PrivilegedAction<Component>() {
  89             public Component run() {
  90                 try {
  91                     final Component component = createNoPermsComponent();
  92 
  93                     // 6482575 - clear the appContext field so as not to leak it
  94                     Field appContextField =
  95 
  96                             Component.class.getDeclaredField("appContext");
  97                     appContextField.setAccessible(true);
  98                     appContextField.set(component, null);
  99 
 100                     return component;
 101                 } catch (Throwable e) {
 102                     // We don't care about component.
 103                     // So don't prevent class initialisation.
 104                     e.printStackTrace();
 105                     return null;
 106                 }
 107             }
 108         });
 109         tracker = new MediaTracker(component);
 110     }
 111 
 112     private static Component createNoPermsComponent() {
 113         // 7020198 - set acc field to no permissions and no subject
 114         // Note, will have appContext set.
 115         return AccessController.doPrivileged(
 116                 new PrivilegedAction<Component>() {
 117                     public Component run() {
 118                         return new Component() {
 119                         };
 120                     }
 121                 },
 122                 new AccessControlContext(new ProtectionDomain[]{
 123                         new ProtectionDomain(null, null)
 124                 })
 125         );
 126     }
 127 
 128     /**
 129      * Id used in loading images from MediaTracker.
 130      */
 131     private static int mediaTrackerID;
 132 
 133     private final static Object TRACKER_KEY = new StringBuilder("TRACKER_KEY");
 134 
 135     int width = -1;
 136     int height = -1;
 137 
 138     /**
 139      * Creates an ImageIcon from the specified file. The image will
 140      * be preloaded by using MediaTracker to monitor the loading state
 141      * of the image.
 142      * @param filename the name of the file containing the image
 143      * @param description a brief textual description of the image
 144      * @see #ImageIcon(String)
 145      */
 146     public ImageIcon(String filename, String description) {
 147         image = Toolkit.getDefaultToolkit().getImage(filename);
 148         if (image == null) {
 149             return;
 150         }
 151         this.filename = filename;
 152         this.description = description;
 153         loadImage(image);
 154     }
 155 
 156     /**
 157      * Creates an ImageIcon from the specified file. The image will
 158      * be preloaded by using MediaTracker to monitor the loading state
 159      * of the image. The specified String can be a file name or a
 160      * file path. When specifying a path, use the Internet-standard
 161      * forward-slash ("/") as a separator.
 162      * (The string is converted to an URL, so the forward-slash works
 163      * on all systems.)
 164      * For example, specify:
 165      * <pre>
 166      *    new ImageIcon("images/myImage.gif") </pre>
 167      * The description is initialized to the <code>filename</code> string.
 168      *
 169      * @param filename a String specifying a filename or path
 170      * @see #getDescription
 171      */
 172     @ConstructorProperties({"description"})
 173     public ImageIcon (String filename) {
 174         this(filename, filename);
 175     }
 176 
 177     /**
 178      * Creates an ImageIcon from the specified URL. The image will
 179      * be preloaded by using MediaTracker to monitor the loaded state
 180      * of the image.
 181      * @param location the URL for the image
 182      * @param description a brief textual description of the image
 183      * @see #ImageIcon(String)
 184      */
 185     public ImageIcon(URL location, String description) {
 186         image = Toolkit.getDefaultToolkit().getImage(location);
 187         if (image == null) {
 188             return;
 189         }
 190         this.location = location;
 191         this.description = description;
 192         loadImage(image);
 193     }
 194 
 195     /**
 196      * Creates an ImageIcon from the specified URL. The image will
 197      * be preloaded by using MediaTracker to monitor the loaded state
 198      * of the image.
 199      * The icon's description is initialized to be
 200      * a string representation of the URL.
 201      * @param location the URL for the image
 202      * @see #getDescription
 203      */
 204     public ImageIcon (URL location) {
 205         this(location, location.toExternalForm());
 206     }
 207 
 208     /**
 209      * Creates an ImageIcon from the image.
 210      * @param image the image
 211      * @param description a brief textual description of the image
 212      */
 213     public ImageIcon(Image image, String description) {
 214         this(image);
 215         this.description = description;
 216     }
 217 
 218     /**
 219      * Creates an ImageIcon from an image object.
 220      * If the image has a "comment" property that is a string,
 221      * then the string is used as the description of this icon.
 222      * @param image the image
 223      * @see #getDescription
 224      * @see java.awt.Image#getProperty
 225      */
 226     public ImageIcon (Image image) {
 227         this.image = image;
 228         Object o = image.getProperty("comment", imageObserver);
 229         if (o instanceof String) {
 230             description = (String) o;
 231         }
 232         loadImage(image);
 233     }
 234 
 235     /**
 236      * Creates an ImageIcon from an array of bytes which were
 237      * read from an image file containing a supported image format,
 238      * such as GIF, JPEG, or (as of 1.3) PNG.
 239      * Normally this array is created
 240      * by reading an image using Class.getResourceAsStream(), but
 241      * the byte array may also be statically stored in a class.
 242      *
 243      * @param  imageData an array of pixels in an image format supported
 244      *         by the AWT Toolkit, such as GIF, JPEG, or (as of 1.3) PNG
 245      * @param  description a brief textual description of the image
 246      * @see    java.awt.Toolkit#createImage
 247      */
 248     public ImageIcon (byte[] imageData, String description) {
 249         this.image = Toolkit.getDefaultToolkit().createImage(imageData);
 250         if (image == null) {
 251             return;
 252         }
 253         this.description = description;
 254         loadImage(image);
 255     }
 256 
 257     /**
 258      * Creates an ImageIcon from an array of bytes which were
 259      * read from an image file containing a supported image format,
 260      * such as GIF, JPEG, or (as of 1.3) PNG.
 261      * Normally this array is created
 262      * by reading an image using Class.getResourceAsStream(), but
 263      * the byte array may also be statically stored in a class.
 264      * If the resulting image has a "comment" property that is a string,
 265      * then the string is used as the description of this icon.
 266      *
 267      * @param  imageData an array of pixels in an image format supported by
 268      *             the AWT Toolkit, such as GIF, JPEG, or (as of 1.3) PNG
 269      * @see    java.awt.Toolkit#createImage
 270      * @see #getDescription
 271      * @see java.awt.Image#getProperty
 272      */
 273     public ImageIcon (byte[] imageData) {
 274         this.image = Toolkit.getDefaultToolkit().createImage(imageData);
 275         if (image == null) {
 276             return;
 277         }
 278         Object o = image.getProperty("comment", imageObserver);
 279         if (o instanceof String) {
 280             description = (String) o;
 281         }
 282         loadImage(image);
 283     }
 284 
 285     /**
 286      * Creates an uninitialized image icon.
 287      */
 288     public ImageIcon() {
 289     }
 290 
 291     /**
 292      * Loads the image, returning only when the image is loaded.
 293      * @param image the image
 294      */
 295     protected void loadImage(Image image) {
 296         MediaTracker mTracker = getTracker();
 297         synchronized(mTracker) {
 298             int id = getNextID();
 299 
 300             mTracker.addImage(image, id);
 301             try {
 302                 mTracker.waitForID(id, 0);
 303             } catch (InterruptedException e) {
 304                 System.out.println("INTERRUPTED while loading Image");
 305             }
 306             loadStatus = mTracker.statusID(id, false);
 307             mTracker.removeImage(image, id);
 308 
 309             width = image.getWidth(imageObserver);
 310             height = image.getHeight(imageObserver);
 311         }
 312     }
 313 
 314     /**
 315      * Returns an ID to use with the MediaTracker in loading an image.
 316      */
 317     private int getNextID() {
 318         synchronized(getTracker()) {
 319             return ++mediaTrackerID;
 320         }
 321     }
 322 
 323     /**
 324      * Returns the MediaTracker for the current AppContext, creating a new
 325      * MediaTracker if necessary.
 326      */
 327     private MediaTracker getTracker() {
 328         Object trackerObj;
 329         AppContext ac = AppContext.getAppContext();
 330         // Opt: Only synchronize if trackerObj comes back null?
 331         // If null, synchronize, re-check for null, and put new tracker
 332         synchronized(ac) {
 333             trackerObj = ac.get(TRACKER_KEY);
 334             if (trackerObj == null) {
 335                 Component comp = new Component() {};
 336                 trackerObj = new MediaTracker(comp);
 337                 ac.put(TRACKER_KEY, trackerObj);
 338             }
 339         }
 340         return (MediaTracker) trackerObj;
 341     }
 342 
 343     /**
 344      * Returns the status of the image loading operation.
 345      * @return the loading status as defined by java.awt.MediaTracker
 346      * @see java.awt.MediaTracker#ABORTED
 347      * @see java.awt.MediaTracker#ERRORED
 348      * @see java.awt.MediaTracker#COMPLETE
 349      */
 350     public int getImageLoadStatus() {
 351         return loadStatus;
 352     }
 353 
 354     /**
 355      * Returns this icon's <code>Image</code>.
 356      * @return the <code>Image</code> object for this <code>ImageIcon</code>
 357      */
 358     @Transient
 359     public Image getImage() {
 360         return image;
 361     }
 362 
 363     /**
 364      * Sets the image displayed by this icon.
 365      * @param image the image
 366      */
 367     public void setImage(Image image) {
 368         this.image = image;
 369         loadImage(image);
 370     }
 371 
 372     /**
 373      * Gets the description of the image.  This is meant to be a brief
 374      * textual description of the object.  For example, it might be
 375      * presented to a blind user to give an indication of the purpose
 376      * of the image.
 377      * The description may be null.
 378      *
 379      * @return a brief textual description of the image
 380      */
 381     public String getDescription() {
 382         return description;
 383     }
 384 
 385     /**
 386      * Sets the description of the image.  This is meant to be a brief
 387      * textual description of the object.  For example, it might be
 388      * presented to a blind user to give an indication of the purpose
 389      * of the image.
 390      * @param description a brief textual description of the image
 391      */
 392     public void setDescription(String description) {
 393         this.description = description;
 394     }
 395 
 396     /**
 397      * Paints the icon.
 398      * The top-left corner of the icon is drawn at
 399      * the point (<code>x</code>, <code>y</code>)
 400      * in the coordinate space of the graphics context <code>g</code>.
 401      * If this icon has no image observer,
 402      * this method uses the <code>c</code> component
 403      * as the observer.
 404      *
 405      * @param c the component to be used as the observer
 406      *          if this icon has no image observer
 407      * @param g the graphics context
 408      * @param x the X coordinate of the icon's top-left corner
 409      * @param y the Y coordinate of the icon's top-left corner
 410      */
 411     public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
 412         if(imageObserver == null) {
 413            g.drawImage(image, x, y, c);
 414         } else {
 415            g.drawImage(image, x, y, imageObserver);
 416         }
 417     }
 418 
 419     /**
 420      * Gets the width of the icon.
 421      *
 422      * @return the width in pixels of this icon
 423      */
 424     public int getIconWidth() {
 425         return width;
 426     }
 427 
 428     /**
 429      * Gets the height of the icon.
 430      *
 431      * @return the height in pixels of this icon
 432      */
 433     public int getIconHeight() {
 434         return height;
 435     }
 436 
 437     /**
 438      * Sets the image observer for the image.  Set this
 439      * property if the ImageIcon contains an animated GIF, so
 440      * the observer is notified to update its display.
 441      * For example:
 442      * <pre>
 443      *     icon = new ImageIcon(...)
 444      *     button.setIcon(icon);
 445      *     icon.setImageObserver(button);
 446      * </pre>
 447      *
 448      * @param observer the image observer
 449      */
 450     public void setImageObserver(ImageObserver observer) {
 451         imageObserver = observer;
 452     }
 453 
 454     /**
 455      * Returns the image observer for the image.
 456      *
 457      * @return the image observer, which may be null
 458      */
 459     @Transient
 460     public ImageObserver getImageObserver() {
 461         return imageObserver;
 462     }
 463 
 464     /**
 465      * Returns a string representation of this image.
 466      *
 467      * @return a string representing this image
 468      */
 469     public String toString() {
 470         if (description != null) {
 471             return description;
 472         }
 473         return super.toString();
 474     }
 475 
 476     private void readObject(ObjectInputStream s)
 477         throws ClassNotFoundException, IOException
 478     {
 479         s.defaultReadObject();
 480 
 481         int w = s.readInt();
 482         int h = s.readInt();
 483         int[] pixels = (int[])(s.readObject());
 484 
 485         if (pixels != null) {
 486             Toolkit tk = Toolkit.getDefaultToolkit();
 487             ColorModel cm = ColorModel.getRGBdefault();
 488             image = tk.createImage(new MemoryImageSource(w, h, cm, pixels, 0, w));
 489             loadImage(image);
 490         }
 491     }
 492 
 493 
 494     private void writeObject(ObjectOutputStream s)
 495         throws IOException
 496     {
 497         s.defaultWriteObject();
 498 
 499         int w = getIconWidth();
 500         int h = getIconHeight();
 501         int[] pixels = image != null? new int[w * h] : null;
 502 
 503         if (image != null) {
 504             try {
 505                 PixelGrabber pg = new PixelGrabber(image, 0, 0, w, h, pixels, 0, w);
 506                 pg.grabPixels();
 507                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
 508                     throw new IOException("failed to load image contents");
 509                 }
 510             }
 511             catch (InterruptedException e) {
 512                 throw new IOException("image load interrupted");
 513             }
 514         }
 515 
 516         s.writeInt(w);
 517         s.writeInt(h);
 518         s.writeObject(pixels);
 519     }
 520 
 521     /**
 522      * --- Accessibility Support ---
 523      */
 524 
 525     private AccessibleImageIcon accessibleContext = null;
 526 
 527     /**
 528      * Gets the AccessibleContext associated with this ImageIcon.
 529      * For image icons, the AccessibleContext takes the form of an
 530      * AccessibleImageIcon.
 531      * A new AccessibleImageIcon instance is created if necessary.
 532      *
 533      * @return an AccessibleImageIcon that serves as the
 534      *         AccessibleContext of this ImageIcon
 535      * @beaninfo
 536      *       expert: true
 537      *  description: The AccessibleContext associated with this ImageIcon.
 538      * @since 1.3
 539      */
 540     public AccessibleContext getAccessibleContext() {
 541         if (accessibleContext == null) {
 542             accessibleContext = new AccessibleImageIcon();
 543         }
 544         return accessibleContext;
 545     }
 546 
 547     /**
 548      * This class implements accessibility support for the
 549      * <code>ImageIcon</code> class.  It provides an implementation of the
 550      * Java Accessibility API appropriate to image icon user-interface
 551      * elements.
 552      * <p>
 553      * <strong>Warning:</strong>
 554      * Serialized objects of this class will not be compatible with
 555      * future Swing releases. The current serialization support is
 556      * appropriate for short term storage or RMI between applications running
 557      * the same version of Swing.  As of 1.4, support for long term storage
 558      * of all JavaBeans<sup><font size="-2">TM</font></sup>
 559      * has been added to the <code>java.beans</code> package.
 560      * Please see {@link java.beans.XMLEncoder}.
 561      * @since 1.3
 562      */
 563     protected class AccessibleImageIcon extends AccessibleContext
 564         implements AccessibleIcon, Serializable {
 565 
 566         /*
 567          * AccessibleContest implementation -----------------
 568          */
 569 
 570         /**
 571          * Gets the role of this object.
 572          *
 573          * @return an instance of AccessibleRole describing the role of the
 574          * object
 575          * @see AccessibleRole
 576          */
 577         public AccessibleRole getAccessibleRole() {
 578             return AccessibleRole.ICON;
 579         }
 580 
 581         /**
 582          * Gets the state of this object.
 583          *
 584          * @return an instance of AccessibleStateSet containing the current
 585          * state set of the object
 586          * @see AccessibleState
 587          */
 588         public AccessibleStateSet getAccessibleStateSet() {
 589             return null;
 590         }
 591 
 592         /**
 593          * Gets the Accessible parent of this object.  If the parent of this
 594          * object implements Accessible, this method should simply return
 595          * getParent().
 596          *
 597          * @return the Accessible parent of this object -- can be null if this
 598          * object does not have an Accessible parent
 599          */
 600         public Accessible getAccessibleParent() {
 601             return null;
 602         }
 603 
 604         /**
 605          * Gets the index of this object in its accessible parent.
 606          *
 607          * @return the index of this object in its parent; -1 if this
 608          * object does not have an accessible parent.
 609          * @see #getAccessibleParent
 610          */
 611         public int getAccessibleIndexInParent() {
 612             return -1;
 613         }
 614 
 615         /**
 616          * Returns the number of accessible children in the object.  If all
 617          * of the children of this object implement Accessible, than this
 618          * method should return the number of children of this object.
 619          *
 620          * @return the number of accessible children in the object.
 621          */
 622         public int getAccessibleChildrenCount() {
 623             return 0;
 624         }
 625 
 626         /**
 627          * Returns the nth Accessible child of the object.
 628          *
 629          * @param i zero-based index of child
 630          * @return the nth Accessible child of the object
 631          */
 632         public Accessible getAccessibleChild(int i) {
 633             return null;
 634         }
 635 
 636         /**
 637          * Returns the locale of this object.
 638          *
 639          * @return the locale of this object
 640          */
 641         public Locale getLocale() throws IllegalComponentStateException {
 642             return null;
 643         }
 644 
 645         /*
 646          * AccessibleIcon implementation -----------------
 647          */
 648 
 649         /**
 650          * Gets the description of the icon.  This is meant to be a brief
 651          * textual description of the object.  For example, it might be
 652          * presented to a blind user to give an indication of the purpose
 653          * of the icon.
 654          *
 655          * @return the description of the icon
 656          */
 657         public String getAccessibleIconDescription() {
 658             return ImageIcon.this.getDescription();
 659         }
 660 
 661         /**
 662          * Sets the description of the icon.  This is meant to be a brief
 663          * textual description of the object.  For example, it might be
 664          * presented to a blind user to give an indication of the purpose
 665          * of the icon.
 666          *
 667          * @param description the description of the icon
 668          */
 669         public void setAccessibleIconDescription(String description) {
 670             ImageIcon.this.setDescription(description);
 671         }
 672 
 673         /**
 674          * Gets the height of the icon.
 675          *
 676          * @return the height of the icon
 677          */
 678         public int getAccessibleIconHeight() {
 679             return ImageIcon.this.height;
 680         }
 681 
 682         /**
 683          * Gets the width of the icon.
 684          *
 685          * @return the width of the icon
 686          */
 687         public int getAccessibleIconWidth() {
 688             return ImageIcon.this.width;
 689         }
 690 
 691         private void readObject(ObjectInputStream s)
 692             throws ClassNotFoundException, IOException
 693         {
 694             s.defaultReadObject();
 695         }
 696 
 697         private void writeObject(ObjectOutputStream s)
 698             throws IOException
 699         {
 700             s.defaultWriteObject();
 701         }
 702     }  // AccessibleImageIcon
 703 }