KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > html > ImageView


1 /*
2  * @(#)ImageView.java 1.56 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.html;
8
9 import java.awt.*;
10 import java.awt.event.*;
11 import java.awt.image.ImageObserver JavaDoc;
12 import java.io.*;
13 import java.net.*;
14 import java.util.Dictionary JavaDoc;
15 import javax.swing.*;
16 import javax.swing.text.*;
17 import javax.swing.event.*;
18
19 /**
20  * View of an Image, intended to support the HTML <IMG> tag.
21  * Supports scaling via the HEIGHT and WIDTH attributes of the tag.
22  * If the image is unable to be loaded any text specified via the
23  * <code>ALT</code> attribute will be rendered.
24  * <p>
25  * While this class has been part of swing for a while now, it is public
26  * as of 1.4.
27  *
28  * @author Scott Violet
29  * @version 1.56 12/19/03
30  * @see IconView
31  * @since 1.4
32  */

33 public class ImageView extends View {
34     /**
35      * If true, when some of the bits are available a repaint is done.
36      * <p>
37      * This is set to false as swing does not offer a repaint that takes a
38      * delay. If this were true, a bunch of immediate repaints would get
39      * generated that end up significantly delaying the loading of the image
40      * (or anything else going on for that matter).
41      */

42     private static boolean sIsInc = false;
43     /**
44      * Repaint delay when some of the bits are available.
45      */

46     private static int sIncRate = 100;
47     /**
48      * Icon used while the image is being loaded.
49      */

50     private static Icon sPendingImageIcon;
51     /**
52      * Icon used if the image could not be found.
53      */

54     private static Icon sMissingImageIcon;
55     /**
56      * File name for <code>sPendingImageIcon</code>.
57      */

58     private static final String JavaDoc PENDING_IMAGE_SRC = "icons/image-delayed.gif";
59     /**
60      * File name for <code>sMissingImageIcon</code>.
61      */

62     private static final String JavaDoc MISSING_IMAGE_SRC = "icons/image-failed.gif";
63
64     /**
65      * Document property for image cache.
66      */

67     private static final String JavaDoc IMAGE_CACHE_PROPERTY = "imageCache";
68     
69     // Height/width to use before we know the real size, these should at least
70
// the size of <code>sMissingImageIcon</code> and
71
// <code>sPendingImageIcon</code>
72
private static final int DEFAULT_WIDTH = 38;
73     private static final int DEFAULT_HEIGHT= 38;
74
75     /**
76      * Default border to use if one is not specified.
77      */

78     private static final int DEFAULT_BORDER = 2;
79
80     // Bitmask values
81
private static final int LOADING_FLAG = 1;
82     private static final int LINK_FLAG = 2;
83     private static final int WIDTH_FLAG = 4;
84     private static final int HEIGHT_FLAG = 8;
85     private static final int RELOAD_FLAG = 16;
86     private static final int RELOAD_IMAGE_FLAG = 32;
87     private static final int SYNC_LOAD_FLAG = 64;
88
89     private AttributeSet attr;
90     private Image image;
91     private int width;
92     private int height;
93     /** Bitmask containing some of the above bitmask values. Because the
94      * image loading notification can happen on another thread access to
95      * this is synchronized (at least for modifying it). */

96     private int state;
97     private Container container;
98     private Rectangle fBounds;
99     private Color borderColor;
100     // Size of the border, the insets contains this valid. For example, if
101
// the HSPACE attribute was 4 and BORDER 2, leftInset would be 6.
102
private short borderSize;
103     // Insets, obtained from the painter.
104
private short leftInset;
105     private short rightInset;
106     private short topInset;
107     private short bottomInset;
108     /**
109      * We don't directly implement ImageObserver, instead we use an instance
110      * that calls back to us.
111      */

112     private ImageObserver JavaDoc imageObserver;
113     /**
114      * Used for alt text. Will be non-null if the image couldn't be found,
115      * and there is valid alt text.
116      */

117     private View altView;
118     /** Alignment along the vertical (Y) axis. */
119     private float vAlign;
120
121
122
123     /**
124      * Creates a new view that represents an IMG element.
125      *
126      * @param elem the element to create a view for
127      */

128     public ImageView(Element elem) {
129         super(elem);
130     fBounds = new Rectangle();
131         imageObserver = new ImageHandler();
132         state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
133     }
134
135     /**
136      * Returns the text to display if the image can't be loaded. This is
137      * obtained from the Elements attribute set with the attribute name
138      * <code>HTML.Attribute.ALT</code>.
139      */

140     public String JavaDoc getAltText() {
141         return (String JavaDoc)getElement().getAttributes().getAttribute
142             (HTML.Attribute.ALT);
143     }
144
145     /**
146      * Return a URL for the image source,
147      * or null if it could not be determined.
148      */

149     public URL getImageURL() {
150     String JavaDoc src = (String JavaDoc)getElement().getAttributes().
151                              getAttribute(HTML.Attribute.SRC);
152     if (src == null) {
153             return null;
154         }
155
156     URL reference = ((HTMLDocument JavaDoc)getDocument()).getBase();
157         try {
158         URL u = new URL(reference,src);
159         return u;
160         } catch (MalformedURLException e) {
161         return null;
162         }
163     }
164     
165     /**
166      * Returns the icon to use if the image couldn't be found.
167      */

168     public Icon getNoImageIcon() {
169         loadDefaultIconsIfNecessary();
170         return sMissingImageIcon;
171     }
172
173     /**
174      * Returns the icon to use while in the process of loading the image.
175      */

176     public Icon getLoadingImageIcon() {
177         loadDefaultIconsIfNecessary();
178         return sPendingImageIcon;
179     }
180
181     /**
182      * Returns the image to render.
183      */

184     public Image getImage() {
185         sync();
186         return image;
187     }
188
189     /**
190      * Sets how the image is loaded. If <code>newValue</code> is true,
191      * the image we be loaded when first asked for, otherwise it will
192      * be loaded asynchronously. The default is to not load synchronously,
193      * that is to load the image asynchronously.
194      */

195     public void setLoadsSynchronously(boolean newValue) {
196         synchronized(this) {
197             if (newValue) {
198                 state |= SYNC_LOAD_FLAG;
199             }
200             else {
201                 state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG;
202             }
203         }
204     }
205
206     /**
207      * Returns true if the image should be loaded when first asked for.
208      */

209     public boolean getLoadsSynchronously() {
210         return ((state & SYNC_LOAD_FLAG) != 0);
211     }
212
213     /**
214      * Convenience method to get the StyleSheet.
215      */

216     protected StyleSheet JavaDoc getStyleSheet() {
217     HTMLDocument JavaDoc doc = (HTMLDocument JavaDoc) getDocument();
218     return doc.getStyleSheet();
219     }
220
221     /**
222      * Fetches the attributes to use when rendering. This is
223      * implemented to multiplex the attributes specified in the
224      * model with a StyleSheet.
225      */

226     public AttributeSet getAttributes() {
227         sync();
228     return attr;
229     }
230
231     /**
232      * For images the tooltip text comes from text specified with the
233      * <code>ALT</code> attribute. This is overriden to return
234      * <code>getAltText</code>.
235      *
236      * @see JTextComponent#getToolTipText
237      */

238     public String JavaDoc getToolTipText(float x, float y, Shape allocation) {
239         return getAltText();
240     }
241
242     /**
243      * Update any cached values that come from attributes.
244      */

245     protected void setPropertiesFromAttributes() {
246         StyleSheet JavaDoc sheet = getStyleSheet();
247         this.attr = sheet.getViewAttributes(this);
248
249         // Gutters
250
borderSize = (short)getIntAttr(HTML.Attribute.BORDER, isLink() ?
251                                        DEFAULT_BORDER : 0);
252
253         leftInset = rightInset = (short)(getIntAttr(HTML.Attribute.HSPACE,
254                                                     0) + borderSize);
255         topInset = bottomInset = (short)(getIntAttr(HTML.Attribute.VSPACE,
256                                                     0) + borderSize);
257
258         borderColor = ((StyledDocument)getDocument()).getForeground
259                       (getAttributes());
260
261         AttributeSet attr = getElement().getAttributes();
262
263         // Alignment.
264
// PENDING: This needs to be changed to support the CSS versions
265
// when conversion from ALIGN to VERTICAL_ALIGN is complete.
266
Object JavaDoc alignment = attr.getAttribute(HTML.Attribute.ALIGN);
267
268         vAlign = 1.0f;
269         if (alignment != null) {
270             alignment = alignment.toString();
271             if ("top".equals(alignment)) {
272                 vAlign = 0f;
273             }
274             else if ("middle".equals(alignment)) {
275                 vAlign = .5f;
276             }
277         }
278
279         AttributeSet anchorAttr = (AttributeSet)attr.getAttribute(HTML.Tag.A);
280         if (anchorAttr != null && anchorAttr.isDefined
281             (HTML.Attribute.HREF)) {
282             synchronized(this) {
283                 state |= LINK_FLAG;
284             }
285         }
286         else {
287             synchronized(this) {
288                 state = (state | LINK_FLAG) ^ LINK_FLAG;
289             }
290         }
291     }
292
293     /**
294      * Establishes the parent view for this view.
295      * Seize this moment to cache the AWT Container I'm in.
296      */

297     public void setParent(View parent) {
298         View oldParent = getParent();
299     super.setParent(parent);
300     container = (parent != null) ? getContainer() : null;
301         if (oldParent != parent) {
302             synchronized(this) {
303                 state |= RELOAD_FLAG;
304             }
305         }
306     }
307
308     /**
309      * Invoked when the Elements attributes have changed. Recreates the image.
310      */

311     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
312         super.changedUpdate(e,a,f);
313
314         synchronized(this) {
315             state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG;
316         }
317
318         // Assume the worst.
319
preferenceChanged(null, true, true);
320     }
321
322     /**
323      * Paints the View.
324      *
325      * @param g the rendering surface to use
326      * @param a the allocated region to render into
327      * @see View#paint
328      */

