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