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+ */
25+package javax.swing.text.html;
26+
27+import java.awt.*;
28+import java.awt.image.ImageObserver;
29+import java.net.*;
30+import java.util.Dictionary;
31+import javax.swing.*;
32+import javax.swing.text.*;
33+import 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+ */
48+public 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+}