329     public void paint(Graphics g, Shape a) {
330         sync();
331
332     Rectangle rect = (a instanceof Rectangle) ? (Rectangle)a :
333                          a.getBounds();
334
335         Image image = getImage();
336         Rectangle clip = g.getClipBounds();
337
338     fBounds.setBounds(rect);
339         paintHighlights(g, a);
340         paintBorder(g, rect);
341         if (clip != null) {
342             g.clipRect(rect.x + leftInset, rect.y + topInset,
343                        rect.width - leftInset - rightInset,
344                        rect.height - topInset - bottomInset);
345         }
346         if (image != null) {
347             if (!hasPixels(image)) {
348                 // No pixels yet, use the default
349
Icon icon = (image == null) ? getNoImageIcon() :
350                                                getLoadingImageIcon();
351
352                 if (icon != null) {
353                     icon.paintIcon(getContainer(), g, rect.x + leftInset,
354                                    rect.y + topInset);
355                 }
356             }
357             else {
358                 // Draw the image
359
g.drawImage(image, rect.x + leftInset, rect.y + topInset,
360                             width, height, imageObserver);
361             }
362         }
363         else {
364             Icon icon = getNoImageIcon();
365
366             if (icon != null) {
367                 icon.paintIcon(getContainer(), g, rect.x + leftInset,
368                                rect.y + topInset);
369             }
370             View view = getAltView();
371             // Paint the view representing the alt text, if its non-null
372
if (view != null && ((state & WIDTH_FLAG) == 0 ||
373                                  width > DEFAULT_WIDTH)) {
374                 // Assume layout along the y direction
375
Rectangle altRect = new Rectangle
376                     (rect.x + leftInset + DEFAULT_WIDTH, rect.y + topInset,
377                      rect.width - leftInset - rightInset - DEFAULT_WIDTH,
378                      rect.height - topInset - bottomInset);
379
380                 view.paint(g, altRect);
381             }
382         }
383         if (clip != null) {
384             // Reset clip.
385
g.setClip(clip.x, clip.y, clip.width, clip.height);
386         }
387     }
388
389     private void paintHighlights(Graphics g, Shape shape) {
390     if (container instanceof JTextComponent) {
391         JTextComponent tc = (JTextComponent)container;
392         Highlighter h = tc.getHighlighter();
393         if (h instanceof LayeredHighlighter) {
394         ((LayeredHighlighter)h).paintLayeredHighlights
395             (g, getStartOffset(), getEndOffset(), shape, tc, this);
396         }
397     }
398     }
399
400     private void paintBorder(Graphics g, Rectangle rect) {
401         Color color = borderColor;
402
403         if ((borderSize > 0 || image == null) && color != null) {
404             int xOffset = leftInset - borderSize;
405             int yOffset = topInset - borderSize;
406             g.setColor(color);
407             int n = (image == null) ? 1 : borderSize;
408         for (int counter = 0; counter < n; counter++) {
409             g.drawRect(rect.x + xOffset + counter,
410                            rect.y + yOffset + counter,
411                            rect.width - counter - counter - xOffset -xOffset-1,
412                            rect.height - counter - counter -yOffset-yOffset-1);
413             }
414         }
415     }
416
417     /**
418      * Determines the preferred span for this view along an
419      * axis.
420      *
421      * @param axis may be either X_AXIS or Y_AXIS
422      * @return the span the view would like to be rendered into;
423      * typically the view is told to render into the span
424      * that is returned, although there is no guarantee;
425      * the parent may choose to resize or break the view
426      */

