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