Save This Page
Home » openjdk-7 » javax » swing » text » html » [javadoc | source]
    1   /*
    2    * Copyright 1997-2005 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   package javax.swing.text.html;
   26   
   27   import java.awt;
   28   import java.awt.event;
   29   import java.awt.image.ImageObserver;
   30   import java.io;
   31   import java.net;
   32   import java.util.Dictionary;
   33   import javax.swing;
   34   import javax.swing.text;
   35   import javax.swing.event;
   36   
   37   /**
   38    * View of an Image, intended to support the HTML <IMG> tag.
   39    * Supports scaling via the HEIGHT and WIDTH attributes of the tag.
   40    * If the image is unable to be loaded any text specified via the
   41    * <code>ALT</code> attribute will be rendered.
   42    * <p>
   43    * While this class has been part of swing for a while now, it is public
   44    * as of 1.4.
   45    *
   46    * @author  Scott Violet
   47    * @see IconView
   48    * @since 1.4
   49    */
   50   public class ImageView extends View {
   51       /**
   52        * If true, when some of the bits are available a repaint is done.
   53        * <p>
   54        * This is set to false as swing does not offer a repaint that takes a
   55        * delay. If this were true, a bunch of immediate repaints would get
   56        * generated that end up significantly delaying the loading of the image
   57        * (or anything else going on for that matter).
   58        */
   59       private static boolean sIsInc = false;
   60       /**
   61        * Repaint delay when some of the bits are available.
   62        */
   63       private static int sIncRate = 100;
   64       /**
   65        * Property name for pending image icon
   66        */
   67       private static final String PENDING_IMAGE = "html.pendingImage";
   68       /**
   69        * Property name for missing image icon
   70        */
   71       private static final String MISSING_IMAGE = "html.missingImage";
   72   
   73       /**
   74        * Document property for image cache.
   75        */
   76       private static final String IMAGE_CACHE_PROPERTY = "imageCache";
   77   
   78       // Height/width to use before we know the real size, these should at least
   79       // the size of <code>sMissingImageIcon</code> and
   80       // <code>sPendingImageIcon</code>
   81       private static final int DEFAULT_WIDTH = 38;
   82       private static final int DEFAULT_HEIGHT= 38;
   83   
   84       /**
   85        * Default border to use if one is not specified.
   86        */
   87       private static final int DEFAULT_BORDER = 2;
   88   
   89       // Bitmask values
   90       private static final int LOADING_FLAG = 1;
   91       private static final int LINK_FLAG = 2;
   92       private static final int WIDTH_FLAG = 4;
   93       private static final int HEIGHT_FLAG = 8;
   94       private static final int RELOAD_FLAG = 16;
   95       private static final int RELOAD_IMAGE_FLAG = 32;
   96       private static final int SYNC_LOAD_FLAG = 64;
   97   
   98       private AttributeSet attr;
   99       private Image image;
  100       private int width;
  101       private int height;
  102       /** Bitmask containing some of the above bitmask values. Because the
  103        * image loading notification can happen on another thread access to
  104        * this is synchronized (at least for modifying it). */
  105       private int state;
  106       private Container container;
  107       private Rectangle fBounds;
  108       private Color borderColor;
  109       // Size of the border, the insets contains this valid. For example, if
  110       // the HSPACE attribute was 4 and BORDER 2, leftInset would be 6.
  111       private short borderSize;
  112       // Insets, obtained from the painter.
  113       private short leftInset;
  114       private short rightInset;
  115       private short topInset;
  116       private short bottomInset;
  117       /**
  118        * We don't directly implement ImageObserver, instead we use an instance
  119        * that calls back to us.
  120        */
  121       private ImageObserver imageObserver;
  122       /**
  123        * Used for alt text. Will be non-null if the image couldn't be found,
  124        * and there is valid alt text.
  125        */
  126       private View altView;
  127       /** Alignment along the vertical (Y) axis. */
  128       private float vAlign;
  129   
  130   
  131   
  132       /**
  133        * Creates a new view that represents an IMG element.
  134        *
  135        * @param elem the element to create a view for
  136        */
  137       public ImageView(Element elem) {
  138           super(elem);
  139           fBounds = new Rectangle();
  140           imageObserver = new ImageHandler();
  141           state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
  142       }
  143   
  144       /**
  145        * Returns the text to display if the image can't be loaded. This is
  146        * obtained from the Elements attribute set with the attribute name
  147        * <code>HTML.Attribute.ALT</code>.
  148        */
  149       public String getAltText() {
  150           return (String)getElement().getAttributes().getAttribute
  151               (HTML.Attribute.ALT);
  152       }
  153   
  154       /**
  155        * Return a URL for the image source,
  156        * or null if it could not be determined.
  157        */
  158       public URL getImageURL() {
  159           String src = (String)getElement().getAttributes().
  160                                getAttribute(HTML.Attribute.SRC);
  161           if (src == null) {
  162               return null;
  163           }
  164   
  165           URL reference = ((HTMLDocument)getDocument()).getBase();
  166           try {
  167               URL u = new URL(reference,src);
  168               return u;
  169           } catch (MalformedURLException e) {
  170               return null;
  171           }
  172       }
  173   
  174       /**
  175        * Returns the icon to use if the image couldn't be found.
  176        */
  177       public Icon getNoImageIcon() {
  178           return (Icon) UIManager.getLookAndFeelDefaults().get(MISSING_IMAGE);
  179       }
  180   
  181       /**
  182        * Returns the icon to use while in the process of loading the image.
  183        */
  184       public Icon getLoadingImageIcon() {
  185           return (Icon) UIManager.getLookAndFeelDefaults().get(PENDING_IMAGE);
  186       }
  187   
  188       /**
  189        * Returns the image to render.
  190        */
  191       public Image getImage() {
  192           sync();
  193           return image;
  194       }
  195   
  196       /**
  197        * Sets how the image is loaded. If <code>newValue</code> is true,
  198        * the image we be loaded when first asked for, otherwise it will
  199        * be loaded asynchronously. The default is to not load synchronously,
  200        * that is to load the image asynchronously.
  201        */
  202       public void setLoadsSynchronously(boolean newValue) {
  203           synchronized(this) {
  204               if (newValue) {
  205                   state |= SYNC_LOAD_FLAG;
  206               }
  207               else {
  208                   state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG;
  209               }
  210           }
  211       }
  212   
  213       /**
  214        * Returns true if the image should be loaded when first asked for.
  215        */
  216       public boolean getLoadsSynchronously() {
  217           return ((state & SYNC_LOAD_FLAG) != 0);
  218       }
  219   
  220       /**
  221        * Convenience method to get the StyleSheet.
  222        */
  223       protected StyleSheet getStyleSheet() {
  224           HTMLDocument doc = (HTMLDocument) getDocument();
  225           return doc.getStyleSheet();
  226       }
  227   
  228       /**
  229        * Fetches the attributes to use when rendering.  This is
  230        * implemented to multiplex the attributes specified in the
  231        * model with a StyleSheet.
  232        */
  233       public AttributeSet getAttributes() {
  234           sync();
  235           return attr;
  236       }
  237   
  238       /**
  239        * For images the tooltip text comes from text specified with the
  240        * <code>ALT</code> attribute. This is overriden to return
  241        * <code>getAltText</code>.
  242        *
  243        * @see JTextComponent#getToolTipText
  244        */
  245       public String getToolTipText(float x, float y, Shape allocation) {
  246           return getAltText();
  247       }
  248   
  249       /**
  250        * Update any cached values that come from attributes.
  251        */
  252       protected void setPropertiesFromAttributes() {
  253           StyleSheet sheet = getStyleSheet();
  254           this.attr = sheet.getViewAttributes(this);
  255   
  256           // Gutters
  257           borderSize = (short)getIntAttr(HTML.Attribute.BORDER, isLink() ?
  258                                          DEFAULT_BORDER : 0);
  259   
  260           leftInset = rightInset = (short)(getIntAttr(HTML.Attribute.HSPACE,
  261                                                       0) + borderSize);
  262           topInset = bottomInset = (short)(getIntAttr(HTML.Attribute.VSPACE,
  263                                                       0) + borderSize);
  264   
  265           borderColor = ((StyledDocument)getDocument()).getForeground
  266                         (getAttributes());
  267   
  268           AttributeSet attr = getElement().getAttributes();
  269   
  270           // Alignment.
  271           // PENDING: This needs to be changed to support the CSS versions
  272           // when conversion from ALIGN to VERTICAL_ALIGN is complete.
  273           Object alignment = attr.getAttribute(HTML.Attribute.ALIGN);
  274   
  275           vAlign = 1.0f;
  276           if (alignment != null) {
  277               alignment = alignment.toString();
  278               if ("top".equals(alignment)) {
  279                   vAlign = 0f;
  280               }
  281               else if ("middle".equals(alignment)) {
  282                   vAlign = .5f;
  283               }
  284           }
  285   
  286           AttributeSet anchorAttr = (AttributeSet)attr.getAttribute(HTML.Tag.A);
  287           if (anchorAttr != null && anchorAttr.isDefined
  288               (HTML.Attribute.HREF)) {
  289               synchronized(this) {
  290                   state |= LINK_FLAG;
  291               }
  292           }
  293           else {
  294               synchronized(this) {
  295                   state = (state | LINK_FLAG) ^ LINK_FLAG;
  296               }
  297           }
  298       }
  299   
  300       /**
  301        * Establishes the parent view for this view.
  302        * Seize this moment to cache the AWT Container I'm in.
  303        */
  304       public void setParent(View parent) {
  305           View oldParent = getParent();
  306           super.setParent(parent);
  307           container = (parent != null) ? getContainer() : null;
  308           if (oldParent != parent) {
  309               synchronized(this) {
  310                   state |= RELOAD_FLAG;
  311               }
  312           }
  313       }
  314   
  315       /**
  316        * Invoked when the Elements attributes have changed. Recreates the image.
  317        */
  318       public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  319           super.changedUpdate(e,a,f);
  320   
  321           synchronized(this) {
  322               state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG;
  323           }
  324   
  325           // Assume the worst.
  326           preferenceChanged(null, true, true);
  327       }
  328   
  329       /**
  330        * Paints the View.
  331        *
  332        * @param g the rendering surface to use
  333        * @param a the allocated region to render into
  334        * @see View#paint
  335        */
  336       public void paint(Graphics g, Shape a) {
  337           sync();
  338   
  339           Rectangle rect = (a instanceof Rectangle) ? (Rectangle)a :
  340                            a.getBounds();
  341   
  342           Image image = getImage();
  343           Rectangle clip = g.getClipBounds();
  344   
  345           fBounds.setBounds(rect);
  346           paintHighlights(g, a);
  347           paintBorder(g, rect);
  348           if (clip != null) {
  349               g.clipRect(rect.x + leftInset, rect.y + topInset,
  350                          rect.width - leftInset - rightInset,
  351                          rect.height - topInset - bottomInset);
  352           }
  353           if (image != null) {
  354               if (!hasPixels(image)) {
  355                   // No pixels yet, use the default
  356                   Icon icon = (image == null) ? getNoImageIcon() :
  357                                                  getLoadingImageIcon();
  358   
  359                   if (icon != null) {
  360                       icon.paintIcon(getContainer(), g, rect.x + leftInset,
  361                                      rect.y + topInset);
  362                   }
  363               }
  364               else {
  365                   // Draw the image
  366                   g.drawImage(image, rect.x + leftInset, rect.y + topInset,
  367                               width, height, imageObserver);
  368               }
  369           }
  370           else {
  371               Icon icon = getNoImageIcon();
  372   
  373               if (icon != null) {
  374                   icon.paintIcon(getContainer(), g, rect.x + leftInset,
  375                                  rect.y + topInset);
  376               }
  377               View view = getAltView();
  378               // Paint the view representing the alt text, if its non-null
  379               if (view != null && ((state & WIDTH_FLAG) == 0 ||
  380                                    width > DEFAULT_WIDTH)) {
  381                   // Assume layout along the y direction
  382                   Rectangle altRect = new Rectangle
  383                       (rect.x + leftInset + DEFAULT_WIDTH, rect.y + topInset,
  384                        rect.width - leftInset - rightInset - DEFAULT_WIDTH,
  385                        rect.height - topInset - bottomInset);
  386   
  387                   view.paint(g, altRect);
  388               }
  389           }
  390           if (clip != null) {
  391               // Reset clip.
  392               g.setClip(clip.x, clip.y, clip.width, clip.height);
  393           }
  394       }
  395   
  396       private void paintHighlights(Graphics g, Shape shape) {
  397           if (container instanceof JTextComponent) {
  398               JTextComponent tc = (JTextComponent)container;
  399               Highlighter h = tc.getHighlighter();
  400               if (h instanceof LayeredHighlighter) {
  401                   ((LayeredHighlighter)h).paintLayeredHighlights
  402                       (g, getStartOffset(), getEndOffset(), shape, tc, this);
  403               }
  404           }
  405       }
  406   
  407       private void paintBorder(Graphics g, Rectangle rect) {
  408           Color color = borderColor;
  409   
  410           if ((borderSize > 0 || image == null) && color != null) {
  411               int xOffset = leftInset - borderSize;
  412               int yOffset = topInset - borderSize;
  413               g.setColor(color);
  414               int n = (image == null) ? 1 : borderSize;
  415               for (int counter = 0; counter < n; counter++) {
  416                   g.drawRect(rect.x + xOffset + counter,
  417                              rect.y + yOffset + counter,
  418                              rect.width - counter - counter - xOffset -xOffset-1,
  419                              rect.height - counter - counter -yOffset-yOffset-1);
  420               }
  421           }
  422       }
  423   
  424       /**
  425        * Determines the preferred span for this view along an
  426        * axis.
  427        *
  428        * @param axis may be either X_AXIS or Y_AXIS
  429        * @return   the span the view would like to be rendered into;
  430        *           typically the view is told to render into the span
  431        *           that is returned, although there is no guarantee;
  432        *           the parent may choose to resize or break the view
  433        */
  434       public float getPreferredSpan(int axis) {
  435           sync();
  436   
  437           // If the attributes specified a width/height, always use it!
  438           if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) {
  439               getPreferredSpanFromAltView(axis);
  440               return width + leftInset + rightInset;
  441           }
  442           if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) {
  443               getPreferredSpanFromAltView(axis);
  444               return height + topInset + bottomInset;
  445           }
  446   
  447           Image image = getImage();
  448   
  449           if (image != null) {
  450               switch (axis) {
  451               case View.X_AXIS:
  452                   return width + leftInset + rightInset;
  453               case View.Y_AXIS:
  454                   return height + topInset + bottomInset;
  455               default:
  456                   throw new IllegalArgumentException("Invalid axis: " + axis);
  457               }
  458           }
  459           else {
  460               View view = getAltView();
  461               float retValue = 0f;
  462   
  463               if (view != null) {
  464                   retValue = view.getPreferredSpan(axis);
  465               }
  466               switch (axis) {
  467               case View.X_AXIS:
  468                   return retValue + (float)(width + leftInset + rightInset);
  469               case View.Y_AXIS:
  470                   return retValue + (float)(height + topInset + bottomInset);
  471               default:
  472                   throw new IllegalArgumentException("Invalid axis: " + axis);
  473               }
  474           }
  475       }
  476   
  477       /**
  478        * Determines the desired alignment for this view along an
  479        * axis.  This is implemented to give the alignment to the
  480        * bottom of the icon along the y axis, and the default
  481        * along the x axis.
  482        *
  483        * @param axis may be either X_AXIS or Y_AXIS
  484        * @return the desired alignment; this should be a value
  485        *   between 0.0 and 1.0 where 0 indicates alignment at the
  486        *   origin and 1.0 indicates alignment to the full span
  487        *   away from the origin; an alignment of 0.5 would be the
  488        *   center of the view
  489        */
  490       public float getAlignment(int axis) {
  491           switch (axis) {
  492           case View.Y_AXIS:
  493               return vAlign;
  494           default:
  495               return super.getAlignment(axis);
  496           }
  497       }
  498   
  499       /**
  500        * Provides a mapping from the document model coordinate space
  501        * to the coordinate space of the view mapped to it.
  502        *
  503        * @param pos the position to convert
  504        * @param a the allocated region to render into
  505        * @return the bounding box of the given position
  506        * @exception BadLocationException  if the given position does not represent a
  507        *   valid location in the associated document
  508        * @see View#modelToView
  509        */
  510       public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  511           int p0 = getStartOffset();
  512           int p1 = getEndOffset();
  513           if ((pos >= p0) && (pos <= p1)) {
  514               Rectangle r = a.getBounds();
  515               if (pos == p1) {
  516                   r.x += r.width;
  517               }
  518               r.width = 0;
  519               return r;
  520           }
  521           return null;
  522       }
  523   
  524       /**
  525        * Provides a mapping from the view coordinate space to the logical
  526        * coordinate space of the model.
  527        *
  528        * @param x the X coordinate
  529        * @param y the Y coordinate
  530        * @param a the allocated region to render into
  531        * @return the location within the model that best represents the
  532        *  given point of view
  533        * @see View#viewToModel
  534        */
  535       public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  536           Rectangle alloc = (Rectangle) a;
  537           if (x < alloc.x + alloc.width) {
  538               bias[0] = Position.Bias.Forward;
  539               return getStartOffset();
  540           }
  541           bias[0] = Position.Bias.Backward;
  542           return getEndOffset();
  543       }
  544   
  545       /**
  546        * Sets the size of the view.  This should cause
  547        * layout of the view if it has any layout duties.
  548        *
  549        * @param width the width >= 0
  550        * @param height the height >= 0
  551        */
  552       public void setSize(float width, float height) {
  553           sync();
  554   
  555           if (getImage() == null) {
  556               View view = getAltView();
  557   
  558               if (view != null) {
  559                   view.setSize(Math.max(0f, width - (float)(DEFAULT_WIDTH + leftInset + rightInset)),
  560                                Math.max(0f, height - (float)(topInset + bottomInset)));
  561               }
  562           }
  563       }
  564   
  565       /**
  566        * Returns true if this image within a link?
  567        */
  568       private boolean isLink() {
  569           return ((state & LINK_FLAG) == LINK_FLAG);
  570       }
  571   
  572       /**
  573        * Returns true if the passed in image has a non-zero width and height.
  574        */
  575       private boolean hasPixels(Image image) {
  576           return image != null &&
  577               (image.getHeight(imageObserver) > 0) &&
  578               (image.getWidth(imageObserver) > 0);
  579       }
  580   
  581       /**
  582        * Returns the preferred span of the View used to display the alt text,
  583        * or 0 if the view does not exist.
  584        */
  585       private float getPreferredSpanFromAltView(int axis) {
  586           if (getImage() == null) {
  587               View view = getAltView();
  588   
  589               if (view != null) {
  590                   return view.getPreferredSpan(axis);
  591               }
  592           }
  593           return 0f;
  594       }
  595   
  596       /**
  597        * Request that this view be repainted.
  598        * Assumes the view is still at its last-drawn location.
  599        */
  600       private void repaint(long delay) {
  601           if (container != null && fBounds != null) {
  602               container.repaint(delay, fBounds.x, fBounds.y, fBounds.width,
  603                                  fBounds.height);
  604           }
  605       }
  606   
  607       /**
  608        * Convenience method for getting an integer attribute from the elements
  609        * AttributeSet.
  610        */
  611       private int getIntAttr(HTML.Attribute name, int deflt) {
  612           AttributeSet attr = getElement().getAttributes();
  613           if (attr.isDefined(name)) {             // does not check parents!
  614               int i;
  615               String val = (String)attr.getAttribute(name);
  616               if (val == null) {
  617                   i = deflt;
  618               }
  619               else {
  620                   try{
  621                       i = Math.max(0, Integer.parseInt(val));
  622                   }catch( NumberFormatException x ) {
  623                       i = deflt;
  624                   }
  625               }
  626               return i;
  627           } else
  628               return deflt;
  629       }
  630   
  631       /**
  632        * Makes sure the necessary properties and image is loaded.
  633        */
  634       private void sync() {
  635           int s = state;
  636           if ((s & RELOAD_IMAGE_FLAG) != 0) {
  637               refreshImage();
  638           }
  639           s = state;
  640           if ((s & RELOAD_FLAG) != 0) {
  641               synchronized(this) {
  642                   state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
  643               }
  644               setPropertiesFromAttributes();
  645           }
  646       }
  647   
  648       /**
  649        * Loads the image and updates the size accordingly. This should be
  650        * invoked instead of invoking <code>loadImage</code> or
  651        * <code>updateImageSize</code> directly.
  652        */
  653       private void refreshImage() {
  654           synchronized(this) {
  655               // clear out width/height/realoadimage flag and set loading flag
  656               state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG |
  657                        HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG |
  658                                        RELOAD_IMAGE_FLAG);
  659               image = null;
  660               width = height = 0;
  661           }
  662   
  663           try {
  664               // Load the image
  665               loadImage();
  666   
  667               // And update the size params
  668               updateImageSize();
  669           }
  670           finally {
  671               synchronized(this) {
  672                   // Clear out state in case someone threw an exception.
  673                   state = (state | LOADING_FLAG) ^ LOADING_FLAG;
  674               }
  675           }
  676       }
  677   
  678       /**
  679        * Loads the image from the URL <code>getImageURL</code>. This should
  680        * only be invoked from <code>refreshImage</code>.
  681        */
  682       private void loadImage() {
  683           URL src = getImageURL();
  684           Image newImage = null;
  685           if (src != null) {
  686               Dictionary cache = (Dictionary)getDocument().
  687                                       getProperty(IMAGE_CACHE_PROPERTY);
  688               if (cache != null) {
  689                   newImage = (Image)cache.get(src);
  690               }
  691               else {
  692                   newImage = Toolkit.getDefaultToolkit().createImage(src);
  693                   if (newImage != null && getLoadsSynchronously()) {
  694                       // Force the image to be loaded by using an ImageIcon.
  695                       ImageIcon ii = new ImageIcon();
  696                       ii.setImage(newImage);
  697                   }
  698               }
  699           }
  700           image = newImage;
  701       }
  702   
  703       /**
  704        * Recreates and reloads the image.  This should
  705        * only be invoked from <code>refreshImage</code>.
  706        */
  707       private void updateImageSize() {
  708           int newWidth = 0;
  709           int newHeight = 0;
  710           int newState = 0;
  711           Image newImage = getImage();
  712   
  713           if (newImage != null) {
  714               Element elem = getElement();
  715               AttributeSet attr = elem.getAttributes();
  716   
  717               // Get the width/height and set the state ivar before calling
  718               // anything that might cause the image to be loaded, and thus the
  719               // ImageHandler to be called.
  720               newWidth = getIntAttr(HTML.Attribute.WIDTH, -1);
  721               if (newWidth > 0) {
  722                   newState |= WIDTH_FLAG;
  723               }
  724               newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1);
  725               if (newHeight > 0) {
  726                   newState |= HEIGHT_FLAG;
  727               }
  728   
  729               if (newWidth <= 0) {
  730                   newWidth = newImage.getWidth(imageObserver);
  731                   if (newWidth <= 0) {
  732                       newWidth = DEFAULT_WIDTH;
  733                   }
  734               }
  735   
  736               if (newHeight <= 0) {
  737                   newHeight = newImage.getHeight(imageObserver);
  738                   if (newHeight <= 0) {
  739                       newHeight = DEFAULT_HEIGHT;
  740                   }
  741               }
  742   
  743               // Make sure the image starts loading:
  744               if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
  745                   Toolkit.getDefaultToolkit().prepareImage(newImage, newWidth,
  746                                                            newHeight,
  747                                                            imageObserver);
  748               }
  749               else {
  750                   Toolkit.getDefaultToolkit().prepareImage(newImage, -1, -1,
  751                                                            imageObserver);
  752               }
  753   
  754               boolean createText = false;
  755               synchronized(this) {
  756                   // If imageloading failed, other thread may have called
  757                   // ImageLoader which will null out image, hence we check
  758                   // for it.
  759                   if (image != null) {
  760                       if ((newState & WIDTH_FLAG) == WIDTH_FLAG || width == 0) {
  761                           width = newWidth;
  762                       }
  763                       if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG ||
  764                           height == 0) {
  765                           height = newHeight;
  766                       }
  767                   }
  768                   else {
  769                       createText = true;
  770                       if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
  771                           width = newWidth;
  772                       }
  773                       if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
  774                           height = newHeight;
  775                       }
  776                   }
  777                   state = state | newState;
  778                   state = (state | LOADING_FLAG) ^ LOADING_FLAG;
  779               }
  780               if (createText) {
  781                   // Only reset if this thread determined image is null
  782                   updateAltTextView();
  783               }
  784           }
  785           else {
  786               width = height = DEFAULT_HEIGHT;
  787               updateAltTextView();
  788           }
  789       }
  790   
  791       /**
  792        * Updates the view representing the alt text.
  793        */
  794       private void updateAltTextView() {
  795           String text = getAltText();
  796   
  797           if (text != null) {
  798               ImageLabelView newView;
  799   
  800               newView = new ImageLabelView(getElement(), text);
  801               synchronized(this) {
  802                   altView = newView;
  803               }
  804           }
  805       }
  806   
  807       /**
  808        * Returns the view to use for alternate text. This may be null.
  809        */
  810       private View getAltView() {
  811           View view;
  812   
  813           synchronized(this) {
  814               view = altView;
  815           }
  816           if (view != null && view.getParent() == null) {
  817               view.setParent(getParent());
  818           }
  819           return view;
  820       }
  821   
  822       /**
  823        * Invokes <code>preferenceChanged</code> on the event displatching
  824        * thread.
  825        */
  826       private void safePreferenceChanged() {
  827           if (SwingUtilities.isEventDispatchThread()) {
  828               Document doc = getDocument();
  829               if (doc instanceof AbstractDocument) {
  830                   ((AbstractDocument)doc).readLock();
  831               }
  832               preferenceChanged(null, true, true);
  833               if (doc instanceof AbstractDocument) {
  834                   ((AbstractDocument)doc).readUnlock();
  835               }
  836           }
  837           else {
  838               SwingUtilities.invokeLater(new Runnable() {
  839                       public void run() {
  840                           safePreferenceChanged();
  841                       }
  842                   });
  843           }
  844       }
  845   
  846       /**
  847        * ImageHandler implements the ImageObserver to correctly update the
  848        * display as new parts of the image become available.
  849        */
  850       private class ImageHandler implements ImageObserver {
  851           // This can come on any thread. If we are in the process of reloading
  852           // the image and determining our state (loading == true) we don't fire
  853           // preference changed, or repaint, we just reset the fWidth/fHeight as
  854           // necessary and return. This is ok as we know when loading finishes
  855           // it will pick up the new height/width, if necessary.
  856           public boolean imageUpdate(Image img, int flags, int x, int y,
  857                                      int newWidth, int newHeight ) {
  858               if (image == null || image != img || getParent() == null) {
  859                   return false;
  860               }
  861   
  862               // Bail out if there was an error:
  863               if ((flags & (ABORT|ERROR)) != 0) {
  864                   repaint(0);
  865                   synchronized(ImageView.this) {
  866                       if (image == img) {
  867                           // Be sure image hasn't changed since we don't
  868                           // initialy synchronize
  869                           image = null;
  870                           if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
  871                               width = DEFAULT_WIDTH;
  872                           }
  873                           if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
  874                               height = DEFAULT_HEIGHT;
  875                           }
  876                       }
  877                       if ((state & LOADING_FLAG) == LOADING_FLAG) {
  878                           // No need to resize or repaint, still in the process
  879                           // of loading.
  880                           return false;
  881                       }
  882                   }
  883                   updateAltTextView();
  884                   safePreferenceChanged();
  885                   return false;
  886               }
  887   
  888               // Resize image if necessary:
  889               short changed = 0;
  890               if ((flags & ImageObserver.HEIGHT) != 0 && !getElement().
  891                     getAttributes().isDefined(HTML.Attribute.HEIGHT)) {
  892                   changed |= 1;
  893               }
  894               if ((flags & ImageObserver.WIDTH) != 0 && !getElement().
  895                     getAttributes().isDefined(HTML.Attribute.WIDTH)) {
  896                   changed |= 2;
  897               }
  898   
  899               synchronized(ImageView.this) {
  900                   if (image != img) {
  901                       return false;
  902                   }
  903                   if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) {
  904                       width = newWidth;
  905                   }
  906                   if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) {
  907                       height = newHeight;
  908                   }
  909                   if ((state & LOADING_FLAG) == LOADING_FLAG) {
  910                       // No need to resize or repaint, still in the process of
  911                       // loading.
  912                       return true;
  913                   }
  914               }
  915               if (changed != 0) {
  916                   // May need to resize myself, asynchronously:
  917                   safePreferenceChanged();
  918                   return true;
  919               }
  920   
  921               // Repaint when done or when new pixels arrive:
  922               if ((flags & (FRAMEBITS|ALLBITS)) != 0) {
  923                   repaint(0);
  924               }
  925               else if ((flags & SOMEBITS) != 0 && sIsInc) {
  926                   repaint(sIncRate);
  927               }
  928               return ((flags & ALLBITS) == 0);
  929           }
  930       }
  931   
  932   
  933       /**
  934        * ImageLabelView is used if the image can't be loaded, and
  935        * the attribute specified an alt attribute. It overriden a handle of
  936        * methods as the text is hardcoded and does not come from the document.
  937        */
  938       private class ImageLabelView extends InlineView {
  939           private Segment segment;
  940           private Color fg;
  941   
  942           ImageLabelView(Element e, String text) {
  943               super(e);
  944               reset(text);
  945           }
  946   
  947           public void reset(String text) {
  948               segment = new Segment(text.toCharArray(), 0, text.length());
  949           }
  950   
  951           public void paint(Graphics g, Shape a) {
  952               // Don't use supers paint, otherwise selection will be wrong
  953               // as our start/end offsets are fake.
  954               GlyphPainter painter = getGlyphPainter();
  955   
  956               if (painter != null) {
  957                   g.setColor(getForeground());
  958                   painter.paint(this, g, a, getStartOffset(), getEndOffset());
  959               }
  960           }
  961   
  962           public Segment getText(int p0, int p1) {
  963               if (p0 < 0 || p1 > segment.array.length) {
  964                   throw new RuntimeException("ImageLabelView: Stale view");
  965               }
  966               segment.offset = p0;
  967               segment.count = p1 - p0;
  968               return segment;
  969           }
  970   
  971           public int getStartOffset() {
  972               return 0;
  973           }
  974   
  975           public int getEndOffset() {
  976               return segment.array.length;
  977           }
  978   
  979           public View breakView(int axis, int p0, float pos, float len) {
  980               // Don't allow a break
  981               return this;
  982           }
  983   
  984           public Color getForeground() {
  985               View parent;
  986               if (fg == null && (parent = getParent()) != null) {
  987                   Document doc = getDocument();
  988                   AttributeSet attr = parent.getAttributes();
  989   
  990                   if (attr != null && (doc instanceof StyledDocument)) {
  991                       fg = ((StyledDocument)doc).getForeground(attr);
  992                   }
  993               }
  994               return fg;
  995           }
  996       }
  997   }

Save This Page
Home » openjdk-7 » javax » swing » text » html » [javadoc | source]