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