Source for javax.swing.text.PlainView

   1: /* PlainView.java --
   2:    Copyright (C) 2004, 2005, 2006  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.Component;
  43: import java.awt.Font;
  44: import java.awt.FontMetrics;
  45: import java.awt.Graphics;
  46: import java.awt.Rectangle;
  47: import java.awt.Shape;
  48: 
  49: import javax.swing.SwingUtilities;
  50: import javax.swing.event.DocumentEvent;
  51: import javax.swing.event.DocumentEvent.ElementChange;
  52: 
  53: public class PlainView extends View implements TabExpander
  54: {
  55:   Color selectedColor;
  56:   Color unselectedColor;
  57: 
  58:   /**
  59:    * The color that is used to draw disabled text fields.
  60:    */
  61:   Color disabledColor;
  62: 
  63:   /**
  64:    * While painting this is the textcomponent's current start index
  65:    * of the selection.
  66:    */
  67:   int selectionStart;
  68: 
  69:   /**
  70:    * While painting this is the textcomponent's current end index
  71:    * of the selection.
  72:    */
  73:   int selectionEnd;
  74: 
  75:   Font font;
  76: 
  77:   /** The length of the longest line in the Document **/
  78:   float maxLineLength = -1;
  79: 
  80:   /** The longest line in the Document **/
  81:   Element longestLine = null;
  82: 
  83:   protected FontMetrics metrics;
  84: 
  85:   /**
  86:    * The instance returned by {@link #getLineBuffer()}.
  87:    */
  88:   private transient Segment lineBuffer;
  89: 
  90:   /**
  91:    * The base offset for tab calculations.
  92:    */
  93:   private int tabBase;
  94: 
  95:   /**
  96:    * The tab size.
  97:    */
  98:   private int tabSize;
  99: 
 100:   public PlainView(Element elem)
 101:   {
 102:     super(elem);
 103:   }
 104: 
 105:   /**
 106:    * @since 1.4
 107:    */
 108:   protected void updateMetrics()
 109:   {
 110:     Component component = getContainer();
 111:     Font font = component.getFont();
 112: 
 113:     if (this.font != font)
 114:       {
 115:         this.font = font;
 116:         metrics = component.getFontMetrics(font);
 117:         tabSize = getTabSize() * metrics.charWidth('m');
 118:       }
 119:   }
 120: 
 121:   /**
 122:    * @since 1.4
 123:    */
 124:   protected Rectangle lineToRect(Shape a, int line)
 125:   {
 126:     // Ensure metrics are up-to-date.
 127:     updateMetrics();
 128: 
 129:     Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
 130:     int fontHeight = metrics.getHeight();
 131:     return new Rectangle(rect.x, rect.y + (line * fontHeight),
 132:                          rect.width, fontHeight);
 133:   }
 134: 
 135:   public Shape modelToView(int position, Shape a, Position.Bias b)
 136:     throws BadLocationException
 137:   {
 138:     // Ensure metrics are up-to-date.
 139:     updateMetrics();
 140: 
 141:     Document document = getDocument();
 142: 
 143:     // Get rectangle of the line containing position.
 144:     int lineIndex = getElement().getElementIndex(position);
 145:     Rectangle rect = lineToRect(a, lineIndex);
 146:     tabBase = rect.x;
 147: 
 148:     // Get the rectangle for position.
 149:     Element line = getElement().getElement(lineIndex);
 150:     int lineStart = line.getStartOffset();
 151:     Segment segment = getLineBuffer();
 152:     document.getText(lineStart, position - lineStart, segment);
 153:     int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase,
 154:                                                this, lineStart);
 155: 
 156:     // Calc the real rectangle.
 157:     rect.x += xoffset;
 158:     rect.width = 1;
 159:     rect.height = metrics.getHeight();
 160: 
 161:     return rect;
 162:   }
 163: 
 164:   /**
 165:    * Draws a line of text. The X and Y coordinates specify the start of
 166:    * the <em>baseline</em> of the line.
 167:    *
 168:    * @param lineIndex the index of the line
 169:    * @param g the graphics to use for drawing the text
 170:    * @param x the X coordinate of the baseline
 171:    * @param y the Y coordinate of the baseline
 172:    */
 173:   protected void drawLine(int lineIndex, Graphics g, int x, int y)
 174:   {
 175:     try
 176:       {
 177:         Element line = getElement().getElement(lineIndex);
 178:         int startOffset = line.getStartOffset();
 179:         int endOffset = line.getEndOffset() - 1;
 180: 
 181:         if (selectionStart <= startOffset)
 182:           // Selection starts before the line ...
 183:           if (selectionEnd <= startOffset)
 184:             {
 185:               // end ends before the line: Draw completely unselected text.
 186:               drawUnselectedText(g, x, y, startOffset, endOffset);
 187:             }
 188:           else if (selectionEnd <= endOffset)
 189:             {
 190:               // and ends within the line: First part is selected,
 191:               // second is not.
 192:               x = drawSelectedText(g, x, y, startOffset, selectionEnd);
 193:               drawUnselectedText(g, x, y, selectionEnd, endOffset);
 194:             }
 195:           else
 196:             // and ends behind the line: Draw completely selected text.
 197:             drawSelectedText(g, x, y, startOffset, endOffset);
 198:         else if (selectionStart < endOffset)
 199:           // Selection starts within the line ..
 200:           if (selectionEnd < endOffset)
 201:             {
 202:               // and ends within it: First part unselected, second part
 203:               // selected, third part unselected.
 204:               x = drawUnselectedText(g, x, y, startOffset, selectionStart);
 205:               x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
 206:               drawUnselectedText(g, x, y, selectionEnd, endOffset);
 207:             }
 208:           else
 209:             {
 210:               // and ends behind the line: First part unselected, second
 211:               // part selected.
 212:               x = drawUnselectedText(g, x, y, startOffset, selectionStart);
 213:               drawSelectedText(g, x, y, selectionStart, endOffset);
 214:             }
 215:         else
 216:           // Selection is behind this line: Draw completely unselected text.
 217:           drawUnselectedText(g, x, y, startOffset, endOffset);
 218:       }
 219:     catch (BadLocationException e)
 220:     {
 221:       AssertionError ae = new AssertionError("Unexpected bad location");
 222:       ae.initCause(e);
 223:       throw ae;
 224:     }
 225:   }
 226: 
 227:   protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
 228:     throws BadLocationException
 229:   {
 230:     g.setColor(selectedColor);
 231:     Segment segment = getLineBuffer();
 232:     getDocument().getText(p0, p1 - p0, segment);
 233:     return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
 234:   }
 235: 
 236:   /**
 237:    * Draws a chunk of unselected text.
 238:    *
 239:    * @param g the graphics to use for drawing the text
 240:    * @param x the X coordinate of the baseline
 241:    * @param y the Y coordinate of the baseline
 242:    * @param p0 the start position in the text model
 243:    * @param p1 the end position in the text model
 244:    *
 245:    * @return the X location of the end of the range
 246:    *
 247:    * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
 248:    *         invalid
 249:    */
 250:   protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
 251:     throws BadLocationException
 252:   {
 253:     JTextComponent textComponent = (JTextComponent) getContainer();
 254:     if (textComponent.isEnabled())
 255:       g.setColor(unselectedColor);
 256:     else
 257:       g.setColor(disabledColor);
 258: 
 259:     Segment segment = getLineBuffer();
 260:     getDocument().getText(p0, p1 - p0, segment);
 261:     return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
 262:   }
 263: 
 264:   public void paint(Graphics g, Shape s)
 265:   {
 266:     // Ensure metrics are up-to-date.
 267:     updateMetrics();
 268: 
 269:     JTextComponent textComponent = (JTextComponent) getContainer();
 270: 
 271:     selectedColor = textComponent.getSelectedTextColor();
 272:     unselectedColor = textComponent.getForeground();
 273:     disabledColor = textComponent.getDisabledTextColor();
 274:     selectionStart = textComponent.getSelectionStart();
 275:     selectionEnd = textComponent.getSelectionEnd();
 276: 
 277:     Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
 278:     tabBase = rect.x;
 279: 
 280:     // FIXME: Text may be scrolled.
 281:     Document document = textComponent.getDocument();
 282:     Element root = getElement();
 283:     int height = metrics.getHeight();
 284: 
 285:     // For layered highlighters we need to paint the layered highlights
 286:     // before painting any text.
 287:     LayeredHighlighter hl = null;
 288:     Highlighter h = textComponent.getHighlighter();
 289:     if (h instanceof LayeredHighlighter)
 290:       hl = (LayeredHighlighter) h;
 291: 
 292:     int count = root.getElementCount();
 293: 
 294:     // Determine first and last line inside the clip.
 295:     Rectangle clip = g.getClipBounds();
 296:     SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height,
 297:                                        clip);
 298:     int line0 = (clip.y - rect.y) / height;
 299:     line0 = Math.max(0, Math.min(line0, count - 1));
 300:     int line1 = (clip.y + clip.height - rect.y) / height;
 301:     line1 = Math.max(0, Math.min(line1, count - 1));
 302:     int y = rect.y + metrics.getAscent() + height * line0;
 303:     for (int i = line0; i <= line1; i++)
 304:       {
 305:         if (hl != null)
 306:           {
 307:             Element lineEl = root.getElement(i);
 308:             // Exclude the trailing newline from beeing highlighted.
 309:             if (i == count)
 310:               hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
 311:                                         lineEl.getEndOffset(), s, textComponent,
 312:                                         this);
 313:             else
 314:               hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
 315:                                         lineEl.getEndOffset() - 1, s,
 316:                                         textComponent, this);
 317:           }
 318:         drawLine(i, g, rect.x, y);
 319:         y += height;
 320:       }
 321:   }
 322: 
 323:   /**
 324:    * Returns the tab size of a tab.  Checks the Document's
 325:    * properties for PlainDocument.tabSizeAttribute and returns it if it is
 326:    * defined, otherwise returns 8.
 327:    *
 328:    * @return the tab size.
 329:    */
 330:   protected int getTabSize()
 331:   {
 332:     Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
 333:     if (tabSize == null)
 334:       return 8;
 335:     return ((Integer)tabSize).intValue();
 336:   }
 337: 
 338:   /**
 339:    * Returns the next tab stop position after a given reference position.
 340:    *
 341:    * This implementation ignores the <code>tabStop</code> argument.
 342:    *
 343:    * @param x the current x position in pixels
 344:    * @param tabStop the position within the text stream that the tab occured at
 345:    */
 346:   public float nextTabStop(float x, int tabStop)
 347:   {
 348:     float next = x;
 349:     if (tabSize != 0)
 350:       {
 351:         int numTabs = (((int) x) - tabBase) / tabSize;
 352:         next = tabBase + (numTabs + 1) * tabSize;
 353:       }
 354:     return next;
 355:   }
 356: 
 357:   /**
 358:    * Returns the length of the longest line, used for getting the span
 359:    * @return the length of the longest line
 360:    */
 361:   float determineMaxLineLength()
 362:   {
 363:     // if the longest line is cached, return the cached value
 364:     if (maxLineLength != -1)
 365:       return maxLineLength;
 366: 
 367:     // otherwise we have to go through all the lines and find it
 368:     Element el = getElement();
 369:     Segment seg = getLineBuffer();
 370:     float span = 0;
 371:     for (int i = 0; i < el.getElementCount(); i++)
 372:       {
 373:         Element child = el.getElement(i);
 374:         int start = child.getStartOffset();
 375:         int end = child.getEndOffset() - 1;
 376:         try
 377:           {
 378:             el.getDocument().getText(start, end - start, seg);
 379:           }
 380:         catch (BadLocationException ex)
 381:           {
 382:             AssertionError ae = new AssertionError("Unexpected bad location");
 383:             ae.initCause(ex);
 384:             throw ae;
 385:           }
 386: 
 387:         if (seg == null || seg.array == null || seg.count == 0)
 388:           continue;
 389: 
 390:         int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
 391:         if (width > span)
 392:           {
 393:             longestLine = child;
 394:             span = width;
 395:           }
 396:       }
 397:     maxLineLength = span;
 398:     return maxLineLength;
 399:   }
 400: 
 401:   public float getPreferredSpan(int axis)
 402:   {
 403:     if (axis != X_AXIS && axis != Y_AXIS)
 404:       throw new IllegalArgumentException();
 405: 
 406:     // make sure we have the metrics
 407:     updateMetrics();
 408: 
 409:     Element el = getElement();
 410:     float span;
 411: 
 412:     switch (axis)
 413:       {
 414:       case X_AXIS:
 415:         span = determineMaxLineLength();
 416:         break;
 417:       case Y_AXIS:
 418:       default:
 419:         span = metrics.getHeight() * el.getElementCount();
 420:         break;
 421:       }
 422: 
 423:     return span;
 424:   }
 425: 
 426:   /**
 427:    * Maps coordinates from the <code>View</code>'s space into a position
 428:    * in the document model.
 429:    *
 430:    * @param x the x coordinate in the view space
 431:    * @param y the y coordinate in the view space
 432:    * @param a the allocation of this <code>View</code>
 433:    * @param b the bias to use
 434:    *
 435:    * @return the position in the document that corresponds to the screen
 436:    *         coordinates <code>x, y</code>
 437:    */
 438:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 439:   {
 440:     Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
 441:     tabBase = rec.x;
 442: 
 443:     int pos;
 444:     if ((int) y < rec.y)
 445:       // Above our area vertically. Return start offset.
 446:       pos = getStartOffset();
 447:     else if ((int) y > rec.y + rec.height)
 448:       // Below our area vertically. Return end offset.
 449:       pos = getEndOffset() - 1;
 450:     else
 451:       {
 452:         // Inside the allocation vertically. Determine line and X offset.
 453:         Document doc = getDocument();
 454:         Element root = doc.getDefaultRootElement();
 455:         int line = Math.abs(((int) y - rec.y) / metrics.getHeight());
 456:         if (line >= root.getElementCount())
 457:           pos = getEndOffset() - 1;
 458:         else
 459:           {
 460:             Element lineEl = root.getElement(line);
 461:             if (x < rec.x)
 462:               // To the left of the allocation area.
 463:               pos = lineEl.getStartOffset();
 464:             else if (x > rec.x + rec.width)
 465:               // To the right of the allocation area.
 466:               pos = lineEl.getEndOffset() - 1;
 467:             else
 468:               {
 469:                 try
 470:                   {
 471:                     int p0 = lineEl.getStartOffset();
 472:                     int p1 = lineEl.getEndOffset();
 473:                     Segment s = new Segment();
 474:                     doc.getText(p0, p1 - p0, s);
 475:                     tabBase = rec.x;
 476:                     pos = p0 + Utilities.getTabbedTextOffset(s, metrics,
 477:                                                              tabBase, (int) x,
 478:                                                              this, p0);
 479:                   }
 480:                 catch (BadLocationException ex)
 481:                   {
 482:                     // Should not happen.
 483:                     pos = -1;
 484:                   }
 485:               }
 486: 
 487:           }
 488:       }
 489:     // Bias is always forward.
 490:     b[0] = Position.Bias.Forward;
 491:     return pos;
 492:   }
 493: 
 494:   /**
 495:    * Since insertUpdate and removeUpdate each deal with children
 496:    * Elements being both added and removed, they both have to perform
 497:    * the same checks.  So they both simply call this method.
 498:    * @param changes the DocumentEvent for the changes to the Document.
 499:    * @param a the allocation of the View.
 500:    * @param f the ViewFactory to use for rebuilding.
 501:    */
 502:   protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
 503:   {
 504:     // This happens during initialization.
 505:     if (metrics == null)
 506:       {
 507:         updateMetrics();
 508:         preferenceChanged(null, true, true);
 509:         return;
 510:       }
 511: 
 512:     Element element = getElement();
 513: 
 514:     // Find longest line if it hasn't been initialized yet.
 515:     if (longestLine == null)
 516:       findLongestLine(0, element.getElementCount() - 1);
 517: 
 518:     ElementChange change = changes.getChange(element);
 519:     if (changes.getType() == DocumentEvent.EventType.INSERT)
 520:       {
 521:         // Handles character/line insertion.
 522: 
 523:         // Determine if lines have been added. In this case we repaint
 524:         // differently.
 525:         boolean linesAdded = true;
 526:         if (change == null)
 527:           linesAdded = false;
 528: 
 529:         // Determine the start line.
 530:         int start;
 531:         if (linesAdded)
 532:           start = change.getIndex();
 533:         else
 534:           start = element.getElementIndex(changes.getOffset());
 535: 
 536:         // Determine the length of the updated region.
 537:         int length = 0;
 538:         if (linesAdded)
 539:           length = change.getChildrenAdded().length - 1;
 540: 
 541:         // Update the longest line and length.
 542:         int oldMaxLength = (int) maxLineLength;
 543:         if (longestLine.getEndOffset() < changes.getOffset()
 544:             || longestLine.getStartOffset() > changes.getOffset()
 545:                                              + changes.getLength())
 546:           {
 547:             findLongestLine(start, start + length);
 548:           }
 549:         else
 550:           {
 551:             findLongestLine(0, element.getElementCount() - 1);
 552:           }
 553: 
 554:         // Trigger a preference change so that the layout gets updated
 555:         // correctly.
 556:         preferenceChanged(null, maxLineLength != oldMaxLength, linesAdded);
 557: 
 558:         // Damage the updated line range.
 559:         int endLine = start;
 560:         if (linesAdded)
 561:           endLine = element.getElementCount() - 1;
 562:         damageLineRange(start, endLine, a, getContainer());
 563: 
 564:       }
 565:     else
 566:       {
 567:         // Handles character/lines removals.
 568: 
 569:         // Update the longest line and length and trigger preference changed.
 570:         int oldMaxLength = (int) maxLineLength;
 571:         if (change != null)
 572:           {
 573:             // Line(s) have been removed.
 574:             findLongestLine(0, element.getElementCount() - 1);
 575:             preferenceChanged(null, maxLineLength != oldMaxLength, true);
 576:           }
 577:         else
 578:           {
 579:             // No line has been removed.
 580:             int lineNo = getElement().getElementIndex(changes.getOffset());
 581:             Element line = getElement().getElement(lineNo);
 582:             if (longestLine == line)
 583:               {
 584:                 findLongestLine(0, element.getElementCount() - 1);
 585:                 preferenceChanged(null, maxLineLength != oldMaxLength, false);
 586:             }
 587:             damageLineRange(lineNo, lineNo, a, getContainer());
 588:         }
 589:       }
 590:   }
 591: 
 592:   /**
 593:    * This method is called when something is inserted into the Document
 594:    * that this View is displaying.
 595:    *
 596:    * @param changes the DocumentEvent for the changes.
 597:    * @param a the allocation of the View
 598:    * @param f the ViewFactory used to rebuild
 599:    */
 600:   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
 601:   {
 602:     updateDamage(changes, a, f);
 603:   }
 604: 
 605:   /**
 606:    * This method is called when something is removed from the Document
 607:    * that this View is displaying.
 608:    *
 609:    * @param changes the DocumentEvent for the changes.
 610:    * @param a the allocation of the View
 611:    * @param f the ViewFactory used to rebuild
 612:    */
 613:   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
 614:   {
 615:     updateDamage(changes, a, f);
 616:   }
 617: 
 618:   /**
 619:    * This method is called when attributes were changed in the
 620:    * Document in a location that this view is responsible for.
 621:    */
 622:   public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
 623:   {
 624:     updateDamage(changes, a, f);
 625:   }
 626: 
 627:   /**
 628:    * Repaint the given line range.  This is called from insertUpdate,
 629:    * changedUpdate, and removeUpdate when no new lines were added
 630:    * and no lines were removed, to repaint the line that was
 631:    * modified.
 632:    *
 633:    * @param line0 the start of the range
 634:    * @param line1 the end of the range
 635:    * @param a the rendering region of the host
 636:    * @param host the Component that uses this View (used to call repaint
 637:    * on that Component)
 638:    *
 639:    * @since 1.4
 640:    */
 641:   protected void damageLineRange (int line0, int line1, Shape a, Component host)
 642:   {
 643:     if (a == null)
 644:       return;
 645: 
 646:     Rectangle rec0 = lineToRect(a, line0);
 647:     Rectangle rec1 = lineToRect(a, line1);
 648: 
 649:     if (rec0 == null || rec1 == null)
 650:       // something went wrong, repaint the entire host to be safe
 651:       host.repaint();
 652:     else
 653:       {
 654:         Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y,
 655:                                                            rec0.width,
 656:                                                            rec0.height, rec1);
 657:         host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
 658:                      repaintRec.height);
 659:       }
 660:   }
 661: 
 662:   /**
 663:    * Provides a {@link Segment} object, that can be used to fetch text from
 664:    * the document.
 665:    *
 666:    * @returna {@link Segment} object, that can be used to fetch text from
 667:    *          the document
 668:    */
 669:   protected final Segment getLineBuffer()
 670:   {
 671:     if (lineBuffer == null)
 672:       lineBuffer = new Segment();
 673:     return lineBuffer;
 674:   }
 675: 
 676:   /**
 677:    * Finds and updates the longest line in the view inside an interval of
 678:    * lines.
 679:    *
 680:    * @param start the start of the search interval
 681:    * @param end the end of the search interval
 682:    */
 683:   private void findLongestLine(int start, int end)
 684:   {
 685:     for (int i = start; i <= end; i++)
 686:       {
 687:         int w = getLineLength(i);
 688:         if (w > maxLineLength)
 689:           {
 690:             maxLineLength = w;
 691:             longestLine = getElement().getElement(i);
 692:           }
 693:       }
 694:   }
 695: 
 696:   /**
 697:    * Determines the length of the specified line.
 698:    *
 699:    * @param line the number of the line
 700:    *
 701:    * @return the length of the line in pixels
 702:    */
 703:   private int getLineLength(int line)
 704:   {
 705:     Element lineEl = getElement().getElement(line);
 706:     Segment buffer = getLineBuffer();
 707:     try
 708:       {
 709:         Document doc = getDocument();
 710:         doc.getText(lineEl.getStartOffset(),
 711:                     lineEl.getEndOffset() - lineEl.getStartOffset() - 1,
 712:                     buffer);
 713:       }
 714:     catch (BadLocationException ex)
 715:       {
 716:         AssertionError err = new AssertionError("Unexpected bad location");
 717:         err.initCause(ex);
 718:         throw err;
 719:       }
 720: 
 721:     return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this,
 722:                                         lineEl.getStartOffset());
 723:   }
 724: }