Source for javax.swing.text.GlyphView

   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: }