Frames | No Frames |
1: /* GlyphView.java -- A view to render styled text 2: Copyright (C) 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import gnu.classpath.SystemProperties; 42: 43: import java.awt.Color; 44: import java.awt.Container; 45: import java.awt.Font; 46: import java.awt.FontMetrics; 47: import java.awt.Graphics; 48: import java.awt.Graphics2D; 49: import java.awt.Rectangle; 50: import java.awt.Shape; 51: import java.awt.Toolkit; 52: import java.awt.font.FontRenderContext; 53: import java.awt.font.TextHitInfo; 54: import java.awt.font.TextLayout; 55: import java.awt.geom.Rectangle2D; 56: 57: import javax.swing.SwingConstants; 58: import javax.swing.event.DocumentEvent; 59: import javax.swing.text.Position.Bias; 60: 61: /** 62: * Renders a run of styled text. This {@link View} subclass paints the 63: * characters of the <code>Element</code> it is responsible for using 64: * the style information from that <code>Element</code>. 65: * 66: * @author Roman Kennke (roman@kennke.org) 67: */ 68: public class GlyphView extends View implements TabableView, Cloneable 69: { 70: 71: /** 72: * An abstract base implementation for a glyph painter for 73: * <code>GlyphView</code>. 74: */ 75: public abstract static class GlyphPainter 76: { 77: /** 78: * Creates a new <code>GlyphPainer</code>. 79: */ 80: public GlyphPainter() 81: { 82: // Nothing to do here. 83: } 84: 85: /** 86: * Returns the ascent of the font that is used by this glyph painter. 87: * 88: * @param v the glyph view 89: * 90: * @return the ascent of the font that is used by this glyph painter 91: */ 92: public abstract float getAscent(GlyphView v); 93: 94: /** 95: * Returns the descent of the font that is used by this glyph painter. 96: * 97: * @param v the glyph view 98: * 99: * @return the descent of the font that is used by this glyph painter 100: */ 101: public abstract float getDescent(GlyphView v); 102: 103: /** 104: * Returns the full height of the rendered text. 105: * 106: * @return the full height of the rendered text 107: */ 108: public abstract float getHeight(GlyphView view); 109: 110: /** 111: * Determines the model offset, so that the text between <code>p0</code> 112: * and this offset fits within the span starting at <code>x</code> with 113: * the length of <code>len</code>. 114: * 115: * @param v the glyph view 116: * @param p0 the starting offset in the model 117: * @param x the start location in the view 118: * @param len the length of the span in the view 119: */ 120: public abstract int getBoundedPosition(GlyphView v, int p0, float x, 121: float len); 122: 123: /** 124: * Paints the glyphs. 125: * 126: * @param view the glyph view to paint 127: * @param g the graphics context to use for painting 128: * @param a the allocation of the glyph view 129: * @param p0 the start position (in the model) from which to paint 130: * @param p1 the end position (in the model) to which to paint 131: */ 132: public abstract void paint(GlyphView view, Graphics g, Shape a, int p0, 133: int p1); 134: 135: /** 136: * Maps a position in the document into the coordinate space of the View. 137: * The output rectangle usually reflects the font height but has a width 138: * of zero. 139: * 140: * @param view the glyph view 141: * @param pos the position of the character in the model 142: * @param a the area that is occupied by the view 143: * @param b either {@link Position.Bias#Forward} or 144: * {@link Position.Bias#Backward} depending on the preferred 145: * direction bias. If <code>null</code> this defaults to 146: * <code>Position.Bias.Forward</code> 147: * 148: * @return a rectangle that gives the location of the document position 149: * inside the view coordinate space 150: * 151: * @throws BadLocationException if <code>pos</code> is invalid 152: * @throws IllegalArgumentException if b is not one of the above listed 153: * valid values 154: */ 155: public abstract Shape modelToView(GlyphView view, int pos, Position.Bias b, 156: Shape a) 157: throws BadLocationException; 158: 159: /** 160: * Maps a visual position into a document location. 161: * 162: * @param v the glyph view 163: * @param x the X coordinate of the visual position 164: * @param y the Y coordinate of the visual position 165: * @param a the allocated region 166: * @param biasRet filled with the bias of the model location on method exit 167: * 168: * @return the model location that represents the specified view location 169: */ 170: public abstract int viewToModel(GlyphView v, float x, float y, Shape a, 171: Position.Bias[] biasRet); 172: 173: /** 174: * Determine the span of the glyphs from location <code>p0</code> to 175: * location <code>p1</code>. If <code>te</code> is not <code>null</code>, 176: * then TABs are expanded using this <code>TabExpander</code>. 177: * The parameter <code>x</code> is the location at which the view is 178: * located (this is important when using TAB expansion). 179: * 180: * @param view the glyph view 181: * @param p0 the starting location in the document model 182: * @param p1 the end location in the document model 183: * @param te the tab expander to use 184: * @param x the location at which the view is located 185: * 186: * @return the span of the glyphs from location <code>p0</code> to 187: * location <code>p1</code>, possibly using TAB expansion 188: */ 189: public abstract float getSpan(GlyphView view, int p0, int p1, 190: TabExpander te, float x); 191: 192: 193: /** 194: * Returns the model location that should be used to place a caret when 195: * moving the caret through the document. 196: * 197: * @param v the glyph view 198: * @param pos the current model location 199: * @param b the bias for <code>p</code> 200: * @param a the allocated region for the glyph view 201: * @param direction the direction from the current position; Must be one of 202: * {@link SwingConstants#EAST}, {@link SwingConstants#WEST}, 203: * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH} 204: * @param biasRet filled with the bias of the resulting location when method 205: * returns 206: * 207: * @return the location within the document that should be used to place the 208: * caret when moving the caret around the document 209: * 210: * @throws BadLocationException if <code>pos</code> is an invalid model 211: * location 212: * @throws IllegalArgumentException if <code>d</code> is invalid 213: */ 214: public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, 215: Shape a, int direction, 216: Position.Bias[] biasRet) 217: throws BadLocationException 218: 219: { 220: int result = pos; 221: switch (direction) 222: { 223: case SwingConstants.EAST: 224: result = pos + 1; 225: break; 226: case SwingConstants.WEST: 227: result = pos - 1; 228: break; 229: case SwingConstants.NORTH: 230: case SwingConstants.SOUTH: 231: default: 232: // This should be handled in enclosing view, since the glyph view 233: // does not layout vertically. 234: break; 235: } 236: return result; 237: } 238: 239: /** 240: * Returns a painter that can be used to render the specified glyph view. 241: * If this glyph painter is stateful, then it should return a new instance. 242: * However, if this painter is stateless it should return itself. The 243: * default behaviour is to return itself. 244: * 245: * @param v the glyph view for which to create a painter 246: * @param p0 the start offset of the rendered area 247: * @param p1 the end offset of the rendered area 248: * 249: * @return a painter that can be used to render the specified glyph view 250: */ 251: public GlyphPainter getPainter(GlyphView v, int p0, int p1) 252: { 253: return this; 254: } 255: } 256: 257: /** 258: * A GlyphPainter implementation based on TextLayout. This should give 259: * better performance in Java2D environments. 260: */ 261: private static class J2DGlyphPainter 262: extends GlyphPainter 263: { 264: 265: /** 266: * The text layout. 267: */ 268: TextLayout textLayout; 269: 270: /** 271: * Creates a new J2DGlyphPainter. 272: * 273: * @param str the string 274: * @param font the font 275: * @param frc the font render context 276: */ 277: J2DGlyphPainter(String str, Font font, FontRenderContext frc) 278: { 279: textLayout = new TextLayout(str, font, frc); 280: } 281: 282: /** 283: * Returns null so that GlyphView.checkPainter() creates a new instance. 284: */ 285: public GlyphPainter getPainter(GlyphView v, int p0, int p1) 286: { 287: return null; 288: } 289: 290: /** 291: * Delegates to the text layout. 292: */ 293: public float getAscent(GlyphView v) 294: { 295: return textLayout.getAscent(); 296: } 297: 298: /** 299: * Delegates to the text layout. 300: */ 301: public int getBoundedPosition(GlyphView v, int p0, float x, float len) 302: { 303: int pos; 304: TextHitInfo hit = textLayout.hitTestChar(len, 0); 305: if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight()) 306: pos = v.getEndOffset(); 307: else 308: { 309: pos = hit.isLeadingEdge() ? hit.getInsertionIndex() 310: : hit.getInsertionIndex() - 1; 311: pos += v.getStartOffset(); 312: } 313: return pos; 314: } 315: 316: /** 317: * Delegates to the text layout. 318: */ 319: public float getDescent(GlyphView v) 320: { 321: return textLayout.getDescent(); 322: } 323: 324: /** 325: * Delegates to the text layout. 326: */ 327: public float getHeight(GlyphView view) 328: { 329: return textLayout.getAscent() + textLayout.getDescent() 330: + textLayout.getLeading(); 331: } 332: 333: /** 334: * Delegates to the text layout. 335: */ 336: public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x) 337: { 338: float span; 339: if (p0 == v.getStartOffset() && p1 == v.getEndOffset()) 340: span = textLayout.getAdvance(); 341: else 342: { 343: int start = v.getStartOffset(); 344: int i0 = p0 - start; 345: int i1 = p1 - start; 346: TextHitInfo hit0 = TextHitInfo.afterOffset(i0); 347: TextHitInfo hit1 = TextHitInfo.afterOffset(i1); 348: float x0 = textLayout.getCaretInfo(hit0)[0]; 349: float x1 = textLayout.getCaretInfo(hit1)[0]; 350: span = Math.abs(x1 - x0); 351: } 352: return span; 353: } 354: 355: /** 356: * Delegates to the text layout. 357: */ 358: public Shape modelToView(GlyphView v, int pos, Bias b, Shape a) 359: throws BadLocationException 360: { 361: int offs = pos - v.getStartOffset(); 362: // Create copy here to protect original shape. 363: Rectangle2D bounds = a.getBounds2D(); 364: TextHitInfo hit = 365: b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs) 366: : TextHitInfo.beforeOffset(offs); 367: float[] loc = textLayout.getCaretInfo(hit); 368: bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1, 369: bounds.getHeight()); 370: return bounds; 371: } 372: 373: /** 374: * Delegates to the text layout. 375: */ 376: public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1) 377: { 378: // Can't paint this with plain graphics. 379: if (g instanceof Graphics2D) 380: { 381: Graphics2D g2d = (Graphics2D) g; 382: Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a 383: : a.getBounds2D(); 384: float x = (float) b.getX(); 385: float y = (float) b.getY() + textLayout.getAscent() 386: + textLayout.getLeading(); 387: // TODO: Try if clipping makes things faster for narrow views. 388: textLayout.draw(g2d, x, y); 389: } 390: } 391: 392: /** 393: * Delegates to the text layout. 394: */ 395: public int viewToModel(GlyphView v, float x, float y, Shape a, 396: Bias[] biasRet) 397: { 398: Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a 399: : a.getBounds2D(); 400: TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0); 401: int pos = hit.getInsertionIndex(); 402: biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward 403: : Position.Bias.Backward; 404: return pos + v.getStartOffset(); 405: } 406: 407: } 408: 409: /** 410: * The default <code>GlyphPainter</code> used in <code>GlyphView</code>. 411: */ 412: static class DefaultGlyphPainter extends GlyphPainter 413: { 414: FontMetrics fontMetrics; 415: 416: /** 417: * Returns the full height of the rendered text. 418: * 419: * @return the full height of the rendered text 420: */ 421: public float getHeight(GlyphView view) 422: { 423: updateFontMetrics(view); 424: float height = fontMetrics.getHeight(); 425: return height; 426: } 427: 428: /** 429: * Paints the glyphs. 430: * 431: * @param view the glyph view to paint 432: * @param g the graphics context to use for painting 433: * @param a the allocation of the glyph view 434: * @param p0 the start position (in the model) from which to paint 435: * @param p1 the end position (in the model) to which to paint 436: */ 437: public void paint(GlyphView view, Graphics g, Shape a, int p0, 438: int p1) 439: { 440: updateFontMetrics(view); 441: Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 442: TabExpander tabEx = view.getTabExpander(); 443: Segment txt = view.getText(p0, p1); 444: 445: // Find out the X location at which we have to paint. 446: int x = r.x; 447: int p = view.getStartOffset(); 448: if (p != p0) 449: { 450: int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx, 451: p); 452: x += width; 453: } 454: // Find out Y location. 455: int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent(); 456: 457: // Render the thing. 458: g.setFont(fontMetrics.getFont()); 459: Utilities.drawTabbedText(txt, x, y, g, tabEx, p0); 460: 461: } 462: 463: /** 464: * Maps a position in the document into the coordinate space of the View. 465: * The output rectangle usually reflects the font height but has a width 466: * of zero. 467: * 468: * @param view the glyph view 469: * @param pos the position of the character in the model 470: * @param a the area that is occupied by the view 471: * @param b either {@link Position.Bias#Forward} or 472: * {@link Position.Bias#Backward} depending on the preferred 473: * direction bias. If <code>null</code> this defaults to 474: * <code>Position.Bias.Forward</code> 475: * 476: * @return a rectangle that gives the location of the document position 477: * inside the view coordinate space 478: * 479: * @throws BadLocationException if <code>pos</code> is invalid 480: * @throws IllegalArgumentException if b is not one of the above listed 481: * valid values 482: */ 483: public Shape modelToView(GlyphView view, int pos, Position.Bias b, 484: Shape a) 485: throws BadLocationException 486: { 487: updateFontMetrics(view); 488: Element el = view.getElement(); 489: Segment txt = view.getText(el.getStartOffset(), pos); 490: Rectangle bounds = a instanceof Rectangle ? (Rectangle) a 491: : a.getBounds(); 492: TabExpander expander = view.getTabExpander(); 493: int width = Utilities.getTabbedTextWidth(txt, fontMetrics, bounds.x, 494: expander, 495: view.getStartOffset()); 496: int height = fontMetrics.getHeight(); 497: Rectangle result = new Rectangle(bounds.x + width, bounds.y, 498: 0, height); 499: return result; 500: } 501: 502: /** 503: * Determine the span of the glyphs from location <code>p0</code> to 504: * location <code>p1</code>. If <code>te</code> is not <code>null</code>, 505: * then TABs are expanded using this <code>TabExpander</code>. 506: * The parameter <code>x</code> is the location at which the view is 507: * located (this is important when using TAB expansion). 508: * 509: * @param view the glyph view 510: * @param p0 the starting location in the document model 511: * @param p1 the end location in the document model 512: * @param te the tab expander to use 513: * @param x the location at which the view is located 514: * 515: * @return the span of the glyphs from location <code>p0</code> to 516: * location <code>p1</code>, possibly using TAB expansion 517: */ 518: public float getSpan(GlyphView view, int p0, int p1, 519: TabExpander te, float x) 520: { 521: updateFontMetrics(view); 522: Segment txt = view.getText(p0, p1); 523: int span = Utilities.getTabbedTextWidth(txt, fontMetrics, (int) x, te, 524: p0); 525: return span; 526: } 527: 528: /** 529: * Returns the ascent of the text run that is rendered by this 530: * <code>GlyphPainter</code>. 531: * 532: * @param v the glyph view 533: * 534: * @return the ascent of the text run that is rendered by this 535: * <code>GlyphPainter</code> 536: * 537: * @see FontMetrics#getAscent() 538: */ 539: public float getAscent(GlyphView v) 540: { 541: updateFontMetrics(v); 542: return fontMetrics.getAscent(); 543: } 544: 545: /** 546: * Returns the descent of the text run that is rendered by this 547: * <code>GlyphPainter</code>. 548: * 549: * @param v the glyph view 550: * 551: * @return the descent of the text run that is rendered by this 552: * <code>GlyphPainter</code> 553: * 554: * @see FontMetrics#getDescent() 555: */ 556: public float getDescent(GlyphView v) 557: { 558: updateFontMetrics(v); 559: return fontMetrics.getDescent(); 560: } 561: 562: /** 563: * Determines the model offset, so that the text between <code>p0</code> 564: * and this offset fits within the span starting at <code>x</code> with 565: * the length of <code>len</code>. 566: * 567: * @param v the glyph view 568: * @param p0 the starting offset in the model 569: * @param x the start location in the view 570: * @param len the length of the span in the view 571: */ 572: public int getBoundedPosition(GlyphView v, int p0, float x, float len) 573: { 574: updateFontMetrics(v); 575: TabExpander te = v.getTabExpander(); 576: Segment txt = v.getText(p0, v.getEndOffset()); 577: int pos = Utilities.getTabbedTextOffset(txt, fontMetrics, (int) x, 578: (int) (x + len), te, p0, false); 579: return pos + p0; 580: } 581: 582: /** 583: * Maps a visual position into a document location. 584: * 585: * @param v the glyph view 586: * @param x the X coordinate of the visual position 587: * @param y the Y coordinate of the visual position 588: * @param a the allocated region 589: * @param biasRet filled with the bias of the model location on method exit 590: * 591: * @return the model location that represents the specified view location 592: */ 593: public int viewToModel(GlyphView v, float x, float y, Shape a, 594: Bias[] biasRet) 595: { 596: Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 597: int p0 = v.getStartOffset(); 598: int p1 = v.getEndOffset(); 599: TabExpander te = v.getTabExpander(); 600: Segment s = v.getText(p0, p1); 601: int offset = Utilities.getTabbedTextOffset(s, fontMetrics, r.x, (int) x, 602: te, p0); 603: int ret = p0 + offset; 604: if (ret == p1) 605: ret--; 606: biasRet[0] = Position.Bias.Forward; 607: return ret; 608: } 609: 610: private void updateFontMetrics(GlyphView v) 611: { 612: Font font = v.getFont(); 613: if (fontMetrics == null || ! font.equals(fontMetrics.getFont())) 614: { 615: Container c = v.getContainer(); 616: FontMetrics fm; 617: if (c != null) 618: fm = c.getFontMetrics(font); 619: else 620: fm = Toolkit.getDefaultToolkit().getFontMetrics(font); 621: fontMetrics = fm; 622: } 623: } 624: } 625: 626: /** 627: * The GlyphPainer used for painting the glyphs. 628: */ 629: GlyphPainter glyphPainter; 630: 631: /** 632: * The start offset within the document for this view. 633: */ 634: private int offset; 635: 636: /** 637: * The end offset within the document for this view. 638: */ 639: private int length; 640: 641: /** 642: * The x location against which the tab expansion is done. 643: */ 644: private float tabX; 645: 646: /** 647: * The tab expander that is used in this view. 648: */ 649: private TabExpander tabExpander; 650: 651: /** 652: * Creates a new <code>GlyphView</code> for the given <code>Element</code>. 653: * 654: * @param element the element that is rendered by this GlyphView 655: */ 656: public GlyphView(Element element) 657: { 658: super(element); 659: offset = 0; 660: length = 0; 661: } 662: 663: /** 664: * Returns the <code>GlyphPainter</code> that is used by this 665: * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed 666: * <code>null</code> is returned. 667: * 668: * @return the glyph painter that is used by this 669: * glyph view or <code>null</code> if no glyph painter has been 670: * installed 671: */ 672: public GlyphPainter getGlyphPainter() 673: { 674: return glyphPainter; 675: } 676: 677: /** 678: * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>. 679: * 680: * @param painter the glyph painter to be used for this glyph view 681: */ 682: public void setGlyphPainter(GlyphPainter painter) 683: { 684: glyphPainter = painter; 685: } 686: 687: /** 688: * Checks if a <code>GlyphPainer</code> is installed. If this is not the 689: * case, a default painter is installed. 690: */ 691: protected void checkPainter() 692: { 693: if (glyphPainter == null) 694: { 695: if ("true".equals( 696: SystemProperties.getProperty("gnu.javax.swing.noGraphics2D"))) 697: { 698: glyphPainter = new DefaultGlyphPainter(); 699: } 700: else 701: { 702: Segment s = getText(getStartOffset(), getEndOffset()); 703: glyphPainter = new J2DGlyphPainter(s.toString(), getFont(), 704: new FontRenderContext(null, 705: false, 706: false)); 707: } 708: } 709: } 710: 711: /** 712: * Renders the <code>Element</code> that is associated with this 713: * <code>View</code>. 714: * 715: * @param g the <code>Graphics</code> context to render to 716: * @param a the allocated region for the <code>Element</code> 717: */ 718: public void paint(Graphics g, Shape a) 719: { 720: checkPainter(); 721: int p0 = getStartOffset(); 722: int p1 = getEndOffset(); 723: 724: Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 725: Container c = getContainer(); 726: 727: Color fg = getForeground(); 728: JTextComponent tc = null; 729: if (c instanceof JTextComponent) 730: { 731: tc = (JTextComponent) c; 732: if (! tc.isEnabled()) 733: fg = tc.getDisabledTextColor(); 734: } 735: Color bg = getBackground(); 736: if (bg != null) 737: { 738: g.setColor(bg); 739: g.fillRect(r.x, r.y, r.width, r.height); 740: } 741: 742: 743: // Paint layered highlights if there are any. 744: if (tc != null) 745: { 746: Highlighter h = tc.getHighlighter(); 747: if (h instanceof LayeredHighlighter) 748: { 749: LayeredHighlighter lh = (LayeredHighlighter) h; 750: lh.paintLayeredHighlights(g, p0, p1, a, tc, this); 751: } 752: } 753: 754: g.setColor(fg); 755: glyphPainter.paint(this, g, a, p0, p1); 756: boolean underline = isUnderline(); 757: boolean striked = isStrikeThrough(); 758: if (underline || striked) 759: { 760: View parent = getParent(); 761: // X coordinate. 762: if (parent != null && parent.getEndOffset() == p1) 763: { 764: // Strip whitespace. 765: Segment s = getText(p0, p1); 766: while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1])) 767: { 768: p1--; 769: s.count--; 770: } 771: } 772: int x0 = r.x; 773: int p = getStartOffset(); 774: TabExpander tabEx = getTabExpander(); 775: if (p != p0) 776: x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0); 777: int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0); 778: // Y coordinate. 779: int y = r.y + r.height - (int) glyphPainter.getDescent(this); 780: if (underline) 781: { 782: int yTmp = y; 783: yTmp += 1; 784: g.drawLine(x0, yTmp, x1, yTmp); 785: } 786: if (striked) 787: { 788: int yTmp = y; 789: yTmp -= (int) glyphPainter.getAscent(this); 790: g.drawLine(x0, yTmp, x1, yTmp); 791: } 792: } 793: } 794: 795: 796: /** 797: * Returns the preferred span of the content managed by this 798: * <code>View</code> along the specified <code>axis</code>. 799: * 800: * @param axis the axis 801: * 802: * @return the preferred span of this <code>View</code>. 803: */ 804: public float getPreferredSpan(int axis) 805: { 806: float span = 0; 807: checkPainter(); 808: GlyphPainter painter = getGlyphPainter(); 809: switch (axis) 810: { 811: case X_AXIS: 812: TabExpander tabEx = null; 813: View parent = getParent(); 814: if (parent instanceof TabExpander) 815: tabEx = (TabExpander) parent; 816: span = painter.getSpan(this, getStartOffset(), getEndOffset(), 817: tabEx, 0.F); 818: break; 819: case Y_AXIS: 820: span = painter.getHeight(this); 821: if (isSuperscript()) 822: span += span / 3; 823: break; 824: default: 825: throw new IllegalArgumentException("Illegal axis"); 826: } 827: return span; 828: } 829: 830: /** 831: * Maps a position in the document into the coordinate space of the View. 832: * The output rectangle usually reflects the font height but has a width 833: * of zero. 834: * 835: * @param pos the position of the character in the model 836: * @param a the area that is occupied by the view 837: * @param b either {@link Position.Bias#Forward} or 838: * {@link Position.Bias#Backward} depending on the preferred 839: * direction bias. If <code>null</code> this defaults to 840: * <code>Position.Bias.Forward</code> 841: * 842: * @return a rectangle that gives the location of the document position 843: * inside the view coordinate space 844: * 845: * @throws BadLocationException if <code>pos</code> is invalid 846: * @throws IllegalArgumentException if b is not one of the above listed 847: * valid values 848: */ 849: public Shape modelToView(int pos, Shape a, Position.Bias b) 850: throws BadLocationException 851: { 852: GlyphPainter p = getGlyphPainter(); 853: return p.modelToView(this, pos, b, a); 854: } 855: 856: /** 857: * Maps coordinates from the <code>View</code>'s space into a position 858: * in the document model. 859: * 860: * @param x the x coordinate in the view space 861: * @param y the y coordinate in the view space 862: * @param a the allocation of this <code>View</code> 863: * @param b the bias to use 864: * 865: * @return the position in the document that corresponds to the screen 866: * coordinates <code>x, y</code> 867: */ 868: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 869: { 870: checkPainter(); 871: GlyphPainter painter = getGlyphPainter(); 872: return painter.viewToModel(this, x, y, a, b); 873: } 874: 875: /** 876: * Return the {@link TabExpander} to use. 877: * 878: * @return the {@link TabExpander} to use 879: */ 880: public TabExpander getTabExpander() 881: { 882: return tabExpander; 883: } 884: 885: /** 886: * Returns the preferred span of this view for tab expansion. 887: * 888: * @param x the location of the view 889: * @param te the tab expander to use 890: * 891: * @return the preferred span of this view for tab expansion 892: */ 893: public float getTabbedSpan(float x, TabExpander te) 894: { 895: checkPainter(); 896: TabExpander old = tabExpander; 897: tabExpander = te; 898: if (tabExpander != old) 899: { 900: // Changing the tab expander will lead to a relayout in the X_AXIS. 901: preferenceChanged(null, true, false); 902: } 903: tabX = x; 904: return getGlyphPainter().getSpan(this, getStartOffset(), 905: getEndOffset(), tabExpander, x); 906: } 907: 908: /** 909: * Returns the span of a portion of the view. This is used in TAB expansion 910: * for fragments that don't contain TABs. 911: * 912: * @param p0 the start index 913: * @param p1 the end index 914: * 915: * @return the span of the specified portion of the view 916: */ 917: public float getPartialSpan(int p0, int p1) 918: { 919: checkPainter(); 920: return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX); 921: } 922: 923: /** 924: * Returns the start offset in the document model of the portion 925: * of text that this view is responsible for. 926: * 927: * @return the start offset in the document model of the portion 928: * of text that this view is responsible for 929: */ 930: public int getStartOffset() 931: { 932: Element el = getElement(); 933: int offs = el.getStartOffset(); 934: if (length > 0) 935: offs += offset; 936: return offs; 937: } 938: 939: /** 940: * Returns the end offset in the document model of the portion 941: * of text that this view is responsible for. 942: * 943: * @return the end offset in the document model of the portion 944: * of text that this view is responsible for 945: */ 946: public int getEndOffset() 947: { 948: Element el = getElement(); 949: int offs; 950: if (length > 0) 951: offs = el.getStartOffset() + offset + length; 952: else 953: offs = el.getEndOffset(); 954: return offs; 955: } 956: 957: private Segment cached = new Segment(); 958: 959: /** 960: * Returns the text segment that this view is responsible for. 961: * 962: * @param p0 the start index in the document model 963: * @param p1 the end index in the document model 964: * 965: * @return the text segment that this view is responsible for 966: */ 967: public Segment getText(int p0, int p1) 968: { 969: try 970: { 971: getDocument().getText(p0, p1 - p0, cached); 972: } 973: catch (BadLocationException ex) 974: { 975: AssertionError ae; 976: ae = new AssertionError("BadLocationException should not be " 977: + "thrown here. p0 = " + p0 + ", p1 = " + p1); 978: ae.initCause(ex); 979: throw ae; 980: } 981: 982: return cached; 983: } 984: 985: /** 986: * Returns the font for the text run for which this <code>GlyphView</code> 987: * is responsible. 988: * 989: * @return the font for the text run for which this <code>GlyphView</code> 990: * is responsible 991: */ 992: public Font getFont() 993: { 994: Document doc = getDocument(); 995: Font font = null; 996: if (doc instanceof StyledDocument) 997: { 998: StyledDocument styledDoc = (StyledDocument) doc; 999: font = styledDoc.getFont(getAttributes()); 1000: } 1001: else 1002: { 1003: Container c = getContainer(); 1004: if (c != null) 1005: font = c.getFont(); 1006: } 1007: return font; 1008: } 1009: 1010: /** 1011: * Returns the foreground color which should be used to paint the text. 1012: * This is fetched from the associated element's text attributes using 1013: * {@link StyleConstants#getForeground}. 1014: * 1015: * @return the foreground color which should be used to paint the text 1016: */ 1017: public Color getForeground() 1018: { 1019: Element el = getElement(); 1020: AttributeSet atts = el.getAttributes(); 1021: return StyleConstants.getForeground(atts); 1022: } 1023: 1024: /** 1025: * Returns the background color which should be used to paint the text. 1026: * This is fetched from the associated element's text attributes using 1027: * {@link StyleConstants#getBackground}. 1028: * 1029: * @return the background color which should be used to paint the text 1030: */ 1031: public Color getBackground() 1032: { 1033: Element el = getElement(); 1034: AttributeSet atts = el.getAttributes(); 1035: // We cannot use StyleConstants.getBackground() here, because that returns 1036: // BLACK as default (when background == null). What we need is the 1037: // background setting of the text component instead, which is what we get 1038: // when background == null anyway. 1039: return (Color) atts.getAttribute(StyleConstants.Background); 1040: } 1041: 1042: /** 1043: * Determines whether the text should be rendered strike-through or not. This 1044: * is determined using the method 1045: * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of 1046: * this view. 1047: * 1048: * @return whether the text should be rendered strike-through or not 1049: */ 1050: public boolean isStrikeThrough() 1051: { 1052: Element el = getElement(); 1053: AttributeSet atts = el.getAttributes(); 1054: return StyleConstants.isStrikeThrough(atts); 1055: } 1056: 1057: /** 1058: * Determines whether the text should be rendered as subscript or not. This 1059: * is determined using the method 1060: * {@link StyleConstants#isSubscript(AttributeSet)} on the element of 1061: * this view. 1062: * 1063: * @return whether the text should be rendered as subscript or not 1064: */ 1065: public boolean isSubscript() 1066: { 1067: Element el = getElement(); 1068: AttributeSet atts = el.getAttributes(); 1069: return StyleConstants.isSubscript(atts); 1070: } 1071: 1072: /** 1073: * Determines whether the text should be rendered as superscript or not. This 1074: * is determined using the method 1075: * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of 1076: * this view. 1077: * 1078: * @return whether the text should be rendered as superscript or not 1079: */ 1080: public boolean isSuperscript() 1081: { 1082: Element el = getElement(); 1083: AttributeSet atts = el.getAttributes(); 1084: return StyleConstants.isSuperscript(atts); 1085: } 1086: 1087: /** 1088: * Determines whether the text should be rendered as underlined or not. This 1089: * is determined using the method 1090: * {@link StyleConstants#isUnderline(AttributeSet)} on the element of 1091: * this view. 1092: * 1093: * @return whether the text should be rendered as underlined or not 1094: */ 1095: public boolean isUnderline() 1096: { 1097: Element el = getElement(); 1098: AttributeSet atts = el.getAttributes(); 1099: return StyleConstants.isUnderline(atts); 1100: } 1101: 1102: /** 1103: * Creates and returns a shallow clone of this GlyphView. This is used by 1104: * the {@link #createFragment} and {@link #breakView} methods. 1105: * 1106: * @return a shallow clone of this GlyphView 1107: */ 1108: protected final Object clone() 1109: { 1110: try 1111: { 1112: return super.clone(); 1113: } 1114: catch (CloneNotSupportedException ex) 1115: { 1116: AssertionError err = new AssertionError("CloneNotSupportedException " 1117: + "must not be thrown here"); 1118: err.initCause(ex); 1119: throw err; 1120: } 1121: } 1122: 1123: /** 1124: * Tries to break the view near the specified view span <code>len</code>. 1125: * The glyph view can only be broken in the X direction. For Y direction it 1126: * returns itself. 1127: * 1128: * @param axis the axis for breaking, may be {@link View#X_AXIS} or 1129: * {@link View#Y_AXIS} 1130: * @param p0 the model location where the fragment should start 1131: * @param pos the view position along the axis where the fragment starts 1132: * @param len the desired length of the fragment view 1133: * 1134: * @return the fragment view, or <code>this</code> if breaking was not 1135: * possible 1136: */ 1137: public View breakView(int axis, int p0, float pos, float len) 1138: { 1139: View brokenView = this; 1140: if (axis == X_AXIS) 1141: { 1142: checkPainter(); 1143: int end = glyphPainter.getBoundedPosition(this, p0, pos, len); 1144: int breakLoc = getBreakLocation(p0, end); 1145: if (breakLoc != -1) 1146: end = breakLoc; 1147: if (p0 != getStartOffset() || end != getEndOffset()) 1148: { 1149: brokenView = createFragment(p0, end); 1150: if (brokenView instanceof GlyphView) 1151: ((GlyphView) brokenView).tabX = pos; 1152: } 1153: } 1154: return brokenView; 1155: } 1156: 1157: /** 1158: * Determines how well the specified view location is suitable for inserting 1159: * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then 1160: * this method forwards to the superclass, if <code>axis</code> is 1161: * <code>View.X_AXIS</code> then this method returns 1162: * {@link View#ExcellentBreakWeight} if there is a suitable break location 1163: * (usually whitespace) within the specified view span, or 1164: * {@link View#GoodBreakWeight} if not. 1165: * 1166: * @param axis the axis along which the break weight is requested 1167: * @param pos the starting view location 1168: * @param len the length of the span at which the view should be broken 1169: * 1170: * @return the break weight 1171: */ 1172: public int getBreakWeight(int axis, float pos, float len) 1173: { 1174: int weight; 1175: if (axis == Y_AXIS) 1176: weight = super.getBreakWeight(axis, pos, len); 1177: else 1178: { 1179: checkPainter(); 1180: int start = getStartOffset(); 1181: int end = glyphPainter.getBoundedPosition(this, start, pos, len); 1182: if (end == 0) 1183: weight = BadBreakWeight; 1184: else 1185: { 1186: if (getBreakLocation(start, end) != -1) 1187: weight = ExcellentBreakWeight; 1188: else 1189: weight = GoodBreakWeight; 1190: } 1191: } 1192: return weight; 1193: } 1194: 1195: private int getBreakLocation(int start, int end) 1196: { 1197: int loc = -1; 1198: Segment s = getText(start, end); 1199: for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous()) 1200: { 1201: if (Character.isWhitespace(c)) 1202: { 1203: loc = s.getIndex() - s.getBeginIndex() + 1 + start; 1204: } 1205: } 1206: return loc; 1207: } 1208: 1209: /** 1210: * Receives notification that some text attributes have changed within the 1211: * text fragment that this view is responsible for. This calls 1212: * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for 1213: * both width and height. 1214: * 1215: * @param e the document event describing the change; not used here 1216: * @param a the view allocation on screen; not used here 1217: * @param vf the view factory; not used here 1218: */ 1219: public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) 1220: { 1221: preferenceChanged(null, true, true); 1222: } 1223: 1224: /** 1225: * Receives notification that some text has been inserted within the 1226: * text fragment that this view is responsible for. This calls 1227: * {@link View#preferenceChanged(View, boolean, boolean)} for the 1228: * direction in which the glyphs are rendered. 1229: * 1230: * @param e the document event describing the change; not used here 1231: * @param a the view allocation on screen; not used here 1232: * @param vf the view factory; not used here 1233: */ 1234: public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) 1235: { 1236: preferenceChanged(null, true, false); 1237: } 1238: 1239: /** 1240: * Receives notification that some text has been removed within the 1241: * text fragment that this view is responsible for. This calls 1242: * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for 1243: * width. 1244: * 1245: * @param e the document event describing the change; not used here 1246: * @param a the view allocation on screen; not used here 1247: * @param vf the view factory; not used here 1248: */ 1249: public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) 1250: { 1251: preferenceChanged(null, true, false); 1252: } 1253: 1254: /** 1255: * Creates a fragment view of this view that starts at <code>p0</code> and 1256: * ends at <code>p1</code>. 1257: * 1258: * @param p0 the start location for the fragment view 1259: * @param p1 the end location for the fragment view 1260: * 1261: * @return the fragment view 1262: */ 1263: public View createFragment(int p0, int p1) 1264: { 1265: checkPainter(); 1266: Element el = getElement(); 1267: GlyphView fragment = (GlyphView) clone(); 1268: fragment.offset = p0 - el.getStartOffset(); 1269: fragment.length = p1 - p0; 1270: fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1); 1271: return fragment; 1272: } 1273: 1274: /** 1275: * Returns the alignment of this view along the specified axis. For the Y 1276: * axis this is <code>(height - descent) / height</code> for the used font, 1277: * so that it is aligned along the baseline. 1278: * For the X axis the superclass is called. 1279: */ 1280: public float getAlignment(int axis) 1281: { 1282: checkPainter(); 1283: float align; 1284: if (axis == Y_AXIS) 1285: { 1286: GlyphPainter painter = getGlyphPainter(); 1287: float height = painter.getHeight(this); 1288: float descent = painter.getDescent(this); 1289: float ascent = painter.getAscent(this); 1290: if (isSuperscript()) 1291: align = 1.0F; 1292: else if (isSubscript()) 1293: align = height > 0 ? (height - (descent + (ascent / 2))) / height 1294: : 0; 1295: else 1296: align = height > 0 ? (height - descent) / height : 0; 1297: } 1298: else 1299: align = super.getAlignment(axis); 1300: 1301: return align; 1302: } 1303: 1304: /** 1305: * Returns the model location that should be used to place a caret when 1306: * moving the caret through the document. 1307: * 1308: * @param pos the current model location 1309: * @param bias the bias for <code>p</code> 1310: * @param a the allocated region for the glyph view 1311: * @param direction the direction from the current position; Must be one of 1312: * {@link SwingConstants#EAST}, {@link SwingConstants#WEST}, 1313: * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH} 1314: * @param biasRet filled with the bias of the resulting location when method 1315: * returns 1316: * 1317: * @return the location within the document that should be used to place the 1318: * caret when moving the caret around the document 1319: * 1320: * @throws BadLocationException if <code>pos</code> is an invalid model 1321: * location 1322: * @throws IllegalArgumentException if <code>d</code> is invalid 1323: */ 1324: public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a, 1325: int direction, Position.Bias[] biasRet) 1326: throws BadLocationException 1327: { 1328: checkPainter(); 1329: GlyphPainter painter = getGlyphPainter(); 1330: return painter.getNextVisualPositionFrom(this, pos, bias, a, direction, 1331: biasRet); 1332: } 1333: }