0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.visualweb.css2;
0042:
0043: import org.netbeans.modules.visualweb.api.designer.cssengine.CssComputedValue;
0044: import org.netbeans.modules.visualweb.api.designer.cssengine.CssProvider;
0045: import org.netbeans.modules.visualweb.api.designer.cssengine.CssValue;
0046: import org.netbeans.modules.visualweb.designer.CssUtilities;
0047: import java.awt.Container;
0048: import java.awt.Graphics;
0049: import java.awt.Image;
0050: import java.awt.Rectangle;
0051: import java.awt.Toolkit;
0052: import java.awt.image.ImageObserver;
0053: import java.net.URL;
0054:
0055: import javax.swing.Icon;
0056: import javax.swing.ImageIcon;
0057:
0058: import org.openide.ErrorManager;
0059: import org.openide.util.NbBundle;
0060: import org.openide.util.Utilities;
0061: import org.w3c.dom.Element;
0062:
0063: import org.netbeans.modules.visualweb.designer.WebForm;
0064: import org.netbeans.modules.visualweb.api.designer.cssengine.XhtmlCss;
0065: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
0066: import org.netbeans.modules.visualweb.designer.ImageCache;
0067:
0068: /**
0069: * ImageBox renders an image.
0070: * <p>
0071: * @todo Handle borders
0072: * @todo Handle ALT text
0073: * @todo Handle width and height settings when the image is not
0074: * found
0075: * @todo Is the "broken image" handling still working? It should have
0076: * a border too!
0077: * <p>
0078: * Portions of this code taken from javax.swing.text.html.ImageView
0079: * <p>
0080: * @author Scott Violet
0081: * @author Tor Norbye
0082: */
0083: public class ImageBox extends CssBox {
0084: /*
0085: protected String paramString() {
0086: return super.paramString() + ", " +
0087: "element=" + element;
0088: }
0089: */
0090:
0091: // Code from ImageView (modified for Rave purposes)
0092: /**
0093: * If true, when some of the bits are available a repaint is done.
0094: * <p>
0095: * This is set to false as swing does not offer a repaint that takes a
0096: * delay. If this were true, a bunch of immediate repaints would get
0097: * generated that end up significantly delaying the loading of the image
0098: * (or anything else going on for that matter).
0099: */
0100: private static boolean sIsInc = false;
0101:
0102: /**
0103: * Repaint delay when some of the bits are available.
0104: */
0105: private static int sIncRate = 100;
0106:
0107: // /**
0108: // * Icon used while the image is being loaded.
0109: // */
0110: // private static Icon sPendingImageIcon;
0111: //
0112: // /**
0113: // * Icon used if the image could not be found.
0114: // */
0115: // private static Icon sMissingImageIcon;
0116: //
0117: // private static final String PENDING_IMAGE_SRC = "icons/image-delayed.gif";
0118: // private static final String MISSING_IMAGE_SRC = "icons/image-failed.gif";
0119:
0120: // Height/width to use before we know the real size, these should at least
0121: // the size of <code>sMissingImageIcon</code> and
0122: // <code>sPendingImageIcon</code>
0123: private static final int DEFAULT_WIDTH = 38;
0124: private static final int DEFAULT_HEIGHT = 38;
0125:
0126: // BEGIN RAVE MODIFICATIONS
0127: // Actually, reverted. Ignore this.
0128: //private static final int DEFAULT_WIDTH = 200;
0129: //private static final int DEFAULT_HEIGHT= 65;
0130: // END RAVE MODIFICATIONS
0131: // Bitmask values
0132: private static final int LOADING_FLAG = 1;
0133: private static final int LINK_FLAG = 2;
0134: private static final int WIDTH_FLAG = 4;
0135: private static final int HEIGHT_FLAG = 8;
0136: private static final int RELOAD_FLAG = 16;
0137: private static final int RELOAD_IMAGE_FLAG = 32;
0138:
0139: // private AttributeSet attr;
0140: private Image image;
0141: private int width;
0142: private int height;
0143:
0144: /** Bitmask containing some of the above bitmask values. Because the
0145: * image loading notification can happen on another thread access to
0146: * this is synchronized (at least for modifying it). */
0147: private int state;
0148: private Container container;
0149: private Rectangle fBounds;
0150:
0151: /**
0152: * We don't directly implement ImageObserver, instead we use an instance
0153: * that calls back to us.
0154: */
0155: private ImageObserver imageObserver;
0156:
0157: public ImageBox(WebForm webform, Element element,
0158: Container container, BoxType boxType, boolean inline) {
0159: super (webform, element, boxType, inline, true);
0160: this .container = container;
0161:
0162: fBounds = new Rectangle();
0163: imageObserver = new ImageHandler();
0164: state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
0165: preloadImage();
0166: }
0167:
0168: public static CssBox getImageBox(WebForm webform, Element element,
0169: Container container, BoxType boxType, boolean inline) {
0170: ImageBox imageBox = new ImageBox(webform, element, container,
0171: boxType, inline);
0172:
0173: //if (imageBox.image != null) {
0174: if (imageBox.isValidUrl()) {
0175: return imageBox;
0176: }
0177:
0178: String alt = element.getAttribute(HtmlAttribute.ALT);
0179:
0180: if ((alt.length() == 0)
0181: && element.hasAttribute(HtmlAttribute.ALT)
0182: && !element.hasAttribute(HtmlAttribute.SRC)) {
0183: // User specifically wants no alt, and has no image -- use sized box
0184: // For example, google does underlines this way: with an img
0185: // tag
0186: return new CssBox(webform, element, boxType, inline, true);
0187: }
0188:
0189: // if (((alt == null) || (alt.length() == 0)) && (imageBox.getDesignBean() != null)) {
0190: // if (((alt == null) || (alt.length() == 0)) && (CssBox.getMarkupDesignBeanForCssBox(imageBox) != null)) {
0191: if (((alt == null) || (alt.length() == 0))
0192: && (CssBox.getElementForComponentRootCssBox(imageBox) != null)) {
0193: alt = NbBundle.getMessage(ImageBox.class, "LBL_Image");
0194: }
0195:
0196: if (alt != null) {
0197: // TODO - pass in our width/height as the intrinsic
0198: // image width!
0199: // int width = CssLookup.getLength(element, XhtmlCss.WIDTH_INDEX);
0200: // int height = CssLookup.getLength(element, XhtmlCss.HEIGHT_INDEX);
0201: int width = CssUtilities.getCssLength(element,
0202: XhtmlCss.WIDTH_INDEX);
0203: int height = CssUtilities.getCssLength(element,
0204: XhtmlCss.HEIGHT_INDEX);
0205:
0206: if (width == 0) {
0207: width = AUTO;
0208: }
0209:
0210: if (height == 0) {
0211: height = AUTO;
0212: }
0213:
0214: imageBox.initializeBorder();
0215:
0216: StringBox sb = new StringBox(webform, element, boxType,
0217: alt, imageBox.border, width, height);
0218:
0219: //sb.setDrawBorder(true);
0220: return sb;
0221: } else {
0222: return imageBox;
0223: }
0224: }
0225:
0226: protected void initializeHorizontalWidths(FormatContext context) {
0227: super .initializeHorizontalWidths(context);
0228:
0229: // Ensure that we initialize width, contentWidth etc. since those
0230: // are normally delayed for images until layout time, but during
0231: // table layout for an ancestor we need the width before our own
0232: // box has been laid out
0233: if (super .width == UNINITIALIZED) {
0234: this .relayout(context);
0235: }
0236: }
0237:
0238: protected void initializeBorder() {
0239: Element element = getElement();
0240: // Border: percentages are not allowed
0241: int borderWidth = HtmlAttribute.getIntegerAttributeValue(
0242: element, HtmlAttribute.BORDER, 0);
0243:
0244: if (borderWidth < 0) {
0245: borderWidth = 0;
0246: }
0247:
0248: int defStyle = (borderWidth == 0) ? CssBorder.STYLE_NONE
0249: : CssBorder.STYLE_OUTSET;
0250: border = CssBorder.getBorder(element, borderWidth, defStyle,
0251: CssBorder.FRAME_BOX);
0252:
0253: if (border != null) {
0254: leftBorderWidth = border.getLeftBorderWidth();
0255: topBorderWidth = border.getTopBorderWidth();
0256: bottomBorderWidth = border.getBottomBorderWidth();
0257: rightBorderWidth = border.getRightBorderWidth();
0258: }
0259:
0260: considerDesignBorder();
0261: }
0262:
0263: public void relayout(FormatContext context) {
0264: sync();
0265: width = (int) getPreferredSpan(X_AXIS);
0266: height = (int) getPreferredSpan(Y_AXIS);
0267: this .contentWidth = this .width;
0268: this .contentHeight = this .height;
0269:
0270: //super.width = width;
0271: //super.height = height;
0272: super .width = leftBorderWidth + leftPadding + contentWidth
0273: + rightPadding + rightBorderWidth;
0274: super .height = topBorderWidth + topPadding + contentHeight
0275: + bottomPadding + bottomBorderWidth;
0276: }
0277:
0278: // public String toString() {
0279: // return "ImageBox[" + paramString() + "]";
0280: // }
0281:
0282: /**
0283: * Returns the text to display if the image can't be loaded. This is
0284: * obtained from the Elements attribute set with the attribute name
0285: * <code>HTML.HtmlAttribute.ALT</code>.
0286: */
0287: public String getAltText() {
0288: Element element = getElement();
0289: return element == null ? null : element
0290: .getAttribute(HtmlAttribute.ALT);
0291: }
0292:
0293: /**
0294: * Return a URL for the image source,
0295: * or null if it could not be determined.
0296: */
0297: public URL getImageURL() {
0298: Element element = getElement();
0299: String src = element == null ? null : element
0300: .getAttribute(HtmlAttribute.SRC);
0301:
0302: if ((src == null) || (src.length() == 0)) {
0303: return null;
0304: }
0305:
0306: // return InSyncService.getProvider().resolveUrl(webform.getMarkup().getBase(), webform.getJspDom(), src);
0307: return webform.resolveUrl(src);
0308: }
0309:
0310: /**
0311: * Returns the icon to use if the image couldn't be found.
0312: */
0313: private static Icon getNoImageIcon() {
0314: return loadIcon("org/netbeans/modules/visualweb/designer/resources/image-failed.gif"); // NOI18N
0315: }
0316:
0317: /**
0318: * Returns the icon to use while in the process of loading the image.
0319: */
0320: private static Icon getLoadingImageIcon() {
0321: return loadIcon("org/netbeans/modules/visualweb/designer/resources/image-delayed.gif"); // NOI18N
0322: }
0323:
0324: /**
0325: * Returns the image to render.
0326: */
0327: public Image getImage() {
0328: sync();
0329:
0330: return image;
0331: }
0332:
0333: public void paint(Graphics g, int px, int py) {
0334: if (hidden) {
0335: return;
0336: }
0337:
0338: //super.paint(g, px, py);
0339: sync();
0340:
0341: px += getX();
0342: py += getY();
0343:
0344: px += leftMargin;
0345: py += effectiveTopMargin;
0346:
0347: Image image = getImage();
0348:
0349: if ((Math.abs(px) > 50000) || (Math.abs(py) > 50000)
0350: || (Math.abs(width) > 50000)
0351: || (Math.abs(height) > 50000)) {
0352: // g.setColor(Color.RED);
0353: // g.drawString("Fatal Painting Error: box " + this.toString(), 0,
0354: // g.getFontMetrics().getHeight());
0355: // XXX Improving the above error handling.
0356: // TODO Why is actually this state invalid?
0357: ErrorManager.getDefault()
0358: .notify(
0359: ErrorManager.INFORMATIONAL,
0360: new IllegalStateException(
0361: "Fatal painting error:" // NOI18N
0362: + "\nbad box="
0363: + this // NOI18N
0364: + "\nparent of bad box="
0365: + this .getParent())); // NOI18N
0366:
0367: return;
0368: }
0369:
0370: paintBackground(g, px, py);
0371:
0372: //fBounds.setBounds(rect);
0373: int x = px + leftBorderWidth + leftPadding;
0374: int y = py + topBorderWidth + topPadding;
0375:
0376: // XXX Don't I need to add in margins?
0377: fBounds.setBounds(x, y, getWidth(), getHeight());
0378:
0379: //paintHighlights(g, a);
0380:
0381: /* No - box parent should deal with borders, margins, etc.
0382: if (clip != null) {
0383: g.clipRect(rect.x + leftInset, rect.y + topInset,
0384: rect.width - leftInset - rightInset,
0385: rect.height - topInset - bottomInset);
0386: }
0387: */
0388: if (image != null) {
0389: if (!hasPixels(image)) {
0390: // No pixels yet, use the default
0391: Icon icon = getLoadingImageIcon();
0392:
0393: if (icon != null) {
0394: icon
0395: .paintIcon(container, g,
0396: x /*rect.x + leftInset*/, y /*rect.y + topInset */);
0397: }
0398: } else {
0399: // Draw the image
0400: g.drawImage(image, x /*rect.x + leftInset*/,
0401: y /*rect.y + topInset*/, width, height,
0402: imageObserver);
0403: }
0404: } else {
0405: Icon icon = getNoImageIcon();
0406:
0407: if (icon != null) {
0408: icon.paintIcon(container, g, x /*rect.x + leftInset*/,
0409: y /*rect.y + topInset*/);
0410: }
0411:
0412: // TODO - alt view
0413:
0414: /*
0415: View view = getAltView();
0416: // Paint the view representing the alt text, if its non-null
0417: if (view != null && ((state & WIDTH_FLAG) == 0 ||
0418: width > DEFAULT_WIDTH)) {
0419: // Assume layout along the y direction
0420: Rectangle altRect = new Rectangle
0421: (rect.x + leftInset + DEFAULT_WIDTH, rect.y + topInset,
0422: rect.width - leftInset - rightInset - DEFAULT_WIDTH,
0423: rect.height - topInset - bottomInset);
0424:
0425: view.paint(g, altRect);
0426: }
0427: */
0428: }
0429:
0430: /* Already removed the clip code above
0431: if (clip != null) {
0432: // Reset clip.
0433: g.setClip(clip.x, clip.y, clip.width, clip.height);
0434: }
0435: */
0436:
0437: // if (width > WATERMARK_SIZE && height > WATERMARK_SIZE) { // don't draw watermarks for tiny images
0438: // paintFacesWatermark(g, px, py);
0439: // }
0440: if (paintPositioning) {
0441: paintDebugPositioning(g);
0442: }
0443: }
0444:
0445: /**
0446: * Determines the preferred span for this view along an
0447: * axis.
0448: *
0449: * @param axis may be either X_AXIS or Y_AXIS
0450: * @return the span the view would like to be rendered into;
0451: * typically the view is told to render into the span
0452: * that is returned, although there is no guarantee;
0453: * the parent may choose to resize or break the view
0454: */
0455: public float getPreferredSpan(int axis) {
0456: sync();
0457:
0458: // If the attributes specified a width/height, always use it!
0459: if ((axis == CssBox.X_AXIS)
0460: && ((state & WIDTH_FLAG) == WIDTH_FLAG)) {
0461: //getPreferredSpanFromAltView(axis);
0462: return width;
0463: }
0464:
0465: if ((axis == CssBox.Y_AXIS)
0466: && ((state & HEIGHT_FLAG) == HEIGHT_FLAG)) {
0467: //getPreferredSpanFromAltView(axis);
0468: return height;
0469: }
0470:
0471: Image image = getImage();
0472:
0473: if (image != null) {
0474: switch (axis) {
0475: case CssBox.X_AXIS:
0476: return width;
0477:
0478: case CssBox.Y_AXIS:
0479: return height;
0480:
0481: default:
0482: throw new IllegalArgumentException("Invalid axis: "
0483: + axis);
0484: }
0485: } else {
0486: // TODO - handle alt view
0487:
0488: /*
0489: View view = getAltView();
0490: float retValue = 0f;
0491:
0492: if (view != null) {
0493: retValue = view.getPreferredSpan(axis);
0494: }
0495: switch (axis) {
0496: case CssBox.X_AXIS:
0497: return retValue + (float)(width + leftInset + rightInset);
0498: case CssBox.Y_AXIS:
0499: return retValue + (float)(height + topInset + bottomInset);
0500: default:
0501: throw new IllegalArgumentException("Invalid axis: " + axis);
0502: }
0503: */
0504: return 0;
0505: }
0506: }
0507:
0508: /**
0509: * Returns true if the passed in image has a non-zero width and height.
0510: */
0511: private boolean hasPixels(Image image) {
0512: return (image != null) && (image.getHeight(imageObserver) > 0)
0513: && (image.getWidth(imageObserver) > 0);
0514: }
0515:
0516: // /**
0517: // * Fetch a resource relative to the HTMLEditorKit classfile.
0518: // * If this is called on 1.2 the loading will occur under the
0519: // * protection of a doPrivileged call to allow the HTMLEditorKit
0520: // * to function when used in an applet.
0521: // *
0522: // * @param name the name of the resource, relative to the
0523: // * HTMLEditorKit class
0524: // * @return a stream representing the resource
0525: // */
0526: // static InputStream getResourceAsStream(String name) {
0527: // return ImageBox.class.getResourceAsStream(name);
0528: //
0529: // /*
0530: // try {
0531: // return ResourceLoader.getResourceAsStream(name);
0532: // } catch (Throwable e) {
0533: // // If the class doesn't exist or we have some other
0534: // // problem we just try to call getResourceAsStream directly.
0535: // return ImageAbstractBox.class.getResourceAsStream(name);
0536: // }
0537: // */
0538: // }
0539: //
0540: // private Icon makeIcon(final String gifFile) throws IOException {
0541: // /* Copy resource into a byte array. This is
0542: // * necessary because several browsers consider
0543: // * Class.getResource a security risk because it
0544: // * can be used to load additional classes.
0545: // * Class.getResourceAsStream just returns raw
0546: // * bytes, which we can convert to an image.
0547: // */
0548: // InputStream resource = getResourceAsStream(gifFile);
0549: //
0550: // if (resource == null) {
0551: // ErrorManager.getDefault().log(ImageBox.class.getName() + "/" + gifFile + " not found.");
0552: //
0553: // return null;
0554: // }
0555: //
0556: // BufferedInputStream in = new BufferedInputStream(resource);
0557: // ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
0558: // byte[] buffer = new byte[1024];
0559: // int n;
0560: //
0561: // while ((n = in.read(buffer)) > 0) {
0562: // out.write(buffer, 0, n);
0563: // }
0564: //
0565: // in.close();
0566: // out.flush();
0567: //
0568: // buffer = out.toByteArray();
0569: //
0570: // if (buffer.length == 0) {
0571: // ErrorManager.getDefault().log("warning: " + gifFile + " is zero-length");
0572: //
0573: // return null;
0574: // }
0575: //
0576: // return new ImageIcon(buffer);
0577: // }
0578:
0579: private static Icon loadIcon(String iconResource) {
0580: Image image = Utilities.loadImage(iconResource);
0581: if (image == null) {
0582: ErrorManager.getDefault()
0583: .notify(
0584: ErrorManager.INFORMATIONAL,
0585: new NullPointerException(
0586: "No image for iconResource="
0587: + iconResource)); // NOI18N
0588: }
0589: return image == null ? null : new ImageIcon(image);
0590: }
0591:
0592: /**
0593: * Request that this view be repainted.
0594: * Assumes the view is still at its last-drawn location.
0595: */
0596: private void repaint(long delay) {
0597: if ((container != null) && (fBounds != null)) {
0598: container.repaint(delay, fBounds.x, fBounds.y,
0599: fBounds.width, fBounds.height);
0600: }
0601: }
0602:
0603: /**
0604: * Makes sure the necessary properties and image is loaded.
0605: */
0606: private void sync() {
0607: int s = state;
0608:
0609: if ((s & RELOAD_IMAGE_FLAG) != 0) {
0610: refreshImage();
0611: }
0612:
0613: s = state;
0614:
0615: if ((s & RELOAD_FLAG) != 0) {
0616: synchronized (this ) {
0617: state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
0618: }
0619: }
0620: }
0621:
0622: /** Return true iff the url is set and valid such that we'll most
0623: * likely be able to display this image.
0624: */
0625: private boolean isValidUrl() {
0626: // try to access the resource
0627: return (image != null);
0628: }
0629:
0630: /**
0631: * Loads the image and updates the size accordingly. This should be
0632: * invoked instead of invoking <code>loadImage</code> or
0633: * <code>updateImageSize</code> directly.
0634: * @return true if any of the sizes are relative to the containing block
0635: * (e.g. a percentage).
0636: */
0637: private boolean refreshImage() {
0638: synchronized (this ) {
0639: // clear out width/height/realoadimage flag and set loading flag
0640: state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG
0641: | WIDTH_FLAG | HEIGHT_FLAG)
0642: ^ (WIDTH_FLAG | HEIGHT_FLAG | RELOAD_IMAGE_FLAG);
0643: image = null;
0644: width = height = 0;
0645: }
0646:
0647: try {
0648: // Load the image
0649: loadImage();
0650:
0651: // And update the size params
0652: return updateImageSize();
0653: } finally {
0654: synchronized (this ) {
0655: // Clear out state in case someone threw an exception.
0656: state = (state | LOADING_FLAG) ^ LOADING_FLAG;
0657: }
0658: }
0659: }
0660:
0661: /** Try to load in the image early. This will tell us if we need
0662: * to use a text box instead for example if the url is bad.
0663: */
0664: private void preloadImage() {
0665: boolean relative = refreshImage();
0666:
0667: if (relative) {
0668: // Ensure that we do this again once we know the viewport
0669: // size - set flag such that sync() in relout will recompute
0670: // picture
0671: state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
0672:
0673: // Clear out the style related values for this element
0674: // since we may have accessed them - they would now get cached
0675: // until next time which would still keep the old
0676: // containing block width (-1) instead of looking up a new one
0677: // CssLookup.clearComputedStyles(getElement());
0678: CssProvider.getEngineService()
0679: .clearComputedStylesForElement(getElement());
0680: }
0681: }
0682:
0683: /**
0684: * Loads the image from the URL <code>getImageURL</code>. This should
0685: * only be invoked from <code>refreshImage</code>.
0686: */
0687: private void loadImage() {
0688: URL src = getImageURL();
0689: Image newImage = null;
0690:
0691: if (src != null) {
0692: // ImageCache cache = webform.getDocument().getImageCache();
0693: ImageCache cache = webform.getImageCache();
0694: ImageIcon ii = cache.get(src);
0695:
0696: if (ii == null) {
0697: newImage = Toolkit.getDefaultToolkit().createImage(src);
0698:
0699: // Always load synchronously - we need size info
0700: // for layout
0701: if (newImage != null) {
0702: // Force the image to be loaded by using an ImageIcon.
0703: ii = new ImageIcon();
0704: ii.setImage(newImage);
0705: cache.put(src, ii);
0706: }
0707: } else {
0708: newImage = ii.getImage();
0709: }
0710: }
0711:
0712: image = newImage;
0713: }
0714:
0715: /**
0716: * Recreates and reloads the image. This should
0717: * only be invoked from <code>refreshImage</code>.
0718: * @return true if any of the sizes are relative to the containing block
0719: * (e.g. a percentage).
0720: */
0721: private boolean updateImageSize() {
0722: int newWidth = 0;
0723: int newHeight = 0;
0724: int newState = 0;
0725: Image newImage = getImage();
0726: boolean relative = false;
0727:
0728: if (newImage != null) {
0729: Element elem = getElement();
0730:
0731: // Get the width/height and set the state ivar before calling
0732: // anything that might cause the image to be loaded, and thus the
0733: // ImageHandler to be called.
0734: //newWidth = Css.getLength(elem, XhtmlCss.WIDTH_INDEX);
0735: // We want the value itself so we can check for percentages
0736: // Value val = CssLookup.getValue(elem, XhtmlCss.WIDTH_INDEX);
0737: CssValue cssValue = CssProvider.getEngineService()
0738: .getComputedValueForElement(elem,
0739: XhtmlCss.WIDTH_INDEX);
0740:
0741: // if (val == CssValueConstants.AUTO_VALUE) {
0742: if (CssProvider.getValueService().isAutoValue(cssValue)) {
0743: newWidth = CssBox.AUTO;
0744: } else {
0745: // newWidth = (int)val.getFloatValue();
0746: newWidth = (int) cssValue.getFloatValue();
0747: // relative =
0748: // relative ||
0749: // (val instanceof ComputedValue &&
0750: // (((ComputedValue)val).getCascadedValue().getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE));
0751: relative = relative
0752: || (cssValue instanceof CssComputedValue && CssProvider
0753: .getValueService()
0754: .isOfPrimitivePercentageType(
0755: ((CssComputedValue) cssValue)
0756: .getCascadedValue()));
0757: }
0758:
0759: if ((newWidth > 0) && (newWidth != AUTO)) {
0760: newState |= WIDTH_FLAG;
0761: }
0762:
0763: //newHeight = Css.getLength(elem, XhtmlCss.HEIGHT_INDEX);
0764: // val = CssLookup.getValue(elem, XhtmlCss.HEIGHT_INDEX);
0765: CssValue cssValue2 = CssProvider.getEngineService()
0766: .getComputedValueForElement(elem,
0767: XhtmlCss.HEIGHT_INDEX);
0768:
0769: // if (val == CssValueConstants.AUTO_VALUE) {
0770: if (CssProvider.getValueService().isAutoValue(cssValue2)) {
0771: newHeight = CssBox.AUTO;
0772: } else {
0773: // newHeight = (int)val.getFloatValue();
0774: newHeight = (int) cssValue2.getFloatValue();
0775: // relative =
0776: // relative ||
0777: // (val instanceof ComputedValue &&
0778: // (((ComputedValue)val).getCascadedValue().getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE));
0779: relative = relative
0780: || (cssValue2 instanceof CssComputedValue && CssProvider
0781: .getValueService()
0782: .isOfPrimitivePercentageType(
0783: ((CssComputedValue) cssValue2)
0784: .getCascadedValue()));
0785: }
0786:
0787: if ((newHeight > 0) && (newHeight != AUTO)) {
0788: newState |= HEIGHT_FLAG;
0789: }
0790:
0791: if ((newWidth <= 0) || (newWidth == AUTO)) {
0792: newWidth = newImage.getWidth(imageObserver);
0793:
0794: if (newWidth <= 0) {
0795: newWidth = DEFAULT_WIDTH;
0796: }
0797: }
0798:
0799: if ((newHeight <= 0) || (newHeight == AUTO)) {
0800: newHeight = newImage.getHeight(imageObserver);
0801:
0802: if (newHeight <= 0) {
0803: newHeight = DEFAULT_HEIGHT;
0804: }
0805: }
0806:
0807: // Make sure the image starts loading:
0808: if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
0809: Toolkit.getDefaultToolkit().prepareImage(newImage,
0810: newWidth, newHeight, imageObserver);
0811: } else {
0812: Toolkit.getDefaultToolkit().prepareImage(newImage, -1,
0813: -1, imageObserver);
0814: }
0815:
0816: boolean createText = false;
0817:
0818: synchronized (this ) {
0819: // If imageloading failed, other thread may have called
0820: // ImageLoader which will null out image, hence we check
0821: // for it.
0822: if (image != null) {
0823: if (((newState & WIDTH_FLAG) == WIDTH_FLAG)
0824: || (width == 0)) {
0825: width = newWidth;
0826: }
0827:
0828: if (((newState & HEIGHT_FLAG) == HEIGHT_FLAG)
0829: || (height == 0)) {
0830: height = newHeight;
0831: }
0832: } else {
0833: createText = true;
0834:
0835: if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
0836: width = newWidth;
0837: }
0838:
0839: if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
0840: height = newHeight;
0841: }
0842: }
0843:
0844: state = state | newState;
0845: state = (state | LOADING_FLAG) ^ LOADING_FLAG;
0846: }
0847:
0848: // if (createText) {
0849: // // Only reset if this thread determined image is null
0850: // // TODO: show text instead?
0851: // }
0852: } else {
0853: width = height = DEFAULT_HEIGHT;
0854: updateBorderForNoImage();
0855:
0856: // TODO - no image, show text instead
0857: }
0858:
0859: return relative;
0860: }
0861:
0862: /**
0863: * Invokes <code>preferenceChanged</code> on the event displatching
0864: * thread.
0865: */
0866: private void safePreferenceChanged() {
0867: Thread.dumpStack();
0868:
0869: /*
0870: if (SwingUtilities.isEventDispatchThread()) {
0871: preferenceChanged(null, true, true);
0872: }
0873: else {
0874: SwingUtilities.invokeLater(new Runnable() {
0875: public void run() {
0876: preferenceChanged(null, true, true);
0877: }
0878: });
0879: }
0880: */
0881: }
0882:
0883: /**
0884: * Invoked if no image is found, in which case a default border is
0885: * used if one isn't specified.
0886: */
0887: private void updateBorderForNoImage() {
0888: }
0889:
0890: public int getIntrinsicWidth() {
0891: return width;
0892: }
0893:
0894: public int getIntrinsicHeight() {
0895: return height;
0896: }
0897:
0898: /**
0899: * ImageHandler implements the ImageObserver to correctly update the
0900: * display as new parts of the image become available.
0901: */
0902: private class ImageHandler implements ImageObserver {
0903: // This can come on any thread. If we are in the process of reloading
0904: // the image and determining our state (loading == true) we don't fire
0905: // preference changed, or repaint, we just reset the fWidth/fHeight as
0906: // necessary and return. This is ok as we know when loading finishes
0907: // it will pick up the new height/width, if necessary.
0908: public boolean imageUpdate(Image img, int flags, int x, int y,
0909: int newWidth, int newHeight) {
0910: if ((image == null) || (image != img)) {
0911: return false;
0912: }
0913:
0914: // Bail out if there was an error:
0915: if ((flags & (ABORT | ERROR)) != 0) {
0916: repaint(0);
0917:
0918: synchronized (ImageBox.this ) {
0919: if (image == img) {
0920: // Be sure image hasn't changed since we don't
0921: // initialy synchronize
0922: image = null;
0923:
0924: if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
0925: width = DEFAULT_WIDTH;
0926: }
0927:
0928: if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
0929: height = DEFAULT_HEIGHT;
0930: }
0931:
0932: // No image, use a default border.
0933: updateBorderForNoImage();
0934: }
0935:
0936: if ((state & LOADING_FLAG) == LOADING_FLAG) {
0937: // No need to resize or repaint, still in the process
0938: // of loading.
0939: return false;
0940: }
0941: }
0942:
0943: // TODO - show text instead?
0944: safePreferenceChanged();
0945:
0946: return false;
0947: }
0948:
0949: // Resize image if necessary:
0950: short changed = 0;
0951:
0952: Element element = getElement();
0953: if (((flags & ImageObserver.HEIGHT) != 0)
0954: && !element.hasAttribute(HtmlAttribute.HEIGHT)) {
0955: changed |= 1;
0956: }
0957:
0958: if (((flags & ImageObserver.WIDTH) != 0)
0959: && !element.hasAttribute(HtmlAttribute.WIDTH)) {
0960: changed |= 2;
0961: }
0962:
0963: synchronized (ImageBox.this ) {
0964: if (image != img) {
0965: return false;
0966: }
0967:
0968: if (((changed & 1) == 1) && ((state & WIDTH_FLAG) == 0)) {
0969: width = newWidth;
0970: }
0971:
0972: if (((changed & 2) == 2)
0973: && ((state & HEIGHT_FLAG) == 0)) {
0974: height = newHeight;
0975: }
0976:
0977: if ((state & LOADING_FLAG) == LOADING_FLAG) {
0978: // No need to resize or repaint, still in the process of
0979: // loading.
0980: return true;
0981: }
0982: }
0983:
0984: // Repaint when done or when new pixels arrive:
0985: if ((flags & (FRAMEBITS | ALLBITS)) != 0) {
0986: repaint(0);
0987: } else if (((flags & SOMEBITS) != 0) && sIsInc) {
0988: repaint(sIncRate);
0989: }
0990:
0991: return ((flags & ALLBITS) == 0);
0992: }
0993: }
0994:
0995: public int getBaseline() {
0996: return getHeight();
0997: }
0998:
0999: public int getContributingBaseline() {
1000: // Images behave funny: they are baseline aligned but do not contribute
1001: // to make a taller baseline.
1002: return 0;
1003: }
1004:
1005: }
|