- /*
- * @(#)ImageView.java 1.56 03/12/19
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.text.html;
-
- import java.awt.*;
- import java.awt.event.*;
- import java.awt.image.ImageObserver;
- import java.io.*;
- import java.net.*;
- import java.util.Dictionary;
- import javax.swing.*;
- import javax.swing.text.*;
- import javax.swing.event.*;
-
- /**
- * View of an Image, intended to support the HTML <IMG> tag.
- * Supports scaling via the HEIGHT and WIDTH attributes of the tag.
- * If the image is unable to be loaded any text specified via the
- * <code>ALT</code> attribute will be rendered.
- * <p>
- * While this class has been part of swing for a while now, it is public
- * as of 1.4.
- *
- * @author Scott Violet
- * @version 1.56 12/19/03
- * @see IconView
- * @since 1.4
- */
- public class ImageView extends View {
- /**
- * If true, when some of the bits are available a repaint is done.
- * <p>
- * This is set to false as swing does not offer a repaint that takes a
- * delay. If this were true, a bunch of immediate repaints would get
- * generated that end up significantly delaying the loading of the image
- * (or anything else going on for that matter).
- */
- private static boolean sIsInc = false;
- /**
- * Repaint delay when some of the bits are available.
- */
- private static int sIncRate = 100;
- /**
- * Icon used while the image is being loaded.
- */
- private static Icon sPendingImageIcon;
- /**
- * Icon used if the image could not be found.
- */
- private static Icon sMissingImageIcon;
- /**
- * File name for <code>sPendingImageIcon</code>.
- */
- private static final String PENDING_IMAGE_SRC = "icons/image-delayed.gif";
- /**
- * File name for <code>sMissingImageIcon</code>.
- */
- private static final String MISSING_IMAGE_SRC = "icons/image-failed.gif";
-
- /**
- * Document property for image cache.
- */
- private static final String IMAGE_CACHE_PROPERTY = "imageCache";
-
- // Height/width to use before we know the real size, these should at least
- // the size of <code>sMissingImageIcon</code> and
- // <code>sPendingImageIcon</code>
- private static final int DEFAULT_WIDTH = 38;
- private static final int DEFAULT_HEIGHT= 38;
-
- /**
- * Default border to use if one is not specified.
- */
- private static final int DEFAULT_BORDER = 2;
-
- // Bitmask values
- private static final int LOADING_FLAG = 1;
- private static final int LINK_FLAG = 2;
- private static final int WIDTH_FLAG = 4;
- private static final int HEIGHT_FLAG = 8;
- private static final int RELOAD_FLAG = 16;
- private static final int RELOAD_IMAGE_FLAG = 32;
- private static final int SYNC_LOAD_FLAG = 64;
-
- private AttributeSet attr;
- private Image image;
- private int width;
- private int height;
- /** Bitmask containing some of the above bitmask values. Because the
- * image loading notification can happen on another thread access to
- * this is synchronized (at least for modifying it). */
- private int state;
- private Container container;
- private Rectangle fBounds;
- private Color borderColor;
- // Size of the border, the insets contains this valid. For example, if
- // the HSPACE attribute was 4 and BORDER 2, leftInset would be 6.
- private short borderSize;
- // Insets, obtained from the painter.
- private short leftInset;
- private short rightInset;
- private short topInset;
- private short bottomInset;
- /**
- * We don't directly implement ImageObserver, instead we use an instance
- * that calls back to us.
- */
- private ImageObserver imageObserver;
- /**
- * Used for alt text. Will be non-null if the image couldn't be found,
- * and there is valid alt text.
- */
- private View altView;
- /** Alignment along the vertical (Y) axis. */
- private float vAlign;
-
-
-
- /**
- * Creates a new view that represents an IMG element.
- *
- * @param elem the element to create a view for
- */
- public ImageView(Element elem) {
- super(elem);
- fBounds = new Rectangle();
- imageObserver = new ImageHandler();
- state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
- }
-
- /**
- * Returns the text to display if the image can't be loaded. This is
- * obtained from the Elements attribute set with the attribute name
- * <code>HTML.Attribute.ALT</code>.
- */
- public String getAltText() {
- return (String)getElement().getAttributes().getAttribute
- (HTML.Attribute.ALT);
- }
-
- /**
- * Return a URL for the image source,
- * or null if it could not be determined.
- */
- public URL getImageURL() {
- String src = (String)getElement().getAttributes().
- getAttribute(HTML.Attribute.SRC);
- if (src == null) {
- return null;
- }
-
- URL reference = ((HTMLDocument)getDocument()).getBase();
- try {
- URL u = new URL(reference,src);
- return u;
- } catch (MalformedURLException e) {
- return null;
- }
- }
-
- /**
- * Returns the icon to use if the image couldn't be found.
- */
- public Icon getNoImageIcon() {
- loadDefaultIconsIfNecessary();
- return sMissingImageIcon;
- }
-
- /**
- * Returns the icon to use while in the process of loading the image.
- */
- public Icon getLoadingImageIcon() {
- loadDefaultIconsIfNecessary();
- return sPendingImageIcon;
- }
-
- /**
- * Returns the image to render.
- */
- public Image getImage() {
- sync();
- return image;
- }
-
- /**
- * Sets how the image is loaded. If <code>newValue</code> is true,
- * the image we be loaded when first asked for, otherwise it will
- * be loaded asynchronously. The default is to not load synchronously,
- * that is to load the image asynchronously.
- */
- public void setLoadsSynchronously(boolean newValue) {
- synchronized(this) {
- if (newValue) {
- state |= SYNC_LOAD_FLAG;
- }
- else {
- state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG;
- }
- }
- }
-
- /**
- * Returns true if the image should be loaded when first asked for.
- */
- public boolean getLoadsSynchronously() {
- return ((state & SYNC_LOAD_FLAG) != 0);
- }
-
- /**
- * Convenience method to get the StyleSheet.
- */
- protected StyleSheet getStyleSheet() {
- HTMLDocument doc = (HTMLDocument) getDocument();
- return doc.getStyleSheet();
- }
-
- /**
- * Fetches the attributes to use when rendering. This is
- * implemented to multiplex the attributes specified in the
- * model with a StyleSheet.
- */
- public AttributeSet getAttributes() {
- sync();
- return attr;
- }
-
- /**
- * For images the tooltip text comes from text specified with the
- * <code>ALT</code> attribute. This is overriden to return
- * <code>getAltText</code>.
- *
- * @see JTextComponent#getToolTipText
- */
- public String getToolTipText(float x, float y, Shape allocation) {
- return getAltText();
- }
-
- /**
- * Update any cached values that come from attributes.
- */
- protected void setPropertiesFromAttributes() {
- StyleSheet sheet = getStyleSheet();
- this.attr = sheet.getViewAttributes(this);
-
- // Gutters
- borderSize = (short)getIntAttr(HTML.Attribute.BORDER, isLink() ?
- DEFAULT_BORDER : 0);
-
- leftInset = rightInset = (short)(getIntAttr(HTML.Attribute.HSPACE,
- 0) + borderSize);
- topInset = bottomInset = (short)(getIntAttr(HTML.Attribute.VSPACE,
- 0) + borderSize);
-
- borderColor = ((StyledDocument)getDocument()).getForeground
- (getAttributes());
-
- AttributeSet attr = getElement().getAttributes();
-
- // Alignment.
- // PENDING: This needs to be changed to support the CSS versions
- // when conversion from ALIGN to VERTICAL_ALIGN is complete.
- Object alignment = attr.getAttribute(HTML.Attribute.ALIGN);
-
- vAlign = 1.0f;
- if (alignment != null) {
- alignment = alignment.toString();
- if ("top".equals(alignment)) {
- vAlign = 0f;
- }
- else if ("middle".equals(alignment)) {
- vAlign = .5f;
- }
- }
-
- AttributeSet anchorAttr = (AttributeSet)attr.getAttribute(HTML.Tag.A);
- if (anchorAttr != null && anchorAttr.isDefined
- (HTML.Attribute.HREF)) {
- synchronized(this) {
- state |= LINK_FLAG;
- }
- }
- else {
- synchronized(this) {
- state = (state | LINK_FLAG) ^ LINK_FLAG;
- }
- }
- }
-
- /**
- * Establishes the parent view for this view.
- * Seize this moment to cache the AWT Container I'm in.
- */
- public void setParent(View parent) {
- View oldParent = getParent();
- super.setParent(parent);
- container = (parent != null) ? getContainer() : null;
- if (oldParent != parent) {
- synchronized(this) {
- state |= RELOAD_FLAG;
- }
- }
- }
-
- /**
- * Invoked when the Elements attributes have changed. Recreates the image.
- */
- public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
- super.changedUpdate(e,a,f);
-
- synchronized(this) {
- state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG;
- }
-
- // Assume the worst.
- preferenceChanged(null, true, true);
- }
-
- /**
- * Paints the View.
- *
- * @param g the rendering surface to use
- * @param a the allocated region to render into
- * @see View#paint
- */
- public void paint(Graphics g, Shape a) {
- sync();
-
- Rectangle rect = (a instanceof Rectangle) ? (Rectangle)a :
- a.getBounds();
-
- Image image = getImage();
- Rectangle clip = g.getClipBounds();
-
- fBounds.setBounds(rect);
- paintHighlights(g, a);
- paintBorder(g, rect);
- if (clip != null) {
- g.clipRect(rect.x + leftInset, rect.y + topInset,
- rect.width - leftInset - rightInset,
- rect.height - topInset - bottomInset);
- }
- if (image != null) {
- if (!hasPixels(image)) {
- // No pixels yet, use the default
- Icon icon = (image == null) ? getNoImageIcon() :
- getLoadingImageIcon();
-
- if (icon != null) {
- icon.paintIcon(getContainer(), g, rect.x + leftInset,
- rect.y + topInset);
- }
- }
- else {
- // Draw the image
- g.drawImage(image, rect.x + leftInset, rect.y + topInset,
- width, height, imageObserver);
- }
- }
- else {
- Icon icon = getNoImageIcon();
-
- if (icon != null) {
- icon.paintIcon(getContainer(), g, rect.x + leftInset,
- rect.y + topInset);
- }
- View view = getAltView();
- // Paint the view representing the alt text, if its non-null
- if (view != null && ((state & WIDTH_FLAG) == 0 ||
- width > DEFAULT_WIDTH)) {
- // Assume layout along the y direction
- Rectangle altRect = new Rectangle
- (rect.x + leftInset + DEFAULT_WIDTH, rect.y + topInset,
- rect.width - leftInset - rightInset - DEFAULT_WIDTH,
- rect.height - topInset - bottomInset);
-
- view.paint(g, altRect);
- }
- }
- if (clip != null) {
- // Reset clip.
- g.setClip(clip.x, clip.y, clip.width, clip.height);
- }
- }
-
- private void paintHighlights(Graphics g, Shape shape) {
- if (container instanceof JTextComponent) {
- JTextComponent tc = (JTextComponent)container;
- Highlighter h = tc.getHighlighter();
- if (h instanceof LayeredHighlighter) {
- ((LayeredHighlighter)h).paintLayeredHighlights
- (g, getStartOffset(), getEndOffset(), shape, tc, this);
- }
- }
- }
-
- private void paintBorder(Graphics g, Rectangle rect) {
- Color color = borderColor;
-
- if ((borderSize > 0 || image == null) && color != null) {
- int xOffset = leftInset - borderSize;
- int yOffset = topInset - borderSize;
- g.setColor(color);
- int n = (image == null) ? 1 : borderSize;
- for (int counter = 0; counter < n; counter++) {
- g.drawRect(rect.x + xOffset + counter,
- rect.y + yOffset + counter,
- rect.width - counter - counter - xOffset -xOffset-1,
- rect.height - counter - counter -yOffset-yOffset-1);
- }
- }
- }
-
- /**
- * Determines the preferred span for this view along an
- * axis.
- *
- * @param axis may be either X_AXIS or Y_AXIS
- * @return the span the view would like to be rendered into;
- * typically the view is told to render into the span
- * that is returned, although there is no guarantee;
- * the parent may choose to resize or break the view
- */
- public float getPreferredSpan(int axis) {
- sync();
-
- // If the attributes specified a width/height, always use it!
- if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) {
- getPreferredSpanFromAltView(axis);
- return width + leftInset + rightInset;
- }
- if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) {
- getPreferredSpanFromAltView(axis);
- return height + topInset + bottomInset;
- }
-
- Image image = getImage();
-
- if (image != null) {
- switch (axis) {
- case View.X_AXIS:
- return width + leftInset + rightInset;
- case View.Y_AXIS:
- return height + topInset + bottomInset;
- default:
- throw new IllegalArgumentException("Invalid axis: " + axis);
- }
- }
- else {
- View view = getAltView();
- float retValue = 0f;
-
- if (view != null) {
- retValue = view.getPreferredSpan(axis);
- }
- switch (axis) {
- case View.X_AXIS:
- return retValue + (float)(width + leftInset + rightInset);
- case View.Y_AXIS:
- return retValue + (float)(height + topInset + bottomInset);
- default:
- throw new IllegalArgumentException("Invalid axis: " + axis);
- }
- }
- }
-
- /**
- * Determines the desired alignment for this view along an
- * axis. This is implemented to give the alignment to the
- * bottom of the icon along the y axis, and the default
- * along the x axis.
- *
- * @param axis may be either X_AXIS or Y_AXIS
- * @return the desired alignment; this should be a value
- * between 0.0 and 1.0 where 0 indicates alignment at the
- * origin and 1.0 indicates alignment to the full span
- * away from the origin; an alignment of 0.5 would be the
- * center of the view
- */
- public float getAlignment(int axis) {
- switch (axis) {
- case View.Y_AXIS:
- return vAlign;
- default:
- return super.getAlignment(axis);
- }
- }
-
- /**
- * Provides a mapping from the document model coordinate space
- * to the coordinate space of the view mapped to it.
- *
- * @param pos the position to convert
- * @param a the allocated region to render into
- * @return the bounding box of the given position
- * @exception BadLocationException if the given position does not represent a
- * valid location in the associated document
- * @see View#modelToView
- */
- public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
- int p0 = getStartOffset();
- int p1 = getEndOffset();
- if ((pos >= p0) && (pos <= p1)) {
- Rectangle r = a.getBounds();
- if (pos == p1) {
- r.x += r.width;
- }
- r.width = 0;
- return r;
- }
- return null;
- }
-
- /**
- * Provides a mapping from the view coordinate space to the logical
- * coordinate space of the model.
- *
- * @param x the X coordinate
- * @param y the Y coordinate
- * @param a the allocated region to render into
- * @return the location within the model that best represents the
- * given point of view
- * @see View#viewToModel
- */
- public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
- Rectangle alloc = (Rectangle) a;
- if (x < alloc.x + alloc.width) {
- bias[0] = Position.Bias.Forward;
- return getStartOffset();
- }
- bias[0] = Position.Bias.Backward;
- return getEndOffset();
- }
-
- /**
- * Sets the size of the view. This should cause
- * layout of the view if it has any layout duties.
- *
- * @param width the width >= 0
- * @param height the height >= 0
- */
- public void setSize(float width, float height) {
- sync();
-
- if (getImage() == null) {
- View view = getAltView();
-
- if (view != null) {
- view.setSize(Math.max(0f, width - (float)(DEFAULT_WIDTH + leftInset + rightInset)),
- Math.max(0f, height - (float)(topInset + bottomInset)));
- }
- }
- }
-
- /**
- * Returns true if this image within a link?
- */
- private boolean isLink() {
- return ((state & LINK_FLAG) == LINK_FLAG);
- }
-
- /**
- * Returns true if the passed in image has a non-zero width and height.
- */
- private boolean hasPixels(Image image) {
- return image != null &&
- (image.getHeight(imageObserver) > 0) &&
- (image.getWidth(imageObserver) > 0);
- }
-
- /**
- * Returns the preferred span of the View used to display the alt text,
- * or 0 if the view does not exist.
- */
- private float getPreferredSpanFromAltView(int axis) {
- if (getImage() == null) {
- View view = getAltView();
-
- if (view != null) {
- return view.getPreferredSpan(axis);
- }
- }
- return 0f;
- }
-
- private Icon makeIcon(final String gifFile) throws IOException {
- /* Copy resource into a byte array. This is
- * necessary because several browsers consider
- * Class.getResource a security risk because it
- * can be used to load additional classes.
- * Class.getResourceAsStream just returns raw
- * bytes, which we can convert to an image.
- */
- InputStream resource = HTMLEditorKit.getResourceAsStream(gifFile);
-
- if (resource == null) {
- System.err.println(ImageView.class.getName() + "/" +
- gifFile + " not found.");
- return null;
- }
- BufferedInputStream in =
- new BufferedInputStream(resource);
- ByteArrayOutputStream out =
- new ByteArrayOutputStream(1024);
- byte[] buffer = new byte[1024];
- int n;
- while ((n = in.read(buffer)) > 0) {
- out.write(buffer, 0, n);
- }
- in.close();
- out.flush();
-
- buffer = out.toByteArray();
- if (buffer.length == 0) {
- System.err.println("warning: " + gifFile +
- " is zero-length");
- return null;
- }
- return new ImageIcon(buffer);
- }
-
- /**
- * Request that this view be repainted.
- * Assumes the view is still at its last-drawn location.
- */
- private void repaint(long delay) {
- if (container != null && fBounds != null) {
- container.repaint(delay, fBounds.x, fBounds.y, fBounds.width,
- fBounds.height);
- }
- }
-
- private void loadDefaultIconsIfNecessary() {
- try {
- if (sPendingImageIcon == null)
- sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
- if (sMissingImageIcon == null)
- sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
- } catch(Exception x) {
- System.err.println("ImageView: Couldn't load image icons");
- }
- }
-
- /**
- * Convenience method for getting an integer attribute from the elements
- * AttributeSet.
- */
- private int getIntAttr(HTML.Attribute name, int deflt) {
- AttributeSet attr = getElement().getAttributes();
- if (attr.isDefined(name)) { // does not check parents!
- int i;
- String val = (String)attr.getAttribute(name);
- if (val == null) {
- i = deflt;
- }
- else {
- try{
- i = Math.max(0, Integer.parseInt(val));
- }catch( NumberFormatException x ) {
- i = deflt;
- }
- }
- return i;
- } else
- return deflt;
- }
-
- /**
- * Makes sure the necessary properties and image is loaded.
- */
- private void sync() {
- int s = state;
- if ((s & RELOAD_IMAGE_FLAG) != 0) {
- refreshImage();
- }
- s = state;
- if ((s & RELOAD_FLAG) != 0) {
- synchronized(this) {
- state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
- }
- setPropertiesFromAttributes();
- }
- }
-
- /**
- * Loads the image and updates the size accordingly. This should be
- * invoked instead of invoking <code>loadImage</code> or
- * <code>updateImageSize</code> directly.
- */
- private void refreshImage() {
- synchronized(this) {
- // clear out width/height/realoadimage flag and set loading flag
- state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG |
- HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG |
- RELOAD_IMAGE_FLAG);
- image = null;
- width = height = 0;
- }
-
- try {
- // Load the image
- loadImage();
-
- // And update the size params
- updateImageSize();
- }
- finally {
- synchronized(this) {
- // Clear out state in case someone threw an exception.
- state = (state | LOADING_FLAG) ^ LOADING_FLAG;
- }
- }
- }
-
- /**
- * Loads the image from the URL <code>getImageURL</code>. This should
- * only be invoked from <code>refreshImage</code>.
- */
- private void loadImage() {
- URL src = getImageURL();
- Image newImage = null;
- if (src != null) {
- Dictionary cache = (Dictionary)getDocument().
- getProperty(IMAGE_CACHE_PROPERTY);
- if (cache != null) {
- newImage = (Image)cache.get(src);
- }
- else {
- newImage = Toolkit.getDefaultToolkit().createImage(src);
- if (newImage != null && getLoadsSynchronously()) {
- // Force the image to be loaded by using an ImageIcon.
- ImageIcon ii = new ImageIcon();
- ii.setImage(newImage);
- }
- }
- }
- image = newImage;
- }
-
- /**
- * Recreates and reloads the image. This should
- * only be invoked from <code>refreshImage</code>.
- */
- private void updateImageSize() {
- int newWidth = 0;
- int newHeight = 0;
- int newState = 0;
- Image newImage = getImage();
-
- if (newImage != null) {
- Element elem = getElement();
- AttributeSet attr = elem.getAttributes();
-
- // Get the width/height and set the state ivar before calling
- // anything that might cause the image to be loaded, and thus the
- // ImageHandler to be called.
- newWidth = getIntAttr(HTML.Attribute.WIDTH, -1);
- if (newWidth > 0) {
- newState |= WIDTH_FLAG;
- }
- newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1);
- if (newHeight > 0) {
- newState |= HEIGHT_FLAG;
- }
-
- if (newWidth <= 0) {
- newWidth = newImage.getWidth(imageObserver);
- if (newWidth <= 0) {
- newWidth = DEFAULT_WIDTH;
- }
- }
-
- if (newHeight <= 0) {
- newHeight = newImage.getHeight(imageObserver);
- if (newHeight <= 0) {
- newHeight = DEFAULT_HEIGHT;
- }
- }
-
- // Make sure the image starts loading:
- if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
- Toolkit.getDefaultToolkit().prepareImage(newImage, newWidth,
- newHeight,
- imageObserver);
- }
- else {
- Toolkit.getDefaultToolkit().prepareImage(newImage, -1, -1,
- imageObserver);
- }
-
- boolean createText = false;
- synchronized(this) {
- // If imageloading failed, other thread may have called
- // ImageLoader which will null out image, hence we check
- // for it.
- if (image != null) {
- if ((newState & WIDTH_FLAG) == WIDTH_FLAG || width == 0) {
- width = newWidth;
- }
- if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG ||
- height == 0) {
- height = newHeight;
- }
- }
- else {
- createText = true;
- if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
- width = newWidth;
- }
- if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
- height = newHeight;
- }
- }
- state = state | newState;
- state = (state | LOADING_FLAG) ^ LOADING_FLAG;
- }
- if (createText) {
- // Only reset if this thread determined image is null
- updateAltTextView();
- }
- }
- else {
- width = height = DEFAULT_HEIGHT;
- updateAltTextView();
- }
- }
-
- /**
- * Updates the view representing the alt text.
- */
- private void updateAltTextView() {
- String text = getAltText();
-
- if (text != null) {
- ImageLabelView newView;
-
- newView = new ImageLabelView(getElement(), text);
- synchronized(this) {
- altView = newView;
- }
- }
- }
-
- /**
- * Returns the view to use for alternate text. This may be null.
- */
- private View getAltView() {
- View view;
-
- synchronized(this) {
- view = altView;
- }
- if (view != null && view.getParent() == null) {
- view.setParent(getParent());
- }
- return view;
- }
-
- /**
- * Invokes <code>preferenceChanged</code> on the event displatching
- * thread.
- */
- private void safePreferenceChanged() {
- if (SwingUtilities.isEventDispatchThread()) {
- Document doc = getDocument();
- if (doc instanceof AbstractDocument) {
- ((AbstractDocument)doc).readLock();
- }
- preferenceChanged(null, true, true);
- if (doc instanceof AbstractDocument) {
- ((AbstractDocument)doc).readUnlock();
- }
- }
- else {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- safePreferenceChanged();
- }
- });
- }
- }
-
- /**
- * ImageHandler implements the ImageObserver to correctly update the
- * display as new parts of the image become available.
- */
- private class ImageHandler implements ImageObserver {
- // This can come on any thread. If we are in the process of reloading
- // the image and determining our state (loading == true) we don't fire
- // preference changed, or repaint, we just reset the fWidth/fHeight as
- // necessary and return. This is ok as we know when loading finishes
- // it will pick up the new height/width, if necessary.
- public boolean imageUpdate(Image img, int flags, int x, int y,
- int newWidth, int newHeight ) {
- if (image == null || image != img || getParent() == null) {
- return false;
- }
-
- // Bail out if there was an error:
- if ((flags & (ABORT|ERROR)) != 0) {
- repaint(0);
- synchronized(ImageView.this) {
- if (image == img) {
- // Be sure image hasn't changed since we don't
- // initialy synchronize
- image = null;
- if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
- width = DEFAULT_WIDTH;
- }
- if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
- height = DEFAULT_HEIGHT;
- }
- }
- if ((state & LOADING_FLAG) == LOADING_FLAG) {
- // No need to resize or repaint, still in the process
- // of loading.
- return false;
- }
- }
- updateAltTextView();
- safePreferenceChanged();
- return false;
- }
-
- // Resize image if necessary:
- short changed = 0;
- if ((flags & ImageObserver.HEIGHT) != 0 && !getElement().
- getAttributes().isDefined(HTML.Attribute.HEIGHT)) {
- changed |= 1;
- }
- if ((flags & ImageObserver.WIDTH) != 0 && !getElement().
- getAttributes().isDefined(HTML.Attribute.WIDTH)) {
- changed |= 2;
- }
-
- synchronized(ImageView.this) {
- if (image != img) {
- return false;
- }
- if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) {
- width = newWidth;
- }
- if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) {
- height = newHeight;
- }
- if ((state & LOADING_FLAG) == LOADING_FLAG) {
- // No need to resize or repaint, still in the process of
- // loading.
- return true;
- }
- }
- if (changed != 0) {
- // May need to resize myself, asynchronously:
- safePreferenceChanged();
- return true;
- }
-
- // Repaint when done or when new pixels arrive:
- if ((flags & (FRAMEBITS|ALLBITS)) != 0) {
- repaint(0);
- }
- else if ((flags & SOMEBITS) != 0 && sIsInc) {
- repaint(sIncRate);
- }
- return ((flags & ALLBITS) == 0);
- }
- }
-
-
- /**
- * ImageLabelView is used if the image can't be loaded, and
- * the attribute specified an alt attribute. It overriden a handle of
- * methods as the text is hardcoded and does not come from the document.
- */
- private class ImageLabelView extends InlineView {
- private Segment segment;
- private Color fg;
-
- ImageLabelView(Element e, String text) {
- super(e);
- reset(text);
- }
-
- public void reset(String text) {
- segment = new Segment(text.toCharArray(), 0, text.length());
- }
-
- public void paint(Graphics g, Shape a) {
- // Don't use supers paint, otherwise selection will be wrong
- // as our start/end offsets are fake.
- GlyphPainter painter = getGlyphPainter();
-
- if (painter != null) {
- g.setColor(getForeground());
- painter.paint(this, g, a, getStartOffset(), getEndOffset());
- }
- }
-
- public Segment getText(int p0, int p1) {
- if (p0 < 0 || p1 > segment.array.length) {
- throw new RuntimeException("ImageLabelView: Stale view");
- }
- segment.offset = p0;
- segment.count = p1 - p0;
- return segment;
- }
-
- public int getStartOffset() {
- return 0;
- }
-
- public int getEndOffset() {
- return segment.array.length;
- }
-
- public View breakView(int axis, int p0, float pos, float len) {
- // Don't allow a break
- return this;
- }
-
- public Color getForeground() {
- View parent;
- if (fg == null && (parent = getParent()) != null) {
- Document doc = getDocument();
- AttributeSet attr = parent.getAttributes();
-
- if (attr != null && (doc instanceof StyledDocument)) {
- fg = ((StyledDocument)doc).getForeground(attr);
- }
- }
- return fg;
- }
- }
- }