OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / html / ImageView.java
index 84b0210..bf906e4 100644 (file)
@@ -1,18 +1,21 @@
 package javax.swing.text.html;
 
-import gnu.javax.swing.text.html.CombinedAttributes;
 import gnu.javax.swing.text.html.ImageViewIconFactory;
+import gnu.javax.swing.text.html.css.Length;
 
 import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.MediaTracker;
 import java.awt.Rectangle;
 import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.image.ImageObserver;
 import java.net.MalformedURLException;
 import java.net.URL;
 
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
+import javax.swing.SwingUtilities;
+import javax.swing.text.AbstractDocument;
 import javax.swing.text.AttributeSet;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
@@ -29,15 +32,38 @@ import javax.swing.text.html.HTML.Attribute;
 public class ImageView extends View
 {
   /**
+   * Tracks image loading state and performs the necessary layout updates.
+   */
+  class Observer
+    implements ImageObserver
+  {
+
+    public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height)
+    {
+      boolean widthChanged = false;
+      if ((flags & ImageObserver.WIDTH) != 0 && spans[X_AXIS] == null)
+        widthChanged = true;
+      boolean heightChanged = false;
+      if ((flags & ImageObserver.HEIGHT) != 0 && spans[Y_AXIS] == null)
+        heightChanged = true;
+      if (widthChanged || heightChanged)
+        safePreferenceChanged(ImageView.this, widthChanged, heightChanged);
+      boolean ret = (flags & ALLBITS) != 0;
+      return ret;
+    }
+    
+  }
+
+  /**
    * True if the image loads synchronuosly (on demand). By default, the image
    * loads asynchronuosly.
    */
   boolean loadOnDemand;
-  
+
   /**
    * The image icon, wrapping the image,
    */
-  ImageIcon imageIcon;
+  Image image;
  
   /**
    * The image state.
@@ -45,6 +71,58 @@ public class ImageView extends View
   byte imageState = MediaTracker.LOADING;
 
   /**
+   * True when the image needs re-loading, false otherwise.
+   */
+  private boolean reloadImage;
+
+  /**
+   * True when the image properties need re-loading, false otherwise.
+   */
+  private boolean reloadProperties;
+
+  /**
+   * True when the width is set as CSS/HTML attribute.
+   */
+  private boolean haveWidth;
+
+  /**
+   * True when the height is set as CSS/HTML attribute.
+   */
+  private boolean haveHeight;
+
+  /**
+   * True when the image is currently loading.
+   */
+  private boolean loading;
+
+  /**
+   * The current width of the image.
+   */
+  private int width;
+
+  /**
+   * The current height of the image.
+   */
+  private int height;
+
+  /**
+   * Our ImageObserver for tracking the loading state.
+   */
+  private ImageObserver observer;
+
+  /**
+   * The CSS width and height.
+   *
+   * Package private to avoid synthetic accessor methods.
+   */
+  Length[] spans;
+
+  /**
+   * The cached attributes.
+   */
+  private AttributeSet attributes;
+
+  /**
    * Creates the image view that represents the given element.
    * 
    * @param element the element, represented by this image view.
@@ -52,25 +130,36 @@ public class ImageView extends View
   public ImageView(Element element)
   {
     super(element);
+    spans = new Length[2];
+    observer = new Observer();
+    reloadProperties = true;
+    reloadImage = true;
+    loadOnDemand = false;
   }
  
   /**
    * Load or reload the image. This method initiates the image reloading. After
    * the image is ready, the repaint event will be scheduled. The current image,
    * if it already exists, will be discarded.
-   * 
-   * @param itsTime
-   *          also load if the "on demand" property is set
    */
