Source for javax.swing.text.WrappedPlainView

   1: /* WrappedPlainView.java -- 
   2:    Copyright (C) 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.Container;
  43: import java.awt.FontMetrics;
  44: import java.awt.Graphics;
  45: import java.awt.Rectangle;
  46: import java.awt.Shape;
  47: 
  48: import javax.swing.event.DocumentEvent;
  49: import javax.swing.text.Position.Bias;
  50: 
  51: /**
  52:  * @author Anthony Balkissoon abalkiss at redhat dot com
  53:  *
  54:  */
  55: public class WrappedPlainView extends BoxView implements TabExpander
  56: {
  57:   /** The color for selected text **/
  58:   Color selectedColor;
  59:   
  60:   /** The color for unselected text **/
  61:   Color unselectedColor;
  62:   
  63:   /** The color for disabled components **/
  64:   Color disabledColor;
  65:   
  66:   /**
  67:    * Stores the font metrics. This is package private to avoid synthetic
  68:    * accessor method.
  69:    */
  70:   FontMetrics metrics;
  71:   
  72:   /** Whether or not to wrap on word boundaries **/
  73:   boolean wordWrap;
  74:   
  75:   /** A ViewFactory that creates WrappedLines **/
  76:   ViewFactory viewFactory = new WrappedLineCreator();
  77:   
  78:   /** The start of the selected text **/
  79:   int selectionStart;
  80:   
  81:   /** The end of the selected text **/
  82:   int selectionEnd;
  83:   
  84:   /** The height of the line (used while painting) **/
  85:   int lineHeight;
  86:   
  87:   /**
  88:    * The instance returned by {@link #getLineBuffer()}.
  89:    */
  90:   private transient Segment lineBuffer;
  91:   
  92:   public WrappedPlainView (Element elem)
  93:   {
  94:     this (elem, false);
  95:   }
  96:   
  97:   public WrappedPlainView (Element elem, boolean wordWrap)
  98:   {
  99:     super (elem, Y_AXIS);
 100:     this.wordWrap = wordWrap;    
 101:   }  
 102:   
 103:   /**
 104:    * Provides access to the Segment used for retrievals from the Document.
 105:    * @return the Segment.
 106:    */
 107:   protected final Segment getLineBuffer()
 108:   {
 109:     if (lineBuffer == null)
 110:       lineBuffer = new Segment();
 111:     return lineBuffer;
 112:   }
 113:   
 114:   /**
 115:    * Returns the next tab stop position after a given reference position.
 116:    *
 117:    * This implementation ignores the <code>tabStop</code> argument.
 118:    * 
 119:    * @param x the current x position in pixels
 120:    * @param tabStop the position within the text stream that the tab occured at
 121:    */
 122:   public float nextTabStop(float x, int tabStop)
 123:   {
 124:     JTextComponent host = (JTextComponent)getContainer();
 125:     float tabSizePixels = getTabSize()
 126:                           * host.getFontMetrics(host.getFont()).charWidth('m');
 127:     return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
 128:   }
 129:   
 130:   /**
 131:    * Returns the tab size for the Document based on 
 132:    * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is
 133:    * not defined
 134:    * 
 135:    * @return the tab size.
 136:    */
 137:   protected int getTabSize()
 138:   {
 139:     Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
 140:     if (tabSize == null)
 141:       return 8;
 142:     return ((Integer)tabSize).intValue();
 143:   }
 144:   
 145:   /**
 146:    * Draws a line of text, suppressing white space at the end and expanding
 147:    * tabs.  Calls drawSelectedText and drawUnselectedText.
 148:    * @param p0 starting document position to use
 149:    * @param p1 ending document position to use
 150:    * @param g graphics context
 151:    * @param x starting x position
 152:    * @param y starting y position
 153:    */
 154:   protected void drawLine(int p0, int p1, Graphics g, int x, int y)
 155:   {
 156:     try
 157:     {
 158:       // We have to draw both selected and unselected text.  There are
 159:       // several cases:
 160:       //  - entire range is unselected
 161:       //  - entire range is selected
 162:       //  - start of range is selected, end of range is unselected
 163:       //  - start of range is unselected, end of range is selected
 164:       //  - middle of range is selected, start and end of range is unselected
 165:       
 166:       // entire range unselected:      
 167:       if ((selectionStart == selectionEnd) || 
 168:           (p0 > selectionEnd || p1 < selectionStart))
 169:         drawUnselectedText(g, x, y, p0, p1);
 170:       
 171:       // entire range selected
 172:       else if (p0 >= selectionStart && p1 <= selectionEnd)
 173:         drawSelectedText(g, x, y, p0, p1);
 174:       
 175:       // start of range selected, end of range unselected
 176:       else if (p0 >= selectionStart)
 177:         {
 178:           x = drawSelectedText(g, x, y, p0, selectionEnd);
 179:           drawUnselectedText(g, x, y, selectionEnd, p1);
 180:         }
 181:       
 182:       // start of range unselected, end of range selected
 183:       else if (selectionStart > p0 && selectionEnd > p1)
 184:         {
 185:           x = drawUnselectedText(g, x, y, p0, selectionStart);
 186:           drawSelectedText(g, x, y, selectionStart, p1);
 187:         }
 188:       
 189:       // middle of range selected
 190:       else if (selectionStart > p0)
 191:         {
 192:           x = drawUnselectedText(g, x, y, p0, selectionStart);
 193:           x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
 194:           drawUnselectedText(g, x, y, selectionEnd, p1);
 195:         }        
 196:     }
 197:     catch (BadLocationException ble)
 198:     {
 199:       // shouldn't happen
 200:     }
 201:   }
 202: 
 203:   /**
 204:    * Renders the range of text as selected text.  Just paints the text 
 205:    * in the color specified by the host component.  Assumes the highlighter
 206:    * will render the selected background.
 207:    * @param g the graphics context
 208:    * @param x the starting X coordinate
 209:    * @param y the starting Y coordinate
 210:    * @param p0 the starting model location
 211:    * @param p1 the ending model location 
 212:    * @return the X coordinate of the end of the text
 213:    * @throws BadLocationException if the given range is invalid
 214:    */
 215:   protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
 216:       throws BadLocationException
 217:   {
 218:     g.setColor(selectedColor);
 219:     Segment segment = getLineBuffer();
 220:     getDocument().getText(p0, p1 - p0, segment);
 221:     return Utilities.drawTabbedText(segment, x, y, g, this, p0);
 222:   }
 223: 
 224:   /**
 225:    * Renders the range of text as normal unhighlighted text.
 226:    * @param g the graphics context
 227:    * @param x the starting X coordinate
 228:    * @param y the starting Y coordinate
 229:    * @param p0 the starting model location
 230:    * @param p1 the end model location
 231:    * @return the X location of the end off the range
 232:    * @throws BadLocationException if the range given is invalid
 233:    */
 234:   protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
 235:       throws BadLocationException
 236:   {    
 237:     JTextComponent textComponent = (JTextComponent) getContainer();
 238:     if (textComponent.isEnabled())
 239:       g.setColor(unselectedColor);
 240:     else
 241:       g.setColor(disabledColor);
 242: 
 243:     Segment segment = getLineBuffer();
 244:     getDocument().getText(p0, p1 - p0, segment);
 245:     return Utilities.drawTabbedText(segment, x, y, g, this, p0);
 246:   }  
 247:   
 248:   /**
 249:    * Loads the children to initiate the view.  Called by setParent.
 250:    * Creates a WrappedLine for each child Element.
 251:    */
 252:   protected void loadChildren (ViewFactory f)
 253:   {
 254:     Element root = getElement();
 255:     int numChildren = root.getElementCount();
 256:     if (numChildren == 0)
 257:       return;
 258:     
 259:     View[] children = new View[numChildren];
 260:     for (int i = 0; i < numChildren; i++)
 261:       children[i] = new WrappedLine(root.getElement(i));
 262:     replace(0, 0, children);
 263:   }
 264:   
 265:   /**
 266:    * Calculates the break position for the text between model positions
 267:    * p0 and p1.  Will break on word boundaries or character boundaries
 268:    * depending on the break argument given in construction of this 
 269:    * WrappedPlainView.  Used by the nested WrappedLine class to determine
 270:    * when to start the next logical line.
 271:    * @param p0 the start model position
 272:    * @param p1 the end model position
 273:    * @return the model position at which to break the text
 274:    */
 275:   protected int calculateBreakPosition(int p0, int p1)
 276:   {
 277:     Container c = getContainer();
 278:     
 279:     int li = getLeftInset();
 280:     int ti = getTopInset();
 281:     
 282:     Rectangle alloc = new Rectangle(li, ti,
 283:                                     getWidth()-getRightInset()-li,
 284:                                     getHeight()-getBottomInset()-ti);
 285: 
 286:     // Mimic a behavior observed in the RI.
 287:     if (alloc.isEmpty())
 288:       return 0;
 289:     
 290:     updateMetrics();
 291:     
 292:     try
 293:       {
 294:         getDocument().getText(p0, p1 - p0, getLineBuffer());
 295:       }
 296:     catch (BadLocationException ble)
 297:       {
 298:         // this shouldn't happen
 299:         throw new InternalError("Invalid offsets p0: " + p0 + " - p1: " + p1);
 300:       }
 301: 
 302:     if (wordWrap)
 303:       return Utilities.getBreakLocation(lineBuffer, metrics, alloc.x,
 304:                                           alloc.x + alloc.width, this, p0);
 305:     else
 306:       return p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x,
 307:                                              alloc.x + alloc.width, this, 0,
 308:                                              true);
 309:   }
 310:   
 311:   void updateMetrics()
 312:   {
 313:     Container component = getContainer();
 314:     metrics = component.getFontMetrics(component.getFont());
 315:   }
 316:   
 317:   /**
 318:    * Determines the preferred span along the given axis.  Implemented to 
 319:    * cache the font metrics and then call the super classes method.
 320:    */
 321:   public float getPreferredSpan (int axis)
 322:   {
 323:     updateMetrics();
 324:     return super.getPreferredSpan(axis);
 325:   }
 326:   
 327:   /**
 328:    * Determines the minimum span along the given axis.  Implemented to 
 329:    * cache the font metrics and then call the super classes method.
 330:    */
 331:   public float getMinimumSpan (int axis)
 332:   {
 333:     updateMetrics();
 334:     return super.getMinimumSpan(axis);
 335:   }
 336:   
 337:   /**
 338:    * Determines the maximum span along the given axis.  Implemented to 
 339:    * cache the font metrics and then call the super classes method.
 340:    */
 341:   public float getMaximumSpan (int axis)
 342:   {
 343:     updateMetrics();
 344:     return super.getMaximumSpan(axis);
 345:   }
 346:   
 347:   /**
 348:    * Called when something was inserted.  Overridden so that
 349:    * the view factory creates WrappedLine views.
 350:    */
 351:   public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
 352:   {
 353:     super.insertUpdate(e, a, viewFactory);
 354: 
 355:     // No repaint needed, as this is done by the WrappedLine instances.
 356:   }
 357:   
 358:   /**
 359:    * Called when something is removed.  Overridden so that
 360:    * the view factory creates WrappedLine views.
 361:    */
 362:   public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
 363:   {
 364:     super.removeUpdate(e, a, viewFactory);
 365:     
 366:     // No repaint needed, as this is done by the WrappedLine instances.
 367:   }
 368:   
 369:   /**
 370:    * Called when the portion of the Document that this View is responsible
 371:    * for changes.  Overridden so that the view factory creates
 372:    * WrappedLine views.
 373:    */
 374:   public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
 375:   {
 376:     super.changedUpdate(e, a, viewFactory);
 377:     
 378:     // No repaint needed, as this is done by the WrappedLine instances.
 379:   }
 380:     
 381:   class WrappedLineCreator implements ViewFactory
 382:   {
 383:     // Creates a new WrappedLine
 384:     public View create(Element elem)
 385:     {
 386:       return new WrappedLine(elem);
 387:     }    
 388:   }
 389:   
 390:   /**
 391:    * Renders the <code>Element</code> that is associated with this
 392:    * <code>View</code>.  Caches the metrics and then calls
 393:    * super.paint to paint all the child views.
 394:    *
 395:    * @param g the <code>Graphics</code> context to render to
 396:    * @param a the allocated region for the <code>Element</code>
 397:    */
 398:   public void paint(Graphics g, Shape a)
 399:   {
 400:     JTextComponent comp = (JTextComponent)getContainer();
 401:     // Ensure metrics are up-to-date.
 402:     updateMetrics();
 403:     
 404:     selectionStart = comp.getSelectionStart();
 405:     selectionEnd = comp.getSelectionEnd();
 406: 
 407:     selectedColor = comp.getSelectedTextColor();
 408:     unselectedColor = comp.getForeground();
 409:     disabledColor = comp.getDisabledTextColor();
 410:     selectedColor = comp.getSelectedTextColor();
 411:     lineHeight = metrics.getHeight();
 412:     g.setFont(comp.getFont());
 413: 
 414:     super.paint(g, a);
 415:   }
 416:   
 417:   /**
 418:    * Sets the size of the View.  Implemented to update the metrics
 419:    * and then call super method.
 420:    */
 421:   public void setSize (float width, float height)
 422:   {
 423:     updateMetrics();
 424:     if (width != getWidth())
 425:       preferenceChanged(null, true, true);
 426:     super.setSize(width, height);
 427:   }
 428:   
 429:   class WrappedLine extends View
 430:   { 
 431:     /** Used to cache the number of lines for this View **/
 432:     int numLines = 1;
 433:     
 434:     public WrappedLine(Element elem)
 435:     {
 436:       super(elem);
 437:       determineNumLines();
 438:     }
 439: 
 440:     /**
 441:      * Renders this (possibly wrapped) line using the given Graphics object
 442:      * and on the given rendering surface.
 443:      */
 444:     public void paint(Graphics g, Shape s)
 445:     {
 446:       Rectangle rect = s.getBounds();
 447: 
 448:       int end = getEndOffset();
 449:       int currStart = getStartOffset();
 450:       int currEnd;
 451:       int count = 0;
 452:       while (currStart < end)
 453:         {
 454:           currEnd = calculateBreakPosition(currStart, end);
 455: 
 456:           drawLine(currStart, currEnd, g, rect.x, rect.y + metrics.getAscent());
 457:           
 458:           rect.y += lineHeight;          
 459:           if (currEnd == currStart)
 460:             currStart ++;
 461:           else
 462:             currStart = currEnd;
 463:           
 464:           count++;
 465:           
 466:         }
 467:       
 468:       if (count != numLines)
 469:         {
 470:           numLines = count;
 471:           preferenceChanged(this, false, true);
 472:         }
 473:       
 474:     }
 475:     
 476:     /**
 477:      * Calculates the number of logical lines that the Element
 478:      * needs to be displayed and updates the variable numLines
 479:      * accordingly.
 480:      */
 481:     void determineNumLines()
 482:     {      
 483:       numLines = 0;
 484:       int end = getEndOffset();
 485:       if (end == 0)
 486:         return;
 487:             
 488:       int breakPoint;
 489:       for (int i = getStartOffset(); i < end;)
 490:         {
 491:           numLines ++;
 492:           // careful: check that there's no off-by-one problem here
 493:           // depending on which position calculateBreakPosition returns
 494:           breakPoint = calculateBreakPosition(i, end);
 495:           
 496:           if (breakPoint == 0)
 497:             return;
 498:           
 499:           // If breakPoint is equal to the current index no further
 500:           // line is needed and we can end the loop.
 501:           if (breakPoint == i)
 502:             break;
 503:           else
 504:             i = breakPoint;
 505:         }
 506:     }
 507:     
 508:     /**
 509:      * Determines the preferred span for this view along the given axis.
 510:      * 
 511:      * @param axis the axis (either X_AXIS or Y_AXIS)
 512:      * 
 513:      * @return the preferred span along the given axis.
 514:      * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS
 515:      */
 516:     public float getPreferredSpan(int axis)
 517:     {
 518:       if (axis == X_AXIS)
 519:         return getWidth();
 520:       else if (axis == Y_AXIS)
 521:         {
 522:           if (metrics == null)
 523:             updateMetrics();
 524:           return numLines * metrics.getHeight();
 525:         }
 526:       
 527:       throw new IllegalArgumentException("Invalid axis for getPreferredSpan: "
 528:                                          + axis);
 529:     }
 530:     
 531:     /**
 532:      * Provides a mapping from model space to view space.
 533:      * 
 534:      * @param pos the position in the model
 535:      * @param a the region into which the view is rendered
 536:      * @param b the position bias (forward or backward)
 537:      * 
 538:      * @return a box in view space that represents the given position 
 539:      * in model space
 540:      * @throws BadLocationException if the given model position is invalid
 541:      */
 542:     public Shape modelToView(int pos, Shape a, Bias b)
 543:         throws BadLocationException
 544:     {
 545:       Rectangle rect = a.getBounds();
 546:       
 547:       // Throwing a BadLocationException is an observed behavior of the RI.
 548:       if (rect.isEmpty())
 549:         throw new BadLocationException("Unable to calculate view coordinates "
 550:                                        + "when allocation area is empty.", 5);
 551:       
 552:       Segment s = getLineBuffer();
 553:       int lineHeight = metrics.getHeight();
 554:       
 555:       // Return a rectangle with width 1 and height equal to the height 
 556:       // of the text
 557:       rect.height = lineHeight;
 558:       rect.width = 1;
 559: 
 560:       int currLineStart = getStartOffset();
 561:       int end = getEndOffset();
 562:       
 563:       if (pos < currLineStart || pos >= end)
 564:         throw new BadLocationException("invalid offset", pos);
 565:            
 566:       while (true)
 567:         {
 568:           int currLineEnd = calculateBreakPosition(currLineStart, end);
 569:           // If pos is between currLineStart and currLineEnd then just find
 570:           // the width of the text from currLineStart to pos and add that
 571:           // to rect.x
 572:           if (pos >= currLineStart && pos < currLineEnd)
 573:             {             
 574:               try
 575:                 {
 576:                   getDocument().getText(currLineStart, pos - currLineStart, s);
 577:                 }
 578:               catch (BadLocationException ble)
 579:                 {
 580:                   // Shouldn't happen
 581:                 }
 582:               rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x,
 583:                                                      WrappedPlainView.this,
 584:                                                      currLineStart);
 585:               return rect;
 586:             }
 587:           // Increment rect.y so we're checking the next logical line
 588:           rect.y += lineHeight;
 589:           
 590:           // Increment currLineStart to the model position of the start
 591:           // of the next logical line
 592:           if (currLineEnd == currLineStart)
 593:             currLineStart = end;
 594:           else
 595:             currLineStart = currLineEnd;
 596:         }
 597: 
 598:     }
 599: 
 600:     /**
 601:      * Provides a mapping from view space to model space.
 602:      * 
 603:      * @param x the x coordinate in view space
 604:      * @param y the y coordinate in view space
 605:      * @param a the region into which the view is rendered
 606:      * @param b the position bias (forward or backward)
 607:      * 
 608:      * @return the location in the model that best represents the
 609:      * given point in view space
 610:      */
 611:     public int viewToModel(float x, float y, Shape a, Bias[] b)
 612:     {
 613:       Segment s = getLineBuffer();
 614:       Rectangle rect = a.getBounds();
 615:       int currLineStart = getStartOffset();
 616:       
 617:       // Although calling modelToView with the last possible offset will
 618:       // cause a BadLocationException in CompositeView it is allowed
 619:       // to return that offset in viewToModel.
 620:       int end = getEndOffset();
 621:       
 622:       int lineHeight = metrics.getHeight();
 623:       if (y < rect.y)
 624:         return currLineStart;
 625: 
 626:       if (y > rect.y + rect.height)
 627:         return end;
 628:       
 629:       // Note: rect.x and rect.width do not represent the width of painted
 630:       // text but the area where text *may* be painted. This means the width
 631:       // is most of the time identical to the component's width.
 632: 
 633:       while (currLineStart != end)
 634:         {
 635:           int currLineEnd = calculateBreakPosition(currLineStart, end);
 636: 
 637:           // If we're at the right y-position that means we're on the right
 638:           // logical line and we should look for the character
 639:           if (y >= rect.y && y < rect.y + lineHeight)
 640:             {
 641:               try
 642:                 {
 643:                   getDocument().getText(currLineStart, currLineEnd - currLineStart, s);
 644:                 }
 645:               catch (BadLocationException ble)
 646:                 {
 647:                   // Shouldn't happen
 648:                 }
 649:               
 650:               int offset = Utilities.getTabbedTextOffset(s, metrics, rect.x,
 651:                                                    (int) x,
 652:                                                    WrappedPlainView.this,
 653:                                                    currLineStart);
 654:               // If the calculated offset is the end of the line (in the
 655:               // document (= start of the next line) return the preceding
 656:               // offset instead. This makes sure that clicking right besides
 657:               // the last character in a line positions the cursor after the
 658:               // last character and not in the beginning of the next line.
 659:               return (offset == currLineEnd) ? offset - 1 : offset;
 660:             }
 661:           // Increment rect.y so we're checking the next logical line
 662:           rect.y += lineHeight;
 663:           
 664:           // Increment currLineStart to the model position of the start
 665:           // of the next logical line.
 666:           currLineStart = currLineEnd;
 667: 
 668:         }
 669:       
 670:       return end;
 671:     }    
 672:     
 673:     /**
 674:      * <p>This method is called from insertUpdate and removeUpdate.</p>
 675:      * 
 676:      * <p>If the number of lines in the document has changed, just repaint
 677:      * the whole thing (note, could improve performance by not repainting 
 678:      * anything above the changes).  If the number of lines hasn't changed, 
 679:      * just repaint the given Rectangle.</p>
 680:      * 
 681:      * <p>Note that the <code>Rectangle</code> argument may be <code>null</code>
 682:      * when the allocation area is empty.</code> 
 683:      * 
 684:      * @param a the Rectangle to repaint if the number of lines hasn't changed
 685:      */
 686:     void updateDamage (Rectangle a)
 687:     {
 688:       // If the allocation area is empty we can't do anything useful.
 689:       // As determining the number of lines is impossible in that state we
 690:       // reset it to an invalid value which can then be recalculated at a
 691:       // later point.
 692:       if (a == null || a.isEmpty())
 693:         {
 694:           numLines = 1;
 695:           return;
 696:         }
 697:       
 698:       int oldNumLines = numLines;
 699:       determineNumLines();
 700:       
 701:       if (numLines != oldNumLines)
 702:         preferenceChanged(this, false, true);
 703:       else
 704:         getContainer().repaint(a.x, a.y, a.width, a.height);
 705:     }
 706:     
 707:     /**
 708:      * This method is called when something is inserted into the Document
 709:      * that this View is displaying.
 710:      * 
 711:      * @param changes the DocumentEvent for the changes.
 712:      * @param a the allocation of the View
 713:      * @param f the ViewFactory used to rebuild
 714:      */
 715:     public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f)
 716:     {
 717:       updateDamage((Rectangle)a); 
 718:     }
 719:     
 720:     /**
 721:      * This method is called when something is removed from the Document
 722:      * that this View is displaying.
 723:      * 
 724:      * @param changes the DocumentEvent for the changes.
 725:      * @param a the allocation of the View
 726:      * @param f the ViewFactory used to rebuild
 727:      */
 728:     public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f)
 729:     {
 730:       // Note: This method is not called when characters from the
 731:       // end of the document are removed. The reason for this
 732:       // can be found in the implementation of View.forwardUpdate:
 733:       // The document event will denote offsets which do not exist
 734:       // any more, getViewIndex() will therefore return -1 and this
 735:       // makes View.forwardUpdate() skip this method call.
 736:       // However this seems to cause no trouble and as it reduces the
 737:       // number of method calls it can stay this way.
 738:       
 739:       updateDamage((Rectangle)a); 
 740:     }
 741:   }
 742: }