GNU Classpath (0.95) | |
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: System.err.println("fill background: " + bg); 740: g.fillRect(r.x, r.y, r.width, r.height); 741: } 742: 743: 744: // Paint layered highlights if there are any. 745: if (tc != null) 746: { 747: Highlighter h = tc.getHighlighter(); 748: if (h instanceof LayeredHighlighter) 749: { 750: LayeredHighlighter lh = (LayeredHighlighter) h; 751: lh.paintLayeredHighlights(g, p0, p1, a, tc, this); 752: } 753: } 754: 755: g.setColor(fg); 756: glyphPainter.paint(this, g, a, p0, p1); 757: boolean underline = isUnderline(); 758: boolean striked = isStrikeThrough(); 759: if (underline || striked) 760: { 761: View parent = getParent(); 762: // X coordinate. 763: if (parent != null && parent.getEndOffset() == p1) 764: { 765: // Strip whitespace. 766: Segment s = getText(p0, p1); 767: while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1])) 768: { 769: p1--; 770: s.count--; 771: } 772: } 773: int x0 = r.x; 774: int p = getStartOffset(); 775: TabExpander tabEx = getTabExpander(); 776: if (p != p0) 777: x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0); 778: int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0); 779: // Y coordinate. 780: int y = r.y + r.height - (int) glyphPainter.getDescent(this); 781: if (underline) 782: { 783: int yTmp = y; 784: yTmp += 1; 785: g.drawLine(x0, yTmp, x1, yTmp); 786: } 787: if (striked) 788: { 789: int yTmp = y; 790: yTmp -= (int) glyphPainter.getAscent(this); 791: g.drawLine(x0, yTmp, x1, yTmp); 792: } 793: } 794: } 795: 796: 797: /** 798: * Returns the preferred span of the content managed by this 799: * <code>View</code> along the specified <code>axis</code>. 800: * 801: * @param axis the axis 802: * 803: * @return the preferred span of this <code>View</code>. 804: */ 805: public float getPreferredSpan(int axis) 806: { 807: float span = 0; 808: checkPainter(); 809: GlyphPainter painter = getGlyphPainter(); 810: switch (axis) 811: { 812: case X_AXIS: 813: TabExpander tabEx = null; 814: View parent = getParent(); 815: if (parent instanceof TabExpander) 816: tabEx = (TabExpander) parent; 817: span = painter.getSpan(this, getStartOffset(), getEndOffset(), 818: tabEx, 0.F); 819: break; 820: case Y_AXIS: 821: span = painter.getHeight(this); 822: if (isSuperscript()) 823: span += span / 3; 824: break; 825: default: 826: throw new IllegalArgumentException("Illegal axis"); 827: } 828: return span; 829: } 830: 831: /** 832: * Maps a position in the document into the coordinate space of the View. 833: * The output rectangle usually reflects the font height but has a width 834: * of zero. 835: * 836: * @param pos the position of the character in the model 837: * @param a the area that is occupied by the view 838: * @param b either {@link Position.Bias#Forward} or 839: * {@link Position.Bias#Backward} depending on the preferred 840: * direction bias. If <code>null</code> this defaults to 841: * <code>Position.Bias.Forward</code> 842: * 843: * @return a rectangle that gives the location of the document position 844: * inside the view coordinate space 845: * 846: * @throws BadLocationException if <code>pos</code> is invalid 847: * @throws IllegalArgumentException if b is not one of the above listed 848: * valid values 849: */ 850: public Shape modelToView(int pos, Shape a, Position.Bias b) 851: throws BadLocationException 852: { 853: GlyphPainter p = getGlyphPainter(); 854: return p.modelToView(this, pos, b, a); 855: } 856: 857: /** 858: * Maps coordinates from the <code>View</code>'s space into a position 859: * in the document model. 860: * 861: * @param x the x coordinate in the view space 862: * @param y the y coordinate in the view space 863: * @param a the allocation of this <code>View</code> 864: * @param b the bias to use 865: * 866: * @return the position in the document that corresponds to the screen 867: * coordinates <code>x, y</code> 868: */ 869: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 870: { 871: checkPainter(); 872: GlyphPainter painter = getGlyphPainter(); 873: return painter.viewToModel(this, x, y, a, b); 874: } 875: 876: /** 877: * Return the {@link TabExpander} to use. 878: * 879: * @return the {@link TabExpander} to use 880: */ 881: public TabExpander getTabExpander() 882: { 883: return tabExpander; 884: } 885: 886: /** 887: * Returns the preferred span of this view for tab expansion. 888: * 889: * @param x the location of the view 890: * @param te the tab expander to use 891: * 892: * @return the preferred span of this view for tab expansion 893: */ 894: public float getTabbedSpan(float x, TabExpander te) 895: { 896: checkPainter(); 897: TabExpander old = tabExpander; 898: tabExpander = te; 899: if (tabExpander != old) 900: { 901: // Changing the tab expander will lead to a relayout in the X_AXIS. 902: preferenceChanged(null, true, false); 903: } 904: tabX = x; 905: return getGlyphPainter().getSpan(this, getStartOffset(), 906: getEndOffset(), tabExpander, x); 907: } 908: 909: /** 910: * Returns the span of a portion of the view. This is used in TAB expansion 911: * for fragments that don't contain TABs. 912: * 913: * @param p0 the start index 914: * @param p1 the end index 915: * 916: * @return the span of the specified portion of the view 917: */ 918: public float getPartialSpan(int p0, int p1) 919: { 920: checkPainter(); 921: return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX); 922: } 923: 924: /** 925: * Returns the start offset in the document model of the portion 926: * of text that this view is responsible for. 927: * 928: * @return the start offset in the document model of the portion 929: * of text that this view is responsible for 930: */ 931: public int getStartOffset() 932: { 933: Element el = getElement(); 934: int offs = el.getStartOffset(); 935: if (length > 0) 936: offs += offset; 937: return offs; 938: } 939: 940: /** 941: * Returns the end offset in the document model of the portion 942: * of text that this view is responsible for. 943: * 944: * @return the end offset in the document model of the portion 945: * of text that this view is responsible for 946: */ 947: public int getEndOffset() 948: { 949: Element el = getElement(); 950: int offs; 951: if (length > 0) 952: offs = el.getStartOffset() + offset + length; 953: else 954: offs = el.getEndOffset(); 955: return offs; 956: } 957: 958: private Segment cached = new Segment(); 959: 960: /** 961: * Returns the text segment that this view is responsible for. 962: * 963: * @param p0 the start index in the document model 964: * @param p1 the end index in the document model 965: * 966: * @return the text segment that this view is responsible for 967: */ 968: public Segment getText(int p0, int p1) 969: { 970: try 971: { 972: getDocument().getText(p0, p1 - p0, cached); 973: } 974: catch (BadLocationException ex) 975: { 976: AssertionError ae; 977: ae = new AssertionError("BadLocationException should not be " 978: + "thrown here. p0 = " + p0 + ", p1 = " + p1); 979: ae.initCause(ex); 980: throw ae; 981: } 982: 983: return cached; 984: } 985: 986: /** 987: * Returns the font for the text run for which this <code>GlyphView</code> 988: * is responsible. 989: * 990: * @return the font for the text run for which this <code>GlyphView</code> 991: * is responsible 992: */ 993: public Font getFont() 994: { 995: Document doc = getDocument(); 996: Font font = null; 997: if (doc instanceof StyledDocument) 998: { 999: StyledDocument styledDoc = (StyledDocument) doc; 1000: font = styledDoc.getFont(getAttributes()); 1001: } 1002: else 1003: { 1004: Container c = getContainer(); 1005: if (c != null) 1006: font = c.getFont(); 1007: } 1008: return font; 1009: } 1010: 1011: /** 1012: * Returns the foreground color which should be used to paint the text. 1013: * This is fetched from the associated element's text attributes using 1014: * {@link StyleConstants#getForeground}. 1015: * 1016: * @return the foreground color which should be used to paint the text 1017: */ 1018: public Color getForeground() 1019: { 1020: Element el = getElement(); 1021: AttributeSet atts = el.getAttributes(); 1022: return StyleConstants.getForeground(atts); 1023: } 1024: 1025: /** 1026: * Returns the background color which should be used to paint the text. 1027: * This is fetched from the associated element's text attributes using 1028: * {@link StyleConstants#getBackground}. 1029: * 1030: * @return the background color which should be used to paint the text 1031: */ 1032: public Color getBackground() 1033: { 1034: Element el = getElement(); 1035: AttributeSet atts = el.getAttributes(); 1036: // We cannot use StyleConstants.getBackground() here, because that returns 1037: // BLACK as default (when background == null). What we need is the 1038: // background setting of the text component instead, which is what we get 1039: // when background == null anyway. 1040: return (Color) atts.getAttribute(StyleConstants.Background); 1041: } 1042: 1043: /** 1044: * Determines whether the text should be rendered strike-through or not. This 1045: * is determined using the method 1046: * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of 1047: * this view. 1048: * 1049: * @return whether the text should be rendered strike-through or not 1050: */ 1051: public boolean isStrikeThrough() 1052: { 1053: Element el = getElement(); 1054: AttributeSet atts = el.getAttributes(); 1055: return StyleConstants.isStrikeThrough(atts); 1056: } 1057: 1058: /** 1059: * Determines whether the text should be rendered as subscript or not. This 1060: * is determined using the method 1061: * {@link StyleConstants#isSubscript(AttributeSet)} on the element of 1062: * this view. 1063: * 1064: * @return whether the text should be rendered as subscript or not 1065: */ 1066: public boolean isSubscript() 1067: { 1068: Element el = getElement(); 1069: AttributeSet atts = el.getAttributes(); 1070: return StyleConstants.isSubscript(atts); 1071: } 1072: 1073: /** 1074: * Determines whether the text should be rendered as superscript or not. This 1075: * is determined using the method 1076: * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of 1077: * this view. 1078: * 1079: * @return whether the text should be rendered as superscript or not 1080: */ 1081: public boolean isSuperscript() 1082: { 1083: Element el = getElement(); 1084: AttributeSet atts = el.getAttributes(); 1085: return StyleConstants.isSuperscript(atts); 1086: } 1087: 1088: /** 1089: * Determines whether the text should be rendered as underlined or not. This 1090: * is determined using the method 1091: * {@link StyleConstants#isUnderline(AttributeSet)} on the element of 1092: * this view. 1093: * 1094: * @return whether the text should be rendered as underlined or not 1095: */ 1096: public boolean isUnderline() 1097: { 1098: Element el = getElement(); 1099: AttributeSet atts = el.getAttributes(); 1100: return StyleConstants.isUnderline(atts); 1101: } 1102: 1103: /** 1104: * Creates and returns a shallow clone of this GlyphView. This is used by 1105: * the {@link #createFragment} and {@link #breakView} methods. 1106: * 1107: * @return a shallow clone of this GlyphView 1108: */ 1109: protected final Object clone() 1110: { 1111: try 1112: { 1113: return super.clone(); 1114: } 1115: catch (CloneNotSupportedException ex) 1116: { 1117: AssertionError err = new AssertionError("CloneNotSupportedException " 1118: + "must not be thrown here"); 1119: err.initCause(ex); 1120: throw err; 1121: } 1122: } 1123: 1124: /** 1125: * Tries to break the view near the specified view span <code>len</code>. 1126: * The glyph view can only be broken in the X direction. For Y direction it 1127: * returns itself. 1128: * 1129: * @param axis the axis for breaking, may be {@link View#X_AXIS} or 1130: * {@link View#Y_AXIS} 1131: * @param p0 the model location where the fragment should start 1132: * @param pos the view position along the axis where the fragment starts 1133: * @param len the desired length of the fragment view 1134: * 1135: * @return the fragment view, or <code>this</code> if breaking was not 1136: * possible 1137: */ 1138: public View breakView(int axis, int p0, float pos, float len) 1139: { 1140: View brokenView = this; 1141: if (axis == X_AXIS) 1142: { 1143: checkPainter(); 1144: int end = glyphPainter.getBoundedPosition(this, p0, pos, len); 1145: int breakLoc = getBreakLocation(p0, end); 1146: if (breakLoc != -1) 1147: end = breakLoc; 1148: if (p0 != getStartOffset() || end != getEndOffset()) 1149: { 1150: brokenView = createFragment(p0, end); 1151: if (brokenView instanceof GlyphView) 1152: ((GlyphView) brokenView).tabX = pos; 1153: } 1154: } 1155: return brokenView; 1156: } 1157: 1158: /** 1159: * Determines how well the specified view location is suitable for inserting 1160: * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then 1161: * this method forwards to the superclass, if <code>axis</code> is 1162: * <code>View.X_AXIS</code> then this method returns 1163: * {@link View#ExcellentBreakWeight} if there is a suitable break location 1164: * (usually whitespace) within the specified view span, or 1165: * {@link View#GoodBreakWeight} if not. 1166: * 1167: * @param axis the axis along which the break weight is requested 1168: * @param pos the starting view location 1169: * @param len the length of the span at which the view should be broken 1170: * 1171: * @return the break weight 1172: */ 1173: public int getBreakWeight(int axis, float pos, float len) 1174: { 1175: int weight; 1176: if (axis == Y_AXIS) 1177: weight = super.getBreakWeight(axis, pos, len); 1178: else 1179: { 1180: checkPainter(); 1181: int start = getStartOffset(); 1182: int end = glyphPainter.getBoundedPosition(this, start, pos, len); 1183: if (end == 0) 1184: weight = BadBreakWeight; 1185: else 1186: { 1187: if (getBreakLocation(start, end) != -1) 1188: weight = ExcellentBreakWeight; 1189: else 1190: weight = GoodBreakWeight; 1191: } 1192: } 1193: return weight; 1194: } 1195: 1196: private int getBreakLocation(int start, int end) 1197: { 1198: int loc = -1; 1199: Segment s = getText(start, end); 1200: for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous()) 1201: { 1202: if (Character.isWhitespace(c)) 1203: { 1204: loc = s.getIndex() - s.getBeginIndex() + 1 + start; 1205: } 1206: } 1207: return loc; 1208: } 1209: 1210: /** 1211: * Receives notification that some text attributes have changed within the 1212: * text fragment that this view is responsible for. This calls 1213: * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for 1214: * both width and height. 1215: * 1216: * @param e the document event describing the change; not used here 1217: * @param a the view allocation on screen; not used here 1218: * @param vf the view factory; not used here 1219: */ 1220: public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) 1221: { 1222: preferenceChanged(null, true, true); 1223: } 1224: 1225: /** 1226: * Receives notification that some text has been inserted within the 1227: * text fragment that this view is responsible for. This calls 1228: * {@link View#preferenceChanged(View, boolean, boolean)} for the 1229: * direction in which the glyphs are rendered. 1230: * 1231: * @param e the document event describing the change; not used here 1232: * @param a the view allocation on screen; not used here 1233: * @param vf the view factory; not used here 1234: */ 1235: public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) 1236: { 1237: preferenceChanged(null, true, false); 1238: } 1239: 1240: /** 1241: * Receives notification that some text has been removed within the 1242: * text fragment that this view is responsible for. This calls 1243: * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for 1244: * width. 1245: * 1246: * @param e the document event describing the change; not used here 1247: * @param a the view allocation on screen; not used here 1248: * @param vf the view factory; not used here 1249: */ 1250: public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) 1251: { 1252: preferenceChanged(null, true, false); 1253: } 1254: 1255: /** 1256: * Creates a fragment view of this view that starts at <code>p0</code> and 1257: * ends at <code>p1</code>. 1258: * 1259: * @param p0 the start location for the fragment view 1260: * @param p1 the end location for the fragment view 1261: * 1262: * @return the fragment view 1263: */ 1264: public View createFragment(int p0, int p1) 1265: { 1266: checkPainter(); 1267: Element el = getElement(); 1268: GlyphView fragment = (GlyphView) clone(); 1269: fragment.offset = p0 - el.getStartOffset(); 1270: fragment.length = p1 - p0; 1271: fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1); 1272: return fragment; 1273: } 1274: 1275: /** 1276: * Returns the alignment of this view along the specified axis. For the Y 1277: * axis this is <code>(height - descent) / height</code> for the used font, 1278: * so that it is aligned along the baseline. 1279: * For the X axis the superclass is called. 1280: */ 1281: public float getAlignment(int axis) 1282: { 1283: checkPainter(); 1284: float align; 1285: if (axis == Y_AXIS) 1286: { 1287: GlyphPainter painter = getGlyphPainter(); 1288: float height = painter.getHeight(this); 1289: float descent = painter.getDescent(this); 1290: float ascent = painter.getAscent(this); 1291: if (isSuperscript()) 1292: align = 1.0F; 1293: else if (isSubscript()) 1294: align = height > 0 ? (height - (descent + (ascent / 2))) / height 1295: : 0; 1296: else 1297: align = height > 0 ? (height - descent) / height : 0; 1298: } 1299: else 1300: align = super.getAlignment(axis); 1301: 1302: return align; 1303: } 1304: 1305: /** 1306: * Returns the model location that should be used to place a caret when 1307: * moving the caret through the document. 1308: * 1309: * @param pos the current model location 1310: * @param bias the bias for <code>p</code> 1311: * @param a the allocated region for the glyph view 1312: * @param direction the direction from the current position; Must be one of 1313: * {@link SwingConstants#EAST}, {@link SwingConstants#WEST}, 1314: * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH} 1315: * @param biasRet filled with the bias of the resulting location when method 1316: * returns 1317: * 1318: * @return the location within the document that should be used to place the 1319: * caret when moving the caret around the document 1320: * 1321: * @throws BadLocationException if <code>pos</code> is an invalid model 1322: * location 1323: * @throws IllegalArgumentException if <code>d</code> is invalid 1324: */ 1325: public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a, 1326: int direction, Position.Bias[] biasRet) 1327: throws BadLocationException 1328: { 1329: checkPainter(); 1330: GlyphPainter painter = getGlyphPainter(); 1331: return painter.getNextVisualPositionFrom(this, pos, bias, a, direction, 1332: biasRet); 1333: } 1334: }
GNU Classpath (0.95) |