0001 /*
0002 * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text.html;
0026
0027 import java.awt.*;
0028 import java.awt.event.*;
0029 import java.awt.image.ImageObserver;
0030 import java.io.*;
0031 import java.net.*;
0032 import java.util.Dictionary;
0033 import javax.swing.*;
0034 import javax.swing.text.*;
0035 import javax.swing.event.*;
0036
0037 /**
0038 * View of an Image, intended to support the HTML <IMG> tag.
0039 * Supports scaling via the HEIGHT and WIDTH attributes of the tag.
0040 * If the image is unable to be loaded any text specified via the
0041 * <code>ALT</code> attribute will be rendered.
0042 * <p>
0043 * While this class has been part of swing for a while now, it is public
0044 * as of 1.4.
0045 *
0046 * @author Scott Violet
0047 * @version 1.64 05/05/07
0048 * @see IconView
0049 * @since 1.4
0050 */
0051 public class ImageView extends View {
0052 /**
0053 * If true, when some of the bits are available a repaint is done.
0054 * <p>
0055 * This is set to false as swing does not offer a repaint that takes a
0056 * delay. If this were true, a bunch of immediate repaints would get
0057 * generated that end up significantly delaying the loading of the image
0058 * (or anything else going on for that matter).
0059 */
0060 private static boolean sIsInc = false;
0061 /**
0062 * Repaint delay when some of the bits are available.
0063 */
0064 private static int sIncRate = 100;
0065 /**
0066 * Property name for pending image icon
0067 */
0068 private static final String PENDING_IMAGE = "html.pendingImage";
0069 /**
0070 * Property name for missing image icon
0071 */
0072 private static final String MISSING_IMAGE = "html.missingImage";
0073
0074 /**
0075 * Document property for image cache.
0076 */
0077 private static final String IMAGE_CACHE_PROPERTY = "imageCache";
0078
0079 // Height/width to use before we know the real size, these should at least
0080 // the size of <code>sMissingImageIcon</code> and
0081 // <code>sPendingImageIcon</code>
0082 private static final int DEFAULT_WIDTH = 38;
0083 private static final int DEFAULT_HEIGHT = 38;
0084
0085 /**
0086 * Default border to use if one is not specified.
0087 */
0088 private static final int DEFAULT_BORDER = 2;
0089
0090 // Bitmask values
0091 private static final int LOADING_FLAG = 1;
0092 private static final int LINK_FLAG = 2;
0093 private static final int WIDTH_FLAG = 4;
0094 private static final int HEIGHT_FLAG = 8;
0095 private static final int RELOAD_FLAG = 16;
0096 private static final int RELOAD_IMAGE_FLAG = 32;
0097 private static final int SYNC_LOAD_FLAG = 64;
0098
0099 private AttributeSet attr;
0100 private Image image;
0101 private int width;
0102 private int height;
0103 /** Bitmask containing some of the above bitmask values. Because the
0104 * image loading notification can happen on another thread access to
0105 * this is synchronized (at least for modifying it). */
0106 private int state;
0107 private Container container;
0108 private Rectangle fBounds;
0109 private Color borderColor;
0110 // Size of the border, the insets contains this valid. For example, if
0111 // the HSPACE attribute was 4 and BORDER 2, leftInset would be 6.
0112 private short borderSize;
0113 // Insets, obtained from the painter.
0114 private short leftInset;
0115 private short rightInset;
0116 private short topInset;
0117 private short bottomInset;
0118 /**
0119 * We don't directly implement ImageObserver, instead we use an instance
0120 * that calls back to us.
0121 */
0122 private ImageObserver imageObserver;
0123 /**
0124 * Used for alt text. Will be non-null if the image couldn't be found,
0125 * and there is valid alt text.
0126 */
0127 private View altView;
0128 /** Alignment along the vertical (Y) axis. */
0129 private float vAlign;
0130
0131 /**
0132 * Creates a new view that represents an IMG element.
0133 *
0134 * @param elem the element to create a view for
0135 */
0136 public ImageView(Element elem) {
0137 super (elem);
0138 fBounds = new Rectangle();
0139 imageObserver = new ImageHandler();
0140 state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
0141 }
0142
0143 /**
0144 * Returns the text to display if the image can't be loaded. This is
0145 * obtained from the Elements attribute set with the attribute name
0146 * <code>HTML.Attribute.ALT</code>.
0147 */
0148 public String getAltText() {
0149 return (String) getElement().getAttributes().getAttribute(
0150 HTML.Attribute.ALT);
0151 }
0152
0153 /**
0154 * Return a URL for the image source,
0155 * or null if it could not be determined.
0156 */
0157 public URL getImageURL() {
0158 String src = (String) getElement().getAttributes()
0159 .getAttribute(HTML.Attribute.SRC);
0160 if (src == null) {
0161 return null;
0162 }
0163
0164 URL reference = ((HTMLDocument) getDocument()).getBase();
0165 try {
0166 URL u = new URL(reference, src);
0167 return u;
0168 } catch (MalformedURLException e) {
0169 return null;
0170 }
0171 }
0172
0173 /**
0174 * Returns the icon to use if the image couldn't be found.
0175 */
0176 public Icon getNoImageIcon() {
0177 return (Icon) UIManager.getLookAndFeelDefaults().get(
0178 MISSING_IMAGE);
0179 }
0180
0181 /**
0182 * Returns the icon to use while in the process of loading the image.
0183 */
0184 public Icon getLoadingImageIcon() {
0185 return (Icon) UIManager.getLookAndFeelDefaults().get(
0186 PENDING_IMAGE);
0187 }
0188
0189 /**
0190 * Returns the image to render.
0191 */
0192 public Image getImage() {
0193 sync();
0194 return image;
0195 }
0196
0197 /**
0198 * Sets how the image is loaded. If <code>newValue</code> is true,
0199 * the image we be loaded when first asked for, otherwise it will
0200 * be loaded asynchronously. The default is to not load synchronously,
0201 * that is to load the image asynchronously.
0202 */
0203 public void setLoadsSynchronously(boolean newValue) {
0204 synchronized (this ) {
0205 if (newValue) {
0206 state |= SYNC_LOAD_FLAG;
0207 } else {
0208 state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG;
0209 }
0210 }
0211 }
0212
0213 /**
0214 * Returns true if the image should be loaded when first asked for.
0215 */
0216 public boolean getLoadsSynchronously() {
0217 return ((state & SYNC_LOAD_FLAG) != 0);
0218 }
0219
0220 /**
0221 * Convenience method to get the StyleSheet.
0222 */
0223 protected StyleSheet getStyleSheet() {
0224 HTMLDocument doc = (HTMLDocument) getDocument();
0225 return doc.getStyleSheet();
0226 }
0227
0228 /**
0229 * Fetches the attributes to use when rendering. This is
0230 * implemented to multiplex the attributes specified in the
0231 * model with a StyleSheet.
0232 */
0233 public AttributeSet getAttributes() {
0234 sync();
0235 return attr;
0236 }
0237
0238 /**
0239 * For images the tooltip text comes from text specified with the
0240 * <code>ALT</code> attribute. This is overriden to return
0241 * <code>getAltText</code>.
0242 *
0243 * @see JTextComponent#getToolTipText
0244 */
0245 public String getToolTipText(float x, float y, Shape allocation) {
0246 return getAltText();
0247 }
0248
0249 /**
0250 * Update any cached values that come from attributes.
0251 */
0252 protected void setPropertiesFromAttributes() {
0253 StyleSheet sheet = getStyleSheet();
0254 this .attr = sheet.getViewAttributes(this );
0255
0256 // Gutters
0257 borderSize = (short) getIntAttr(HTML.Attribute.BORDER,
0258 isLink() ? DEFAULT_BORDER : 0);
0259
0260 leftInset = rightInset = (short) (getIntAttr(
0261 HTML.Attribute.HSPACE, 0) + borderSize);
0262 topInset = bottomInset = (short) (getIntAttr(
0263 HTML.Attribute.VSPACE, 0) + borderSize);
0264
0265 borderColor = ((StyledDocument) getDocument())
0266 .getForeground(getAttributes());
0267
0268 AttributeSet attr = getElement().getAttributes();
0269
0270 // Alignment.
0271 // PENDING: This needs to be changed to support the CSS versions
0272 // when conversion from ALIGN to VERTICAL_ALIGN is complete.
0273 Object alignment = attr.getAttribute(HTML.Attribute.ALIGN);
0274
0275 vAlign = 1.0f;
0276 if (alignment != null) {
0277 alignment = alignment.toString();
0278 if ("top".equals(alignment)) {
0279 vAlign = 0f;
0280 } else if ("middle".equals(alignment)) {
0281 vAlign = .5f;
0282 }
0283 }
0284
0285 AttributeSet anchorAttr = (AttributeSet) attr
0286 .getAttribute(HTML.Tag.A);
0287 if (anchorAttr != null
0288 && anchorAttr.isDefined(HTML.Attribute.HREF)) {
0289 synchronized (this ) {
0290 state |= LINK_FLAG;
0291 }
0292 } else {
0293 synchronized (this ) {
0294 state = (state | LINK_FLAG) ^ LINK_FLAG;
0295 }
0296 }
0297 }
0298
0299 /**
0300 * Establishes the parent view for this view.
0301 * Seize this moment to cache the AWT Container I'm in.
0302 */
0303 public void setParent(View parent) {
0304 View oldParent = getParent();
0305 super .setParent(parent);
0306 container = (parent != null) ? getContainer() : null;
0307 if (oldParent != parent) {
0308 synchronized (this ) {
0309 state |= RELOAD_FLAG;
0310 }
0311 }
0312 }
0313
0314 /**
0315 * Invoked when the Elements attributes have changed. Recreates the image.
0316 */
0317 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0318 super .changedUpdate(e, a, f);
0319
0320 synchronized (this ) {
0321 state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG;
0322 }
0323
0324 // Assume the worst.
0325 preferenceChanged(null, true, true);
0326 }
0327
0328 /**
0329 * Paints the View.
0330 *
0331 * @param g the rendering surface to use
0332 * @param a the allocated region to render into
0333 * @see View#paint
0334 */
0335 public void paint(Graphics g, Shape a) {
0336 sync();
0337
0338 Rectangle rect = (a instanceof Rectangle) ? (Rectangle) a : a
0339 .getBounds();
0340
0341 Image image = getImage();
0342 Rectangle clip = g.getClipBounds();
0343
0344 fBounds.setBounds(rect);
0345 paintHighlights(g, a);
0346 paintBorder(g, rect);
0347 if (clip != null) {
0348 g.clipRect(rect.x + leftInset, rect.y + topInset,
0349 rect.width - leftInset - rightInset, rect.height
0350 - topInset - bottomInset);
0351 }
0352 if (image != null) {
0353 if (!hasPixels(image)) {
0354 // No pixels yet, use the default
0355 Icon icon = (image == null) ? getNoImageIcon()
0356 : getLoadingImageIcon();
0357
0358 if (icon != null) {
0359 icon.paintIcon(getContainer(), g, rect.x
0360 + leftInset, rect.y + topInset);
0361 }
0362 } else {
0363 // Draw the image
0364 g.drawImage(image, rect.x + leftInset, rect.y
0365 + topInset, width, height, imageObserver);
0366 }
0367 } else {
0368 Icon icon = getNoImageIcon();
0369
0370 if (icon != null) {
0371 icon.paintIcon(getContainer(), g, rect.x + leftInset,
0372 rect.y + topInset);
0373 }
0374 View view = getAltView();
0375 // Paint the view representing the alt text, if its non-null
0376 if (view != null
0377 && ((state & WIDTH_FLAG) == 0 || width > DEFAULT_WIDTH)) {
0378 // Assume layout along the y direction
0379 Rectangle altRect = new Rectangle(rect.x + leftInset
0380 + DEFAULT_WIDTH, rect.y + topInset, rect.width
0381 - leftInset - rightInset - DEFAULT_WIDTH,
0382 rect.height - topInset - bottomInset);
0383
0384 view.paint(g, altRect);
0385 }
0386 }
0387 if (clip != null) {
0388 // Reset clip.
0389 g.setClip(clip.x, clip.y, clip.width, clip.height);
0390 }
0391 }
0392
0393 private void paintHighlights(Graphics g, Shape shape) {
0394 if (container instanceof JTextComponent) {
0395 JTextComponent tc = (JTextComponent) container;
0396 Highlighter h = tc.getHighlighter();
0397 if (h instanceof LayeredHighlighter) {
0398 ((LayeredHighlighter) h).paintLayeredHighlights(g,
0399 getStartOffset(), getEndOffset(), shape, tc,
0400 this );
0401 }
0402 }
0403 }
0404
0405 private void paintBorder(Graphics g, Rectangle rect) {
0406 Color color = borderColor;
0407
0408 if ((borderSize > 0 || image == null) && color != null) {
0409 int xOffset = leftInset - borderSize;
0410 int yOffset = topInset - borderSize;
0411 g.setColor(color);
0412 int n = (image == null) ? 1 : borderSize;
0413 for (int counter = 0; counter < n; counter++) {
0414 g.drawRect(rect.x + xOffset + counter, rect.y + yOffset
0415 + counter, rect.width - counter - counter
0416 - xOffset - xOffset - 1, rect.height - counter
0417 - counter - yOffset - yOffset - 1);
0418 }
0419 }
0420 }
0421
0422 /**
0423 * Determines the preferred span for this view along an
0424 * axis.
0425 *
0426 * @param axis may be either X_AXIS or Y_AXIS
0427 * @return the span the view would like to be rendered into;
0428 * typically the view is told to render into the span
0429 * that is returned, although there is no guarantee;
0430 * the parent may choose to resize or break the view
0431 */
0432 public float getPreferredSpan(int axis) {
0433 sync();
0434
0435 // If the attributes specified a width/height, always use it!
0436 if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) {
0437 getPreferredSpanFromAltView(axis);
0438 return width + leftInset + rightInset;
0439 }
0440 if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) {
0441 getPreferredSpanFromAltView(axis);
0442 return height + topInset + bottomInset;
0443 }
0444
0445 Image image = getImage();
0446
0447 if (image != null) {
0448 switch (axis) {
0449 case View.X_AXIS:
0450 return width + leftInset + rightInset;
0451 case View.Y_AXIS:
0452 return height + topInset + bottomInset;
0453 default:
0454 throw new IllegalArgumentException("Invalid axis: "
0455 + axis);
0456 }
0457 } else {
0458 View view = getAltView();
0459 float retValue = 0f;
0460
0461 if (view != null) {
0462 retValue = view.getPreferredSpan(axis);
0463 }
0464 switch (axis) {
0465 case View.X_AXIS:
0466 return retValue
0467 + (float) (width + leftInset + rightInset);
0468 case View.Y_AXIS:
0469 return retValue
0470 + (float) (height + topInset + bottomInset);
0471 default:
0472 throw new IllegalArgumentException("Invalid axis: "
0473 + axis);
0474 }
0475 }
0476 }
0477
0478 /**
0479 * Determines the desired alignment for this view along an
0480 * axis. This is implemented to give the alignment to the
0481 * bottom of the icon along the y axis, and the default
0482 * along the x axis.
0483 *
0484 * @param axis may be either X_AXIS or Y_AXIS
0485 * @return the desired alignment; this should be a value
0486 * between 0.0 and 1.0 where 0 indicates alignment at the
0487 * origin and 1.0 indicates alignment to the full span
0488 * away from the origin; an alignment of 0.5 would be the
0489 * center of the view
0490 */
0491 public float getAlignment(int axis) {
0492 switch (axis) {
0493 case View.Y_AXIS:
0494 return vAlign;
0495 default:
0496 return super .getAlignment(axis);
0497 }
0498 }
0499
0500 /**
0501 * Provides a mapping from the document model coordinate space
0502 * to the coordinate space of the view mapped to it.
0503 *
0504 * @param pos the position to convert
0505 * @param a the allocated region to render into
0506 * @return the bounding box of the given position
0507 * @exception BadLocationException if the given position does not represent a
0508 * valid location in the associated document
0509 * @see View#modelToView
0510 */
0511 public Shape modelToView(int pos, Shape a, Position.Bias b)
0512 throws BadLocationException {
0513 int p0 = getStartOffset();
0514 int p1 = getEndOffset();
0515 if ((pos >= p0) && (pos <= p1)) {
0516 Rectangle r = a.getBounds();
0517 if (pos == p1) {
0518 r.x += r.width;
0519 }
0520 r.width = 0;
0521 return r;
0522 }
0523 return null;
0524 }
0525
0526 /**
0527 * Provides a mapping from the view coordinate space to the logical
0528 * coordinate space of the model.
0529 *
0530 * @param x the X coordinate
0531 * @param y the Y coordinate
0532 * @param a the allocated region to render into
0533 * @return the location within the model that best represents the
0534 * given point of view
0535 * @see View#viewToModel
0536 */
0537 public int viewToModel(float x, float y, Shape a,
0538 Position.Bias[] bias) {
0539 Rectangle alloc = (Rectangle) a;
0540 if (x < alloc.x + alloc.width) {
0541 bias[0] = Position.Bias.Forward;
0542 return getStartOffset();
0543 }
0544 bias[0] = Position.Bias.Backward;
0545 return getEndOffset();
0546 }
0547
0548 /**
0549 * Sets the size of the view. This should cause
0550 * layout of the view if it has any layout duties.
0551 *
0552 * @param width the width >= 0
0553 * @param height the height >= 0
0554 */
0555 public void setSize(float width, float height) {
0556 sync();
0557
0558 if (getImage() == null) {
0559 View view = getAltView();
0560
0561 if (view != null) {
0562 view
0563 .setSize(
0564 Math
0565 .max(
0566 0f,
0567 width
0568 - (float) (DEFAULT_WIDTH
0569 + leftInset + rightInset)),
0570 Math
0571 .max(
0572 0f,
0573 height
0574 - (float) (topInset + bottomInset)));
0575 }
0576 }
0577 }
0578
0579 /**
0580 * Returns true if this image within a link?
0581 */
0582 private boolean isLink() {
0583 return ((state & LINK_FLAG) == LINK_FLAG);
0584 }
0585
0586 /**
0587 * Returns true if the passed in image has a non-zero width and height.
0588 */
0589 private boolean hasPixels(Image image) {
0590 return image != null && (image.getHeight(imageObserver) > 0)
0591 && (image.getWidth(imageObserver) > 0);
0592 }
0593
0594 /**
0595 * Returns the preferred span of the View used to display the alt text,
0596 * or 0 if the view does not exist.
0597 */
0598 private float getPreferredSpanFromAltView(int axis) {
0599 if (getImage() == null) {
0600 View view = getAltView();
0601
0602 if (view != null) {
0603 return view.getPreferredSpan(axis);
0604 }
0605 }
0606 return 0f;
0607 }
0608
0609 /**
0610 * Request that this view be repainted.
0611 * Assumes the view is still at its last-drawn location.
0612 */
0613 private void repaint(long delay) {
0614 if (container != null && fBounds != null) {
0615 container.repaint(delay, fBounds.x, fBounds.y,
0616 fBounds.width, fBounds.height);
0617 }
0618 }
0619
0620 /**
0621 * Convenience method for getting an integer attribute from the elements
0622 * AttributeSet.
0623 */
0624 private int getIntAttr(HTML.Attribute name, int deflt) {
0625 AttributeSet attr = getElement().getAttributes();
0626 if (attr.isDefined(name)) { // does not check parents!
0627 int i;
0628 String val = (String) attr.getAttribute(name);
0629 if (val == null) {
0630 i = deflt;
0631 } else {
0632 try {
0633 i = Math.max(0, Integer.parseInt(val));
0634 } catch (NumberFormatException x) {
0635 i = deflt;
0636 }
0637 }
0638 return i;
0639 } else
0640 return deflt;
0641 }
0642
0643 /**
0644 * Makes sure the necessary properties and image is loaded.
0645 */
0646 private void sync() {
0647 int s = state;
0648 if ((s & RELOAD_IMAGE_FLAG) != 0) {
0649 refreshImage();
0650 }
0651 s = state;
0652 if ((s & RELOAD_FLAG) != 0) {
0653 synchronized (this ) {
0654 state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
0655 }
0656 setPropertiesFromAttributes();
0657 }
0658 }
0659
0660 /**
0661 * Loads the image and updates the size accordingly. This should be
0662 * invoked instead of invoking <code>loadImage</code> or
0663 * <code>updateImageSize</code> directly.
0664 */
0665 private void refreshImage() {
0666 synchronized (this ) {
0667 // clear out width/height/realoadimage flag and set loading flag
0668 state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG
0669 | WIDTH_FLAG | HEIGHT_FLAG)
0670 ^ (WIDTH_FLAG | HEIGHT_FLAG | RELOAD_IMAGE_FLAG);
0671 image = null;
0672 width = height = 0;
0673 }
0674
0675 try {
0676 // Load the image
0677 loadImage();
0678
0679 // And update the size params
0680 updateImageSize();
0681 } finally {
0682 synchronized (this ) {
0683 // Clear out state in case someone threw an exception.
0684 state = (state | LOADING_FLAG) ^ LOADING_FLAG;
0685 }
0686 }
0687 }
0688
0689 /**
0690 * Loads the image from the URL <code>getImageURL</code>. This should
0691 * only be invoked from <code>refreshImage</code>.
0692 */
0693 private void loadImage() {
0694 URL src = getImageURL();
0695 Image newImage = null;
0696 if (src != null) {
0697 Dictionary cache = (Dictionary) getDocument().getProperty(
0698 IMAGE_CACHE_PROPERTY);
0699 if (cache != null) {
0700 newImage = (Image) cache.get(src);
0701 } else {
0702 newImage = Toolkit.getDefaultToolkit().createImage(src);
0703 if (newImage != null && getLoadsSynchronously()) {
0704 // Force the image to be loaded by using an ImageIcon.
0705 ImageIcon ii = new ImageIcon();
0706 ii.setImage(newImage);
0707 }
0708 }
0709 }
0710 image = newImage;
0711 }
0712
0713 /**
0714 * Recreates and reloads the image. This should
0715 * only be invoked from <code>refreshImage</code>.
0716 */
0717 private void updateImageSize() {
0718 int newWidth = 0;
0719 int newHeight = 0;
0720 int newState = 0;
0721 Image newImage = getImage();
0722
0723 if (newImage != null) {
0724 Element elem = getElement();
0725 AttributeSet attr = elem.getAttributes();
0726
0727 // Get the width/height and set the state ivar before calling
0728 // anything that might cause the image to be loaded, and thus the
0729 // ImageHandler to be called.
0730 newWidth = getIntAttr(HTML.Attribute.WIDTH, -1);
0731 if (newWidth > 0) {
0732 newState |= WIDTH_FLAG;
0733 }
0734 newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1);
0735 if (newHeight > 0) {
0736 newState |= HEIGHT_FLAG;
0737 }
0738
0739 if (newWidth <= 0) {
0740 newWidth = newImage.getWidth(imageObserver);
0741 if (newWidth <= 0) {
0742 newWidth = DEFAULT_WIDTH;
0743 }
0744 }
0745
0746 if (newHeight <= 0) {
0747 newHeight = newImage.getHeight(imageObserver);
0748 if (newHeight <= 0) {
0749 newHeight = DEFAULT_HEIGHT;
0750 }
0751 }
0752
0753 // Make sure the image starts loading:
0754 if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
0755 Toolkit.getDefaultToolkit().prepareImage(newImage,
0756 newWidth, newHeight, imageObserver);
0757 } else {
0758 Toolkit.getDefaultToolkit().prepareImage(newImage, -1,
0759 -1, imageObserver);
0760 }
0761
0762 boolean createText = false;
0763 synchronized (this ) {
0764 // If imageloading failed, other thread may have called
0765 // ImageLoader which will null out image, hence we check
0766 // for it.
0767 if (image != null) {
0768 if ((newState & WIDTH_FLAG) == WIDTH_FLAG
0769 || width == 0) {
0770 width = newWidth;
0771 }
0772 if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG
0773 || height == 0) {
0774 height = newHeight;
0775 }
0776 } else {
0777 createText = true;
0778 if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
0779 width = newWidth;
0780 }
0781 if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
0782 height = newHeight;
0783 }
0784 }
0785 state = state | newState;
0786 state = (state | LOADING_FLAG) ^ LOADING_FLAG;
0787 }
0788 if (createText) {
0789 // Only reset if this thread determined image is null
0790 updateAltTextView();
0791 }
0792 } else {
0793 width = height = DEFAULT_HEIGHT;
0794 updateAltTextView();
0795 }
0796 }
0797
0798 /**
0799 * Updates the view representing the alt text.
0800 */
0801 private void updateAltTextView() {
0802 String text = getAltText();
0803
0804 if (text != null) {
0805 ImageLabelView newView;
0806
0807 newView = new ImageLabelView(getElement(), text);
0808 synchronized (this ) {
0809 altView = newView;
0810 }
0811 }
0812 }
0813
0814 /**
0815 * Returns the view to use for alternate text. This may be null.
0816 */
0817 private View getAltView() {
0818 View view;
0819
0820 synchronized (this ) {
0821 view = altView;
0822 }
0823 if (view != null && view.getParent() == null) {
0824 view.setParent(getParent());
0825 }
0826 return view;
0827 }
0828
0829 /**
0830 * Invokes <code>preferenceChanged</code> on the event displatching
0831 * thread.
0832 */
0833 private void safePreferenceChanged() {
0834 if (SwingUtilities.isEventDispatchThread()) {
0835 Document doc = getDocument();
0836 if (doc instanceof AbstractDocument) {
0837 ((AbstractDocument) doc).readLock();
0838 }
0839 preferenceChanged(null, true, true);
0840 if (doc instanceof AbstractDocument) {
0841 ((AbstractDocument) doc).readUnlock();
0842 }
0843 } else {
0844 SwingUtilities.invokeLater(new Runnable() {
0845 public void run() {
0846 safePreferenceChanged();
0847 }
0848 });
0849 }
0850 }
0851
0852 /**
0853 * ImageHandler implements the ImageObserver to correctly update the
0854 * display as new parts of the image become available.
0855 */
0856 private class ImageHandler implements ImageObserver {
0857 // This can come on any thread. If we are in the process of reloading
0858 // the image and determining our state (loading == true) we don't fire
0859 // preference changed, or repaint, we just reset the fWidth/fHeight as
0860 // necessary and return. This is ok as we know when loading finishes
0861 // it will pick up the new height/width, if necessary.
0862 public boolean imageUpdate(Image img, int flags, int x, int y,
0863 int newWidth, int newHeight) {
0864 if (image == null || image != img || getParent() == null) {
0865 return false;
0866 }
0867
0868 // Bail out if there was an error:
0869 if ((flags & (ABORT | ERROR)) != 0) {
0870 repaint(0);
0871 synchronized (ImageView.this ) {
0872 if (image == img) {
0873 // Be sure image hasn't changed since we don't
0874 // initialy synchronize
0875 image = null;
0876 if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
0877 width = DEFAULT_WIDTH;
0878 }
0879 if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
0880 height = DEFAULT_HEIGHT;
0881 }
0882 }
0883 if ((state & LOADING_FLAG) == LOADING_FLAG) {
0884 // No need to resize or repaint, still in the process
0885 // of loading.
0886 return false;
0887 }
0888 }
0889 updateAltTextView();
0890 safePreferenceChanged();
0891 return false;
0892 }
0893
0894 // Resize image if necessary:
0895 short changed = 0;
0896 if ((flags & ImageObserver.HEIGHT) != 0
0897 && !getElement().getAttributes().isDefined(
0898 HTML.Attribute.HEIGHT)) {
0899 changed |= 1;
0900 }
0901 if ((flags & ImageObserver.WIDTH) != 0
0902 && !getElement().getAttributes().isDefined(
0903 HTML.Attribute.WIDTH)) {
0904 changed |= 2;
0905 }
0906
0907 synchronized (ImageView.this ) {
0908 if (image != img) {
0909 return false;
0910 }
0911 if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) {
0912 width = newWidth;
0913 }
0914 if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) {
0915 height = newHeight;
0916 }
0917 if ((state & LOADING_FLAG) == LOADING_FLAG) {
0918 // No need to resize or repaint, still in the process of
0919 // loading.
0920 return true;
0921 }
0922 }
0923 if (changed != 0) {
0924 // May need to resize myself, asynchronously:
0925 safePreferenceChanged();
0926 return true;
0927 }
0928
0929 // Repaint when done or when new pixels arrive:
0930 if ((flags & (FRAMEBITS | ALLBITS)) != 0) {
0931 repaint(0);
0932 } else if ((flags & SOMEBITS) != 0 && sIsInc) {
0933 repaint(sIncRate);
0934 }
0935 return ((flags & ALLBITS) == 0);
0936 }
0937 }
0938
0939 /**
0940 * ImageLabelView is used if the image can't be loaded, and
0941 * the attribute specified an alt attribute. It overriden a handle of
0942 * methods as the text is hardcoded and does not come from the document.
0943 */
0944 private class ImageLabelView extends InlineView {
0945 private Segment segment;
0946 private Color fg;
0947
0948 ImageLabelView(Element e, String text) {
0949 super (e);
0950 reset(text);
0951 }
0952
0953 public void reset(String text) {
0954 segment = new Segment(text.toCharArray(), 0, text.length());
0955 }
0956
0957 public void paint(Graphics g, Shape a) {
0958 // Don't use supers paint, otherwise selection will be wrong
0959 // as our start/end offsets are fake.
0960 GlyphPainter painter = getGlyphPainter();
0961
0962 if (painter != null) {
0963 g.setColor(getForeground());
0964 painter.paint(this , g, a, getStartOffset(),
0965 getEndOffset());
0966 }
0967 }
0968
0969 public Segment getText(int p0, int p1) {
0970 if (p0 < 0 || p1 > segment.array.length) {
0971 throw new RuntimeException("ImageLabelView: Stale view");
0972 }
0973 segment.offset = p0;
0974 segment.count = p1 - p0;
0975 return segment;
0976 }
0977
0978 public int getStartOffset() {
0979 return 0;
0980 }
0981
0982 public int getEndOffset() {
0983 return segment.array.length;
0984 }
0985
0986 public View breakView(int axis, int p0, float pos, float len) {
0987 // Don't allow a break
0988 return this ;
0989 }
0990
0991 public Color getForeground() {
0992 View parent;
0993 if (fg == null && (parent = getParent()) != null) {
0994 Document doc = getDocument();
0995 AttributeSet attr = parent.getAttributes();
0996
0997 if (attr != null && (doc instanceof StyledDocument)) {
0998 fg = ((StyledDocument) doc).getForeground(attr);
0999 }
1000 }
1001 return fg;
1002 }
1003 }
1004 }
|