427     public float getPreferredSpan(int axis) {
428         sync();
429
430         // If the attributes specified a width/height, always use it!
431
if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) {
432             getPreferredSpanFromAltView(axis);
433             return width + leftInset + rightInset;
434         }
435         if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) {
436             getPreferredSpanFromAltView(axis);
437             return height + topInset + bottomInset;
438         }
439
440         Image image = getImage();
441
442         if (image != null) {
443             switch (axis) {
444             case View.X_AXIS:
445                 return width + leftInset + rightInset;
446             case View.Y_AXIS:
447                 return height + topInset + bottomInset;
448             default:
449                 throw new IllegalArgumentException JavaDoc("Invalid axis: " + axis);
450             }
451         }
452         else {
453             View view = getAltView();
454             float retValue = 0f;
455
456             if (view != null) {
457                 retValue = view.getPreferredSpan(axis);
458             }
459             switch (axis) {
460             case View.X_AXIS:
461                 return retValue + (float)(width + leftInset + rightInset);
462             case View.Y_AXIS:
463                 return retValue + (float)(height + topInset + bottomInset);
464             default:
465                 throw new IllegalArgumentException JavaDoc("Invalid axis: " + axis);
466             }
467         }
468     }
469
470     /**
471      * Determines the desired alignment for this view along an
472      * axis. This is implemented to give the alignment to the
473      * bottom of the icon along the y axis, and the default
474      * along the x axis.
475      *
476      * @param axis may be either X_AXIS or Y_AXIS
477      * @return the desired alignment; this should be a value
478      * between 0.0 and 1.0 where 0 indicates alignment at the
479      * origin and 1.0 indicates alignment to the full span
480      * away from the origin; an alignment of 0.5 would be the
481      * center of the view
482      */

