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 java.awt.Color;
  42: import java.awt.Font;
  43: import java.awt.FontMetrics;
  44: import java.awt.Graphics;
  45: import java.awt.Rectangle;
  46: import java.awt.Shape;
  47: import java.awt.Toolkit;
  48: import java.text.BreakIterator;
  49: 
  50: import javax.swing.SwingConstants;
  51: import javax.swing.event.DocumentEvent;
  52: import javax.swing.text.Position.Bias;
  53: 
  54: /**
  55:  * Renders a run of styled text. This {@link View} subclass paints the
  56:  * characters of the <code>Element</code> it is responsible for using
  57:  * the style information from that <code>Element</code>.
  58:  *
  59:  * @author Roman Kennke (roman@kennke.org)
  60:  */
  61: public class GlyphView extends View implements TabableView, Cloneable
  62: {
  63: 
  64:   /**
  65:    * An abstract base implementation for a glyph painter for
  66:    * <code>GlyphView</code>.
  67:    */
  68:   public abstract static class GlyphPainter
  69:   {
  70:     /**
  71:      * Creates a new <code>GlyphPainer</code>.
  72:      */
  73:     public GlyphPainter()
  74:     {
  75:       // Nothing to do here.
  76:     }
  77: 
  78:     /**
  79:      * Returns the ascent of the font that is used by this glyph painter.
  80:      *
  81:      * @param v the glyph view
  82:      *
  83:      * @return the ascent of the font that is used by this glyph painter
  84:      */
  85:     public abstract float getAscent(GlyphView v);
  86: 
  87:     /**
  88:      * Returns the descent of the font that is used by this glyph painter.
  89:      *
  90:      * @param v the glyph view
  91:      *
  92:      * @return the descent of the font that is used by this glyph painter
  93:      */
  94:     public abstract float getDescent(GlyphView v);
  95: 
  96:     /**
  97:      * Returns the full height of the rendered text.
  98:      *
  99:      * @return the full height of the rendered text
 100:      */
 101:     public abstract float getHeight(GlyphView view);
 102: 
 103:     /**
 104:      * Determines the model offset, so that the text between <code>p0</code>
 105:      * and this offset fits within the span starting at <code>x</code> with
 106:      * the length of <code>len</code>. 
 107:      *
 108:      * @param v the glyph view
 109:      * @param p0 the starting offset in the model
 110:      * @param x the start location in the view
 111:      * @param len the length of the span in the view
 112:      */
 113:     public abstract int getBoundedPosition(GlyphView v, int p0, float x,
 114:                                            float len);
 115: 
 116:     /**
 117:      * Paints the glyphs.
 118:      *
 119:      * @param view the glyph view to paint
 120:      * @param g the graphics context to use for painting
 121:      * @param a the allocation of the glyph view
 122:      * @param p0 the start position (in the model) from which to paint
 123:      * @param p1 the end position (in the model) to which to paint
 124:      */
 125:     public abstract void paint(GlyphView view, Graphics g, Shape a, int p0,
 126:                                int p1);
 127: 
 128:     /**
 129:      * Maps a position in the document into the coordinate space of the View.
 130:      * The output rectangle usually reflects the font height but has a width
 131:      * of zero.
 132:      *
 133:      * @param view the glyph view
 134:      * @param pos the position of the character in the model
 135:      * @param a the area that is occupied by the view
 136:      * @param b either {@link Position.Bias#Forward} or
 137:      *        {@link Position.Bias#Backward} depending on the preferred
 138:      *        direction bias. If <code>null</code> this defaults to
 139:      *        <code>Position.Bias.Forward</code>
 140:      *
 141:      * @return a rectangle that gives the location of the document position
 142:      *         inside the view coordinate space
 143:      *
 144:      * @throws BadLocationException if <code>pos</code> is invalid
 145:      * @throws IllegalArgumentException if b is not one of the above listed
 146:      *         valid values
 147:      */
 148:     public abstract Shape modelToView(GlyphView view, int pos, Position.Bias b,
 149:                                       Shape a)
 150:       throws BadLocationException;
 151: 
 152:     /**
 153:      * Maps a visual position into a document location.
 154:      *
 155:      * @param v the glyph view
 156:      * @param x the X coordinate of the visual position
 157:      * @param y the Y coordinate of the visual position
 158:      * @param a the allocated region
 159:      * @param biasRet filled with the bias of the model location on method exit
 160:      *
 161:      * @return the model location that represents the specified view location
 162:      */
 163:     public abstract int viewToModel(GlyphView v, float x, float y, Shape a,
 164:                                     Position.Bias[] biasRet);
 165: 
 166:     /**
 167:      * Determine the span of the glyphs from location <code>p0</code> to
 168:      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
 169:      * then TABs are expanded using this <code>TabExpander</code>.
 170:      * The parameter <code>x</code> is the location at which the view is
 171:      * located (this is important when using TAB expansion).
 172:      *
 173:      * @param view the glyph view
 174:      * @param p0 the starting location in the document model
 175:      * @param p1 the end location in the document model
 176:      * @param te the tab expander to use
 177:      * @param x the location at which the view is located
 178:      *
 179:      * @return the span of the glyphs from location <code>p0</code> to
 180:      *         location <code>p1</code>, possibly using TAB expansion
 181:      */
 182:     public abstract float getSpan(GlyphView view, int p0, int p1,
 183:                                   TabExpander te, float x);
 184: 
 185: 
 186:     /**
 187:      * Returns the model location that should be used to place a caret when
 188:      * moving the caret through the document.
 189:      *
 190:      * @param v the glyph view
 191:      * @param pos the current model location
 192:      * @param b the bias for <code>p</code>
 193:      * @param a the allocated region for the glyph view
 194:      * @param direction the direction from the current position; Must be one of
 195:      *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
 196:      *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
 197:      * @param biasRet filled with the bias of the resulting location when method
 198:      *        returns
 199:      *
 200:      * @return the location within the document that should be used to place the
 201:      *         caret when moving the caret around the document
 202:      *
 203:      * @throws BadLocationException if <code>pos</code> is an invalid model
 204:      *         location
 205:      * @throws IllegalArgumentException if <code>d</code> is invalid
 206:      */
 207:     public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b,
 208:                                          Shape a, int direction,
 209:                                          Position.Bias[] biasRet)
 210:       throws BadLocationException
 211: 
 212:     {
 213:       int result = pos;
 214:       switch (direction)
 215:       {
 216:         case SwingConstants.EAST:
 217:           result = pos + 1;
 218:           break;
 219:         case SwingConstants.WEST:
 220:           result = pos - 1;
 221:           break;
 222:         case SwingConstants.NORTH:
 223:         case SwingConstants.SOUTH:
 224:         default:
 225:           // This should be handled in enclosing view, since the glyph view
 226:           // does not layout vertically.
 227:           break;
 228:       }
 229:       return result;
 230:     }
 231: 
 232:     /**
 233:      * Returns a painter that can be used to render the specified glyph view.
 234:      * If this glyph painter is stateful, then it should return a new instance.
 235:      * However, if this painter is stateless it should return itself. The
 236:      * default behaviour is to return itself.
 237:      *
 238:      * @param v the glyph view for which to create a painter
 239:      * @param p0 the start offset of the rendered area
 240:      * @param p1 the end offset of the rendered area
 241:      *
 242:      * @return a painter that can be used to render the specified glyph view
 243:      */
 244:     public GlyphPainter getPainter(GlyphView v, int p0, int p1)
 245:     {
 246:       return this;
 247:     }
 248:   }
 249: 
 250:   /**
 251:    * The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
 252:    */
 253:   static class DefaultGlyphPainter extends GlyphPainter
 254:   {
 255:     /**
 256:      * Returns the full height of the rendered text.
 257:      *
 258:      * @return the full height of the rendered text
 259:      */
 260:     public float getHeight(GlyphView view)
 261:     {
 262:       Font font = view.getFont();
 263:       FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
 264:       float height = metrics.getHeight();
 265:       return height;
 266:     }
 267:     
 268:     /**
 269:      * Paints the glyphs.
 270:      *
 271:      * @param view the glyph view to paint
 272:      * @param g the graphics context to use for painting
 273:      * @param a the allocation of the glyph view
 274:      * @param p0 the start position (in the model) from which to paint
 275:      * @param p1 the end position (in the model) to which to paint
 276:      */
 277:     public void paint(GlyphView view, Graphics g, Shape a, int p0,
 278:                       int p1)
 279:     {
 280:       Color oldColor = g.getColor();
 281:       int height = (int) getHeight(view);
 282:       Segment txt = view.getText(p0, p1);
 283:       Rectangle bounds = a.getBounds();
 284:       TabExpander tabEx = null;
 285:       View parent = view.getParent();
 286:       if (parent instanceof TabExpander)
 287:         tabEx = (TabExpander) parent;
 288: 
 289:       int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(),
 290:                                                bounds.x, tabEx, txt.offset);
 291:       // Fill the background of the text run.
 292:       Color background = view.getBackground();
 293:       if (background != null)
 294:         {
 295:           g.setColor(background);
 296:           g.fillRect(bounds.x, bounds.y, width, height);
 297:         }
 298:       // Draw the actual text.
 299:       g.setColor(view.getForeground());
 300:       g.setFont(view.getFont());
 301:       int ascent = g.getFontMetrics().getAscent();
 302:       if (view.isSuperscript())
 303:         // TODO: Adjust font for superscripting.
 304:         Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent - height / 2,
 305:                                  g, tabEx, txt.offset);
 306:       else if (view.isSubscript())
 307:         // TODO: Adjust font for subscripting.
 308:         Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent + height / 2,
 309:                                  g, tabEx, txt.offset);
 310:       else
 311:         Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx,
 312:                                  txt.offset);
 313: 
 314:       if (view.isStrikeThrough())
 315:         {
 316:           int strikeHeight = (int) (getAscent(view) / 2);
 317:           g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width,
 318:                      bounds.y + strikeHeight);
 319:         }
 320:       if (view.isUnderline())
 321:         {
 322:           int lineHeight = (int) getAscent(view);
 323:           g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width,
 324:                      bounds.y + lineHeight);
 325:         }
 326:       g.setColor(oldColor);
 327:     }
 328: 
 329:     /**
 330:      * Maps a position in the document into the coordinate space of the View.
 331:      * The output rectangle usually reflects the font height but has a width
 332:      * of zero.
 333:      *
 334:      * @param view the glyph view
 335:      * @param pos the position of the character in the model
 336:      * @param a the area that is occupied by the view
 337:      * @param b either {@link Position.Bias#Forward} or
 338:      *        {@link Position.Bias#Backward} depending on the preferred
 339:      *        direction bias. If <code>null</code> this defaults to
 340:      *        <code>Position.Bias.Forward</code>
 341:      *
 342:      * @return a rectangle that gives the location of the document position
 343:      *         inside the view coordinate space
 344:      *
 345:      * @throws BadLocationException if <code>pos</code> is invalid
 346:      * @throws IllegalArgumentException if b is not one of the above listed
 347:      *         valid values
 348:      */
 349:     public Shape modelToView(GlyphView view, int pos, Position.Bias b,
 350:                              Shape a)
 351:       throws BadLocationException
 352:     {
 353:       Element el = view.getElement();
 354:       Font font = view.getFont();
 355:       FontMetrics fm = view.getContainer().getFontMetrics(font);
 356:       Segment txt = view.getText(el.getStartOffset(), pos);
 357:       int width = fm.charsWidth(txt.array, txt.offset, txt.count);
 358:       int height = fm.getHeight();
 359:       Rectangle bounds = a.getBounds();
 360:       Rectangle result = new Rectangle(bounds.x + width, bounds.y,
 361:                                        bounds.x + width, height);
 362:       return result;
 363:     }
 364: 
 365:     /**
 366:      * Determine the span of the glyphs from location <code>p0</code> to
 367:      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
 368:      * then TABs are expanded using this <code>TabExpander</code>.
 369:      * The parameter <code>x</code> is the location at which the view is
 370:      * located (this is important when using TAB expansion).
 371:      *
 372:      * @param view the glyph view
 373:      * @param p0 the starting location in the document model
 374:      * @param p1 the end location in the document model
 375:      * @param te the tab expander to use
 376:      * @param x the location at which the view is located
 377:      *
 378:      * @return the span of the glyphs from location <code>p0</code> to
 379:      *         location <code>p1</code>, possibly using TAB expansion
 380:      */
 381:     public float getSpan(GlyphView view, int p0, int p1,
 382:                          TabExpander te, float x)
 383:     {
 384:       Element el = view.getElement();
 385:       Font font = view.getFont();
 386:       FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
 387:       Segment txt = view.getText(p0, p1);
 388:       int span = Utilities.getTabbedTextWidth(txt, fm, (int) x, te, p0);
 389:       return span;
 390:     }
 391: 
 392:     /**
 393:      * Returns the ascent of the text run that is rendered by this
 394:      * <code>GlyphPainter</code>.
 395:      *
 396:      * @param v the glyph view
 397:      *
 398:      * @return the ascent of the text run that is rendered by this
 399:      *         <code>GlyphPainter</code>
 400:      *
 401:      * @see FontMetrics#getAscent()
 402:      */
 403:     public float getAscent(GlyphView v)
 404:     {
 405:       Font font = v.getFont();
 406:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 407:       return fm.getAscent();
 408:     }
 409: 
 410:     /**
 411:      * Returns the descent of the text run that is rendered by this
 412:      * <code>GlyphPainter</code>.
 413:      *
 414:      * @param v the glyph view
 415:      *
 416:      * @return the descent of the text run that is rendered by this
 417:      *         <code>GlyphPainter</code>
 418:      *
 419:      * @see FontMetrics#getDescent()
 420:      */
 421:     public float getDescent(GlyphView v)
 422:     {
 423:       Font font = v.getFont();
 424:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 425:       return fm.getDescent();
 426:     }
 427: 
 428:     /**
 429:      * Determines the model offset, so that the text between <code>p0</code>
 430:      * and this offset fits within the span starting at <code>x</code> with
 431:      * the length of <code>len</code>. 
 432:      *
 433:      * @param v the glyph view
 434:      * @param p0 the starting offset in the model
 435:      * @param x the start location in the view
 436:      * @param len the length of the span in the view
 437:      */
 438:     public int getBoundedPosition(GlyphView v, int p0, float x, float len)
 439:     {
 440:       TabExpander te = v.getTabExpander();
 441:       Segment txt = v.getText(p0, v.getEndOffset());
 442:       Font font = v.getFont();
 443:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 444:       int pos = Utilities.getTabbedTextOffset(txt, fm, (int) x,
 445:                                               (int) (x + len), te, p0, false);
 446:       return pos;
 447:     }
 448: 
 449:     /**
 450:      * Maps a visual position into a document location.
 451:      *
 452:      * @param v the glyph view
 453:      * @param x the X coordinate of the visual position
 454:      * @param y the Y coordinate of the visual position
 455:      * @param a the allocated region
 456:      * @param biasRet filled with the bias of the model location on method exit
 457:      *
 458:      * @return the model location that represents the specified view location
 459:      */
 460:     public int viewToModel(GlyphView v, float x, float y, Shape a,
 461:                            Bias[] biasRet)
 462:     {
 463:       Rectangle b = a.getBounds();
 464:       int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x);
 465:       return pos;
 466:     }
 467:   }
 468: 
 469:   /**
 470:    * The GlyphPainer used for painting the glyphs.
 471:    */
 472:   GlyphPainter glyphPainter;
 473: 
 474:   /**
 475:    * The start offset within the document for this view.
 476:    */
 477:   private int startOffset;
 478: 
 479:   /**
 480:    * The end offset within the document for this view.
 481:    */
 482:   private int endOffset;
 483: 
 484:   /**
 485:    * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
 486:    *
 487:    * @param element the element that is rendered by this GlyphView
 488:    */
 489:   public GlyphView(Element element)
 490:   {
 491:     super(element);
 492:     startOffset = -1;
 493:     endOffset = -1;
 494:   }
 495: 
 496:   /**
 497:    * Returns the <code>GlyphPainter</code> that is used by this
 498:    * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed
 499:    * <code>null</code> is returned.
 500:    *
 501:    * @return the glyph painter that is used by this
 502:    *         glyph view or <code>null</code> if no glyph painter has been
 503:    *         installed
 504:    */
 505:   public GlyphPainter getGlyphPainter()
 506:   {
 507:     return glyphPainter;
 508:   }
 509: 
 510:   /**
 511:    * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>.
 512:    *
 513:    * @param painter the glyph painter to be used for this glyph view
 514:    */
 515:   public void setGlyphPainter(GlyphPainter painter)
 516:   {
 517:     glyphPainter = painter;
 518:   }
 519: 
 520:   /**
 521:    * Checks if a <code>GlyphPainer</code> is installed. If this is not the
 522:    * case, a default painter is installed.
 523:    */
 524:   protected void checkPainter()
 525:   {
 526:     if (glyphPainter == null)
 527:       glyphPainter = new DefaultGlyphPainter();
 528:   }
 529: 
 530:   /**
 531:    * Renders the <code>Element</code> that is associated with this
 532:    * <code>View</code>.
 533:    *
 534:    * @param g the <code>Graphics</code> context to render to
 535:    * @param a the allocated region for the <code>Element</code>
 536:    */
 537:   public void paint(Graphics g, Shape a)
 538:   {
 539:     Element el = getElement();
 540:     checkPainter();
 541:     getGlyphPainter().paint(this, g, a, getStartOffset(), getEndOffset());
 542:   }
 543: 
 544: 
 545:   /**
 546:    * Returns the preferred span of the content managed by this
 547:    * <code>View</code> along the specified <code>axis</code>.
 548:    *
 549:    * @param axis the axis
 550:    *
 551:    * @return the preferred span of this <code>View</code>.
 552:    */
 553:   public float getPreferredSpan(int axis)
 554:   {
 555:     float span = 0;
 556:     checkPainter();
 557:     GlyphPainter painter = getGlyphPainter();
 558:     if (axis == X_AXIS)
 559:       {
 560:         Element el = getElement();
 561:         TabExpander tabEx = null;
 562:         View parent = getParent();
 563:         if (parent instanceof TabExpander)
 564:           tabEx = (TabExpander) parent;
 565:         span = painter.getSpan(this, getStartOffset(), getEndOffset(),
 566:                                tabEx, 0.F);
 567:       }
 568:     else
 569:       span = painter.getHeight(this);
 570: 
 571:     return span;
 572:   }
 573: 
 574:   /**
 575:    * Maps a position in the document into the coordinate space of the View.
 576:    * The output rectangle usually reflects the font height but has a width
 577:    * of zero.
 578:    *
 579:    * @param pos the position of the character in the model
 580:    * @param a the area that is occupied by the view
 581:    * @param b either {@link Position.Bias#Forward} or
 582:    *        {@link Position.Bias#Backward} depending on the preferred
 583:    *        direction bias. If <code>null</code> this defaults to
 584:    *        <code>Position.Bias.Forward</code>
 585:    *
 586:    * @return a rectangle that gives the location of the document position
 587:    *         inside the view coordinate space
 588:    *
 589:    * @throws BadLocationException if <code>pos</code> is invalid
 590:    * @throws IllegalArgumentException if b is not one of the above listed
 591:    *         valid values
 592:    */
 593:   public Shape modelToView(int pos, Shape a, Position.Bias b)
 594:     throws BadLocationException
 595:   {
 596:     GlyphPainter p = getGlyphPainter();
 597:     return p.modelToView(this, pos, b, a);
 598:   }
 599: 
 600:   /**
 601:    * Maps coordinates from the <code>View</code>'s space into a position
 602:    * in the document model.
 603:    *
 604:    * @param x the x coordinate in the view space
 605:    * @param y the y coordinate in the view space
 606:    * @param a the allocation of this <code>View</code>
 607:    * @param b the bias to use
 608:    *
 609:    * @return the position in the document that corresponds to the screen
 610:    *         coordinates <code>x, y</code>
 611:    */
 612:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 613:   {
 614:     checkPainter();
 615:     GlyphPainter painter = getGlyphPainter();
 616:     return painter.viewToModel(this, x, y, a, b);
 617:   }
 618: 
 619:   /**
 620:    * Return the {@link TabExpander} to use.
 621:    *
 622:    * @return the {@link TabExpander} to use
 623:    */
 624:   public TabExpander getTabExpander()
 625:   {
 626:     TabExpander te = null;
 627:     View parent = getParent();
 628: 
 629:     if (parent instanceof TabExpander)
 630:       te = (TabExpander) parent;
 631:     
 632:     return te;
 633:   }
 634: 
 635:   /**
 636:    * Returns the preferred span of this view for tab expansion.
 637:    *
 638:    * @param x the location of the view
 639:    * @param te the tab expander to use
 640:    *
 641:    * @return the preferred span of this view for tab expansion
 642:    */
 643:   public float getTabbedSpan(float x, TabExpander te)
 644:   {
 645:     Element el = getElement();
 646:     return getGlyphPainter().getSpan(this, el.getStartOffset(),
 647:                                      el.getEndOffset(), te, x);
 648:   }
 649: 
 650:   /**
 651:    * Returns the span of a portion of the view. This is used in TAB expansion
 652:    * for fragments that don't contain TABs.
 653:    *
 654:    * @param p0 the start index
 655:    * @param p1 the end index
 656:    *
 657:    * @return the span of the specified portion of the view
 658:    */
 659:   public float getPartialSpan(int p0, int p1)
 660:   {
 661:     Element el = getElement();
 662:     Document doc = el.getDocument();
 663:     Segment seg = new Segment();
 664:     try
 665:       {
 666:         doc.getText(p0, p1 - p0, seg);
 667:       }
 668:     catch (BadLocationException ex)
 669:       {
 670:     AssertionError ae;
 671:         ae = new AssertionError("BadLocationException must not be thrown "
 672:                 + "here");
 673:     ae.initCause(ex);
 674:     throw ae;
 675:       }
 676:     FontMetrics fm = null; // Fetch font metrics somewhere.
 677:     return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0);
 678:   }
 679: 
 680:   /**
 681:    * Returns the start offset in the document model of the portion
 682:    * of text that this view is responsible for.
 683:    *
 684:    * @return the start offset in the document model of the portion
 685:    *         of text that this view is responsible for
 686:    */
 687:   public int getStartOffset()
 688:   {
 689:     int start = startOffset;
 690:     if (start < 0)
 691:       start = super.getStartOffset();
 692:     return start;
 693:   }
 694: 
 695:   /**
 696:    * Returns the end offset in the document model of the portion
 697:    * of text that this view is responsible for.
 698:    *
 699:    * @return the end offset in the document model of the portion
 700:    *         of text that this view is responsible for
 701:    */
 702:   public int getEndOffset()
 703:   {
 704:     int end = endOffset;
 705:     if (end < 0)
 706:       end = super.getEndOffset();
 707:     return end;
 708:   }
 709: 
 710:   /**
 711:    * Returns the text segment that this view is responsible for.
 712:    *
 713:    * @param p0 the start index in the document model
 714:    * @param p1 the end index in the document model
 715:    *
 716:    * @return the text segment that this view is responsible for
 717:    */
 718:   public Segment getText(int p0, int p1)
 719:   {
 720:     Segment txt = new Segment();
 721:     try
 722:       {
 723:         getDocument().getText(p0, p1 - p0, txt);
 724:       }
 725:     catch (BadLocationException ex)
 726:       {
 727:     AssertionError ae;
 728:         ae = new AssertionError("BadLocationException should not be "
 729:                 + "thrown here. p0 = " + p0 + ", p1 = " + p1);
 730:     ae.initCause(ex);
 731:     throw ae;
 732:       }
 733: 
 734:     return txt;
 735:   }
 736: 
 737:   /**
 738:    * Returns the font for the text run for which this <code>GlyphView</code>
 739:    * is responsible.
 740:    *
 741:    * @return the font for the text run for which this <code>GlyphView</code>
 742:    *         is responsible
 743:    */
 744:   public Font getFont()
 745:   {
 746:     Element el = getElement();
 747:     AttributeSet atts = el.getAttributes();
 748:     String family = StyleConstants.getFontFamily(atts);
 749:     int size = StyleConstants.getFontSize(atts);
 750:     int style = Font.PLAIN;
 751:     if (StyleConstants.isBold(atts))
 752:         style |= Font.BOLD;
 753:     if (StyleConstants.isItalic(atts))
 754:       style |= Font.ITALIC;
 755:     Font font = new Font(family, style, size);
 756:     return font;
 757:   }
 758: 
 759:   /**
 760:    * Returns the foreground color which should be used to paint the text.
 761:    * This is fetched from the associated element's text attributes using
 762:    * {@link StyleConstants#getForeground}.
 763:    *
 764:    * @return the foreground color which should be used to paint the text
 765:    */
 766:   public Color getForeground()
 767:   {
 768:     Element el = getElement();
 769:     AttributeSet atts = el.getAttributes();
 770:     return StyleConstants.getForeground(atts);
 771:   }
 772: 
 773:   /**
 774:    * Returns the background color which should be used to paint the text.
 775:    * This is fetched from the associated element's text attributes using
 776:    * {@link StyleConstants#getBackground}.
 777:    *
 778:    * @return the background color which should be used to paint the text
 779:    */
 780:   public Color getBackground()
 781:   {
 782:     Element el = getElement();
 783:     AttributeSet atts = el.getAttributes();
 784:     // We cannot use StyleConstants.getBackground() here, because that returns
 785:     // BLACK as default (when background == null). What we need is the
 786:     // background setting of the text component instead, which is what we get
 787:     // when background == null anyway.
 788:     return (Color) atts.getAttribute(StyleConstants.Background);
 789:   }
 790: 
 791:   /**
 792:    * Determines whether the text should be rendered strike-through or not. This
 793:    * is determined using the method
 794:    * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
 795:    * this view.
 796:    *
 797:    * @return whether the text should be rendered strike-through or not
 798:    */
 799:   public boolean isStrikeThrough()
 800:   {
 801:     Element el = getElement();
 802:     AttributeSet atts = el.getAttributes();
 803:     return StyleConstants.isStrikeThrough(atts);
 804:   }
 805: 
 806:   /**
 807:    * Determines whether the text should be rendered as subscript or not. This
 808:    * is determined using the method
 809:    * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
 810:    * this view.
 811:    *
 812:    * @return whether the text should be rendered as subscript or not
 813:    */
 814:   public boolean isSubscript()
 815:   {
 816:     Element el = getElement();
 817:     AttributeSet atts = el.getAttributes();
 818:     return StyleConstants.isSubscript(atts);
 819:   }
 820: 
 821:   /**
 822:    * Determines whether the text should be rendered as superscript or not. This
 823:    * is determined using the method
 824:    * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
 825:    * this view.
 826:    *
 827:    * @return whether the text should be rendered as superscript or not
 828:    */
 829:   public boolean isSuperscript()
 830:   {
 831:     Element el = getElement();
 832:     AttributeSet atts = el.getAttributes();
 833:     return StyleConstants.isSuperscript(atts);
 834:   }
 835: 
 836:   /**
 837:    * Determines whether the text should be rendered as underlined or not. This
 838:    * is determined using the method
 839:    * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
 840:    * this view.
 841:    *
 842:    * @return whether the text should be rendered as underlined or not
 843:    */
 844:   public boolean isUnderline()
 845:   {
 846:     Element el = getElement();
 847:     AttributeSet atts = el.getAttributes();
 848:     return StyleConstants.isUnderline(atts);
 849:   }
 850: 
 851:   /**
 852:    * Creates and returns a shallow clone of this GlyphView. This is used by
 853:    * the {@link #createFragment} and {@link #breakView} methods.
 854:    *
 855:    * @return a shallow clone of this GlyphView
 856:    */
 857:   protected final Object clone()
 858:   {
 859:     try
 860:       {
 861:         return super.clone();
 862:       }
 863:     catch (CloneNotSupportedException ex)
 864:       {
 865:         AssertionError err = new AssertionError("CloneNotSupportedException "
 866:                                                 + "must not be thrown here");
 867:         err.initCause(ex);
 868:         throw err;
 869:       }
 870:   }
 871: 
 872:   /**
 873:    * Tries to break the view near the specified view span <code>len</code>.
 874:    * The glyph view can only be broken in the X direction. For Y direction it
 875:    * returns itself.
 876:    *
 877:    * @param axis the axis for breaking, may be {@link View#X_AXIS} or
 878:    *        {@link View#Y_AXIS}
 879:    * @param p0 the model location where the fragment should start
 880:    * @param pos the view position along the axis where the fragment starts
 881:    * @param len the desired length of the fragment view
 882:    *
 883:    * @return the fragment view, or <code>this</code> if breaking was not
 884:    *         possible
 885:    */
 886:   public View breakView(int axis, int p0, float pos, float len)
 887:   {
 888:     if (axis == Y_AXIS)
 889:       return this;
 890: 
 891:     checkPainter();
 892:     GlyphPainter painter = getGlyphPainter();
 893: 
 894:     // Try to find a suitable line break.
 895:     BreakIterator lineBreaker = BreakIterator.getLineInstance();
 896:     Segment txt = new Segment();
 897:     try
 898:       {
 899:         int start = getStartOffset();
 900:         int length = getEndOffset() - start;
 901:         getDocument().getText(start, length, txt);
 902:       }
 903:     catch (BadLocationException ex)
 904:       {
 905:         AssertionError err = new AssertionError("BadLocationException must not "
 906:                                                 + "be thrown here.");
 907:         err.initCause(ex);
 908:         throw err;
 909:       }
 910:     int breakLocation =
 911:       Utilities.getBreakLocation(txt, getContainer().getFontMetrics(getFont()),
 912:                                  (int) pos, (int) (pos + len),
 913:                                  getTabExpander(), p0);
 914:     View brokenView = createFragment(p0, breakLocation);
 915:     return brokenView;
 916:   }
 917: 
 918:   /**
 919:    * Determines how well the specified view location is suitable for inserting
 920:    * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
 921:    * this method forwards to the superclass, if <code>axis</code> is
 922:    * <code>View.X_AXIS</code> then this method returns
 923:    * {@link View#ExcellentBreakWeight} if there is a suitable break location
 924:    * (usually whitespace) within the specified view span, or
 925:    * {@link View#GoodBreakWeight} if not.
 926:    *
 927:    * @param axis the axis along which the break weight is requested
 928:    * @param pos the starting view location
 929:    * @param len the length of the span at which the view should be broken
 930:    *
 931:    * @return the break weight
 932:    */
 933:   public int getBreakWeight(int axis, float pos, float len)
 934:   {
 935:     int weight;
 936:     if (axis == Y_AXIS)
 937:       weight = super.getBreakWeight(axis, pos, len);
 938:     else
 939:       {
 940:         // FIXME: Commented out because the Utilities.getBreakLocation method
 941:         // is still buggy. The GoodBreakWeight is a reasonable workaround for
 942:         // now.
 943: //        int startOffset = getStartOffset();
 944: //        int endOffset = getEndOffset() - 1;
 945: //        Segment s = getText(startOffset, endOffset);
 946: //        Container c = getContainer();
 947: //        FontMetrics fm = c.getFontMetrics(c.getFont());
 948: //        int x0 = (int) pos;
 949: //        int x = (int) (pos + len);
 950: //        int breakLoc = Utilities.getBreakLocation(s, fm, x0, x,
 951: //                                                  getTabExpander(),
 952: //                                                  startOffset);
 953: //        if (breakLoc == startOffset || breakLoc == endOffset)
 954: //          weight = GoodBreakWeight;
 955: //        else
 956: //          weight = ExcellentBreakWeight;
 957:         weight = GoodBreakWeight;
 958:       }
 959:     return weight;
 960:   }
 961: 
 962:   /**
 963:    * Receives notification that some text attributes have changed within the
 964:    * text fragment that this view is responsible for. This calls
 965:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 966:    * both width and height.
 967:    *
 968:    * @param e the document event describing the change; not used here
 969:    * @param a the view allocation on screen; not used here
 970:    * @param vf the view factory; not used here
 971:    */
 972:   public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 973:   {
 974:     preferenceChanged(this, true, true);
 975:   }
 976: 
 977:   /**
 978:    * Receives notification that some text has been inserted within the
 979:    * text fragment that this view is responsible for. This calls
 980:    * {@link View#preferenceChanged(View, boolean, boolean)} for the
 981:    * direction in which the glyphs are rendered.
 982:    *
 983:    * @param e the document event describing the change; not used here
 984:    * @param a the view allocation on screen; not used here
 985:    * @param vf the view factory; not used here
 986:    */
 987:   public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 988:   {
 989:     preferenceChanged(this, true, false);
 990:   }
 991: 
 992:   /**
 993:    * Receives notification that some text has been removed within the
 994:    * text fragment that this view is responsible for. This calls
 995:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 996:    * width.
 997:    *
 998:    * @param e the document event describing the change; not used here
 999:    * @param a the view allocation on screen; not used here
1000:    * @param vf the view factory; not used here
1001:    */
1002:   public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1003:   {
1004:     preferenceChanged(this, true, false);
1005:   }
1006: 
1007:   /**
1008:    * Creates a fragment view of this view that starts at <code>p0</code> and
1009:    * ends at <code>p1</code>.
1010:    *
1011:    * @param p0 the start location for the fragment view
1012:    * @param p1 the end location for the fragment view
1013:    *
1014:    * @return the fragment view
1015:    */
1016:   public View createFragment(int p0, int p1)
1017:   {
1018:     GlyphView fragment = (GlyphView) clone();
1019:     if (p0 != getStartOffset())
1020:       fragment.startOffset = p0;
1021:     if (p1 != getEndOffset())
1022:       fragment.endOffset = p1;
1023:     return fragment;
1024:   }
1025: 
1026:   /**
1027:    * Returns the alignment of this view along the specified axis. For the Y
1028:    * axis this is <code>(height - descent) / height</code> for the used font,
1029:    * so that it is aligned along the baseline.
1030:    * For the X axis the superclass is called.
1031:    */
1032:   public float getAlignment(int axis)
1033:   {
1034:     float align;
1035:     if (axis == Y_AXIS)
1036:       {
1037:         checkPainter();
1038:         GlyphPainter painter = getGlyphPainter();
1039:         float height = painter.getHeight(this);
1040:         float descent = painter.getDescent(this);
1041:         align = (height - descent) / height; 
1042:       }
1043:     else
1044:       align = super.getAlignment(axis);
1045: 
1046:     return align;
1047:   }
1048: 
1049:   /**
1050:    * Returns the model location that should be used to place a caret when
1051:    * moving the caret through the document.
1052:    *
1053:    * @param pos the current model location
1054:    * @param bias the bias for <code>p</code>
1055:    * @param a the allocated region for the glyph view
1056:    * @param direction the direction from the current position; Must be one of
1057:    *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
1058:    *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
1059:    * @param biasRet filled with the bias of the resulting location when method
1060:    *        returns
1061:    *
1062:    * @return the location within the document that should be used to place the
1063:    *         caret when moving the caret around the document
1064:    *
1065:    * @throws BadLocationException if <code>pos</code> is an invalid model
1066:    *         location
1067:    * @throws IllegalArgumentException if <code>d</code> is invalid
1068:    */
1069:   public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
1070:                                        int direction, Position.Bias[] biasRet)
1071:     throws BadLocationException
1072:   {
1073:     checkPainter();
1074:     GlyphPainter painter = getGlyphPainter();
1075:     return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
1076:                                              biasRet);
1077:   }
1078: }