001 /* GlyphView.java -- A view to render styled text 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text; 040 041 import gnu.classpath.SystemProperties; 042 043 import java.awt.Color; 044 import java.awt.Container; 045 import java.awt.Font; 046 import java.awt.FontMetrics; 047 import java.awt.Graphics; 048 import java.awt.Graphics2D; 049 import java.awt.Rectangle; 050 import java.awt.Shape; 051 import java.awt.Toolkit; 052 import java.awt.font.FontRenderContext; 053 import java.awt.font.TextHitInfo; 054 import java.awt.font.TextLayout; 055 import java.awt.geom.Rectangle2D; 056 057 import javax.swing.SwingConstants; 058 import javax.swing.event.DocumentEvent; 059 import javax.swing.text.Position.Bias; 060 061 /** 062 * Renders a run of styled text. This {@link View} subclass paints the 063 * characters of the <code>Element</code> it is responsible for using 064 * the style information from that <code>Element</code>. 065 * 066 * @author Roman Kennke (roman@kennke.org) 067 */ 068 public class GlyphView extends View implements TabableView, Cloneable 069 { 070 071 /** 072 * An abstract base implementation for a glyph painter for 073 * <code>GlyphView</code>. 074 */ 075 public abstract static class GlyphPainter 076 { 077 /** 078 * Creates a new <code>GlyphPainer</code>. 079 */ 080 public GlyphPainter() 081 { 082 // Nothing to do here. 083 } 084 085 /** 086 * Returns the ascent of the font that is used by this glyph painter. 087 * 088 * @param v the glyph view 089 * 090 * @return the ascent of the font that is used by this glyph painter 091 */ 092 public abstract float getAscent(GlyphView v); 093 094 /** 095 * Returns the descent of the font that is used by this glyph painter. 096 * 097 * @param v the glyph view 098 * 099 * @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 }