483     public float getAlignment(int axis) {
484     switch (axis) {
485     case View.Y_AXIS:
486         return vAlign;
487     default:
488         return super.getAlignment(axis);
489     }
490     }
491
492     /**
493      * Provides a mapping from the document model coordinate space
494      * to the coordinate space of the view mapped to it.
495      *
496      * @param pos the position to convert
497      * @param a the allocated region to render into
498      * @return the bounding box of the given position
499      * @exception BadLocationException if the given position does not represent a
500      * valid location in the associated document
501      * @see View#modelToView
502      */

503     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
504     int p0 = getStartOffset();
505     int p1 = getEndOffset();
506     if ((pos >= p0) && (pos <= p1)) {
507         Rectangle r = a.getBounds();
508         if (pos == p1) {
509         r.x += r.width;
510         }
511         r.width = 0;
512         return r;
513     }
514     return null;
515     }
516
517     /**
518      * Provides a mapping from the view coordinate space to the logical
519      * coordinate space of the model.
520      *
521      * @param x the X coordinate
522      * @param y the Y coordinate
523      * @param a the allocated region to render into
524      * @return the location within the model that best represents the
525      * given point of view
526      * @see View#viewToModel
527      */

528     public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
529     Rectangle alloc = (Rectangle) a;
530     if (x < alloc.x + alloc.width) {
531         bias[0] = Position.Bias.Forward;
532         return getStartOffset();
533     }
534     bias[0] = Position.Bias.Backward;
535     return getEndOffset();
536     }
537
538     /**
539      * Sets the size of the view. This should cause
540      * layout of the view if it has any layout duties.
541      *
542      * @param width the width >= 0
543      * @param height the height >= 0
544      */

