added custom code
[old-projects.git] / ekit / com / hexidec / ekit / component / RelativeImageView.java
1 /*
2 GNU Lesser General Public License
3
4 RelativeImageView
5 Copyright (C) 2001-2002 Frits Jalvingh & Howard Kistler
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 package com.hexidec.ekit.component;
23
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Container;
27 import java.awt.Dimension;
28 import java.awt.Graphics;
29 import java.awt.Image;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Shape;
33 import java.awt.Toolkit;
34 import java.awt.event.MouseEvent;
35 import java.awt.event.MouseListener;
36 import java.awt.event.MouseMotionListener;
37 import java.awt.image.ImageObserver;
38 import java.io.BufferedInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.InputStream;
42 import java.io.IOException;
43 import java.net.MalformedURLException;
44 import java.net.URL;
45 import java.util.Dictionary;
46 import javax.swing.Icon;
47 import javax.swing.ImageIcon;
48 import javax.swing.JEditorPane;
49 import javax.swing.text.AbstractDocument;
50 import javax.swing.text.AttributeSet;
51 import javax.swing.text.BadLocationException;
52 import javax.swing.text.Document;
53 import javax.swing.text.Element;
54 import javax.swing.text.JTextComponent;
55 import javax.swing.text.MutableAttributeSet;
56 import javax.swing.text.Position;
57 import javax.swing.text.SimpleAttributeSet;
58 import javax.swing.text.StyledDocument;
59 import javax.swing.text.View;
60 import javax.swing.text.ViewFactory;
61 import javax.swing.text.html.HTML;
62 import javax.swing.text.html.HTMLDocument;
63 import javax.swing.text.html.StyleSheet;
64 import javax.swing.event.DocumentEvent;
65
66 /**
67 * @author <a href="mailto:jal@grimor.com">Frits Jalvingh</a>
68 * @version 1.0
69 *
70 * This code was modeled after an artice on
71 * <a href="http://www.javaworld.com/javaworld/javatips/jw-javatip109.html">
72 * JavaWorld</a> by Bob Kenworthy.
73 */
74
75 public class RelativeImageView extends View implements ImageObserver, MouseListener, MouseMotionListener
76 {
77 public static final String TOP = "top";
78 public static final String TEXTTOP = "texttop";
79 public static final String MIDDLE = "middle";
80 public static final String ABSMIDDLE = "absmiddle";
81 public static final String CENTER = "center";
82 public static final String BOTTOM = "bottom";
83 public static final String IMAGE_CACHE_PROPERTY = "imageCache";
84
85 private static Icon sPendingImageIcon;
86 private static Icon sMissingImageIcon;
87 private static final String PENDING_IMAGE_SRC = "icons/ImagePendingHK.gif";
88 private static final String MISSING_IMAGE_SRC = "icons/ImageMissingHK.gif";
89 private static final int DEFAULT_WIDTH = 32;
90 private static final int DEFAULT_HEIGHT = 32;
91 private static final int DEFAULT_BORDER = 1;
92
93 private AttributeSet attr;
94 private Element fElement;
95 private Image fImage;
96 private int fHeight;
97 private int fWidth;
98 private Container fContainer;
99 private Rectangle fBounds;
100 private Component fComponent;
101 private Point fGrowBase; // base of drag while growing image
102 private boolean fGrowProportionally; // should grow be proportional?
103 private String BaseUrl;
104 private boolean bLoading; // set to true while the receiver is locked, to indicate the reciever is loading the image. This is used in imageUpdate.
105
106 /** Constructor
107 * Creates a new view that represents an IMG element.
108 * @param elem the element to create a view for
109 */
110 public RelativeImageView(Element elem,String baseurl)
111 {
112 super(elem);
113 BaseUrl = baseurl;
114 initialize(elem);
115 StyleSheet sheet = getStyleSheet();
116 attr = sheet.getViewAttributes(this);
117 }
118
119 private void initialize(Element elem)
120 {
121 synchronized(this)
122 {
123 bLoading = true;
124 fWidth = 0;
125 fHeight = 0;
126 }
127 int width = 0;
128 int height = 0;
129 boolean customWidth = false;
130 boolean customHeight = false;
131 try
132 {
133 fElement = elem;
134 // request image from document's cache
135 AttributeSet attr = elem.getAttributes();
136 if(true || isURL())
137 {
138 URL src = getSourceURL();
139 if(src != null)
140 {
141 Dictionary cache = (Dictionary)getDocument().getProperty(IMAGE_CACHE_PROPERTY);
142 if(cache != null)
143 {
144 fImage = (Image)cache.get(src);
145 }
146 else
147 {
148 fImage = Toolkit.getDefaultToolkit().getImage(src);
149 }
150 }
151 }
152 else
153 {
154 // load image from relative path
155 String src = (String)fElement.getAttributes().getAttribute(HTML.Attribute.SRC);
156 src = processSrcPath(src);
157 fImage = Toolkit.getDefaultToolkit().createImage(src);
158 try
159 {
160 waitForImage();
161 }
162 catch(InterruptedException ie)
163 {
164 fImage = null;
165 // possibly replace with the ImageBroken icon, if that's what is happening
166 }
167 }
168
169 // get height & width from params or image or defaults
170 height = getIntAttr(HTML.Attribute.HEIGHT, -1);
171 customHeight = (height > 0);
172 if(!customHeight && fImage != null)
173 {
174 height = fImage.getHeight(this);
175 }
176 if(height <= 0)
177 {
178 height = DEFAULT_HEIGHT;
179 }
180
181 width = getIntAttr(HTML.Attribute.WIDTH, -1);
182 customWidth = (width > 0);
183 if(!customWidth && fImage != null)
184 {
185 width = fImage.getWidth(this);
186 }
187 if(width <= 0)
188 {
189 width = DEFAULT_WIDTH;
190 }
191
192 if(fImage != null)
193 {
194 if(customHeight && customWidth)
195 {
196 Toolkit.getDefaultToolkit().prepareImage(fImage, height, width, this);
197 }
198 else
199 {
200 Toolkit.getDefaultToolkit().prepareImage(fImage, -1, -1, this);
201 }
202 }
203 }
204 finally
205 {
206 synchronized(this)
207 {
208 bLoading = false;
209 if(customHeight || fHeight == 0)
210 {
211 fHeight = height;
212 }
213 if(customWidth || fWidth == 0)
214 {
215 fWidth = width;
216 }
217 }
218 }
219 }
220
221 /** Determines if path is in the form of a URL
222 */
223 private boolean isURL()
224 {
225 String src = (String)fElement.getAttributes().getAttribute(HTML.Attribute.SRC);
226 return src.toLowerCase().startsWith("file") || src.toLowerCase().startsWith("http");
227 }
228
229 /** Checks to see if the absolute path is availabe thru an application
230 * global static variable or thru a system variable. If so, appends
231 * the relative path to the absolute path and returns the String.
232 */
233 private String processSrcPath(String src)
234 {
235 String val = src;
236 File imageFile = new File(src);
237 if(imageFile.isAbsolute())
238 {
239 return src;
240 }
241 boolean found = false;
242 Document doc = getDocument();
243 if(doc != null)
244 {
245 String pv = (String)doc.getProperty("com.hexidec.ekit.docsource");
246 if(pv != null)
247 {
248 File f = new File(pv);
249 val = (new File(f.getParent(), imageFile.getPath().toString())).toString();
250 found = true;
251 }
252 }
253 if(!found)
254 {
255 String imagePath = System.getProperty("system.image.path.key");
256 if(imagePath != null)
257 {
258 val = (new File(imagePath, imageFile.getPath())).toString();
259 }
260 }
261 return val;
262 }
263
264 /** Method insures that the image is loaded and not a broken reference
265 */
266 private void waitForImage()
267 throws InterruptedException
268 {
269 int w = fImage.getWidth(this);
270 int h = fImage.getHeight(this);
271 while (true)
272 {
273 int flags = Toolkit.getDefaultToolkit().checkImage(fImage, w, h, this);
274 if(((flags & ERROR) != 0) || ((flags & ABORT) != 0 ))
275 {
276 throw new InterruptedException();
277 }
278 else if((flags & (ALLBITS | FRAMEBITS)) != 0)
279 {
280 return;
281 }
282 Thread.sleep(10);
283 }
284 }
285
286 /** Fetches the attributes to use when rendering. This is
287 * implemented to multiplex the attributes specified in the
288 * model with a StyleSheet.
289 */
290 public AttributeSet getAttributes()
291 {
292 return attr;
293 }
294
295 /** Method tests whether the image within a link
296 */
297 boolean isLink()
298 {
299 AttributeSet anchorAttr = (AttributeSet)fElement.getAttributes().getAttribute(HTML.Tag.A);
300 if(anchorAttr != null)
301 {
302 return anchorAttr.isDefined(HTML.Attribute.HREF);
303 }
304 return false;
305 }
306
307 /** Method returns the size of the border to use
308 */
309 int getBorder()
310 {
311 return getIntAttr(HTML.Attribute.BORDER, isLink() ? DEFAULT_BORDER : 0);
312 }
313
314 /** Method returns the amount of extra space to add along an axis
315 */
316 int getSpace(int axis)
317 {
318 return getIntAttr((axis == X_AXIS) ? HTML.Attribute.HSPACE : HTML.Attribute.VSPACE, 0);
319 }
320
321 /** Method returns the border's color, or null if this is not a link
322 */
323 Color getBorderColor()
324 {
325 StyledDocument doc = (StyledDocument)getDocument();
326 return doc.getForeground(getAttributes());
327 }
328
329 /** Method returns the image's vertical alignment
330 */
331 float getVerticalAlignment()
332 {
333 String align = (String)fElement.getAttributes().getAttribute(HTML.Attribute.ALIGN);
334 if(align != null)
335 {
336 align = align.toLowerCase();
337 if(align.equals(TOP) || align.equals(TEXTTOP))
338 {
339 return 0.0f;
340 }
341 else if(align.equals(this.CENTER) || align.equals(MIDDLE) || align.equals(ABSMIDDLE))
342 {
343 return 0.5f;
344 }
345 }
346 return 1.0f; // default alignment is bottom
347 }
348
349 boolean hasPixels(ImageObserver obs)
350 {
351 return ((fImage != null) && (fImage.getHeight(obs) > 0) && (fImage.getWidth(obs) > 0));
352 }
353
354 /** Method returns a URL for the image source, or null if it could not be determined
355 */
356 private URL getSourceURL()
357 {
358 String src = (String)fElement.getAttributes().getAttribute(HTML.Attribute.SRC);
359 if(src == null)
360 {
361 return null;
362 }
363 //URL reference = ((HTMLDocument)getDocument()).getBase();
364 try
365 {
366 URL reference = new URL(BaseUrl);
367 URL u = new URL(reference,src);
368 return u;
369 }
370 catch(MalformedURLException mue)
371 {
372 return null;
373 }
374 }
375
376 /** Method looks up an integer-valued attribute (not recursive!)
377 */
378 private int getIntAttr(HTML.Attribute name, int iDefault)
379 {
380 AttributeSet attr = fElement.getAttributes();
381 if(attr.isDefined(name))
382 {
383 int i;
384 String val = (String)attr.getAttribute(name);
385 if(val == null)
386 {
387 i = iDefault;
388 }
389 else
390 {
391 try
392 {
393 i = Math.max(0, Integer.parseInt(val));
394 }
395 catch(NumberFormatException nfe)
396 {
397 i = iDefault;
398 }
399 }
400 return i;
401 }
402 else
403 {
404 return iDefault;
405 }
406 }
407
408 /**
409 * Establishes the parent view for this view.
410 * Seize this moment to cache the AWT Container I'm in.
411 */
412 public void setParent(View parent)
413 {
414 super.setParent(parent);
415 fContainer = ((parent != null) ? getContainer() : null);
416 if((parent == null) && (fComponent != null))
417 {
418 fComponent.getParent().remove(fComponent);
419 fComponent = null;
420 }
421 }
422
423 /** My attributes may have changed. */
424 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
425 {
426 super.changedUpdate(e, a, f);
427 float align = getVerticalAlignment();
428
429 int height = fHeight;
430 int width = fWidth;
431
432 initialize(getElement());
433
434 boolean hChanged = fHeight != height;
435 boolean wChanged = fWidth != width;
436 if(hChanged || wChanged || getVerticalAlignment() != align)
437 {
438 getParent().preferenceChanged(this, hChanged, wChanged);
439 }
440 }
441
442
443 /**
444 * Paints the image.
445 *
446 * @param g the rendering surface to use
447 * @param a the allocated region to render into
448 * @see View#paint
449 */
450 public void paint(Graphics g, Shape a)
451 {
452 Color oldColor = g.getColor();
453 fBounds = a.getBounds();
454 int border = getBorder();
455 int x = fBounds.x + border + getSpace(X_AXIS);
456 int y = fBounds.y + border + getSpace(Y_AXIS);
457 int width = fWidth;
458 int height = fHeight;
459 int sel = getSelectionState();
460
461 // If no pixels yet, draw gray outline and icon
462 if(!hasPixels(this))
463 {
464 g.setColor(Color.lightGray);
465 g.drawRect(x, y, width - 1, height - 1);
466 g.setColor(oldColor);
467 loadImageStatusIcons();
468 Icon icon = ((fImage == null) ? sMissingImageIcon : sPendingImageIcon);
469 if(icon != null)
470 {
471 icon.paintIcon(getContainer(), g, x, y);
472 }
473 }
474
475 // Draw image
476 if(fImage != null)
477 {
478 g.drawImage(fImage, x, y, width, height, this);
479 }
480
481 // If selected exactly, we need a black border & grow-box
482 Color bc = getBorderColor();
483 if(sel == 2)
484 {
485 // Make sure there's room for a border
486 int delta = 2 - border;
487 if(delta > 0)
488 {
489 x += delta;
490 y += delta;
491 width -= delta << 1;
492 height -= delta << 1;
493 border = 2;
494 }
495 bc = null;
496 g.setColor(Color.black);
497 // Draw grow box
498 g.fillRect(x + width - 5, y + height - 5, 5, 5);
499 }
500
501 // Draw border
502 if(border > 0)
503 {
504 if(bc != null)
505 {
506 g.setColor(bc);
507 }
508 // Draw a thick rectangle:
509 for(int i = 1; i <= border; i++)
510 {
511 g.drawRect(x - i, y - i, width - 1 + i + i, height - 1 + i + i);
512 }
513 g.setColor(oldColor);
514 }
515 }
516
517 /** Request that this view be repainted. Assumes the view is still at its last-drawn location.
518 */
519 protected void repaint(long delay)
520 {
521 if((fContainer != null) && (fBounds != null))
522 {
523 fContainer.repaint(delay, fBounds.x, fBounds.y, fBounds.width, fBounds.height);
524 }
525 }
526
527 /**
528 * Determines whether the image is selected, and if it's the only thing selected.
529 * @return 0 if not selected, 1 if selected, 2 if exclusively selected.
530 * "Exclusive" selection is only returned when editable.
531 */
532 protected int getSelectionState()
533 {
534 int p0 = fElement.getStartOffset();
535 int p1 = fElement.getEndOffset();
536 if(fContainer instanceof JTextComponent)
537 {
538 JTextComponent textComp = (JTextComponent)fContainer;
539 int start = textComp.getSelectionStart();
540 int end = textComp.getSelectionEnd();
541 if((start <= p0) && (end >= p1))
542 {
543 if((start == p0) && (end == p1) && isEditable())
544 {
545 return 2;
546 }
547 else
548 {
549 return 1;
550 }
551 }
552 }
553 return 0;
554 }
555
556 protected boolean isEditable()
557 {
558 return ((fContainer instanceof JEditorPane) && ((JEditorPane)fContainer).isEditable());
559 }
560
561 /** Returns the text editor's highlight color.
562 */
563 protected Color getHighlightColor()
564 {
565 JTextComponent textComp = (JTextComponent)fContainer;
566 return textComp.getSelectionColor();
567 }
568
569 // Progressive display -------------------------------------------------
570
571 // This can come on any thread. If we are in the process of reloading
572 // the image and determining our state (loading == true) we don't fire
573 // preference changed, or repaint, we just reset the fWidth/fHeight as
574 // necessary and return. This is ok as we know when loading finishes
575 // it will pick up the new height/width, if necessary.
576
577 private static boolean sIsInc = true;
578 private static int sIncRate = 100;
579
580 public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height)
581 {
582 if((fImage == null) || (fImage != img))
583 {
584 return false;
585 }
586
587 // Bail out if there was an error
588 if((flags & (ABORT|ERROR)) != 0)
589 {
590 fImage = null;
591 repaint(0);
592 return false;
593 }
594
595 // Resize image if necessary
596 short changed = 0;
597 if((flags & ImageObserver.HEIGHT) != 0)
598 {
599 if(!getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT))
600 {
601 changed |= 1;
602 }
603 }
604 if((flags & ImageObserver.WIDTH) != 0)
605 {
606 if(!getElement().getAttributes().isDefined(HTML.Attribute.WIDTH))
607 {
608 changed |= 2;
609 }
610 }
611
612 synchronized(this)
613 {
614 if((changed & 1) == 1)
615 {
616 fWidth = width;
617 }
618 if((changed & 2) == 2)
619 {
620 fHeight = height;
621 }
622 if(bLoading)
623 {
624 // No need to resize or repaint, still in the process of loading
625 return true;
626 }
627 }
628
629 if(changed != 0)
630 {
631 // May need to resize myself, asynchronously
632 Document doc = getDocument();
633 try
634 {
635 if(doc instanceof AbstractDocument)
636 {
637 ((AbstractDocument)doc).readLock();
638 }
639 preferenceChanged(this, true, true);
640 }
641 finally
642 {
643 if(doc instanceof AbstractDocument)
644 {
645 ((AbstractDocument)doc).readUnlock();
646 }
647 }
648 return true;
649 }
650
651 // Repaint when done or when new pixels arrive
652 if((flags & (FRAMEBITS|ALLBITS)) != 0)
653 {
654 repaint(0);
655 }
656 else if((flags & SOMEBITS) != 0)
657 {
658 if(sIsInc)
659 {
660 repaint(sIncRate);
661 }
662 }
663 return ((flags & ALLBITS) == 0);
664 }
665
666 // Layout --------------------------------------------------------------
667
668 /** Determines the preferred span for this view along an axis.
669 *
670 * @param axis may be either X_AXIS or Y_AXIS
671 * @returns the span the view would like to be rendered into.
672 * Typically the view is told to render into the span
673 * that is returned, although there is no guarantee.
674 * The parent may choose to resize or break the view.
675 */
676 public float getPreferredSpan(int axis)
677 {
678 int extra = 2 * (getBorder() + getSpace(axis));
679 switch(axis)
680 {
681 case View.X_AXIS:
682 return fWidth+extra;
683 case View.Y_AXIS:
684 return fHeight+extra;
685 default:
686 throw new IllegalArgumentException("Invalid axis in getPreferredSpan() : " + axis);
687 }
688 }
689
690 /** Determines the desired alignment for this view along an
691 * axis. This is implemented to give the alignment to the
692 * bottom of the icon along the y axis, and the default
693 * along the x axis.
694 *
695 * @param axis may be either X_AXIS or Y_AXIS
696 * @returns the desired alignment. This should be a value
697 * between 0.0 and 1.0 where 0 indicates alignment at the
698 * origin and 1.0 indicates alignment to the full span
699 * away from the origin. An alignment of 0.5 would be the
700 * center of the view.
701 */
702 public float getAlignment(int axis)
703 {
704 switch(axis)
705 {
706 case View.Y_AXIS:
707 return getVerticalAlignment();
708 default:
709 return super.getAlignment(axis);
710 }
711 }
712
713 /** Provides a mapping from the document model coordinate space
714 * to the coordinate space of the view mapped to it.
715 *
716 * @param pos the position to convert
717 * @param a the allocated region to render into
718 * @return the bounding box of the given position
719 * @exception BadLocationException if the given position does not represent a
720 * valid location in the associated document
721 * @see View#modelToView
722 */
723 public Shape modelToView(int pos, Shape a, Position.Bias b)
724 throws BadLocationException
725 {
726 int p0 = getStartOffset();
727 int p1 = getEndOffset();
728 if((pos >= p0) && (pos <= p1))
729 {
730 Rectangle r = a.getBounds();
731 if(pos == p1)
732 {
733 r.x += r.width;
734 }
735 r.width = 0;
736 return r;
737 }
738 return null;
739 }
740
741 /** Provides a mapping from the view coordinate space to the logical
742 * coordinate space of the model.
743 *
744 * @param x the X coordinate
745 * @param y the Y coordinate
746 * @param a the allocated region to render into
747 * @return the location within the model that best represents the
748 * given point of view
749 * @see View#viewToModel
750 */
751 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
752 {
753 Rectangle alloc = (Rectangle) a;
754 if(x < (alloc.x + alloc.width))
755 {
756 bias[0] = Position.Bias.Forward;
757 return getStartOffset();
758 }
759 bias[0] = Position.Bias.Backward;
760 return getEndOffset();
761 }
762
763 /** Change the size of this image. This alters the HEIGHT and WIDTH
764 * attributes of the Element and causes a re-layout.
765 */
766 protected void resize(int width, int height)
767 {
768 if((width == fWidth) && (height == fHeight))
769 {
770 return;
771 }
772 fWidth = width;
773 fHeight= height;
774 // Replace attributes in document
775 MutableAttributeSet attr = new SimpleAttributeSet();
776 attr.addAttribute(HTML.Attribute.WIDTH ,Integer.toString(width));
777 attr.addAttribute(HTML.Attribute.HEIGHT,Integer.toString(height));
778 ((StyledDocument)getDocument()).setCharacterAttributes(fElement.getStartOffset(), fElement.getEndOffset(), attr, false);
779 }
780
781 // Mouse event handling ------------------------------------------------
782
783 /** Select or grow image when clicked.
784 */
785 public void mousePressed(MouseEvent e)
786 {
787 Dimension size = fComponent.getSize();
788 if((e.getX() >= (size.width - 7)) && (e.getY() >= (size.height - 7)) && (getSelectionState() == 2))
789 {
790 // Click in selected grow-box:
791 Point loc = fComponent.getLocationOnScreen();
792 fGrowBase = new Point(loc.x + e.getX() - fWidth, loc.y + e.getY() - fHeight);
793 fGrowProportionally = e.isShiftDown();
794 }
795 else
796 {
797 // Else select image:
798 fGrowBase = null;
799 JTextComponent comp = (JTextComponent)fContainer;
800 int start = fElement.getStartOffset();
801 int end = fElement.getEndOffset();
802 int mark = comp.getCaret().getMark();
803 int dot = comp.getCaret().getDot();
804 if(e.isShiftDown())
805 {
806 // extend selection if shift key down:
807 if(mark <= start)
808 {
809 comp.moveCaretPosition(end);
810 }
811 else
812 {
813 comp.moveCaretPosition(start);
814 }
815 }
816 else
817 {
818 // just select image, without shift:
819 if(mark != start)
820 {
821 comp.setCaretPosition(start);
822 }
823 if(dot != end)
824 {
825 comp.moveCaretPosition(end);
826 }
827 }
828 }
829 }
830
831 /** Resize image if initial click was in grow-box: */
832 public void mouseDragged(MouseEvent e)
833 {
834 if(fGrowBase != null)
835 {
836 Point loc = fComponent.getLocationOnScreen();
837 int width = Math.max(2, loc.x + e.getX() - fGrowBase.x);
838 int height= Math.max(2, loc.y + e.getY() - fGrowBase.y);
839 if(e.isShiftDown() && fImage != null)
840 {
841 // Make sure size is proportional to actual image size
842 float imgWidth = fImage.getWidth(this);
843 float imgHeight = fImage.getHeight(this);
844 if((imgWidth > 0) && (imgHeight > 0))
845 {
846 float prop = imgHeight / imgWidth;
847 float pwidth = height / prop;
848 float pheight = width * prop;
849 if(pwidth > width)
850 {
851 width = (int)pwidth;
852 }
853 else
854 {
855 height = (int)pheight;
856 }
857 }
858 }
859 resize(width,height);
860 }
861 }
862
863 public void mouseReleased(MouseEvent me)
864 {
865 fGrowBase = null;
866 //! Should post some command to make the action undo-able
867 }
868
869 /** On double-click, open image properties dialog.
870 */
871 public void mouseClicked(MouseEvent me)
872 {
873 if(me.getClickCount() == 2)
874 {
875 //$ IMPLEMENT
876 }
877 }
878
879 public void mouseEntered(MouseEvent me) { ; }
880 public void mouseMoved(MouseEvent me) { ; }
881 public void mouseExited(MouseEvent me) { ; }
882
883 // Static icon accessors -----------------------------------------------
884
885 private Icon makeIcon(final String gifFile)
886 throws IOException
887 {
888 /* Copy resource into a byte array. This is
889 * necessary because several browsers consider
890 * Class.getResource a security risk because it
891 * can be used to load additional classes.
892 * Class.getResourceAsStream just returns raw
893 * bytes, which we can convert to an image.
894 */
895 InputStream resource = RelativeImageView.class.getResourceAsStream(gifFile);
896
897 if(resource == null)
898 {
899 return null;
900 }
901 BufferedInputStream in = new BufferedInputStream(resource);
902 ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
903 byte[] buffer = new byte[1024];
904 int n;
905 while((n = in.read(buffer)) > 0)
906 {
907 out.write(buffer, 0, n);
908 }
909 in.close();
910 out.flush();
911
912 buffer = out.toByteArray();
913 if(buffer.length == 0)
914 {
915 System.err.println("WARNING : " + gifFile + " is zero-length");
916 return null;
917 }
918 return new ImageIcon(buffer);
919 }
920
921 private void loadImageStatusIcons()
922 {
923 try
924 {
925 if(sPendingImageIcon == null)
926 {
927 sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
928 }
929 if(sMissingImageIcon == null)
930 {
931 sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
932 }
933 }
934 catch(Exception e)
935 {
936 System.err.println("ImageView : Couldn't load image icons");
937 }
938 }
939
940 protected StyleSheet getStyleSheet()
941 {
942 HTMLDocument doc = (HTMLDocument)getDocument();
943 return doc.getStyleSheet();
944 }
945
946 }