1008 lines · 33.0 KB · Java
RawDownload
/* * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.text.html; import java.awt.*;import java.awt.image.ImageObserver;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 &lt;IMG&gt; 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 * @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;    /**     * Property name for pending image icon     */    private static final String PENDING_IMAGE = "html.pendingImage";    /**     * Property name for missing image icon     */    private static final String MISSING_IMAGE = "html.missingImage";     /**     * 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 Image disabledImage;    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() {        return (Icon) UIManager.getLookAndFeelDefaults().get(MISSING_IMAGE);    }     /**     * Returns the icon to use while in the process of loading the image.     */    public Icon getLoadingImageIcon() {        return (Icon) UIManager.getLookAndFeelDefaults().get(PENDING_IMAGE);    }     /**     * Returns the image to render.     */    public Image getImage() {        sync();        return image;    }     private Image getImage(boolean enabled) {        Image img = getImage();        if (! enabled) {            if (disabledImage == null) {                disabledImage = GrayFilter.createDisabledImage(img);            }            img = disabledImage;        }        return img;    }     /**     * 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();        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);        }         Container host = getContainer();        Image img = getImage(host == null || host.isEnabled());        if (img != null) {            if (! hasPixels(img)) {                // No pixels yet, use the default                Icon icon = getLoadingImageIcon();                if (icon != null) {                    icon.paintIcon(host, g,                            rect.x + leftInset, rect.y + topInset);                }            }            else {                // Draw the image                g.drawImage(img, rect.x + leftInset, rect.y + topInset,                            width, height, imageObserver);            }        }        else {            Icon icon = getNoImageIcon();            if (icon != null) {                icon.paintIcon(host, 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:

Lines 1–500 of 1008.