home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 November / Chip_1998-11_cd.bin / tema / Cafe / jfc.bin / ImageView.java < prev    next >
Text File  |  1998-02-26  |  24KB  |  733 lines

  1. /*
  2.  * @(#)ImageView.java    1.14 98/02/05
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text.html;
  21.  
  22. import java.awt.*;
  23. import java.awt.event.*;
  24. import java.awt.image.ImageObserver;
  25. import java.io.*;
  26. import java.net.*;
  27. import java.util.Dictionary;
  28. import com.sun.java.swing.*;
  29. import com.sun.java.swing.text.*;
  30. import com.sun.java.swing.event.*;
  31.  
  32. /**
  33.  * View of an Image, intended to support the HTML <IMG> tag.
  34.  * Supports scaling via the HEIGHT and WIDTH parameters.
  35.  *
  36.  * @author Jens Alfke (based on IconView by Timothy Prinzing)
  37.  * @see IconView
  38.  */
  39. class ImageView extends View implements ImageObserver,
  40.                     MouseListener, MouseMotionListener,
  41.                     Runnable, HTMLDefs {
  42.  
  43.     // --- Attribute Values ------------------------------------------
  44.     
  45.     public static final String
  46.         TOP = "top",
  47.         TEXTTOP = "texttop",
  48.         MIDDLE = "middle",
  49.         ABSMIDDLE = "absmiddle",
  50.         CENTER = "center",
  51.         BOTTOM = "bottom";
  52.     
  53.  
  54.     // --- Construction ----------------------------------------------
  55.  
  56.     /** Add an ImageData object as an attribute to the set.
  57.         This is used to tag an IMG Element with data that caches
  58.         attributes such as the Image object and dimensions.
  59.         This happens during parsing, before any views are created,
  60.         because the Element once created is immutable.
  61.         @see com.sun.java.swing.text.html.HTMLDocument#imgAction */
  62.     static void addImageDataAttribute( MutableAttributeSet attr ) {
  63.     ImageInfo img = new ImageInfo();
  64.     attr.addAttribute(ImageInfo.kAttributeName,img);
  65.     }
  66.  
  67.     /**
  68.      * Creates a new view that represents an IMG element.
  69.      *
  70.      * @param elem the element to create a view for
  71.      */
  72.     public ImageView(Element elem) {
  73.     super(elem);
  74.     // Most data is stored in an object attached to the Element:
  75.     fIMG = (ImageInfo) elem.getAttributes().getAttribute(ImageInfo.kAttributeName);
  76.     fIMG.initialize(this,elem);
  77.     fHeight = fIMG.fHeight;
  78.     fWidth  = fIMG.fWidth;
  79.     }
  80.     
  81.     /**
  82.      * Establishes the parent view for this view.
  83.      * Seize this moment to cache the AWT Container I'm in.
  84.      */
  85.     public void setParent(View parent) {
  86.     super.setParent(parent);
  87.     fContainer = parent!=null ?getContainer() :null;
  88.     if( parent==null && fComponent!=null ) {
  89.         fComponent.getParent().remove(fComponent);
  90.         fComponent = null;
  91.     }
  92.     }
  93.  
  94.     /** My attributes may have changed. */
  95.     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  96.     //$ Beware: This method has never been tested, since ParagraphView does
  97.     //  not propagate changedUpdates to its child Views (bug?) --jpa 1/7/98
  98. if(DEBUG) System.out.println("ImageView: changedUpdate begin...");
  99.         super.changedUpdate(e,a,f);
  100.         float align = fIMG.getVerticalAlignment();
  101.         
  102.         fIMG.changedUpdate(this,getElement());
  103.         
  104.         boolean hChanged=false, wChanged=false;
  105.         if( fHeight!=fIMG.fHeight ) {
  106.             fHeight = fIMG.fHeight;
  107.             hChanged = true;
  108.         }
  109.         if( fWidth!=fIMG.fWidth ) {
  110.             fWidth = fIMG.fWidth;
  111.             wChanged = true;
  112.         }
  113.         if( hChanged || wChanged || fIMG.getVerticalAlignment()!=align ) {
  114.             if(DEBUG) System.out.println("ImageView: calling preferenceChanged");
  115.             getParent().preferenceChanged(this,hChanged,wChanged);
  116.         }
  117. if(DEBUG) System.out.println("ImageView: changedUpdate end; valign="+fIMG.getVerticalAlignment());
  118.     }
  119.  
  120.  
  121.     // --- Painting --------------------------------------------------------
  122.  
  123.     /**
  124.      * Paints the image.
  125.      *
  126.      * @param g the rendering surface to use
  127.      * @param a the allocated region to render into
  128.      * @see View#paint
  129.      */
  130.     public void paint(Graphics g, Shape a) {
  131.     Color oldColor = g.getColor();
  132.     fBounds = a.getBounds();
  133.         int border = fIMG.getBorder();
  134.     int x = fBounds.x + border + fIMG.getSpace(X_AXIS);
  135.     int y = fBounds.y + border + fIMG.getSpace(Y_AXIS);
  136.     int width = fIMG.fWidth;
  137.     int height = fIMG.fHeight;
  138.     int sel = getSelectionState();
  139.     
  140.     // Make sure my Component is in the right place:
  141.     if( fComponent == null ) {
  142.         fComponent = new Component() { };
  143.         fComponent.addMouseListener(this);
  144.         fComponent.addMouseMotionListener(this);
  145.         fComponent.setCursor(Cursor.getDefaultCursor());    // use arrow cursor
  146.         fContainer.add(fComponent);
  147.     }
  148.     fComponent.setBounds(x,y,width,height);
  149.     
  150.     // If no pixels yet, draw gray outline and icon:
  151.     if( ! fIMG.hasPixels(this) ) {
  152.         g.setColor(Color.lightGray);
  153.         g.drawRect(x,y,width-1,height-1);
  154.         g.setColor(oldColor);
  155.         loadIcons();
  156.         Icon icon = fIMG.fImage==null ?sMissingImageIcon :sPendingImageIcon;
  157.         if( icon != null )
  158.             icon.paintIcon(getContainer(), g, x, y);
  159.     }
  160.             
  161.     // Draw image:
  162.     if( fIMG.fImage != null ) {
  163.         //  Use Xor mode when selected/highlighted.
  164.         //! Could darken image instead, but it would be more expensive.
  165.         if( sel > 0 )
  166.             g.setXORMode(Color.white);
  167.         g.drawImage(fIMG.fImage,x, y,
  168.                 width,height,this);
  169.         if( sel > 0 )
  170.             g.setPaintMode();
  171.     }
  172.     
  173.     // If selected exactly, we need a black border & grow-box:
  174.     Color bc = fIMG.getBorderColor();
  175.     if( sel == 2 ) {
  176.         // Make sure there's room for a border:
  177.         int delta = 2-border;
  178.         if( delta > 0 ) {
  179.             x += delta;
  180.             y += delta;
  181.             width -= delta<<1;
  182.             height -= delta<<1;
  183.             border = 2;
  184.         }
  185.         bc = null;
  186.         g.setColor(Color.black);
  187.         // Draw grow box:
  188.         g.fillRect(x+width-5,y+height-5,5,5);
  189.     }
  190.  
  191.     // Draw border:
  192.     if( border > 0 ) {
  193.         if( bc != null ) g.setColor(bc);
  194.         // Draw a thick rectangle:
  195.         for( int i=1; i<=border; i++ )
  196.             g.drawRect(x-i, y-i, width-1+i+i, height-1+i+i);
  197.         g.setColor(oldColor);
  198.     }
  199.     }
  200.  
  201.     /** Request that this view be repainted.
  202.         Assumes the view is still at its last-drawn location. */
  203.     protected void repaint( long delay ) {
  204.         if( fContainer != null && fBounds!=null ) {
  205.         fContainer.repaint(delay,
  206.                  fBounds.x,fBounds.y,fBounds.width,fBounds.height);
  207.         }
  208.     }
  209.     
  210.     /** Determines whether the image is selected, and if it's the only thing selected.
  211.         @return  0 if not selected, 1 if selected, 2 if exclusively selected. */
  212.     protected int getSelectionState( ) {
  213.         int p0 = fIMG.fElement.getStartOffset();
  214.         int p1 = fIMG.fElement.getEndOffset();
  215.         JTextComponent textComp = (JTextComponent)fContainer;
  216.         Highlighter highlighter = textComp.getHighlighter();
  217.         Highlighter.Highlight[] hi = highlighter.getHighlights();
  218.         for( int i=hi.length-1; i>=0; i-- ) {
  219.             Highlighter.Highlight h =  hi[i];
  220.             int start = h.getStartOffset();
  221.             int end   = h.getEndOffset();
  222.             if( start<=p0 && end>=p1 ) {
  223.                 if( start==p0 && end==p1 )
  224.                     return 2;
  225.                 else
  226.                     return 1;
  227.             }
  228.         }
  229.         return 0;
  230.     }
  231.     
  232.     /** Returns the text editor's highlight color. */
  233.     protected Color getHighlightColor( ) {
  234.         JTextComponent textComp = (JTextComponent)fContainer;
  235.         return textComp.getSelectionColor();
  236.     }
  237.  
  238.     // --- Progressive display ---------------------------------------------
  239.     
  240.     public boolean imageUpdate( Image img, int flags, int x, int y,
  241.                     int width, int height ) {
  242.         
  243.         if( fIMG.fImage==null )
  244.             return false;
  245.             
  246.         // Bail out if there was an error:
  247.         if( (flags & (ABORT|ERROR)) != 0 ) {
  248.             fIMG.fImage = null;
  249.             repaint(0);
  250.             return false;
  251.         }
  252.         
  253.         // Resize image if necessary:
  254.         if( (flags & ImageObserver.HEIGHT) != 0 )
  255.             if( ! getElement().getAttributes().isDefined(HTMLDefs.HEIGHT) )
  256.                 fIMG.fHeight = height;
  257.         if( (flags & ImageObserver.WIDTH) != 0 )
  258.             if( ! getElement().getAttributes().isDefined(HTMLDefs.WIDTH) )
  259.                 fIMG.fWidth = width;
  260.         if( fHeight != fIMG.fHeight || fWidth != fIMG.fWidth ) {
  261.             // May need to resize myself, asynchronously:
  262.             fHeight = fIMG.fHeight;
  263.             fWidth  = fIMG.fWidth;
  264.             if( DEBUG ) System.out.println("ImageView: resized to "+fWidth+"x"+fHeight);
  265.         SwingUtilities.invokeLater(this);    // call run() later
  266.         return true;
  267.         }
  268.     
  269.     // Repaint when done or when new pixels arrive:
  270.     if( (flags & (FRAMEBITS|ALLBITS)) != 0 )
  271.         repaint(0);
  272.     else if( (flags & SOMEBITS) != 0 )
  273.         if( sIsInc )
  274.             repaint(sIncRate);
  275.         
  276.         return true;
  277.     }
  278.         
  279.     public void run( ) {
  280. if(DEBUG)System.out.println("ImageView: Called preferenceChanged");
  281.         preferenceChanged(this,true,true);
  282.     }
  283.     
  284.     /**
  285.      * Static properties for incremental drawing.
  286.      * Swiped from Component.java
  287.      * @see #imageUpdate
  288.      */
  289.     private static boolean sIsInc;
  290.     private static int sIncRate;
  291.     static {
  292.     String s;
  293.  
  294.     s = System.getProperty("awt.image.incrementaldraw");
  295.     sIsInc = (s == null || s.equals("true"));
  296.  
  297.     s = System.getProperty("awt.image.redrawrate");
  298.     sIncRate = (s != null) ? Integer.parseInt(s) : 100;
  299.     }
  300.  
  301.     // --- Layout ----------------------------------------------------------
  302.  
  303.     /**
  304.      * Determines the preferred span for this view along an
  305.      * axis.
  306.      *
  307.      * @param axis may be either X_AXIS or Y_AXIS
  308.      * @returns  the span the view would like to be rendered into.
  309.      *           Typically the view is told to render into the span
  310.      *           that is returned, although there is no guarantee.  
  311.      *           The parent may choose to resize or break the view.
  312.      */
  313.     public float getPreferredSpan(int axis) {
  314. //if(DEBUG)System.out.println("ImageView: getPreferredSpan");
  315.         int extra = 2*(fIMG.getBorder()+fIMG.getSpace(axis));
  316.     switch (axis) {
  317.     case View.X_AXIS:
  318.         return fIMG.fWidth+extra;
  319.     case View.Y_AXIS:
  320.         return fIMG.fHeight+extra;
  321.     default:
  322.         throw new IllegalArgumentException("Invalid axis: " + axis);
  323.     }
  324.     }
  325.  
  326.     /**
  327.      * Determines the desired alignment for this view along an
  328.      * axis.  This is implemented to give the alignment to the
  329.      * bottom of the icon along the y axis, and the default
  330.      * along the x axis.
  331.      *
  332.      * @param axis may be either X_AXIS or Y_AXIS
  333.      * @returns the desired alignment.  This should be a value
  334.      *   between 0.0 and 1.0 where 0 indicates alignment at the
  335.      *   origin and 1.0 indicates alignment to the full span
  336.      *   away from the origin.  An alignment of 0.5 would be the
  337.      *   center of the view.
  338.      */
  339.     public float getAlignment(int axis) {
  340.     switch (axis) {
  341.     case View.Y_AXIS:
  342.         return fIMG.getVerticalAlignment();
  343.     default:
  344.         return super.getAlignment(axis);
  345.     }
  346.     }
  347.  
  348.     /**
  349.      * Provides a mapping from the document model coordinate space
  350.      * to the coordinate space of the view mapped to it.
  351.      *
  352.      * @param pos the position to convert
  353.      * @param a the allocated region to render into
  354.      * @return the bounding box of the given position
  355.      * @exception BadLocationException  if the given position does not represent a
  356.      *   valid location in the associated document
  357.      * @see View#modelToView
  358.      */
  359.     public Shape modelToView(int pos, Shape a) throws BadLocationException {
  360.     int p0 = getStartOffset();
  361.     int p1 = getEndOffset();
  362.     if ((pos >= p0) && (pos < p1)) {
  363.         Rectangle r = new Rectangle(a.getBounds());
  364.         r.width = 0;
  365.         return r;
  366.     }
  367.     return null;
  368.     }
  369.  
  370.     /**
  371.      * Provides a mapping from the view coordinate space to the logical
  372.      * coordinate space of the model.
  373.      *
  374.      * @param x the X coordinate
  375.      * @param y the Y coordinate
  376.      * @param a the allocated region to render into
  377.      * @return the location within the model that best represents the
  378.      *  given point of view
  379.      * @see View#viewToModel
  380.      */
  381.     public int viewToModel(float x, float y, Shape a) {
  382.     return getStartOffset();
  383.     }
  384.  
  385.     /**
  386.      * Set the size of the view. (Ignored.)
  387.      *
  388.      * @param width the width
  389.      * @param height the height
  390.      */
  391.     public void setSize(float width, float height) {
  392.         // Ignore this -- image size is determined by the tag attrs and
  393.         // the image itself, not the surrounding layout!
  394.     }
  395.     
  396.     /** Change the size of this image. This alters the HEIGHT and WIDTH
  397.         attributes of the Element and causes a re-layout. */
  398.     protected void resize( int width, int height ) {
  399.         if( width==fIMG.fWidth && height==fIMG.fHeight )
  400.             return;
  401.         
  402.         //$ Should not  be necessary if changedUpdate were called,
  403.         //  but currently (1/8/98) it isn't, so...
  404.         fWidth = fIMG.fWidth = width;
  405.         fHeight= fIMG.fHeight= height;
  406.         
  407.         // Replace attributes in document:
  408.     MutableAttributeSet attr = new SimpleAttributeSet();
  409.     attr.addAttribute(HTMLDefs.WIDTH ,Integer.toString(width));
  410.     attr.addAttribute(HTMLDefs.HEIGHT,Integer.toString(height));
  411.     ((StyledDocument)getDocument()).setCharacterAttributes(
  412.             fIMG.fElement.getStartOffset(),
  413.             fIMG.fElement.getEndOffset(),
  414.             attr, false);
  415.     }
  416.     
  417.     // --- Mouse event handling --------------------------------------------
  418.     
  419.     /** Select or grow image when clicked. */
  420.     public void mousePressed(MouseEvent e){
  421.         Dimension size = fComponent.getSize();
  422.         if( e.getX() >= size.width-7 && e.getY() >= size.height-7
  423.                 && getSelectionState()==2 ) {
  424.             // Click in selected grow-box:
  425.             if(DEBUG)System.out.println("ImageView: grow!!! Size="+fWidth+"x"+fHeight);
  426.             Point loc = fComponent.getLocationOnScreen();
  427.             fGrowBase = new Point(loc.x+e.getX() - fWidth,
  428.                           loc.y+e.getY() - fHeight);
  429.             fGrowProportionally = e.isShiftDown();
  430.         } else {
  431.             // Else select image:
  432.             fGrowBase = null;
  433.             JTextComponent comp = (JTextComponent)fContainer;
  434.             int start = fIMG.fElement.getStartOffset();
  435.             int end = fIMG.fElement.getEndOffset();
  436.             int mark = comp.getCaret().getMark();
  437.             int dot  = comp.getCaret().getDot();
  438.             if( e.isShiftDown() ) {
  439.                 // extend selection if shift key down:
  440.                 if( mark <= start )
  441.                     comp.moveCaretPosition(end);
  442.                 else
  443.                     comp.moveCaretPosition(start);
  444.             } else {
  445.                 // just select image, without shift:
  446.                 if( mark!=start )
  447.                     comp.setCaretPosition(start);
  448.                 if( dot!=end )
  449.                     comp.moveCaretPosition(end);
  450.             }
  451.         }
  452.     }
  453.     
  454.     /** Resize image if initial click was in grow-box: */
  455.     public void mouseDragged(MouseEvent e ) {
  456.         if( fGrowBase != null ) {
  457.             Point loc = fComponent.getLocationOnScreen();
  458.             int width = Math.max(2, loc.x+e.getX() - fGrowBase.x);
  459.             int height= Math.max(2, loc.y+e.getY() - fGrowBase.y);
  460.             
  461.             if( e.isShiftDown() && fIMG.fImage!=null ) {
  462.                 // Make sure size is proportional to actual image size:
  463.                 int imgWidth = fIMG.fImage.getWidth(this);
  464.                 int imgHeight = fIMG.fImage.getHeight(this);
  465.                 if( imgWidth>0 && imgHeight>0 ) {
  466.                     float prop = (float)imgHeight / (float)imgWidth;
  467.                     float pwidth = height / prop;
  468.                     float pheight= width * prop;
  469.                     if( pwidth > width )
  470.                         width = (int) pwidth;
  471.                     else
  472.                         height = (int) pheight;
  473.                 }
  474.             }
  475.             
  476.             resize(width,height);
  477.         }
  478.     }
  479.  
  480.     public void mouseReleased(MouseEvent e){
  481.         fGrowBase = null;
  482.         //! Should post some command to make the action undo-able
  483.     }
  484.  
  485.     /** On double-click, open image properties dialog. */
  486.     public void mouseClicked(MouseEvent e){
  487.         if( e.getClickCount() == 2 ) {
  488.             System.out.println("ImageView: Double-click!");    //$ IMPLEMENT
  489.         }
  490.     }
  491.  
  492.     public void mouseEntered(MouseEvent e){
  493.     }
  494.     public void mouseMoved(MouseEvent e ) {
  495.     }
  496.     public void mouseExited(MouseEvent e){
  497.     }
  498.     
  499.  
  500.     // --- Static icon accessors -------------------------------------------
  501.  
  502.     private Icon makeIcon(final String gifFile) throws IOException {
  503.         /* Copy resource into a byte array.  This is
  504.          * necessary because several browsers consider
  505.          * Class.getResource a security risk because it
  506.          * can be used to load additional classes.
  507.          * Class.getResourceAsStream just returns raw
  508.          * bytes, which we can convert to an image.
  509.          */
  510.         InputStream resource = 
  511.             ImageView.class.getResourceAsStream(gifFile);
  512.         if (resource == null) {
  513.             System.err.println(ImageView.class.getName() + "/" + 
  514.                                gifFile + " not found.");
  515.             return null; 
  516.         }
  517.         BufferedInputStream in = 
  518.             new BufferedInputStream(resource);
  519.         ByteArrayOutputStream out = 
  520.             new ByteArrayOutputStream(1024);
  521.         byte[] buffer = new byte[1024];
  522.         int n;
  523.         while ((n = in.read(buffer)) > 0) {
  524.             out.write(buffer, 0, n);
  525.         }
  526.         in.close();
  527.         out.flush();
  528.  
  529.         buffer = out.toByteArray();
  530.         if (buffer.length == 0) {
  531.             System.err.println("warning: " + gifFile + 
  532.                                " is zero-length");
  533.             return null;
  534.         }
  535.         return new ImageIcon(buffer);
  536.     }
  537.  
  538.     private void loadIcons( ) {
  539.         try{
  540.             if( sPendingImageIcon == null )
  541.                 sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
  542.             if( sMissingImageIcon == null )
  543.                 sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
  544.     }catch( Exception x ) {
  545.         System.err.println("ImageView: Couldn't load image icons");
  546.     }
  547.     }
  548.     
  549.     // --- member variables ------------------------------------------------
  550.  
  551.     private ImageInfo fIMG;
  552.     private int       fHeight, fWidth;
  553.     private Container fContainer;
  554.     private Rectangle fBounds;
  555.     private Component fComponent;
  556.     private Point     fGrowBase;        // base of drag while growing image
  557.     private boolean   fGrowProportionally;    // should grow be proportional?
  558.     
  559.     // --- constants and static stuff --------------------------------
  560.  
  561.     private static Icon sPendingImageIcon,
  562.                 sMissingImageIcon;
  563.     private static final String
  564.         PENDING_IMAGE_SRC = "icons/image-delayed.gif",  // both stolen from HotJava
  565.         MISSING_IMAGE_SRC = "icons/image-failed.gif";
  566.     
  567.     private static final boolean DEBUG = false;
  568.  
  569.  
  570.  
  571.  
  572. /** Shared data for Elements on IMG tags. (Inner class!)
  573.  */
  574.  
  575. static class ImageInfo implements HTMLDefs {
  576.  
  577.     ImageInfo( ) {
  578.     }
  579.     
  580.     void initialize( ImageView view, Element elem ) {
  581.         if( fElement != null )
  582.             return;
  583.             
  584.         fElement = elem;
  585.     // Request image from document's cache:
  586.     AttributeSet attr = elem.getAttributes();
  587.     URL src = getSourceURL();
  588.     if( src != null ) {
  589.         Dictionary cache = (Dictionary) view.getDocument().getProperty(IMAGE_CACHE_PROPERTY);
  590.         if( cache != null )
  591.             fImage = (Image) cache.get(src);
  592.         else
  593.             fImage = Toolkit.getDefaultToolkit().getImage(src);
  594.     }
  595.     
  596.     // Get height/width from params or image or defaults:
  597.     fHeight = getIntAttr(HTMLDefs.HEIGHT,-1);
  598.     boolean customHeight = (fHeight>0);
  599.     if( !customHeight && fImage != null )
  600.         fHeight = fImage.getHeight(view);
  601.     if( fHeight <= 0 )
  602.         fHeight = DEFAULT_HEIGHT;
  603.         
  604.     fWidth = getIntAttr(HTMLDefs.WIDTH,-1);
  605.     boolean customWidth = (fWidth>0);
  606.     if( !customWidth && fImage != null )
  607.         fWidth = fImage.getWidth(view);
  608.     if( fWidth <= 0 )
  609.         fWidth = DEFAULT_WIDTH;
  610.     
  611.     // Make sure the image starts loading:
  612.     if( fImage != null )
  613.         if( customWidth && customHeight )
  614.             Toolkit.getDefaultToolkit().prepareImage(fImage,fHeight,fWidth,view);
  615.         else
  616.             Toolkit.getDefaultToolkit().prepareImage(fImage,-1,-1,view);
  617.     
  618.     if( DEBUG ) {
  619.         if( fImage != null )
  620.             System.out.println("ImageInfo: new on "+src+" ("+fWidth+"x"+fHeight+")");
  621.         else
  622.             System.out.println("ImageInfo: couldn't get image at "+src);
  623.         if(isLink()) System.out.println("           It's a link! Border = "+getBorder());
  624.         //((AbstractDocument.AbstractElement)elem).dump(System.out,4);
  625.     }
  626.     
  627.     }
  628.     
  629.     /** Attributes may have changed, so re-read them. */
  630.     void changedUpdate( ImageView view, Element elem ) {
  631.         fElement = null;
  632.         initialize(view,elem);
  633.     }
  634.         
  635.     /** Is this image within a link? */
  636.     boolean isLink( ) {
  637.         //! It would be nice to cache this but in an editor it can change
  638.         // See if I have an HREF attribute courtesy of the enclosing A tag:
  639.         return fElement.getAttributes().isDefined(HREF);
  640.     }
  641.     
  642.     /** Returns the size of the border to use. */
  643.     int getBorder( ) {
  644.         return getIntAttr(BORDER, isLink() ?DEFAULT_BORDER :0);
  645.     }
  646.     
  647.     /** Returns the amount of extra space to add along an axis. */
  648.     int getSpace( int axis ) {
  649.         return getIntAttr( axis==X_AXIS ?HSPACE :VSPACE,
  650.                    0 );
  651.     }
  652.     
  653.     /** Returns the border's color, or null if this is not a link. */
  654.     Color getBorderColor( ) {
  655.         // Basically stolen from LabelView.sync:
  656.         HTMLDocument doc = (HTMLDocument) fElement.getDocument();
  657.         AttributeSet attr = fElement.getAttributes();
  658.         return doc.getForeground(attr);
  659.     }
  660.     
  661.     /** Returns the image's vertical alignment. */
  662.     float getVerticalAlignment( ) {
  663.     String align = (String) fElement.getAttributes().getAttribute(ALIGN);
  664.     if( align != null ) {
  665.         align = align.toLowerCase();
  666.         if( align.equals(TOP) || align.equals(TEXTTOP) )
  667.             return 0.0f;
  668.         else if( align.equals(this.CENTER) || align.equals(MIDDLE)
  669.                            || align.equals(ABSMIDDLE) )
  670.             return 0.5f;
  671.     }
  672.     return 1.0f;        // default alignment is bottom
  673.     }
  674.     
  675.     boolean hasPixels( ImageObserver obs ) {
  676.         return fImage != null && fImage.getHeight(obs)>0
  677.                   && fImage.getWidth(obs)>0;
  678.     }
  679.     
  680.  
  681.     /** Return a URL for the image source, 
  682.         or null if it could not be determined. */
  683.     private URL getSourceURL( ) {
  684.      String src = (String) fElement.getAttributes().getAttribute(SRC);
  685.      if( src==null ) return null;
  686.     URL reference = (URL) fElement.getDocument().getProperty(Document.StreamDescriptionProperty);
  687.         try {
  688.          return new URL(reference,src);     
  689.         } catch (MalformedURLException e) {
  690.         return null;
  691.         }
  692.     }
  693.     
  694.     /** Look up an integer-valued attribute. <b>Not</b> recursive. */
  695.     private int getIntAttr( String name, int deflt ) {
  696.         AttributeSet attr = fElement.getAttributes();
  697.         if( attr.isDefined(name) ) {        // does not check parents!
  698.             int i;
  699.          String val = (String) attr.getAttribute(name);
  700.          if( val == null )
  701.              i = deflt;
  702.          else
  703.              try{
  704.                  i = Math.max(0, Integer.parseInt(val));
  705.              }catch( NumberFormatException x ) {
  706.                  i = deflt;
  707.              }
  708.         return i;
  709.     } else
  710.         return deflt;
  711.     }
  712.     
  713.  
  714.     Element fElement;
  715.     Image   fImage;
  716.     int     fHeight,fWidth;
  717.     
  718.     static final String kAttributeName = "$IMGObject";
  719.     
  720.     //$ move this someplace public
  721.     static final String IMAGE_CACHE_PROPERTY = "imageCache";
  722.     
  723.     // Height/width to use before we know the real size:
  724.     private static final int
  725.         DEFAULT_WIDTH = 32,
  726.         DEFAULT_HEIGHT= 32,
  727.     // Default value of BORDER param:      //? possibly move into stylesheet?
  728.         DEFAULT_BORDER=  2;
  729. } // end of ImageView.ImageInfo
  730.  
  731.  
  732. } // end of ImageView
  733.