This fixes an issue with the HTML ImageView, where the view assumed a
wrong size. This was caused by bad asynchronous image loading and not
updating the size properties appropriately. This is fixed by rewriting
parts of that view.

2006-11-15  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/text/html/ImageView.java
        (Observer): New class. Observes image loading.
        (haveHeight): New field.
        (haveWidth): New field.
        (height): New field.
        (width): New field.
        (image): New field.
        (imageIcon): New field.
        (loading): New field.
        (observer): New field.
        (reloadImage): New field.
        (reloadProperties): New field.
        (ImageView): Initialize observer and some flags.
        (getImage): Update the image state and return the image.
        (loadImage): New helper method. Actually starts loading.
        (paint): Rewritten to paint the image directly, not via Icon.
        (reloadImage): Rewritten. Loads the image and its properties.
        (renderIcon): Removed. No more necessary.
        (setPropertiesFromAttributes): Don't nullify image here.
        Added comment about missing impl.
        (setSize): Added comment about missing impl.
        (updateSize): New helper method. Updates the size attributes.
        (updateState): New helper method. Makes sure the image
        and its properties are valid.

/Roman

Index: javax/swing/text/html/ImageView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/html/ImageView.java,v
retrieving revision 1.3
diff -u -1 -5 -r1.3 ImageView.java
--- javax/swing/text/html/ImageView.java	14 Nov 2006 20:53:58 -0000	1.3
+++ javax/swing/text/html/ImageView.java	15 Nov 2006 22:40:16 -0000
@@ -1,89 +1,163 @@
 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.text.AttributeSet;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 import javax.swing.text.Element;
 import javax.swing.text.View;
 import javax.swing.text.Position.Bias;
 import javax.swing.text.html.HTML.Attribute;
 
 /**
  * A view, representing a single image, represented by the HTML IMG tag.
  * 
  * @author Audrius Meskauskas ([EMAIL PROTECTED]) 
  */
 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
+          && ! getElement().getAttributes().isDefined(HTML.Attribute.WIDTH))
+        widthChanged = true;
+      boolean heightChanged = false;
+      if ((flags & ImageObserver.HEIGHT) != 0
+          && ! getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT))
+        widthChanged = true;
+      if (widthChanged || heightChanged)
+        preferenceChanged(ImageView.this, widthChanged, heightChanged);
+      return (flags & ALLBITS) != 0;
+    }
+    
+  }
+
+  /**
    * 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.
    */
   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;
+
+  /**
    * Creates the image view that represents the given element.
    * 
    * @param element the element, represented by this image view.
    */
   public ImageView(Element element)
   {
     super(element);
+    observer = new Observer();
+    reloadProperties = true;
+    reloadImage = true;
   }
  
   /**
    * 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;
+      }
   }
   
   /**
    * Get the image alignment. This method works handling standart alignment
    * attributes in the HTML IMG tag (align = top bottom middle left right).
    * Depending from the parameter, either horizontal or vertical alingment
    * information is returned.
    * 
    * @param axis -
    *          either X_AXIS or Y_AXIS
    */
   public float getAlignment(int axis)
   {
     AttributeSet attrs = getAttributes();
     Object al = attrs.getAttribute(Attribute.ALIGN);
@@ -148,34 +222,32 @@
   public AttributeSet getAttributes()
   {
     StyleSheet styles = getStyleSheet();
     if (styles == null)
       return super.getAttributes();
     else
       return CombinedAttributes.combine(super.getAttributes(),
                                         styles.getViewAttributes(this));
   }
   
   /**
    * Get the image to render. May return null if the image is not yet loaded.
    */
   public Image getImage()
   {
-    if (imageIcon == null)
-      return null;
-    else
-      return imageIcon.getImage();
+    updateState();
+    return image;
   }
   
   /**
    * Get the URL location of the image to render. If this method returns null,
    * the "no image" icon is rendered instead. By defaul, url must be present as
    * the "src" property of the IMG tag. If it is missing, null is returned and
    * the "no image" icon is rendered.
    * 
    * @return the URL location of the image to render.
    */
   public URL getImageURL()
   {
     Element el = getElement();
     String src = (String) el.getAttributes().getAttribute(Attribute.SRC);
     URL url = null;
@@ -280,140 +352,77 @@
       return ((HTMLDocument) d).getStyleSheet();
     else
       return null;
   }
 
   /**
    * Get the tool tip text. This is overridden to return the value of the
    * [EMAIL PROTECTED] #getAltText()}. The parameters are ignored.
    * 
    * @return that is returned by getAltText().
    */
   public String getToolTipText(float x, float y, Shape shape)
   {
     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
    * is displayed besides the image state icon.
    * 
    * @param g
    *          the Graphics, used for painting.
    * @param bounds
    *          the bounds of the region where the image or replacing icon must be
    *          painted.
    */
   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);
+        g.drawImage(image, r.x, r.y, r.width, r.height, observer);
       }
-        break;
-      }
-  }
-  
-  /**
-   * 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
    * icon, returned by the [EMAIL PROTECTED] #getLoadingImageIcon()}, is displayed.
    */
   public void setLoadsSynchronously(boolean load_on_demand)
   {
     loadOnDemand = load_on_demand;
   }
  
   /**
    * Update all cached properties from the attribute set, returned by the
    * [EMAIL PROTECTED] #getAttributes}.
    */
   protected void setPropertiesFromAttributes()
   {
-    // In the current implementation, nothing is cached yet, unless the image
-    // itself.
-    imageIcon = null;
+    // FIXME: Implement this properly.
   }
   
   /**
    * Maps the picture co-ordinates into the image position in the model. As the
    * image is not divideable, this is currently implemented always to return the
    * start offset.
    */
   public int viewToModel(float x, float y, Shape shape, Bias[] bias)
   {
     return getStartOffset();
   }
   
   /**
    * This is currently implemented always to return the area of the image view,
    * as the image is not divideable by character positions.
@@ -425,20 +434,102 @@
    * @return the shape, where the given character position should be mapped.
    */
   public Shape modelToView(int pos, Shape area, Bias bias)
       throws BadLocationException
   {
     return area;
   }
   
   /**
    * Starts loading the image asynchronuosly. If the image must be loaded
    * synchronuosly instead, the [EMAIL PROTECTED] #setLoadsSynchronously} must be
    * called before calling this method. The passed parameters are not used.
    */
   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.
+        newImage = Toolkit.getDefaultToolkit().getImage(src);
+        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 = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
+        if (l != null)
+          {
+            newW = (int) l.getValue();
+            haveWidth = true;
+          }
+        else
+          {
+            newW = newIm.getWidth(observer);
+          }
+        // Fetch height.
+        l = (Length) atts.getAttribute(CSS.Attribute.HEIGHT);
+        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);
+      }
+  }
 }

Reply via email to