545     public void setSize(float width, float height) {
546         sync();
547
548         if (getImage() == null) {
549             View view = getAltView();
550
551             if (view != null) {
552         view.setSize(Math.max(0f, width - (float)(DEFAULT_WIDTH + leftInset + rightInset)),
553                  Math.max(0f, height - (float)(topInset + bottomInset)));
554             }
555         }
556     }
557
558     /**
559      * Returns true if this image within a link?
560      */

561     private boolean isLink() {
562     return ((state & LINK_FLAG) == LINK_FLAG);
563     }
564
565     /**
566      * Returns true if the passed in image has a non-zero width and height.
567      */

568     private boolean hasPixels(Image image) {
569         return image != null &&
570             (image.getHeight(imageObserver) > 0) &&
571             (image.getWidth(imageObserver) > 0);
572     }
573
574     /**
575      * Returns the preferred span of the View used to display the alt text,
576      * or 0 if the view does not exist.
577      */

578     private float getPreferredSpanFromAltView(int axis) {
579         if (getImage() == null) {
580             View view = getAltView();
581
582             if (view != null) {
583                 return view.getPreferredSpan(axis);
584             }
585         }
586         return 0f;
587     }
588
589     private Icon makeIcon(final String JavaDoc gifFile) throws IOException {
590         /* Copy resource into a byte array. This is
591          * necessary because several browsers consider
592          * Class.getResource a security risk because it
593          * can be used to load additional classes.
594          * Class.getResourceAsStream just returns raw
595          * bytes, which we can convert to an image.
596          */

597     InputStream resource = HTMLEditorKit.getResourceAsStream(gifFile);
598
599         if (resource == null) {
600             System.err.println(ImageView JavaDoc.class.getName() + "/" +
601                                gifFile + " not found.");
602             return null;
603         }
604         BufferedInputStream in =
605             new BufferedInputStream(resource);
606         ByteArrayOutputStream out =
607             new ByteArrayOutputStream(1024);
608         byte[] buffer = new byte[1024];
609         int n;
610         while ((n = in.read(buffer)) > 0) {
611             out.write(buffer, 0, n);
612         }
613         in.close();
614         out.flush();
615
616         buffer = out.toByteArray();
617         if (buffer.length == 0) {
618             System.err.println("warning: " + gifFile +
619                                " is zero-length");
620             return null;
621         }
622         return new ImageIcon(buffer);
623     }
624
625     /**
626      * Request that this view be repainted.
627      * Assumes the view is still at its last-drawn location.
628      */