-  void reloadImage(boolean itsTime)
+  private void reloadImage()
   {
-    URL url = getImageURL();
-    if (url == null)
-      imageState = (byte) MediaTracker.ERRORED;
-    else if (!(loadOnDemand && !itsTime))
-      imageIcon = new ImageIcon(url);
-    else
-      imageState = (byte) MediaTracker.LOADING;
+    loading = true;
+    reloadImage = false;
+    haveWidth = false;
+    haveHeight = false;
+    image = null;
+    width = 0;
+    height = 0;
+    try
+      {
+        loadImage();
+        updateSize();
+      }
+    finally
+      {
+        loading = false;
+      }
   }
   
   /**
@@ -146,12 +235,9 @@ public class ImageView extends View
    */
   public AttributeSet getAttributes()
   {
-    StyleSheet styles = getStyleSheet();
-    if (styles == null)
-      return super.getAttributes();
-    else
-      return CombinedAttributes.combine(super.getAttributes(),
-                                        styles.getViewAttributes(this));
+    if (attributes == null)
+      attributes = getStyleSheet().getViewAttributes(this);
+    return attributes;
   }
   
   /**
@@ -159,10 +245,8 @@ public class ImageView extends View
    */
   public Image getImage()
   {
-    if (imageIcon == null)
-      return null;
-    else
-      return imageIcon.getImage();
+    updateState();
+    return image;
   }
   
   /**
@@ -175,19 +259,22 @@ public class ImageView extends View
    */
   public URL getImageURL()
   {
-    Object url = getAttributes().getAttribute(Attribute.SRC);
-    if (url == null)
-      return null;
-
-    try
+    Element el = getElement();
+    String src = (String) el.getAttributes().getAttribute(Attribute.SRC);
+    URL url = null;
+    if (src != null)
       {
-        return new URL(url.toString());
-      }
-    catch (MalformedURLException e)
-      {
-        // The URL is malformed - no image.
-        return null;
+        URL base = ((HTMLDocument) getDocument()).getBase();
+        try
+          {
+            url = new URL(base, src);
+          }
+        catch (MalformedURLException ex)
+          {
+            // Return null.
+          }
       }
+    return url;
   }
 
   /**
@@ -242,9 +329,8 @@ public class ImageView extends View
 
     if (axis == View.X_AXIS)
       {
-        Object w = attrs.getAttribute(Attribute.WIDTH);
-        if (w != null)
-          return Integer.parseInt(w.toString());
+        if (spans[axis] != null)
+          return spans[axis].getValue();
         else if (image != null)
           return image.getWidth(getContainer());
         else
@@ -252,9 +338,8 @@ public class ImageView extends View
       }
     else if (axis == View.Y_AXIS)
       {
-        Object w = attrs.getAttribute(Attribute.HEIGHT);
-        if (w != null)
-          return Integer.parseInt(w.toString());
+        if (spans[axis] != null)
+          return spans[axis].getValue();
         else if (image != null)
           return image.getHeight(getContainer());
         else
@@ -271,11 +356,8 @@ public class ImageView extends View
    */
   protected StyleSheet getStyleSheet()
   {
-    Document d = getElement().getDocument();
-    if (d instanceof HTMLDocument)
-      return ((HTMLDocument) d).getStyleSheet();
-    else
-      return null;
+    HTMLDocument doc = (HTMLDocument) getDocument();
+    return doc.getStyleSheet();
   }
 
   /**
@@ -288,7 +370,7 @@ public class ImageView extends View
   {
     return getAltText();
   }
-  
+
   /**
    * Paints the image or one of the two image state icons. The image is resized
    * to the shape bounds. If there is no image available, the alternative text
@@ -302,83 +384,22 @@ public class ImageView extends View
    */
   public void paint(Graphics g, Shape bounds)
   {
-    Rectangle r = bounds.getBounds();
-
-    if (imageIcon == null)
-
-      {
-        // Loading image on demand, rendering the loading icon so far.
-        reloadImage(true);
-         
-        // The reloadImage sets the imageIcon, unless the URL is broken 
-        // or malformed.
-        if (imageIcon != null)
-          {
-            if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE)
-              {
-                // Render "not ready" icon, unless the image is ready
-                // immediately.
-                renderIcon(g, r, getLoadingImageIcon());
-                // Add the listener to repaint when the icon will be ready.
-                imageIcon.setImageObserver(getContainer());
-                return;
-              }
-          }
-        else
-          {
-            renderIcon(g, r, getNoImageIcon());
-            return;
-          }
-      }
-
-    imageState = (byte) imageIcon.getImageLoadStatus();
-
-    switch (imageState)
-      {
-      case MediaTracker.ABORTED:
-      case MediaTracker.ERRORED:
-        renderIcon(g, r, getNoImageIcon());
-        break;
-      case MediaTracker.LOADING:
-      // If the image is not loaded completely, we still render it, as the
-      // partial image may be available.
-      case MediaTracker.COMPLETE:
+    updateState();
+    Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds
+                                              : bounds.getBounds();
+    Image image = getImage();
+    if (image != null)
       {
-        // Paint the scaled image.
-        Image scaled = imageIcon.getImage().getScaledInstance(
-                                                              r.width,
-                                                              r.height,
-                                                              Image.SCALE_DEFAULT);
-        ImageIcon painter = new ImageIcon(scaled);
-        painter.paintIcon(getContainer(), g, r.x, r.y);
-      }
-        break;
+        g.drawImage(image, r.x, r.y, r.width, r.height, observer);
       }
-  }
-  
-  /**
-   * Render "no image" icon and the alternative "no image" text. The text is
-   * rendered right from the icon and is aligned to the icon bottom.
-   */
-  private void renderIcon(Graphics g, Rectangle bounds, Icon icon)
-  {
-    Shape current = g.getClip();
-    try
+    else
       {
-        g.setClip(bounds);
+        Icon icon = getNoImageIcon();
         if (icon != null)
-          {
-            icon.paintIcon(getContainer(), g, bounds.x, bounds.y);
-            g.drawString(getAltText(), bounds.x + icon.getIconWidth(),
-                         bounds.y + icon.getIconHeight());
-          }
-      }
-    finally
-      {
-        g.setClip(current);
+          icon.paintIcon(getContainer(), g, r.x, r.y);
       }
   }
