tentative merge of EKIT_0_9H and custom patches
[old-projects.git] / ekit / com / hexidec / ekit / component / RelativeImageView.java
CommitLineData
b6b7ed68
JL
1/*
2GNU Lesser General Public License
3
4RelativeImageView
5Copyright (C) 2001 Frits Jalvingh & Howard Kistler
6
7This library is free software; you can redistribute it and/or
8modify it under the terms of the GNU Lesser General Public
9License as published by the Free Software Foundation; either
10version 2.1 of the License, or (at your option) any later version.
11
12This library is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15Lesser General Public License for more details.
16
17You should have received a copy of the GNU Lesser General Public
18License along with this library; if not, write to the Free Software
19Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20*/
21
22package com.hexidec.ekit.component;
23
24import java.awt.Color;
25import java.awt.Component;
26import java.awt.Container;
27import java.awt.Dimension;
28import java.awt.Graphics;
29import java.awt.Image;
30import java.awt.Point;
31import java.awt.Rectangle;
32import java.awt.Shape;
33import java.awt.Toolkit;
34import java.awt.event.MouseEvent;
35import java.awt.event.MouseListener;
36import java.awt.event.MouseMotionListener;
37import java.awt.image.ImageObserver;
38import java.io.BufferedInputStream;
39import java.io.ByteArrayOutputStream;
40import java.io.File;
41import java.io.InputStream;
42import java.io.IOException;
43import java.net.MalformedURLException;
44import java.net.URL;
45import java.util.Dictionary;
46import javax.swing.Icon;
47import javax.swing.ImageIcon;
48import javax.swing.JEditorPane;
49import javax.swing.text.AbstractDocument;
50import javax.swing.text.AttributeSet;
51import javax.swing.text.BadLocationException;
52import javax.swing.text.Document;
53import javax.swing.text.Element;
54import javax.swing.text.JTextComponent;
55import javax.swing.text.MutableAttributeSet;
56import javax.swing.text.Position;
57import javax.swing.text.SimpleAttributeSet;
58import javax.swing.text.StyledDocument;
59import javax.swing.text.View;
60import javax.swing.text.ViewFactory;
61import javax.swing.text.html.HTML;
62import javax.swing.text.html.HTMLDocument;
63import javax.swing.text.html.StyleSheet;
64import 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
75public 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 try
364 {
365 URL reference = new URL(BaseUrl);
366 URL u = new URL(reference,src);
367 return u;
368 }
369 catch(MalformedURLException mue)
370 {
371 return null;
372 }
373 }
374
375 /** Method looks up an integer-valued attribute (not recursive!)
376 */
377 private int getIntAttr(HTML.Attribute name, int iDefault)
378 {
379 AttributeSet attr = fElement.getAttributes();
380 if(attr.isDefined(name))
381 {
382 int i;
383 String val = (String)attr.getAttribute(name);
384 if(val == null)
385 {
386 i = iDefault;
387 }
388 else
389 {
390 try
391 {
392 i = Math.max(0, Integer.parseInt(val));
393 }
394 catch(NumberFormatException nfe)
395 {
396 i = iDefault;
397 }
398 }
399 return i;
400 }
401 else
402 {
403 return iDefault;
404 }
405 }
406
407 /**
408 * Establishes the parent view for this view.
409 * Seize this moment to cache the AWT Container I'm in.
410 */
411 public void setParent(View parent)
412 {
413 super.setParent(parent);
414 fContainer = ((parent != null) ? getContainer() : null);
415 if((parent == null) && (fComponent != null))
416 {
417 fComponent.getParent().remove(fComponent);
418 fComponent = null;
419 }
420 }
421
422 /** My attributes may have changed. */
423 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
424 {
425 super.changedUpdate(e, a, f);
426 float align = getVerticalAlignment();
427
428 int height = fHeight;
429 int width = fWidth;
430
431 initialize(getElement());
432
433 boolean hChanged = fHeight != height;
434 boolean wChanged = fWidth != width;
435 if(hChanged || wChanged || getVerticalAlignment() != align)
436 {
437 getParent().preferenceChanged(this, hChanged, wChanged);
438 }
439 }
440
441
442 /**
443 * Paints the image.
444 *
445 * @param g the rendering surface to use
446 * @param a the allocated region to render into
447 * @see View#paint
448 */
449 public void paint(Graphics g, Shape a)
450 {
451 Color oldColor = g.getColor();
452 fBounds = a.getBounds();
453 int border = getBorder();
454 int x = fBounds.x + border + getSpace(X_AXIS);
455 int y = fBounds.y + border + getSpace(Y_AXIS);
456 int width = fWidth;
457 int height = fHeight;
458 int sel = getSelectionState();
459
460 // If no pixels yet, draw gray outline and icon
461 if(!hasPixels(this))
462 {
463 g.setColor(Color.lightGray);
464 g.drawRect(x, y, width - 1, height - 1);
465 g.setColor(oldColor);
466 loadImageStatusIcons();
467 Icon icon = ((fImage == null) ? sMissingImageIcon : sPendingImageIcon);
468 if(icon != null)
469 {
470 icon.paintIcon(getContainer(), g, x, y);
471 }
472 }
473
474 // Draw image
475 if(fImage != null)
476 {
477 g.drawImage(fImage, x, y, width, height, this);
478 }
479
480 // If selected exactly, we need a black border & grow-box
481 Color bc = getBorderColor();
482 if(sel == 2)
483 {
484 // Make sure there's room for a border
485 int delta = 2 - border;
486 if(delta > 0)
487 {
488 x += delta;
489 y += delta;
490 width -= delta << 1;
491 height -= delta << 1;
492 border = 2;
493 }
494 bc = null;
495 g.setColor(Color.black);
496 // Draw grow box
497 g.fillRect(x + width - 5, y + height - 5, 5, 5);
498 }
499
500 // Draw border
501 if(border > 0)
502 {
503 if(bc != null)
504 {
505 g.setColor(bc);
506 }
507 // Draw a thick rectangle:
508 for(int i = 1; i <= border; i++)
509 {
510 g.drawRect(x - i, y - i, width - 1 + i + i, height - 1 + i + i);
511 }
512 g.setColor(oldColor);
513 }
514 }
515
516 /** Request that this view be repainted. Assumes the view is still at its last-drawn location.
517 */
518 protected void repaint(long delay)
519 {
520 if((fContainer != null) && (fBounds != null))
521 {
522 fContainer.repaint(delay, fBounds.x, fBounds.y, fBounds.width, fBounds.height);
523 }
524 }
525
526 /**
527 * Determines whether the image is selected, and if it's the only thing selected.
528 * @return 0 if not selected, 1 if selected, 2 if exclusively selected.
529 * "Exclusive" selection is only returned when editable.
530 */
531 protected int getSelectionState()
532 {
533 int p0 = fElement.getStartOffset();
534 int p1 = fElement.getEndOffset();
535 if(fContainer instanceof JTextComponent)
536 {
537 JTextComponent textComp = (JTextComponent)fContainer;
538 int start = textComp.getSelectionStart();
539 int end = textComp.getSelectionEnd();
540 if((start <= p0) && (end >= p1))
541 {
542 if((start == p0) && (end == p1) && isEditable())
543 {
544 return 2;
545 }
546 else
547 {
548 return 1;
549 }
550 }
551 }
552 return 0;
553 }
554
555 protected boolean isEditable()
556 {
557 return ((fContainer instanceof JEditorPane) && ((JEditorPane)fContainer).isEditable());
558 }
559
560 /** Returns the text editor's highlight color.
561 */
562 protected Color getHighlightColor()
563 {
564 JTextComponent textComp = (JTextComponent)fContainer;
565 return textComp.getSelectionColor();
566 }
567
568 // Progressive display -------------------------------------------------
569
570 // This can come on any thread. If we are in the process of reloading
571 // the image and determining our state (loading == true) we don't fire
572 // preference changed, or repaint, we just reset the fWidth/fHeight as
573 // necessary and return. This is ok as we know when loading finishes
574 // it will pick up the new height/width, if necessary.
575
576 private static boolean sIsInc = true;
577 private static int sIncRate = 100;
578
579 public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height)
580 {
581 if((fImage == null) || (fImage != img))
582 {
583 return false;
584 }
585
586 // Bail out if there was an error
587 if((flags & (ABORT|ERROR)) != 0)
588 {
589 fImage = null;
590 repaint(0);
591 return false;
592 }
593
594 // Resize image if necessary
595 short changed = 0;
596 if((flags & ImageObserver.HEIGHT) != 0)
597 {
598 if(!getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT))
599 {
600 changed |= 1;
601 }
602 }
603 if((flags & ImageObserver.WIDTH) != 0)
604 {
605 if(!getElement().getAttributes().isDefined(HTML.Attribute.WIDTH))
606 {
607 changed |= 2;
608 }
609 }
610
611 synchronized(this)
612 {
613 if((changed & 1) == 1)
614 {
615 fWidth = width;
616 }
617 if((changed & 2) == 2)
618 {
619 fHeight = height;
620 }
621 if(bLoading)
622 {
623 // No need to resize or repaint, still in the process of loading
624 return true;
625 }
626 }
627
628 if(changed != 0)
629 {
630 // May need to resize myself, asynchronously
631 Document doc = getDocument();
632 try
633 {
634 if(doc instanceof AbstractDocument)
635 {
636 ((AbstractDocument)doc).readLock();
637 }
638 preferenceChanged(this, true, true);
639 }
640 finally
641 {
642 if(doc instanceof AbstractDocument)
643 {
644 ((AbstractDocument)doc).readUnlock();
645 }
646 }
647 return true;
648 }
649
650 // Repaint when done or when new pixels arrive
651 if((flags & (FRAMEBITS|ALLBITS)) != 0)
652 {
653 repaint(0);
654 }
655 else if((flags & SOMEBITS) != 0)
656 {
657 if(sIsInc)
658 {
659 repaint(sIncRate);
660 }
661 }
662 return ((flags & ALLBITS) == 0);
663 }
664
665 // Layout --------------------------------------------------------------
666
667 /** Determines the preferred span for this view along an axis.
668 *
669 * @param axis may be either X_AXIS or Y_AXIS
670 * @returns the span the view would like to be rendered into.
671 * Typically the view is told to render into the span
672 * that is returned, although there is no guarantee.
673 * The parent may choose to resize or break the view.
674 */
675 public float getPreferredSpan(int axis)
676 {
677 int extra = 2 * (getBorder() + getSpace(axis));
678 switch(axis)
679 {
680 case View.X_AXIS:
681 return fWidth+extra;
682 case View.Y_AXIS:
683 return fHeight+extra;
684 default:
685 throw new IllegalArgumentException("Invalid axis in getPreferredSpan() : " + axis);
686 }
687 }
688
689 /** Determines the desired alignment for this view along an
690 * axis. This is implemented to give the alignment to the
691 * bottom of the icon along the y axis, and the default
692 * along the x axis.
693 *
694 * @param axis may be either X_AXIS or Y_AXIS
695 * @returns the desired alignment. This should be a value
696 * between 0.0 and 1.0 where 0 indicates alignment at the
697 * origin and 1.0 indicates alignment to the full span
698 * away from the origin. An alignment of 0.5 would be the
699 * center of the view.
700 */
701 public float getAlignment(int axis)
702 {
703 switch(axis)
704 {
705 case View.Y_AXIS:
706 return getVerticalAlignment();
707 default:
708 return super.getAlignment(axis);
709 }
710 }
711
712 /** Provides a mapping from the document model coordinate space
713 * to the coordinate space of the view mapped to it.
714 *
715 * @param pos the position to convert
716 * @param a the allocated region to render into
717 * @return the bounding box of the given position
718 * @exception BadLocationException if the given position does not represent a
719 * valid location in the associated document
720 * @see View#modelToView
721 */
722 public Shape modelToView(int pos, Shape a, Position.Bias b)
723 throws BadLocationException
724 {
725 int p0 = getStartOffset();
726 int p1 = getEndOffset();
727 if((pos >= p0) && (pos <= p1))
728 {
729 Rectangle r = a.getBounds();
730 if(pos == p1)
731 {
732 r.x += r.width;
733 }
734 r.width = 0;
735 return r;
736 }
737 return null;
738 }
739
740 /** Provides a mapping from the view coordinate space to the logical
741 * coordinate space of the model.
742 *
743 * @param x the X coordinate
744 * @param y the Y coordinate
745 * @param a the allocated region to render into
746 * @return the location within the model that best represents the
747 * given point of view
748 * @see View#viewToModel
749 */
750 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
751 {
752 Rectangle alloc = (Rectangle) a;
753 if(x < (alloc.x + alloc.width))
754 {
755 bias[0] = Position.Bias.Forward;
756 return getStartOffset();
757 }
758 bias[0] = Position.Bias.Backward;
759 return getEndOffset();
760 }
761
762 /** Change the size of this image. This alters the HEIGHT and WIDTH
763 * attributes of the Element and causes a re-layout.
764 */
765 protected void resize(int width, int height)
766 {
767 if((width == fWidth) && (height == fHeight))
768 {
769 return;
770 }
771 fWidth = width;
772 fHeight= height;
773 // Replace attributes in document
774 MutableAttributeSet attr = new SimpleAttributeSet();
775 attr.addAttribute(HTML.Attribute.WIDTH ,Integer.toString(width));
776 attr.addAttribute(HTML.Attribute.HEIGHT,Integer.toString(height));
777 ((StyledDocument)getDocument()).setCharacterAttributes(fElement.getStartOffset(), fElement.getEndOffset(), attr, false);
778 }
779
780 // Mouse event handling ------------------------------------------------
781
782 /** Select or grow image when clicked.
783 */
784 public void mousePressed(MouseEvent e)
785 {
786 Dimension size = fComponent.getSize();
787 if((e.getX() >= (size.width - 7)) && (e.getY() >= (size.height - 7)) && (getSelectionState() == 2))
788 {
789 // Click in selected grow-box:
790 Point loc = fComponent.getLocationOnScreen();
791 fGrowBase = new Point(loc.x + e.getX() - fWidth, loc.y + e.getY() - fHeight);
792 fGrowProportionally = e.isShiftDown();
793 }
794 else
795 {
796 // Else select image:
797 fGrowBase = null;
798 JTextComponent comp = (JTextComponent)fContainer;
799 int start = fElement.getStartOffset();
800 int end = fElement.getEndOffset();
801 int mark = comp.getCaret().getMark();
802 int dot = comp.getCaret().getDot();
803 if(e.isShiftDown())
804 {
805 // extend selection if shift key down:
806 if(mark <= start)
807 {
808 comp.moveCaretPosition(end);
809 }
810 else
811 {
812 comp.moveCaretPosition(start);
813 }
814 }
815 else
816 {
817 // just select image, without shift:
818 if(mark != start)
819 {
820 comp.setCaretPosition(start);
821 }
822 if(dot != end)
823 {
824 comp.moveCaretPosition(end);
825 }
826 }
827 }
828 }
829
830 /** Resize image if initial click was in grow-box: */
831 public void mouseDragged(MouseEvent e)
832 {
833 if(fGrowBase != null)
834 {
835 Point loc = fComponent.getLocationOnScreen();
836 int width = Math.max(2, loc.x + e.getX() - fGrowBase.x);
837 int height= Math.max(2, loc.y + e.getY() - fGrowBase.y);
838 if(e.isShiftDown() && fImage != null)
839 {
840 // Make sure size is proportional to actual image size
841 float imgWidth = fImage.getWidth(this);
842 float imgHeight = fImage.getHeight(this);
843 if((imgWidth > 0) && (imgHeight > 0))
844 {
845 float prop = imgHeight / imgWidth;
846 float pwidth = height / prop;
847 float pheight = width * prop;
848 if(pwidth > width)
849 {
850 width = (int)pwidth;
851 }
852 else
853 {
854 height = (int)pheight;
855 }
856 }
857 }
858 resize(width,height);
859 }
860 }
861
862 public void mouseReleased(MouseEvent me)
863 {
864 fGrowBase = null;
865 //! Should post some command to make the action undo-able
866 }
867
868 /** On double-click, open image properties dialog.
869 */
870 public void mouseClicked(MouseEvent me)
871 {
872 if(me.getClickCount() == 2)
873 {
874 //$ IMPLEMENT
875 }
876 }
877
878 public void mouseEntered(MouseEvent me) { ; }
879 public void mouseMoved(MouseEvent me) { ; }
880 public void mouseExited(MouseEvent me) { ; }
881
882 // Static icon accessors -----------------------------------------------
883
884 private Icon makeIcon(final String gifFile)
885 throws IOException
886 {
887 /* Copy resource into a byte array. This is
888 * necessary because several browsers consider
889 * Class.getResource a security risk because it
890 * can be used to load additional classes.
891 * Class.getResourceAsStream just returns raw
892 * bytes, which we can convert to an image.
893 */
894 InputStream resource = RelativeImageView.class.getResourceAsStream(gifFile);
895
896 if(resource == null)
897 {
898 return null;
899 }
900 BufferedInputStream in = new BufferedInputStream(resource);
901 ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
902 byte[] buffer = new byte[1024];
903 int n;
904 while((n = in.read(buffer)) > 0)
905 {
906 out.write(buffer, 0, n);
907 }
908 in.close();
909 out.flush();
910
911 buffer = out.toByteArray();
912 if(buffer.length == 0)
913 {
914 System.err.println("WARNING : " + gifFile + " is zero-length");
915 return null;
916 }
917 return new ImageIcon(buffer);
918 }
919
920 private void loadImageStatusIcons()
921 {
922 try
923 {
924 if(sPendingImageIcon == null)
925 {
926 sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
927 }
928 if(sMissingImageIcon == null)
929 {
930 sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
931 }
932 }
933 catch(Exception e)
934 {
935 System.err.println("ImageView : Couldn't load image icons");
936 }
937 }
938
939 protected StyleSheet getStyleSheet()
940 {
941 HTMLDocument doc = (HTMLDocument)getDocument();
942 return doc.getStyleSheet();
943 }
944
945}