629     private void repaint(long delay) {
630         if (container != null && fBounds != null) {
631         container.repaint(delay, fBounds.x, fBounds.y, fBounds.width,
632                                fBounds.height);
633         }
634     }
635
636     private void loadDefaultIconsIfNecessary() {
637         try {
638             if (sPendingImageIcon == null)
639                 sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
640             if (sMissingImageIcon == null)
641                 sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
642     } catch(Exception JavaDoc x) {
643         System.err.println("ImageView: Couldn't load image icons");
644     }
645     }
646     
647     /**
648      * Convenience method for getting an integer attribute from the elements
649      * AttributeSet.
650      */

651     private int getIntAttr(HTML.Attribute JavaDoc name, int deflt) {
652         AttributeSet attr = getElement().getAttributes();
653         if (attr.isDefined(name)) { // does not check parents!
654
int i;
655         String JavaDoc val = (String JavaDoc)attr.getAttribute(name);
656         if (val == null) {
657             i = deflt;
658             }
659         else {
660             try{
661                 i = Math.max(0, Integer.parseInt(val));
662             }catch( NumberFormatException JavaDoc x ) {
663                 i = deflt;
664             }
665             }
666         return i;
667     } else
668         return deflt;
669     }
670
671     /**
672      * Makes sure the necessary properties and image is loaded.
673      */

674     private void sync() {
675         int s = state;
676         if ((s & RELOAD_IMAGE_FLAG) != 0) {
677             refreshImage();
678         }
679         s = state;
680         if ((s & RELOAD_FLAG) != 0) {
681             synchronized(this) {
682                 state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
683             }
684             setPropertiesFromAttributes();
685         }
686     }
687
688     /**
689      * Loads the image and updates the size accordingly. This should be
690      * invoked instead of invoking <code>loadImage</code> or
691      * <code>updateImageSize</code> directly.
692      */

693     private void refreshImage() {
694     synchronized(this) {
695             // clear out width/height/realoadimage flag and set loading flag
696
state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG |
697                      HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG |
698                                      RELOAD_IMAGE_FLAG);
699             image = null;
700             width = height = 0;
701         }
702
703         try {
704             // Load the image
705
loadImage();
706
707             // And update the size params
708
updateImageSize();
709         }
710         finally {
711             synchronized(this) {
712                 // Clear out state in case someone threw an exception.
713
state = (state | LOADING_FLAG) ^ LOADING_FLAG;
714             }
715         }
716     }
717
718     /**
719      * Loads the image from the URL <code>getImageURL</code>. This should
720      * only be invoked from <code>refreshImage</code>.
721      */

722     private void loadImage() {
723         URL src = getImageURL();
724         Image newImage = null;
725         if (src != null) {
726             Dictionary JavaDoc cache = (Dictionary JavaDoc)getDocument().
727                                     getProperty(IMAGE_CACHE_PROPERTY);
728             if (cache != null) {
729                 newImage = (Image)cache.get(src);
730             }
731             else {
732                 newImage = Toolkit.getDefaultToolkit().createImage(src);
733                 if (newImage != null && getLoadsSynchronously()) {
734                     // Force the image to be loaded by using an ImageIcon.
735
ImageIcon ii = new ImageIcon();
736                     ii.setImage(newImage);
737                 }
738             }
739         }
740         image = newImage;
741     }
742
743     /**
744      * Recreates and reloads the image. This should
745      * only be invoked from <code>refreshImage</code>.
746      */