-  
+
   /**
    * Set if the image should be loaded only when needed (synchronuosly). By
    * default, the image loads asynchronuosly. If the image is not yet ready, the
@@ -395,9 +416,20 @@ public class ImageView extends View
    */
   protected void setPropertiesFromAttributes()
   {
-    // In the current implementation, nothing is cached yet, unless the image
-    // itself.
-    imageIcon = null;
+    AttributeSet atts = getAttributes();
+    StyleSheet ss = getStyleSheet();
+    float emBase = ss.getEMBase(atts);
+    float exBase = ss.getEXBase(atts);
+    spans[X_AXIS] = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
+    if (spans[X_AXIS] != null)
+      {
+        spans[X_AXIS].setFontBases(emBase, exBase);
+      }
+    spans[Y_AXIS] = (Length) atts.getAttribute(CSS.Attribute.HEIGHT);
+    if (spans[Y_AXIS] != null)
+      {
+        spans[Y_AXIS].setFontBases(emBase, exBase);
+      }
   }
   
   /**
@@ -433,9 +465,130 @@ public class ImageView extends View
    */
   public void setSize(float width, float height)
   {
-    if (imageIcon == null)
-      reloadImage(false);
+    updateState();
+    // TODO: Implement this when we have an alt view for the alt=... attribute.
   }  
-  
 
+  /**
+   * This makes sure that the image and properties have been loaded.
+   */
+  private void updateState()
+  {
+    if (reloadImage)
+      reloadImage();
+    if (reloadProperties)
+      setPropertiesFromAttributes();
+  }
+
+  /**
+   * Actually loads the image.
+   */
+  private void loadImage()
+  {
+    URL src = getImageURL();
+    Image newImage = null;
+    if (src != null)
+      {
+        // Call getImage(URL) to allow the toolkit caching of that image URL.
+        Toolkit tk = Toolkit.getDefaultToolkit();
+        newImage = tk.getImage(src);
+        tk.prepareImage(newImage, -1, -1, observer);
+        if (newImage != null && getLoadsSynchronously())
+          {
+            // Load image synchronously.
+            MediaTracker tracker = new MediaTracker(getContainer());
+            tracker.addImage(newImage, 0);
+            try
+              {
+                tracker.waitForID(0);
+              }
+            catch (InterruptedException ex)
+              {
+                Thread.interrupted();
+              }
+            
+          }
+      }
+    image = newImage;
+  }
+
+  /**
+   * Updates the size parameters of the image.
+   */
+  private void updateSize()
+  {
+    int newW = 0;
+    int newH = 0;
+    Image newIm = getImage();
+    if (newIm != null)
+      {
+        AttributeSet atts = getAttributes();
+        // Fetch width.
+        Length l = spans[X_AXIS];
+        if (l != null)
+          {
+            newW = (int) l.getValue();
+            haveWidth = true;
+          }
+        else
+          {
+            newW = newIm.getWidth(observer);
+          }
+        // Fetch height.
+        l = spans[Y_AXIS];
+        if (l != null)
+          {
+            newH = (int) l.getValue();
+            haveHeight = true;
+          }
+        else
+          {
+            newW = newIm.getWidth(observer);
+          }
+        // Go and trigger loading.
+        Toolkit tk = Toolkit.getDefaultToolkit();
+        if (haveWidth || haveHeight)
+          tk.prepareImage(newIm, width, height, observer);
+        else
+          tk.prepareImage(newIm, -1, -1, observer);
+      }
+  }
+
+  /**
+   * Calls preferenceChanged from the event dispatch thread and within
+   * a read lock to protect us from threading issues.
+   *
+   * @param v the view
+   * @param width true when the width changed
+   * @param height true when the height changed
+   */
+  void safePreferenceChanged(final View v, final boolean width,
+                             final boolean height)
+  {
+    if (SwingUtilities.isEventDispatchThread())
+      {
+        Document doc = getDocument();
+        if (doc instanceof AbstractDocument)
+          ((AbstractDocument) doc).readLock();
+        try
+          {
+            preferenceChanged(v, width, height);
+          }
+        finally
+          {
+            if (doc instanceof AbstractDocument)
+              ((AbstractDocument) doc).readUnlock();
+          }
+      }
+    else
+      {
+        SwingUtilities.invokeLater(new Runnable()
+        {
+          public void run()
+          {
+            safePreferenceChanged(v, width, height);
+          }
+        });
+      }
+  }
 }