History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/html/ImageView.java (1 of 18352 files). Show all

Unified · Split

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