747     private void updateImageSize() {
748     int newWidth = 0;
749     int newHeight = 0;
750         int newState = 0;
751         Image newImage = getImage();
752
753         if (newImage != null) {
754             Element elem = getElement();
755         AttributeSet attr = elem.getAttributes();
756
757             // Get the width/height and set the state ivar before calling
758
// anything that might cause the image to be loaded, and thus the
759
// ImageHandler to be called.
760
newWidth = getIntAttr(HTML.Attribute.WIDTH, -1);
761             if (newWidth > 0) {
762                 newState |= WIDTH_FLAG;
763             }
764         newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1);
765             if (newHeight > 0) {
766                 newState |= HEIGHT_FLAG;
767             }
768
769             if (newWidth <= 0) {
770         newWidth = newImage.getWidth(imageObserver);
771                 if (newWidth <= 0) {
772                     newWidth = DEFAULT_WIDTH;
773                 }
774             }
775
776             if (newHeight <= 0) {
777         newHeight = newImage.getHeight(imageObserver);
778                 if (newHeight <= 0) {
779                     newHeight = DEFAULT_HEIGHT;
780                 }
781             }
782
783         // Make sure the image starts loading:
784
if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
785                 Toolkit.getDefaultToolkit().prepareImage(newImage, newWidth,
786                                                          newHeight,
787                                                          imageObserver);
788             }
789             else {
790                 Toolkit.getDefaultToolkit().prepareImage(newImage, -1, -1,
791                                                          imageObserver);
792             }
793
794             boolean createText = false;
795         synchronized(this) {
796                 // If imageloading failed, other thread may have called
797
// ImageLoader which will null out image, hence we check
798
// for it.
799
if (image != null) {
800                     if ((newState & WIDTH_FLAG) == WIDTH_FLAG || width == 0) {
801                         width = newWidth;
802                     }
803                     if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG ||
804                         height == 0) {
805                         height = newHeight;
806                     }
807                 }
808                 else {
809                     createText = true;
810                     if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
811                         width = newWidth;
812                     }
813                     if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
814                         height = newHeight;
815                     }
816                 }
817                 state = state | newState;
818                 state = (state | LOADING_FLAG) ^ LOADING_FLAG;
819             }
820             if (createText) {
821                 // Only reset if this thread determined image is null
822
updateAltTextView();
823         }
824     }
825         else {
826             width = height = DEFAULT_HEIGHT;
827             updateAltTextView();
828         }
829     }
830
831     /**
832      * Updates the view representing the alt text.
833      */

834     private void updateAltTextView() {
835         String JavaDoc text = getAltText();
836
837         if (text != null) {
838             ImageLabelView newView;
839
840             newView = new ImageLabelView(getElement(), text);
841             synchronized(this) {
842                 altView = newView;
843             }
844         }
845     }
846
847     /**
848      * Returns the view to use for alternate text. This may be null.
849      */

850     private View getAltView() {
851         View view;
852
853         synchronized(this) {
854             view = altView;
855         }
856         if (view != null && view.getParent() == null) {
857             view.setParent(getParent());
858         }
859         return view;
860     }
861
862     /**
863      * Invokes <code>preferenceChanged</code> on the event displatching
864      * thread.
865      */

866     private void safePreferenceChanged() {
867         if (SwingUtilities.isEventDispatchThread()) {
868         Document doc = getDocument();
869         if (doc instanceof AbstractDocument) {
870         ((AbstractDocument)doc).readLock();
871         }
872             preferenceChanged(null, true, true);
873         if (doc instanceof AbstractDocument) {
874         ((AbstractDocument)doc).readUnlock();
875         }
876         }
877         else {
878             SwingUtilities.invokeLater(new Runnable JavaDoc() {
879                     public void run() {
880                         safePreferenceChanged();
881                     }
882                 });
883         }
884     }
885
886     /**
887      * ImageHandler implements the ImageObserver to correctly update the
888      * display as new parts of the image become available.
889      */

