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