1 7 package javax.swing.text.html; 8 9 import java.awt.*; 10 import java.awt.event.*; 11 import java.awt.image.ImageObserver ; 12 import java.io.*; 13 import java.net.*; 14 import java.util.Dictionary ; 15 import javax.swing.*; 16 import javax.swing.text.*; 17 import javax.swing.event.*; 18 19 33 public class ImageView extends View { 34 42 private static boolean sIsInc = false; 43 46 private static int sIncRate = 100; 47 50 private static Icon sPendingImageIcon; 51 54 private static Icon sMissingImageIcon; 55 58 private static final String PENDING_IMAGE_SRC = "icons/image-delayed.gif"; 59 62 private static final String MISSING_IMAGE_SRC = "icons/image-failed.gif"; 63 64 67 private static final String IMAGE_CACHE_PROPERTY = "imageCache"; 68 69 private static final int DEFAULT_WIDTH = 38; 73 private static final int DEFAULT_HEIGHT= 38; 74 75 78 private static final int DEFAULT_BORDER = 2; 79 80 private static final int LOADING_FLAG = 1; 82 private static final int LINK_FLAG = 2; 83 private static final int WIDTH_FLAG = 4; 84 private static final int HEIGHT_FLAG = 8; 85 private static final int RELOAD_FLAG = 16; 86 private static final int RELOAD_IMAGE_FLAG = 32; 87 private static final int SYNC_LOAD_FLAG = 64; 88 89 private AttributeSet attr; 90 private Image image; 91 private int width; 92 private int height; 93 96 private int state; 97 private Container container; 98 private Rectangle fBounds; 99 private Color borderColor; 100 private short borderSize; 103 private short leftInset; 105 private short rightInset; 106 private short topInset; 107 private short bottomInset; 108 112 private ImageObserver imageObserver; 113 117 private View altView; 118 119 private float vAlign; 120 121 122 123 128 public ImageView(Element elem) { 129 super(elem); 130 fBounds = new Rectangle(); 131 imageObserver = new ImageHandler(); 132 state = RELOAD_FLAG | RELOAD_IMAGE_FLAG; 133 } 134 135 140 public String getAltText() { 141 return (String )getElement().getAttributes().getAttribute 142 (HTML.Attribute.ALT); 143 } 144 145 149 public URL getImageURL() { 150 String src = (String )getElement().getAttributes(). 151 getAttribute(HTML.Attribute.SRC); 152 if (src == null) { 153 return null; 154 } 155 156 URL reference = ((HTMLDocument )getDocument()).getBase(); 157 try { 158 URL u = new URL(reference,src); 159 return u; 160 } catch (MalformedURLException e) { 161 return null; 162 } 163 } 164 165 168 public Icon getNoImageIcon() { 169 loadDefaultIconsIfNecessary(); 170 return sMissingImageIcon; 171 } 172 173 176 public Icon getLoadingImageIcon() { 177 loadDefaultIconsIfNecessary(); 178 return sPendingImageIcon; 179 } 180 181 184 public Image getImage() { 185 sync(); 186 return image; 187 } 188 189 195 public void setLoadsSynchronously(boolean newValue) { 196 synchronized(this) { 197 if (newValue) { 198 state |= SYNC_LOAD_FLAG; 199 } 200 else { 201 state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG; 202 } 203 } 204 } 205 206 209 public boolean getLoadsSynchronously() { 210 return ((state & SYNC_LOAD_FLAG) != 0); 211 } 212 213 216 protected StyleSheet getStyleSheet() { 217 HTMLDocument doc = (HTMLDocument ) getDocument(); 218 return doc.getStyleSheet(); 219 } 220 221 226 public AttributeSet getAttributes() { 227 sync(); 228 return attr; 229 } 230 231 238 public String getToolTipText(float x, float y, Shape allocation) { 239 return getAltText(); 240 } 241 242 245 protected void setPropertiesFromAttributes() { 246 StyleSheet sheet = getStyleSheet(); 247 this.attr = sheet.getViewAttributes(this); 248 249 borderSize = (short)getIntAttr(HTML.Attribute.BORDER, isLink() ? 251 DEFAULT_BORDER : 0); 252 253 leftInset = rightInset = (short)(getIntAttr(HTML.Attribute.HSPACE, 254 0) + borderSize); 255 topInset = bottomInset = (short)(getIntAttr(HTML.Attribute.VSPACE, 256 0) + borderSize); 257 258 borderColor = ((StyledDocument)getDocument()).getForeground 259 (getAttributes()); 260 261 AttributeSet attr = getElement().getAttributes(); 262 263 Object alignment = attr.getAttribute(HTML.Attribute.ALIGN); 267 268 vAlign = 1.0f; 269 if (alignment != null) { 270 alignment = alignment.toString(); 271 if ("top".equals(alignment)) { 272 vAlign = 0f; 273 } 274 else if ("middle".equals(alignment)) { 275 vAlign = .5f; 276 } 277 } 278 279 AttributeSet anchorAttr = (AttributeSet)attr.getAttribute(HTML.Tag.A); 280 if (anchorAttr != null && anchorAttr.isDefined 281 (HTML.Attribute.HREF)) { 282 synchronized(this) { 283 state |= LINK_FLAG; 284 } 285 } 286 else { 287 synchronized(this) { 288 state = (state | LINK_FLAG) ^ LINK_FLAG; 289 } 290 } 291 } 292 293 297 public void setParent(View parent) { 298 View oldParent = getParent(); 299 super.setParent(parent); 300 container = (parent != null) ? getContainer() : null; 301 if (oldParent != parent) { 302 synchronized(this) { 303 state |= RELOAD_FLAG; 304 } 305 } 306 } 307 308 311 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { 312 super.changedUpdate(e,a,f); 313 314 synchronized(this) { 315 state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG; 316 } 317 318 preferenceChanged(null, true, true); 320 } 321 322 329 public void paint(Graphics g, Shape a) { 330 sync(); 331 332 Rectangle rect = (a instanceof Rectangle) ? (Rectangle)a : 333 a.getBounds(); 334 335 Image image = getImage(); 336 Rectangle clip = g.getClipBounds(); 337 338 fBounds.setBounds(rect); 339 paintHighlights(g, a); 340 paintBorder(g, rect); 341 if (clip != null) { 342 g.clipRect(rect.x + leftInset, rect.y + topInset, 343 rect.width - leftInset - rightInset, 344 rect.height - topInset - bottomInset); 345 } 346 if (image != null) { 347 if (!hasPixels(image)) { 348 Icon icon = (image == null) ? getNoImageIcon() : 350 getLoadingImageIcon(); 351 352 if (icon != null) { 353 icon.paintIcon(getContainer(), g, rect.x + leftInset, 354 rect.y + topInset); 355 } 356 } 357 else { 358 g.drawImage(image, rect.x + leftInset, rect.y + topInset, 360 width, height, imageObserver); 361 } 362 } 363 else { 364 Icon icon = getNoImageIcon(); 365 366 if (icon != null) { 367 icon.paintIcon(getContainer(), g, rect.x + leftInset, 368 rect.y + topInset); 369 } 370 View view = getAltView(); 371 if (view != null && ((state & WIDTH_FLAG) == 0 || 373 width > DEFAULT_WIDTH)) { 374 Rectangle altRect = new Rectangle 376 (rect.x + leftInset + DEFAULT_WIDTH, rect.y + topInset, 377 rect.width - leftInset - rightInset - DEFAULT_WIDTH, 378 rect.height - topInset - bottomInset); 379 380 view.paint(g, altRect); 381 } 382 } 383 if (clip != null) { 384 g.setClip(clip.x, clip.y, clip.width, clip.height); 386 } 387 } 388 389 private void paintHighlights(Graphics g, Shape shape) { 390 if (container instanceof JTextComponent) { 391 JTextComponent tc = (JTextComponent)container; 392 Highlighter h = tc.getHighlighter(); 393 if (h instanceof LayeredHighlighter) { 394 ((LayeredHighlighter)h).paintLayeredHighlights 395 (g, getStartOffset(), getEndOffset(), shape, tc, this); 396 } 397 } 398 } 399 400 private void paintBorder(Graphics g, Rectangle rect) { 401 Color color = borderColor; 402 403 if ((borderSize > 0 || image == null) && color != null) { 404 int xOffset = leftInset - borderSize; 405 int yOffset = topInset - borderSize; 406 g.setColor(color); 407 int n = (image == null) ? 1 : borderSize; 408 for (int counter = 0; counter < n; counter++) { 409 g.drawRect(rect.x + xOffset + counter, 410 rect.y + yOffset + counter, 411 rect.width - counter - counter - xOffset -xOffset-1, 412 rect.height - counter - counter -yOffset-yOffset-1); 413 } 414 } 415 } 416 417 427 public float getPreferredSpan(int axis) { 428 sync(); 429 430 if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) { 432 getPreferredSpanFromAltView(axis); 433 return width + leftInset + rightInset; 434 } 435 if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) { 436 getPreferredSpanFromAltView(axis); 437 return height + topInset + bottomInset; 438 } 439 440 Image image = getImage(); 441 442 if (image != null) { 443 switch (axis) { 444 case View.X_AXIS: 445 return width + leftInset + rightInset; 446 case View.Y_AXIS: 447 return height + topInset + bottomInset; 448 default: 449 throw new IllegalArgumentException ("Invalid axis: " + axis); 450 } 451 } 452 else { 453 View view = getAltView(); 454 float retValue = 0f; 455 456 if (view != null) { 457 retValue = view.getPreferredSpan(axis); 458 } 459 switch (axis) { 460 case View.X_AXIS: 461 return retValue + (float)(width + leftInset + rightInset); 462 case View.Y_AXIS: 463 return retValue + (float)(height + topInset + bottomInset); 464 default: 465 throw new IllegalArgumentException ("Invalid axis: " + axis); 466 } 467 } 468 } 469 470 483 public float getAlignment(int axis) { 484 switch (axis) { 485 case View.Y_AXIS: 486 return vAlign; 487 default: 488 return super.getAlignment(axis); 489 } 490 } 491 492 503 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 504 int p0 = getStartOffset(); 505 int p1 = getEndOffset(); 506 if ((pos >= p0) && (pos <= p1)) { 507 Rectangle r = a.getBounds(); 508 if (pos == p1) { 509 r.x += r.width; 510 } 511 r.width = 0; 512 return r; 513 } 514 return null; 515 } 516 517 528 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { 529 Rectangle alloc = (Rectangle) a; 530 if (x < alloc.x + alloc.width) { 531 bias[0] = Position.Bias.Forward; 532 return getStartOffset(); 533 } 534 bias[0] = Position.Bias.Backward; 535 return getEndOffset(); 536 } 537 538 545 public void setSize(float width, float height) { 546 sync(); 547 548 if (getImage() == null) { 549 View view = getAltView(); 550 551 if (view != null) { 552 view.setSize(Math.max(0f, width - (float)(DEFAULT_WIDTH + leftInset + rightInset)), 553 Math.max(0f, height - (float)(topInset + bottomInset))); 554 } 555 } 556 } 557 558 561 private boolean isLink() { 562 return ((state & LINK_FLAG) == LINK_FLAG); 563 } 564 565 568 private boolean hasPixels(Image image) { 569 return image != null && 570 (image.getHeight(imageObserver) > 0) && 571 (image.getWidth(imageObserver) > 0); 572 } 573 574 578 private float getPreferredSpanFromAltView(int axis) { 579 if (getImage() == null) { 580 View view = getAltView(); 581 582 if (view != null) { 583 return view.getPreferredSpan(axis); 584 } 585 } 586 return 0f; 587 } 588 589 private Icon makeIcon(final String gifFile) throws IOException { 590 597 InputStream resource = HTMLEditorKit.getResourceAsStream(gifFile); 598 599 if (resource == null) { 600 System.err.println(ImageView .class.getName() + "/" + 601 gifFile + " not found."); 602 return null; 603 } 604 BufferedInputStream in = 605 new BufferedInputStream(resource); 606 ByteArrayOutputStream out = 607 new ByteArrayOutputStream(1024); 608 byte[] buffer = new byte[1024]; 609 int n; 610 while ((n = in.read(buffer)) > 0) { 611 out.write(buffer, 0, n); 612 } 613 in.close(); 614 out.flush(); 615 616 buffer = out.toByteArray(); 617 if (buffer.length == 0) { 618 System.err.println("warning: " + gifFile + 619 " is zero-length"); 620 return null; 621 } 622 return new ImageIcon(buffer); 623 } 624 625 629 private void repaint(long delay) { 630 if (container != null && fBounds != null) { 631 container.repaint(delay, fBounds.x, fBounds.y, fBounds.width, 632 fBounds.height); 633 } 634 } 635 636 private void loadDefaultIconsIfNecessary() { 637 try { 638 if (sPendingImageIcon == null) 639 sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC); 640 if (sMissingImageIcon == null) 641 sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC); 642 } catch(Exception x) { 643 System.err.println("ImageView: Couldn't load image icons"); 644 } 645 } 646 647 651 private int getIntAttr(HTML.Attribute name, int deflt) { 652 AttributeSet attr = getElement().getAttributes(); 653 if (attr.isDefined(name)) { int i; 655 String val = (String )attr.getAttribute(name); 656 if (val == null) { 657 i = deflt; 658 } 659 else { 660 try{ 661 i = Math.max(0, Integer.parseInt(val)); 662 }catch( NumberFormatException x ) { 663 i = deflt; 664 } 665 } 666 return i; 667 } else 668 return deflt; 669 } 670 671 674 private void sync() { 675 int s = state; 676 if ((s & RELOAD_IMAGE_FLAG) != 0) { 677 refreshImage(); 678 } 679 s = state; 680 if ((s & RELOAD_FLAG) != 0) { 681 synchronized(this) { 682 state = (state | RELOAD_FLAG) ^ RELOAD_FLAG; 683 } 684 setPropertiesFromAttributes(); 685 } 686 } 687 688 693 private void refreshImage() { 694 synchronized(this) { 695 state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG | 697 HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG | 698 RELOAD_IMAGE_FLAG); 699 image = null; 700 width = height = 0; 701 } 702 703 try { 704 loadImage(); 706 707 updateImageSize(); 709 } 710 finally { 711 synchronized(this) { 712 state = (state | LOADING_FLAG) ^ LOADING_FLAG; 714 } 715 } 716 } 717 718 722 private void loadImage() { 723 URL src = getImageURL(); 724 Image newImage = null; 725 if (src != null) { 726 Dictionary cache = (Dictionary )getDocument(). 727 getProperty(IMAGE_CACHE_PROPERTY); 728 if (cache != null) { 729 newImage = (Image)cache.get(src); 730 } 731 else { 732 newImage = Toolkit.getDefaultToolkit().createImage(src); 733 if (newImage != null && getLoadsSynchronously()) { 734 ImageIcon ii = new ImageIcon(); 736 ii.setImage(newImage); 737 } 738 } 739 } 740 image = newImage; 741 } 742 743 747 private void updateImageSize() { 748 int newWidth = 0; 749 int newHeight = 0; 750 int newState = 0; 751 Image newImage = getImage(); 752 753 if (newImage != null) { 754 Element elem = getElement(); 755 AttributeSet attr = elem.getAttributes(); 756 757 newWidth = getIntAttr(HTML.Attribute.WIDTH, -1); 761 if (newWidth > 0) { 762 newState |= WIDTH_FLAG; 763 } 764 newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1); 765 if (newHeight > 0) { 766 newState |= HEIGHT_FLAG; 767 } 768 769 if (newWidth <= 0) { 770 newWidth = newImage.getWidth(imageObserver); 771 if (newWidth <= 0) { 772 newWidth = DEFAULT_WIDTH; 773 } 774 } 775 776 if (newHeight <= 0) { 777 newHeight = newImage.getHeight(imageObserver); 778 if (newHeight <= 0) { 779 newHeight = DEFAULT_HEIGHT; 780 } 781 } 782 783 if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) { 785 Toolkit.getDefaultToolkit().prepareImage(newImage, newWidth, 786 newHeight, 787 imageObserver); 788 } 789 else { 790 Toolkit.getDefaultToolkit().prepareImage(newImage, -1, -1, 791 imageObserver); 792 } 793 794 boolean createText = false; 795 synchronized(this) { 796 if (image != null) { 800 if ((newState & WIDTH_FLAG) == WIDTH_FLAG || width == 0) { 801 width = newWidth; 802 } 803 if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG || 804 height == 0) { 805 height = newHeight; 806 } 807 } 808 else { 809 createText = true; 810 if ((newState & WIDTH_FLAG) == WIDTH_FLAG) { 811 width = newWidth; 812 } 813 if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) { 814 height = newHeight; 815 } 816 } 817 state = state | newState; 818 state = (state | LOADING_FLAG) ^ LOADING_FLAG; 819 } 820 if (createText) { 821 updateAltTextView(); 823 } 824 } 825 else { 826 width = height = DEFAULT_HEIGHT; 827 updateAltTextView(); 828 } 829 } 830 831 834 private void updateAltTextView() { 835 String text = getAltText(); 836 837 if (text != null) { 838 ImageLabelView newView; 839 840 newView = new ImageLabelView(getElement(), text); 841 synchronized(this) { 842 altView = newView; 843 } 844 } 845 } 846 847 850 private View getAltView() { 851 View view; 852 853 synchronized(this) { 854 view = altView; 855 } 856 if (view != null && view.getParent() == null) { 857 view.setParent(getParent()); 858 } 859 return view; 860 } 861 862 866 private void safePreferenceChanged() { 867 if (SwingUtilities.isEventDispatchThread()) { 868 Document doc = getDocument(); 869 if (doc instanceof AbstractDocument) { 870 ((AbstractDocument)doc).readLock(); 871 } 872 preferenceChanged(null, true, true); 873 if (doc instanceof AbstractDocument) { 874 ((AbstractDocument)doc).readUnlock(); 875 } 876 } 877 else { 878 SwingUtilities.invokeLater(new Runnable () { 879 public void run() { 880 safePreferenceChanged(); 881 } 882 }); 883 } 884 } 885 886 890 private class ImageHandler implements ImageObserver { 891 public boolean imageUpdate(Image img, int flags, int x, int y, 897 int newWidth, int newHeight ) { 898 if (image == null || image != img || getParent() == null) { 899 return false; 900 } 901 902 if ((flags & (ABORT|ERROR)) != 0) { 904 repaint(0); 905 synchronized(ImageView.this) { 906 if (image == img) { 907 image = null; 910 if ((state & WIDTH_FLAG) != WIDTH_FLAG) { 911 width = DEFAULT_WIDTH; 912 } 913 if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) { 914 height = DEFAULT_HEIGHT; 915 } 916 } 917 if ((state & LOADING_FLAG) == LOADING_FLAG) { 918 return false; 921 } 922 } 923 updateAltTextView(); 924 safePreferenceChanged(); 925 return false; 926 } 927 928 short changed = 0; 930 if ((flags & ImageObserver.HEIGHT) != 0 && !getElement(). 931 getAttributes().isDefined(HTML.Attribute.HEIGHT)) { 932 changed |= 1; 933 } 934 if ((flags & ImageObserver.WIDTH) != 0 && !getElement(). 935 getAttributes().isDefined(HTML.Attribute.WIDTH)) { 936 changed |= 2; 937 } 938 939 synchronized(ImageView.this) { 940 if (image != img) { 941 return false; 942 } 943 if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) { 944 width = newWidth; 945 } 946 if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) { 947 height = newHeight; 948 } 949 if ((state & LOADING_FLAG) == LOADING_FLAG) { 950 return true; 953 } 954 } 955 if (changed != 0) { 956 safePreferenceChanged(); 958 return true; 959 } 960 961 if ((flags & (FRAMEBITS|ALLBITS)) != 0) { 963 repaint(0); 964 } 965 else if ((flags & SOMEBITS) != 0 && sIsInc) { 966 repaint(sIncRate); 967 } 968 return ((flags & ALLBITS) == 0); 969 } 970 } 971 972 973 978 private class ImageLabelView extends InlineView { 979 private Segment segment; 980 private Color fg; 981 982 ImageLabelView(Element e, String text) { 983 super(e); 984 reset(text); 985 } 986 987 public void reset(String text) { 988 segment = new Segment(text.toCharArray(), 0, text.length()); 989 } 990 991 public void paint(Graphics g, Shape a) { 992 GlyphPainter painter = getGlyphPainter(); 995 996 if (painter != null) { 997 g.setColor(getForeground()); 998 painter.paint(this, g, a, getStartOffset(), getEndOffset()); 999 } 1000 } 1001 1002 public Segment getText(int p0, int p1) { 1003 if (p0 < 0 || p1 > segment.array.length) { 1004 throw new RuntimeException ("ImageLabelView: Stale view"); 1005 } 1006 segment.offset = p0; 1007 segment.count = p1 - p0; 1008 return segment; 1009 } 1010 1011 public int getStartOffset() { 1012 return 0; 1013 } 1014 1015 public int getEndOffset() { 1016 return segment.array.length; 1017 } 1018 1019 public View breakView(int axis, int p0, float pos, float len) { 1020 return this; 1022 } 1023 1024 public Color getForeground() { 1025 View parent; 1026 if (fg == null && (parent = getParent()) != null) { 1027 Document doc = getDocument(); 1028 AttributeSet attr = parent.getAttributes(); 1029 1030 if (attr != null && (doc instanceof StyledDocument)) { 1031 fg = ((StyledDocument)doc).getForeground(attr); 1032 } 1033 } 1034 return fg; 1035 } 1036 } 1037} 1038 | Popular Tags |