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);
+ }
+ }
}