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:       int height = (int) getHeight(view);
 281:       Segment txt = view.getText(p0, p1);
 282:       Rectangle bounds = a.getBounds();
 283: 
 284:       TabExpander tabEx = null;
 285:       View parent = view.getParent();
 286:       if (parent instanceof TabExpander)
 287:         tabEx = (TabExpander) parent;
 288: 
 289:       // Fill the background of the text run.
 290:       Color background = view.getBackground();
 291:       g.setColor(background);
 292:       int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(),
 293:                                                bounds.x, tabEx, txt.offset);
 294:       g.fillRect(bounds.x, bounds.y, width, height);
 295: 
 296:       // Draw the actual text.
 297:       g.setColor(view.getForeground());
 298:       g.setFont(view.getFont());
 299:       if (view.isSuperscript())
 300:         // TODO: Adjust font for superscripting.
 301:         Utilities.drawTabbedText(txt, bounds.x, bounds.y - height / 2, g, tabEx,
 302:                                    txt.offset);
 303:       else if (view.isSubscript())
 304:         // TODO: Adjust font for subscripting.
 305:         Utilities.drawTabbedText(txt, bounds.x, bounds.y + height / 2, g, tabEx,
 306:                                  txt.offset);
 307:       else
 308:         Utilities.drawTabbedText(txt, bounds.x, bounds.y, g, tabEx,
 309:                                  txt.offset);
 310: 
 311:       if (view.isStikeThrough())
 312:         {
 313:           int strikeHeight = (int) (getAscent(view) / 2);
 314:           g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width,
 315:                      bounds.y + strikeHeight);
 316:         }
 317:       if (view.isUnderline())
 318:         {
 319:           int lineHeight = (int) getAscent(view);
 320:           g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width,
 321:                      bounds.y + lineHeight);
 322:         }
 323:     }
 324: 
 325:     /**
 326:      * Maps a position in the document into the coordinate space of the View.
 327:      * The output rectangle usually reflects the font height but has a width
 328:      * of zero.
 329:      *
 330:      * @param view the glyph view
 331:      * @param pos the position of the character in the model
 332:      * @param a the area that is occupied by the view
 333:      * @param b either {@link Position.Bias#Forward} or
 334:      *        {@link Position.Bias#Backward} depending on the preferred
 335:      *        direction bias. If <code>null</code> this defaults to
 336:      *        <code>Position.Bias.Forward</code>
 337:      *
 338:      * @return a rectangle that gives the location of the document position
 339:      *         inside the view coordinate space
 340:      *
 341:      * @throws BadLocationException if <code>pos</code> is invalid
 342:      * @throws IllegalArgumentException if b is not one of the above listed
 343:      *         valid values
 344:      */
 345:     public Shape modelToView(GlyphView view, int pos, Position.Bias b,
 346:                              Shape a)
 347:       throws BadLocationException
 348:     {
 349:       Element el = view.getElement();
 350:       Font font = view.getFont();
 351:       FontMetrics fm = view.getContainer().getFontMetrics(font);
 352:       Segment txt = view.getText(el.getStartOffset(), pos);
 353:       int width = fm.charsWidth(txt.array, txt.offset, txt.count);
 354:       int height = fm.getHeight();
 355:       Rectangle bounds = a.getBounds();
 356:       Rectangle result = new Rectangle(bounds.x + width, bounds.y,
 357:                                        bounds.x + width, height);
 358:       return result;
 359:     }
 360: 
 361:     /**
 362:      * Determine the span of the glyphs from location <code>p0</code> to
 363:      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
 364:      * then TABs are expanded using this <code>TabExpander</code>.
 365:      * The parameter <code>x</code> is the location at which the view is
 366:      * located (this is important when using TAB expansion).
 367:      *
 368:      * @param view the glyph view
 369:      * @param p0 the starting location in the document model
 370:      * @param p1 the end location in the document model
 371:      * @param te the tab expander to use
 372:      * @param x the location at which the view is located
 373:      *
 374:      * @return the span of the glyphs from location <code>p0</code> to
 375:      *         location <code>p1</code>, possibly using TAB expansion
 376:      */
 377:     public float getSpan(GlyphView view, int p0, int p1,
 378:                          TabExpander te, float x)
 379:     {
 380:       Element el = view.getElement();
 381:       Font font = view.getFont();
 382:       FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
 383:       Segment txt = view.getText(p0, p1);
 384:       int span = Utilities.getTabbedTextWidth(txt, fm, (int) x, te, p0);
 385:       return span;
 386:     }
 387: 
 388:     /**
 389:      * Returns the ascent of the text run that is rendered by this
 390:      * <code>GlyphPainter</code>.
 391:      *
 392:      * @param v the glyph view
 393:      *
 394:      * @return the ascent of the text run that is rendered by this
 395:      *         <code>GlyphPainter</code>
 396:      *
 397:      * @see FontMetrics#getAscent()
 398:      */
 399:     public float getAscent(GlyphView v)
 400:     {
 401:       Font font = v.getFont();
 402:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 403:       return fm.getAscent();
 404:     }
 405: 
 406:     /**
 407:      * Returns the descent of the text run that is rendered by this
 408:      * <code>GlyphPainter</code>.
 409:      *
 410:      * @param v the glyph view
 411:      *
 412:      * @return the descent of the text run that is rendered by this
 413:      *         <code>GlyphPainter</code>
 414:      *
 415:      * @see FontMetrics#getDescent()
 416:      */
 417:     public float getDescent(GlyphView v)
 418:     {
 419:       Font font = v.getFont();
 420:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 421:       return fm.getDescent();
 422:     }
 423: 
 424:     /**
 425:      * Determines the model offset, so that the text between <code>p0</code>
 426:      * and this offset fits within the span starting at <code>x</code> with
 427:      * the length of <code>len</code>. 
 428:      *
 429:      * @param v the glyph view
 430:      * @param p0 the starting offset in the model
 431:      * @param x the start location in the view
 432:      * @param len the length of the span in the view
 433:      */
 434:     public int getBoundedPosition(GlyphView v, int p0, float x, float len)
 435:     {
 436:       TabExpander te = v.getTabExpander();
 437:       Segment txt = v.getText(p0, v.getEndOffset());
 438:       Font font = v.getFont();
 439:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 440:       int pos = Utilities.getTabbedTextOffset(txt, fm, (int) x,
 441:                                               (int) (x + len), te, p0, false);
 442:       return pos;
 443:     }
 444: 
 445:     /**
 446:      * Maps a visual position into a document location.
 447:      *
 448:      * @param v the glyph view
 449:      * @param x the X coordinate of the visual position
 450:      * @param y the Y coordinate of the visual position
 451:      * @param a the allocated region
 452:      * @param biasRet filled with the bias of the model location on method exit
 453:      *
 454:      * @return the model location that represents the specified view location
 455:      */
 456:     public int viewToModel(GlyphView v, float x, float y, Shape a,
 457:                            Bias[] biasRet)
 458:     {
 459:       Rectangle b = a.getBounds();
 460:       int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x);
 461:       return pos;
 462:     }
 463:   }
 464: 
 465:   /**
 466:    * The GlyphPainer used for painting the glyphs.
 467:    */
 468:   GlyphPainter glyphPainter;
 469: 
 470:   /**
 471:    * The start offset within the document for this view.
 472:    */
 473:   int startOffset;
 474: 
 475:   /**
 476:    * The end offset within the document for this view.
 477:    */
 478:   int endOffset;
 479: 
 480:   /**
 481:    * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
 482:    *
 483:    * @param element the element that is rendered by this GlyphView
 484:    */
 485:   public GlyphView(Element element)
 486:   {
 487:     super(element);
 488:     startOffset = element.getStartOffset();
 489:     endOffset = element.getEndOffset();
 490:   }
 491: 
 492:   /**
 493:    * Returns the <code>GlyphPainter</code> that is used by this
 494:    * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed
 495:    * <code>null</code> is returned.
 496:    *
 497:    * @return the glyph painter that is used by this
 498:    *         glyph view or <code>null</code> if no glyph painter has been
 499:    *         installed
 500:    */
 501:   public GlyphPainter getGlyphPainter()
 502:   {
 503:     return glyphPainter;
 504:   }
 505: 
 506:   /**
 507:    * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>.
 508:    *
 509:    * @param painter the glyph painter to be used for this glyph view
 510:    */
 511:   public void setGlyphPainter(GlyphPainter painter)
 512:   {
 513:     glyphPainter = painter;
 514:   }
 515: 
 516:   /**
 517:    * Checks if a <code>GlyphPainer</code> is installed. If this is not the
 518:    * case, a default painter is installed.
 519:    */
 520:   protected void checkPainter()
 521:   {
 522:     if (glyphPainter == null)
 523:       glyphPainter = new DefaultGlyphPainter();
 524:   }
 525: 
 526:   /**
 527:    * Renders the <code>Element</code> that is associated with this
 528:    * <code>View</code>.
 529:    *
 530:    * @param g the <code>Graphics</code> context to render to
 531:    * @param a the allocated region for the <code>Element</code>
 532:    */
 533:   public void paint(Graphics g, Shape a)
 534:   {
 535:     Element el = getElement();
 536:     checkPainter();
 537:     getGlyphPainter().paint(this, g, a, el.getStartOffset(),
 538:                             el.getEndOffset());
 539:   }
 540: 
 541: 
 542:   /**
 543:    * Returns the preferred span of the content managed by this
 544:    * <code>View</code> along the specified <code>axis</code>.
 545:    *
 546:    * @param axis the axis
 547:    *
 548:    * @return the preferred span of this <code>View</code>.
 549:    */
 550:   public float getPreferredSpan(int axis)
 551:   {
 552:     float span = 0;
 553:     checkPainter();
 554:     GlyphPainter painter = getGlyphPainter();
 555:     if (axis == X_AXIS)
 556:       {
 557:         Element el = getElement();
 558:         TabExpander tabEx = null;
 559:         View parent = getParent();
 560:         if (parent instanceof TabExpander)
 561:           tabEx = (TabExpander) parent;
 562:         span = painter.getSpan(this, getStartOffset(), getEndOffset(),
 563:                                tabEx, 0.F);
 564:       }
 565:     else
 566:       span = painter.getHeight(this); 
 567:     return span;
 568:   }
 569: 
 570:   /**
 571:    * Maps a position in the document into the coordinate space of the View.
 572:    * The output rectangle usually reflects the font height but has a width
 573:    * of zero.
 574:    *
 575:    * @param pos the position of the character in the model
 576:    * @param a the area that is occupied by the view
 577:    * @param b either {@link Position.Bias#Forward} or
 578:    *        {@link Position.Bias#Backward} depending on the preferred
 579:    *        direction bias. If <code>null</code> this defaults to
 580:    *        <code>Position.Bias.Forward</code>
 581:    *
 582:    * @return a rectangle that gives the location of the document position
 583:    *         inside the view coordinate space
 584:    *
 585:    * @throws BadLocationException if <code>pos</code> is invalid
 586:    * @throws IllegalArgumentException if b is not one of the above listed
 587:    *         valid values
 588:    */
 589:   public Shape modelToView(int pos, Shape a, Position.Bias b)
 590:     throws BadLocationException
 591:   {
 592:     GlyphPainter p = getGlyphPainter();
 593:     return p.modelToView(this, pos, b, a);
 594:   }
 595: 
 596:   /**
 597:    * Maps coordinates from the <code>View</code>'s space into a position
 598:    * in the document model.
 599:    *
 600:    * @param x the x coordinate in the view space
 601:    * @param y the y coordinate in the view space
 602:    * @param a the allocation of this <code>View</code>
 603:    * @param b the bias to use
 604:    *
 605:    * @return the position in the document that corresponds to the screen
 606:    *         coordinates <code>x, y</code>
 607:    */
 608:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 609:   {
 610:     checkPainter();
 611:     GlyphPainter painter = getGlyphPainter();
 612:     return painter.viewToModel(this, x, y, a, b);
 613:   }
 614: 
 615:   /**
 616:    * Return the {@link TabExpander} to use.
 617:    *
 618:    * @return the {@link TabExpander} to use
 619:    */
 620:   public TabExpander getTabExpander()
 621:   {
 622:     TabExpander te = null;
 623:     View parent = getParent();
 624: 
 625:     if (parent instanceof TabExpander)
 626:       te = (TabExpander) parent;
 627:     
 628:     return te;
 629:   }
 630: 
 631:   /**
 632:    * Returns the preferred span of this view for tab expansion.
 633:    *
 634:    * @param x the location of the view
 635:    * @param te the tab expander to use
 636:    *
 637:    * @return the preferred span of this view for tab expansion
 638:    */
 639:   public float getTabbedSpan(float x, TabExpander te)
 640:   {
 641:     Element el = getElement();
 642:     return getGlyphPainter().getSpan(this, el.getStartOffset(),
 643:                                      el.getEndOffset(), te, x);
 644:   }
 645: 
 646:   /**
 647:    * Returns the span of a portion of the view. This is used in TAB expansion
 648:    * for fragments that don't contain TABs.
 649:    *
 650:    * @param p0 the start index
 651:    * @param p1 the end index
 652:    *
 653:    * @return the span of the specified portion of the view
 654:    */
 655:   public float getPartialSpan(int p0, int p1)
 656:   {
 657:     Element el = getElement();
 658:     Document doc = el.getDocument();
 659:     Segment seg = new Segment();
 660:     try
 661:       {
 662:         doc.getText(p0, p1 - p0, seg);
 663:       }
 664:     catch (BadLocationException ex)
 665:       {
 666:     AssertionError ae;
 667:         ae = new AssertionError("BadLocationException must not be thrown "
 668:                 + "here");
 669:     ae.initCause(ex);
 670:     throw ae;
 671:       }
 672:     FontMetrics fm = null; // Fetch font metrics somewhere.
 673:     return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0);
 674:   }
 675: 
 676:   /**
 677:    * Returns the start offset in the document model of the portion
 678:    * of text that this view is responsible for.
 679:    *
 680:    * @return the start offset in the document model of the portion
 681:    *         of text that this view is responsible for
 682:    */
 683:   public int getStartOffset()
 684:   {
 685:     return startOffset;
 686:   }
 687: 
 688:   /**
 689:    * Returns the end offset in the document model of the portion
 690:    * of text that this view is responsible for.
 691:    *
 692:    * @return the end offset in the document model of the portion
 693:    *         of text that this view is responsible for
 694:    */
 695:   public int getEndOffset()
 696:   {
 697:     return endOffset;
 698:   }
 699: 
 700:   /**
 701:    * Returns the text segment that this view is responsible for.
 702:    *
 703:    * @param p0 the start index in the document model
 704:    * @param p1 the end index in the document model
 705:    *
 706:    * @return the text segment that this view is responsible for
 707:    */
 708:   public Segment getText(int p0, int p1)
 709:   {
 710:     Segment txt = new Segment();
 711:     try
 712:       {
 713:         getDocument().getText(p0, p1 - p0, txt);
 714:       }
 715:     catch (BadLocationException ex)
 716:       {
 717:     AssertionError ae;
 718:         ae = new AssertionError("BadLocationException should not be "
 719:                 + "thrown here. p0 = " + p0 + ", p1 = " + p1);
 720:     ae.initCause(ex);
 721:     throw ae;
 722:       }
 723: 
 724:     return txt;
 725:   }
 726: 
 727:   /**
 728:    * Returns the font for the text run for which this <code>GlyphView</code>
 729:    * is responsible.
 730:    *
 731:    * @return the font for the text run for which this <code>GlyphView</code>
 732:    *         is responsible
 733:    */
 734:   public Font getFont()
 735:   {
 736:     Element el = getElement();
 737:     AttributeSet atts = el.getAttributes();
 738:     String family = StyleConstants.getFontFamily(atts);
 739:     int size = StyleConstants.getFontSize(atts);
 740:     int style = Font.PLAIN;
 741:     if (StyleConstants.isBold(atts))
 742:         style |= Font.BOLD;
 743:     if (StyleConstants.isItalic(atts))
 744:       style |= Font.ITALIC;
 745:     Font font = new Font(family, style, size);
 746:     return font;
 747:   }
 748: 
 749:   /**
 750:    * Returns the foreground color which should be used to paint the text.
 751:    * This is fetched from the associated element's text attributes using
 752:    * {@link StyleConstants#getForeground}.
 753:    *
 754:    * @return the foreground color which should be used to paint the text
 755:    */
 756:   public Color getForeground()
 757:   {
 758:     Element el = getElement();
 759:     AttributeSet atts = el.getAttributes();
 760:     return StyleConstants.getForeground(atts);
 761:   }
 762: 
 763:   /**
 764:    * Returns the background color which should be used to paint the text.
 765:    * This is fetched from the associated element's text attributes using
 766:    * {@link StyleConstants#getBackground}.
 767:    *
 768:    * @return the background color which should be used to paint the text
 769:    */
 770:   public Color getBackground()
 771:   {
 772:     Element el = getElement();
 773:     AttributeSet atts = el.getAttributes();
 774:     return StyleConstants.getBackground(atts);
 775:   }
 776: 
 777:   /**
 778:    * Determines whether the text should be rendered strike-through or not. This
 779:    * is determined using the method
 780:    * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
 781:    * this view.
 782:    *
 783:    * @return whether the text should be rendered strike-through or not
 784:    */
 785:   public boolean isStikeThrough()
 786:   {
 787:     Element el = getElement();
 788:     AttributeSet atts = el.getAttributes();
 789:     return StyleConstants.isStrikeThrough(atts);
 790:   }
 791: 
 792:   /**
 793:    * Determines whether the text should be rendered as subscript or not. This
 794:    * is determined using the method
 795:    * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
 796:    * this view.
 797:    *
 798:    * @return whether the text should be rendered as subscript or not
 799:    */
 800:   public boolean isSubscript()
 801:   {
 802:     Element el = getElement();
 803:     AttributeSet atts = el.getAttributes();
 804:     return StyleConstants.isSubscript(atts);
 805:   }
 806: 
 807:   /**
 808:    * Determines whether the text should be rendered as superscript or not. This
 809:    * is determined using the method
 810:    * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
 811:    * this view.
 812:    *
 813:    * @return whether the text should be rendered as superscript or not
 814:    */
 815:   public boolean isSuperscript()
 816:   {
 817:     Element el = getElement();
 818:     AttributeSet atts = el.getAttributes();
 819:     return StyleConstants.isSuperscript(atts);
 820:   }
 821: 
 822:   /**
 823:    * Determines whether the text should be rendered as underlined or not. This
 824:    * is determined using the method
 825:    * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
 826:    * this view.
 827:    *
 828:    * @return whether the text should be rendered as underlined or not
 829:    */
 830:   public boolean isUnderline()
 831:   {
 832:     Element el = getElement();
 833:     AttributeSet atts = el.getAttributes();
 834:     return StyleConstants.isUnderline(atts);
 835:   }
 836: 
 837:   /**
 838:    * Creates and returns a shallow clone of this GlyphView. This is used by
 839:    * the {@link #createFragment} and {@link #breakView} methods.
 840:    *
 841:    * @return a shallow clone of this GlyphView
 842:    */
 843:   protected final Object clone()
 844:   {
 845:     try
 846:       {
 847:         return super.clone();
 848:       }
 849:     catch (CloneNotSupportedException ex)
 850:       {
 851:         AssertionError err = new AssertionError("CloneNotSupportedException "
 852:                                                 + "must not be thrown here");
 853:         err.initCause(ex);
 854:         throw err;
 855:       }
 856:   }
 857: 
 858:   /**
 859:    * Tries to break the view near the specified view span <code>len</code>.
 860:    * The glyph view can only be broken in the X direction. For Y direction it
 861:    * returns itself.
 862:    *
 863:    * @param axis the axis for breaking, may be {@link View#X_AXIS} or
 864:    *        {@link View#Y_AXIS}
 865:    * @param p0 the model location where the fragment should start
 866:    * @param pos the view position along the axis where the fragment starts
 867:    * @param len the desired length of the fragment view
 868:    *
 869:    * @return the fragment view, or <code>this</code> if breaking was not
 870:    *         possible
 871:    */
 872:   public View breakView(int axis, int p0, float pos, float len)
 873:   {
 874:     if (axis == Y_AXIS)
 875:       return this;
 876: 
 877:     checkPainter();
 878:     GlyphPainter painter = getGlyphPainter();
 879:     int breakLocation = painter.getBoundedPosition(this, p0, pos, len);
 880:     // Try to find a suitable line break.
 881:     BreakIterator lineBreaker = BreakIterator.getLineInstance();
 882:     Segment txt = new Segment();
 883:     try
 884:       {
 885:         getDocument().getText(getStartOffset(), getEndOffset(), txt);
 886:       }
 887:     catch (BadLocationException ex)
 888:       {
 889:         AssertionError err = new AssertionError("BadLocationException must not "
 890:                                                 + "be thrown here.");
 891:         err.initCause(ex);
 892:         throw err;
 893:       }
 894:     lineBreaker.setText(txt);
 895:     int goodBreakLocation = lineBreaker.previous();
 896:     if (goodBreakLocation != BreakIterator.DONE)
 897:       breakLocation = goodBreakLocation;
 898: 
 899:     View brokenView = createFragment(p0, breakLocation);
 900:     return brokenView;
 901:   }
 902: 
 903:   /**
 904:    * Determines how well the specified view location is suitable for inserting
 905:    * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
 906:    * this method forwards to the superclass, if <code>axis</code> is
 907:    * <code>View.X_AXIS</code> then this method returns
 908:    * {@link View#ExcellentBreakWeight} if there is a suitable break location
 909:    * (usually whitespace) within the specified view span, or
 910:    * {@link View#GoodBreakWeight} if not.
 911:    *
 912:    * @param axis the axis along which the break weight is requested
 913:    * @param pos the starting view location
 914:    * @param len the length of the span at which the view should be broken
 915:    *
 916:    * @return the break weight
 917:    */
 918:   public int getBreakWeight(int axis, float pos, float len)
 919:   {
 920:     int weight;
 921:     if (axis == Y_AXIS)
 922:       weight = super.getBreakWeight(axis, pos, len);
 923:     else
 924:       {
 925:         // Determine the model locations at pos and pos + len.
 926:         int spanX = (int) getPreferredSpan(X_AXIS);
 927:         int spanY = (int) getPreferredSpan(Y_AXIS);
 928:         Rectangle dummyAlloc = new Rectangle(0, 0, spanX, spanY);
 929:         Position.Bias[] biasRet = new Position.Bias[1];
 930:         int offset1 = viewToModel(pos, spanY / 2, dummyAlloc, biasRet);
 931:         int offset2 = viewToModel(pos, spanY / 2, dummyAlloc, biasRet);
 932:         Segment txt = getText(offset1, offset2);
 933:         BreakIterator lineBreaker = BreakIterator.getLineInstance();
 934:         lineBreaker.setText(txt);
 935:         int breakLoc = lineBreaker.previous();
 936:         if (breakLoc == offset1)
 937:           weight = View.BadBreakWeight;
 938:         else if(breakLoc ==  BreakIterator.DONE)
 939:           weight = View.GoodBreakWeight;
 940:         else
 941:           weight = View.ExcellentBreakWeight;
 942:       }
 943:     return weight;
 944:   }
 945: 
 946:   /**
 947:    * Receives notification that some text attributes have changed within the
 948:    * text fragment that this view is responsible for. This calls
 949:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 950:    * both width and height.
 951:    *
 952:    * @param e the document event describing the change; not used here
 953:    * @param a the view allocation on screen; not used here
 954:    * @param vf the view factory; not used here
 955:    */
 956:   public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 957:   {
 958:     getParent().preferenceChanged(this, true, true);
 959:   }
 960: 
 961:   /**
 962:    * Receives notification that some text has been inserted within the
 963:    * text fragment that this view is responsible for. This calls
 964:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 965:    * width.
 966:    *
 967:    * @param e the document event describing the change; not used here
 968:    * @param a the view allocation on screen; not used here
 969:    * @param vf the view factory; not used here
 970:    */
 971:   public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 972:   {
 973:     getParent().preferenceChanged(this, true, false);
 974:   }
 975: 
 976:   /**
 977:    * Receives notification that some text has been removed within the
 978:    * text fragment that this view is responsible for. This calls
 979:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 980:    * width.
 981:    *
 982:    * @param e the document event describing the change; not used here
 983:    * @param a the view allocation on screen; not used here
 984:    * @param vf the view factory; not used here
 985:    */
 986:   public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 987:   {
 988:     getParent().preferenceChanged(this, true, false);
 989:   }
 990: 
 991:   /**
 992:    * Creates a fragment view of this view that starts at <code>p0</code> and
 993:    * ends at <code>p1</code>.
 994:    *
 995:    * @param p0 the start location for the fragment view
 996:    * @param p1 the end location for the fragment view
 997:    *
 998:    * @return the fragment view
 999:    */
