OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / html / ImageView.java
1 package javax.swing.text.html;
2
3 import gnu.javax.swing.text.html.ImageViewIconFactory;
4 import gnu.javax.swing.text.html.css.Length;
5
6 import java.awt.Graphics;
7 import java.awt.Image;
8 import java.awt.MediaTracker;
9 import java.awt.Rectangle;
10 import java.awt.Shape;
11 import java.awt.Toolkit;
12 import java.awt.image.ImageObserver;
13 import java.net.MalformedURLException;
14 import java.net.URL;
15
16 import javax.swing.Icon;
17 import javax.swing.SwingUtilities;
18 import javax.swing.text.AbstractDocument;
19 import javax.swing.text.AttributeSet;
20 import javax.swing.text.BadLocationException;
21 import javax.swing.text.Document;
22 import javax.swing.text.Element;
23 import javax.swing.text.View;
24 import javax.swing.text.Position.Bias;
25 import javax.swing.text.html.HTML.Attribute;
26
27 /**
28  * A view, representing a single image, represented by the HTML IMG tag.
29  * 
30  * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) 
31  */
32 public class ImageView extends View
33 {
34   /**
35    * Tracks image loading state and performs the necessary layout updates.
36    */
37   class Observer
38     implements ImageObserver
39   {
40
41     public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height)
42     {
43       boolean widthChanged = false;
44       if ((flags & ImageObserver.WIDTH) != 0 && spans[X_AXIS] == null)
45         widthChanged = true;
46       boolean heightChanged = false;
47       if ((flags & ImageObserver.HEIGHT) != 0 && spans[Y_AXIS] == null)
48         heightChanged = true;
49       if (widthChanged || heightChanged)
50         safePreferenceChanged(ImageView.this, widthChanged, heightChanged);
51       boolean ret = (flags & ALLBITS) != 0;
52       return ret;
53     }
54     
55   }
56
57   /**
58    * True if the image loads synchronuosly (on demand). By default, the image
59    * loads asynchronuosly.
60    */
61   boolean loadOnDemand;
62
63   /**
64    * The image icon, wrapping the image,
65    */
66   Image image;
67  
68   /**
69    * The image state.
70    */
71   byte imageState = MediaTracker.LOADING;
72
73   /**
74    * True when the image needs re-loading, false otherwise.
75    */
76   private boolean reloadImage;
77
78   /**
79    * True when the image properties need re-loading, false otherwise.
80    */
81   private boolean reloadProperties;
82
83   /**
84    * True when the width is set as CSS/HTML attribute.
85    */
86   private boolean haveWidth;
87
88   /**
89    * True when the height is set as CSS/HTML attribute.
90    */
91   private boolean haveHeight;
92
93   /**
94    * True when the image is currently loading.
95    */
96   private boolean loading;
97
98   /**
99    * The current width of the image.
100    */
101   private int width;
102
103   /**
104    * The current height of the image.
105    */
106   private int height;
107
108   /**
109    * Our ImageObserver for tracking the loading state.
110    */
111   private ImageObserver observer;
112
113   /**
114    * The CSS width and height.
115    *
116    * Package private to avoid synthetic accessor methods.
117    */
118   Length[] spans;
119
120   /**
121    * The cached attributes.
122    */
123   private AttributeSet attributes;
124
125   /**
126    * Creates the image view that represents the given element.
127    * 
128    * @param element the element, represented by this image view.
129    */
130   public ImageView(Element element)
131   {
132     super(element);
133     spans = new Length[2];
134     observer = new Observer();
135     reloadProperties = true;
136     reloadImage = true;
137     loadOnDemand = false;
138   }
139  
140   /**
141    * Load or reload the image. This method initiates the image reloading. After
142    * the image is ready, the repaint event will be scheduled. The current image,
143    * if it already exists, will be discarded.
144    */
145   private void reloadImage()
146   {
147     loading = true;
148     reloadImage = false;
149     haveWidth = false;
150     haveHeight = false;
151     image = null;
152     width = 0;
153     height = 0;
154     try
155       {
156         loadImage();
157         updateSize();
158       }
159     finally
160       {
161         loading = false;
162       }
163   }
164   
165   /**
166    * Get the image alignment. This method works handling standart alignment
167    * attributes in the HTML IMG tag (align = top bottom middle left right).
168    * Depending from the parameter, either horizontal or vertical alingment
169    * information is returned.
170    * 
171    * @param axis -
172    *          either X_AXIS or Y_AXIS
173    */
174   public float getAlignment(int axis)
175   {
176     AttributeSet attrs = getAttributes();
177     Object al = attrs.getAttribute(Attribute.ALIGN);
178     
179     // Default is top left aligned.
180     if (al == null)
181       return 0.0f;
182
183     String align = al.toString();
184
185     if (axis == View.X_AXIS)
186       {
187         if (align.equals("middle"))
188           return 0.5f;
189         else if (align.equals("left"))
190           return 0.0f;
191         else if (align.equals("right"))
192           return 1.0f;
193         else
194           return 0.0f;
195       }
196     else if (axis == View.Y_AXIS)
197       {
198         if (align.equals("middle"))
199           return 0.5f;
200         else if (align.equals("top"))
201           return 0.0f;
202         else if (align.equals("bottom"))
203           return 1.0f;
204         else
205           return 0.0f;
206       }
207     else
208       throw new IllegalArgumentException("axis " + axis);
209   }
210   
211   /**
212    * Get the text that should be shown as the image replacement and also as the
213    * image tool tip text. The method returns the value of the attribute, having
214    * the name {@link Attribute#ALT}. If there is no such attribute, the image
215    * name from the url is returned. If the URL is not available, the empty
216    * string is returned.
217    */
218   public String getAltText()
219   {
220     Object rt = getAttributes().getAttribute(Attribute.ALT);
221     if (rt != null)
222       return rt.toString();
223     else
224       {
225         URL u = getImageURL();
226         if (u == null)
227           return "";
228         else
229           return u.getFile();
230       }
231   }
232   
233   /**
234    * Returns the combination of the document and the style sheet attributes.
235    */
236   public AttributeSet getAttributes()
237   {
238     if (attributes == null)
239       attributes = getStyleSheet().getViewAttributes(this);
240     return attributes;
241   }
242   
243   /**
244    * Get the image to render. May return null if the image is not yet loaded.
245    */
246   public Image getImage()
247   {
248     updateState();
249     return image;
250   }
251   
252   /**
253    * Get the URL location of the image to render. If this method returns null,
254    * the "no image" icon is rendered instead. By defaul, url must be present as
255    * the "src" property of the IMG tag. If it is missing, null is returned and
256    * the "no image" icon is rendered.
257    * 
258    * @return the URL location of the image to render.
259    */
260   public URL getImageURL()
261   {
262     Element el = getElement();
263     String src = (String) el.getAttributes().getAttribute(Attribute.SRC);
264     URL url = null;
265     if (src != null)
266       {
267         URL base = ((HTMLDocument) getDocument()).getBase();
268         try
269           {
270             url = new URL(base, src);
271           }
272         catch (MalformedURLException ex)
273           {
274             // Return null.
275           }
276       }
277     return url;
278   }
279
280   /**
281    * Get the icon that should be displayed while the image is loading and hence
282    * not yet available.
283    * 
284    * @return an icon, showing a non broken sheet of paper with image.
285    */
286   public Icon getLoadingImageIcon()
287   {
288     return ImageViewIconFactory.getLoadingImageIcon();
289   }
290   
291   /**
292    * Get the image loading strategy.
293    * 
294    * @return false (default) if the image is loaded when the view is
295    *         constructed, true if the image is only loaded on demand when
296    *         rendering.
297    */
298   public boolean getLoadsSynchronously()
299   {
300     return loadOnDemand;
301   }
302
303   /**
304    * Get the icon that should be displayed when the image is not available.
305    * 
306    * @return an icon, showing a broken sheet of paper with image.
307    */
308   public Icon getNoImageIcon()
309   {
310     return ImageViewIconFactory.getNoImageIcon();
311   }
312   
313   /**
314    * Get the preferred span of the image along the axis. The image size is first
315    * requested to the attributes {@link Attribute#WIDTH} and
316    * {@link Attribute#HEIGHT}. If they are missing, and the image is already
317    * loaded, the image size is returned. If there are no attributes, and the
318    * image is not loaded, zero is returned.
319    * 
320    * @param axis -
321    *          either X_AXIS or Y_AXIS
322    * @return either width of height of the image, depending on the axis.
323    */
324   public float getPreferredSpan(int axis)
325   {
326     AttributeSet attrs = getAttributes();
327     
328     Image image = getImage();
329
330     if (axis == View.X_AXIS)
331       {
332         if (spans[axis] != null)
333           return spans[axis].getValue();
334         else if (image != null)
335           return image.getWidth(getContainer());
336         else
337           return getNoImageIcon().getIconWidth();
338       }
339     else if (axis == View.Y_AXIS)
340       {
341         if (spans[axis] != null)
342           return spans[axis].getValue();
343         else if (image != null)
344           return image.getHeight(getContainer());
345         else
346           return getNoImageIcon().getIconHeight();
347       }
348     else
349       throw new IllegalArgumentException("axis " + axis);
350   }
351   
352   /**
353    * Get the associated style sheet from the document.
354    * 
355    * @return the associated style sheet.
356    */
357   protected StyleSheet getStyleSheet()
358   {
359     HTMLDocument doc = (HTMLDocument) getDocument();
360     return doc.getStyleSheet();
361   }
362
363   /**
364    * Get the tool tip text. This is overridden to return the value of the
365    * {@link #getAltText()}. The parameters are ignored.
366    * 
367    * @return that is returned by getAltText().
368    */
369   public String getToolTipText(float x, float y, Shape shape)
370   {
371     return getAltText();
372   }
373
374   /**
375    * Paints the image or one of the two image state icons. The image is resized
376    * to the shape bounds. If there is no image available, the alternative text
377    * is displayed besides the image state icon.
378    * 
379    * @param g
380    *          the Graphics, used for painting.
381    * @param bounds
382    *          the bounds of the region where the image or replacing icon must be
383    *          painted.
384    */
385   public void paint(Graphics g, Shape bounds)
386   {
387     updateState();
388     Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds
389                                               : bounds.getBounds();
390     Image image = getImage();
391     if (image != null)
392       {
393         g.drawImage(image, r.x, r.y, r.width, r.height, observer);
394       }
395     else
396       {
397         Icon icon = getNoImageIcon();
398         if (icon != null)
399           icon.paintIcon(getContainer(), g, r.x, r.y);
400       }
401   }
402
403   /**
404    * Set if the image should be loaded only when needed (synchronuosly). By
405    * default, the image loads asynchronuosly. If the image is not yet ready, the
406    * icon, returned by the {@link #getLoadingImageIcon()}, is displayed.
407    */
408   public void setLoadsSynchronously(boolean load_on_demand)
409   {
410     loadOnDemand = load_on_demand;
411   }
412  
413   /**
414    * Update all cached properties from the attribute set, returned by the
415    * {@link #getAttributes}.
416    */
417   protected void setPropertiesFromAttributes()
418   {
419     AttributeSet atts = getAttributes();
420     StyleSheet ss = getStyleSheet();
421     float emBase = ss.getEMBase(atts);
422     float exBase = ss.getEXBase(atts);
423     spans[X_AXIS] = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
424     if (spans[X_AXIS] != null)
425       {
426         spans[X_AXIS].setFontBases(emBase, exBase);
427       }
428     spans[Y_AXIS] = (Length) atts.getAttribute(CSS.Attribute.HEIGHT);
429     if (spans[Y_AXIS] != null)
430       {
431         spans[Y_AXIS].setFontBases(emBase, exBase);
432       }
433   }
434   
435   /**
436    * Maps the picture co-ordinates into the image position in the model. As the
437    * image is not divideable, this is currently implemented always to return the
438    * start offset.
439    */
440   public int viewToModel(float x, float y, Shape shape, Bias[] bias)
441   {
442     return getStartOffset();
443   }
444   
445   /**
446    * This is currently implemented always to return the area of the image view,
447    * as the image is not divideable by character positions.
448    * 
449    * @param pos character position
450    * @param area of the image view
451    * @param bias bias
452    * 
453    * @return the shape, where the given character position should be mapped.
454    */
455   public Shape modelToView(int pos, Shape area, Bias bias)
456       throws BadLocationException
457   {
458     return area;
459   }
460   
461   /**
462    * Starts loading the image asynchronuosly. If the image must be loaded
463    * synchronuosly instead, the {@link #setLoadsSynchronously} must be
464    * called before calling this method. The passed parameters are not used.
465    */
466   public void setSize(float width, float height)
467   {
468     updateState();
469     // TODO: Implement this when we have an alt view for the alt=... attribute.
470   }  
471
472   /**
473    * This makes sure that the image and properties have been loaded.
474    */
475   private void updateState()
476   {
477     if (reloadImage)
478       reloadImage();
479     if (reloadProperties)
480       setPropertiesFromAttributes();
481   }
482
483   /**
484    * Actually loads the image.
485    */
486   private void loadImage()
487   {
488     URL src = getImageURL();
489     Image newImage = null;
490     if (src != null)
491       {
492         // Call getImage(URL) to allow the toolkit caching of that image URL.
493         Toolkit tk = Toolkit.getDefaultToolkit();
494         newImage = tk.getImage(src);
495         tk.prepareImage(newImage, -1, -1, observer);
496         if (newImage != null && getLoadsSynchronously())
497           {
498             // Load image synchronously.
499             MediaTracker tracker = new MediaTracker(getContainer());
500             tracker.addImage(newImage, 0);
501             try
502               {
503                 tracker.waitForID(0);
504               }
505             catch (InterruptedException ex)
506               {
507                 Thread.interrupted();
508               }
509             
510           }
511       }
512     image = newImage;
513   }
514
515   /**
516    * Updates the size parameters of the image.
517    */
518   private void updateSize()
519   {
520     int newW = 0;
521     int newH = 0;
522     Image newIm = getImage();
523     if (newIm != null)
524       {
525         AttributeSet atts = getAttributes();
526         // Fetch width.
527         Length l = spans[X_AXIS];
528         if (l != null)
529           {
530             newW = (int) l.getValue();
531             haveWidth = true;
532           }
533         else
534           {
535             newW = newIm.getWidth(observer);
536           }
537         // Fetch height.
538         l = spans[Y_AXIS];
539         if (l != null)
540           {
541             newH = (int) l.getValue();
542             haveHeight = true;
543           }
544         else
545           {
546             newW = newIm.getWidth(observer);
547           }
548         // Go and trigger loading.
549         Toolkit tk = Toolkit.getDefaultToolkit();
550         if (haveWidth || haveHeight)
551           tk.prepareImage(newIm, width, height, observer);
552         else
553           tk.prepareImage(newIm, -1, -1, observer);
554       }
555   }
556
557   /**
558    * Calls preferenceChanged from the event dispatch thread and within
559    * a read lock to protect us from threading issues.
560    *
561    * @param v the view
562    * @param width true when the width changed
563    * @param height true when the height changed
564    */
565   void safePreferenceChanged(final View v, final boolean width,
566                              final boolean height)
567   {
568     if (SwingUtilities.isEventDispatchThread())
569       {
570         Document doc = getDocument();
571         if (doc instanceof AbstractDocument)
572           ((AbstractDocument) doc).readLock();
573         try
574           {
575             preferenceChanged(v, width, height);
576           }
577         finally
578           {
579             if (doc instanceof AbstractDocument)
580               ((AbstractDocument) doc).readUnlock();
581           }
582       }
583     else
584       {
585         SwingUtilities.invokeLater(new Runnable()
586         {
587           public void run()
588           {
589             safePreferenceChanged(v, width, height);
590           }
591         });
592       }
593   }
594 }