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