1000:   public View createFragment(int p0, int p1)
1001:   {
1002:     GlyphView fragment = (GlyphView) clone();
1003:     fragment.startOffset = p0;
1004:     fragment.endOffset = p1;
1005:     return fragment;
1006:   }
1007: 
1008:   /**
1009:    * Returns the alignment of this view along the specified axis. For the Y
1010:    * axis this is <code>(height - descent) / height</code> for the used font,
1011:    * so that it is aligned along the baseline.
1012:    * For the X axis the superclass is called.
1013:    */
1014:   public float getAlignment(int axis)
1015:   {
1016:     float align;
1017:     if (axis == Y_AXIS)
1018:       {
1019:         checkPainter();
1020:         GlyphPainter painter = getGlyphPainter();
1021:         float height = painter.getHeight(this);
1022:         float descent = painter.getDescent(this);
1023:         align = (height - descent) / height; 
1024:       }
1025:     else
1026:       align = super.getAlignment(axis);
1027: 
1028:     return align;
1029:   }
1030: 
1031:   /**
1032:    * Returns the model location that should be used to place a caret when
1033:    * moving the caret through the document.
1034:    *
1035:    * @param pos the current model location
1036:    * @param bias the bias for <code>p</code>
1037:    * @param a the allocated region for the glyph view
1038:    * @param direction the direction from the current position; Must be one of
1039:    *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
1040:    *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
1041:    * @param biasRet filled with the bias of the resulting location when method
1042:    *        returns
1043:    *
1044:    * @return the location within the document that should be used to place the
1045:    *         caret when moving the caret around the document
1046:    *
1047:    * @throws BadLocationException if <code>pos</code> is an invalid model
1048:    *         location
1049:    * @throws IllegalArgumentException if <code>d</code> is invalid
1050:    */
1051:   public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
1052:                                        int direction, Position.Bias[] biasRet)
1053:     throws BadLocationException
1054:   {
1055:     checkPainter();
1056:     GlyphPainter painter = getGlyphPainter();
1057:     return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
1058:                                              biasRet);
1059:   }
1060: }