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