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 */ GlyphPainter()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 */ getAscent(GlyphView v)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 */ getDescent(GlyphView v)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 */ getHeight(GlyphView view)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 */ getBoundedPosition(GlyphView v, int p0, float x, float len)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 */ paint(GlyphView view, Graphics g, Shape a, int p0, int p1)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 */ modelToView(GlyphView view, int pos, Position.Bias b, Shape a)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 */ viewToModel(GlyphView v, float x, float y, Shape a, Position.Bias[] biasRet)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 */ getSpan(GlyphView view, int p0, int p1, TabExpander te, float x)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 */ getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet)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 */ getPainter(GlyphView v, int p0, int p1)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 */ J2DGlyphPainter(String str, Font font, FontRenderContext frc)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 */ getPainter(GlyphView v, int p0, int p1)285 public GlyphPainter getPainter(GlyphView v, int p0, int p1) 286 { 287 return null; 288 } 289 290 /** 291 * Delegates to the text layout. 292 */ getAscent(GlyphView v)293 public float getAscent(GlyphView v) 294 { 295 return textLayout.getAscent(); 296 } 297 298 /** 299 * Delegates to the text layout. 300 */ getBoundedPosition(GlyphView v, int p0, float x, float len)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 */ getDescent(GlyphView v)319 public float getDescent(GlyphView v) 320 { 321 return textLayout.getDescent(); 322 } 323 324 /** 325 * Delegates to the text layout. 326 */ getHeight(GlyphView view)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 */ getSpan(GlyphView v, int p0, int p1, TabExpander te, float x)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 */ modelToView(GlyphView v, int pos, Bias b, Shape a)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 */ paint(GlyphView view, Graphics g, Shape a, int p0, int p1)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 */ viewToModel(GlyphView v, float x, float y, Shape a, Bias[] biasRet)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 */ getHeight(GlyphView view)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 */ paint(GlyphView view, Graphics g, Shape a, int p0, int p1)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 */ modelToView(GlyphView view, int pos, Position.Bias b, Shape a)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 */ getSpan(GlyphView view, int p0, int p1, TabExpander te, float x)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 */ getAscent(GlyphView v)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 */ getDescent(GlyphView v)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 */ getBoundedPosition(GlyphView v, int p0, float x, float len)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 */ viewToModel(GlyphView v, float x, float y, Shape a, Bias[] biasRet)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 updateFontMetrics(GlyphView v)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 */ GlyphView(Element element)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 */ getGlyphPainter()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 */ setGlyphPainter(GlyphPainter painter)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 */ checkPainter()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 */ paint(Graphics g, Shape a)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 */ getPreferredSpan(int axis)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 */ modelToView(int pos, Shape a, Position.Bias b)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 */ viewToModel(float x, float y, Shape a, Position.Bias[] b)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 */ getTabExpander()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 */ getTabbedSpan(float x, TabExpander te)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 */ getPartialSpan(int p0, int p1)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 */ getStartOffset()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 */ getEndOffset()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 */ getText(int p0, int p1)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 */ getFont()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 */ getForeground()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 */ getBackground()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 */ isStrikeThrough()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 */ isSubscript()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 */ isSuperscript()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 */ isUnderline()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 */ clone()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 */ breakView(int axis, int p0, float pos, float len)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 */ getBreakWeight(int axis, float pos, float len)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 getBreakLocation(int start, int end)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 */ changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)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 */ insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)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 */ removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)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 */ createFragment(int p0, int p1)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 */ getAlignment(int axis)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 */ getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a, int direction, Position.Bias[] biasRet)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 } 1334