890     private class ImageHandler implements ImageObserver JavaDoc {
891         // This can come on any thread. If we are in the process of reloading
892
// the image and determining our state (loading == true) we don't fire
893
// preference changed, or repaint, we just reset the fWidth/fHeight as
894
// necessary and return. This is ok as we know when loading finishes
895
// it will pick up the new height/width, if necessary.
896
public boolean imageUpdate(Image img, int flags, int x, int y,
897                                    int newWidth, int newHeight ) {
898             if (image == null || image != img || getParent() == null) {
899                 return false;
900             }
901             
902             // Bail out if there was an error:
903
if ((flags & (ABORT|ERROR)) != 0) {
904                 repaint(0);
905                 synchronized(ImageView.this) {
906                     if (image == img) {
907                         // Be sure image hasn't changed since we don't
908
// initialy synchronize
909
image = null;
910                         if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
911                             width = DEFAULT_WIDTH;
912                         }
913                         if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
914                             height = DEFAULT_HEIGHT;
915                         }
916                     }
917                     if ((state & LOADING_FLAG) == LOADING_FLAG) {
918                         // No need to resize or repaint, still in the process
919
// of loading.
920
return false;
921                     }
922                 }
923                 updateAltTextView();
924                 safePreferenceChanged();
925                 return false;
926             }
927
928             // Resize image if necessary:
929
short changed = 0;
930             if ((flags & ImageObserver.HEIGHT) != 0 && !getElement().
931                   getAttributes().isDefined(HTML.Attribute.HEIGHT)) {
932                 changed |= 1;
933             }
934             if ((flags & ImageObserver.WIDTH) != 0 && !getElement().
935                   getAttributes().isDefined(HTML.Attribute.WIDTH)) {
936         changed |= 2;
937             }
938
939             synchronized(ImageView.this) {
940                 if (image != img) {
941                     return false;
942                 }
943                 if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) {
944                     width = newWidth;
945                 }
946                 if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) {
947                     height = newHeight;
948                 }
949                 if ((state & LOADING_FLAG) == LOADING_FLAG) {
950                     // No need to resize or repaint, still in the process of
951
// loading.
952
return true;
953                 }
954             }
955             if (changed != 0) {
956                 // May need to resize myself, asynchronously:
957
safePreferenceChanged();
958                 return true;
959             }
960
961             // Repaint when done or when new pixels arrive:
962
if ((flags & (FRAMEBITS|ALLBITS)) != 0) {
963                 repaint(0);
964             }
965             else if ((flags & SOMEBITS) != 0 && sIsInc) {
966                 repaint(sIncRate);
967             }
968             return ((flags & ALLBITS) == 0);
969         }
970     }
971
972
973     /**
974      * ImageLabelView is used if the image can't be loaded, and
975      * the attribute specified an alt attribute. It overriden a handle of
976      * methods as the text is hardcoded and does not come from the document.
977      */

978     private class ImageLabelView extends InlineView JavaDoc {
979         private Segment segment;
980         private Color fg;
981
982         ImageLabelView(Element e, String JavaDoc text) {
983             super(e);
984             reset(text);
985         }
986
987         public void reset(String JavaDoc text) {
988             segment = new Segment(text.toCharArray(), 0, text.length());
989         }
990
991         public void paint(Graphics g, Shape a) {
992             // Don't use supers paint, otherwise selection will be wrong
993
// as our start/end offsets are fake.
994
GlyphPainter painter = getGlyphPainter();
995
996             if (painter != null) {
997                 g.setColor(getForeground());
998                 painter.paint(this, g, a, getStartOffset(), getEndOffset());
999             }
1000        }
1001
1002        public Segment getText(int p0, int p1) {
1003            if (p0 < 0 || p1 > segment.array.length) {
1004                throw new RuntimeException JavaDoc("ImageLabelView: Stale view");
1005            }
1006            segment.offset = p0;
1007            segment.count = p1 - p0;
1008            return segment;
1009        }
1010
1011        public int getStartOffset() {
1012            return 0;
1013        }
1014    
1015        public int getEndOffset() {
1016            return segment.array.length;
1017        }
1018
1019        public View breakView(int axis, int p0, float pos, float len) {
1020            // Don't allow a break
1021
return this;
1022        }
1023
1024        public Color getForeground() {
1025            View parent;
1026            if (fg == null && (parent = getParent()) != null) {
1027                Document doc = getDocument();
1028                AttributeSet attr = parent.getAttributes();
1029
1030                if (attr != null && (doc instanceof StyledDocument)) {
1031                    fg = ((StyledDocument)doc).getForeground(attr);
1032                }
1033            }
1034            return fg;
1035        }
1036    }
1037}
1038
Popular Tags