Source for javax.swing.text.DefaultStyledDocument

   1: /* DefaultStyledDocument.java --
   2:    Copyright (C) 2004, 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.io.Serializable;
  44: import java.util.Vector;
  45: 
  46: import javax.swing.event.DocumentEvent;
  47: 
  48: /**
  49:  * The default implementation of {@link StyledDocument}.
  50:  *
  51:  * The document is modeled as an {@link Element} tree, which has
  52:  * a {@link SectionElement} as single root, which has one or more
  53:  * {@link AbstractDocument.BranchElement}s as paragraph nodes
  54:  * and each paragraph node having one or more
  55:  * {@link AbstractDocument.LeafElement}s as content nodes.
  56:  *
  57:  * @author Michael Koch (konqueror@gmx.de)
  58:  * @author Roman Kennke (roman@kennke.org)
  59:  */
  60: public class DefaultStyledDocument extends AbstractDocument
  61:   implements StyledDocument
  62: {
  63:   /**
  64:    * Carries specification information for new {@link Element}s that should
  65:    * be created in {@link ElementBuffer}. This allows the parsing process
  66:    * to be decoupled from the <code>Element</code> creation process.
  67:    */
  68:   public static class ElementSpec
  69:   {
  70:     /**
  71:      * This indicates a start tag. This is a possible value for
  72:      * {@link #getType}.
  73:      */
  74:     public static final short StartTagType = 1;
  75: 
  76:     /**
  77:      * This indicates an end tag. This is a possible value for
  78:      * {@link #getType}.
  79:      */
  80:     public static final short EndTagType = 2;
  81: 
  82:     /**
  83:      * This indicates a content element. This is a possible value for
  84:      * {@link #getType}.
  85:      */
  86:     public static final short ContentType = 3;
  87: 
  88:     /**
  89:      * This indicates that the data associated with this spec should be joined
  90:      * with what precedes it. This is a possible value for
  91:      * {@link #getDirection}.
  92:      */
  93:     public static final short JoinPreviousDirection = 4;
  94: 
  95:     /**
  96:      * This indicates that the data associated with this spec should be joined
  97:      * with what follows it. This is a possible value for
  98:      * {@link #getDirection}.
  99:      */
 100:     public static final short JoinNextDirection = 5;
 101: 
 102:     /**
 103:      * This indicates that the data associated with this spec should be used
 104:      * to create a new element. This is a possible value for
 105:      * {@link #getDirection}.
 106:      */
 107:     public static final short OriginateDirection = 6;
 108: 
 109:     /**
 110:      * This indicates that the data associated with this spec should be joined
 111:      * to the fractured element. This is a possible value for
 112:      * {@link #getDirection}.
 113:      */
 114:     public static final short JoinFractureDirection = 7;
 115: 
 116:     /**
 117:      * The type of the tag.
 118:      */
 119:     short type;
 120: 
 121:     /**
 122:      * The direction of the tag.
 123:      */
 124:     short direction;
 125: 
 126:     /**
 127:      * The offset of the content.
 128:      */
 129:     int offset;
 130: 
 131:     /**
 132:      * The length of the content.
 133:      */
 134:     int length;
 135: 
 136:     /**
 137:      * The actual content.
 138:      */
 139:     char[] content;
 140: 
 141:     /**
 142:      * The attributes for the tag.
 143:      */
 144:     AttributeSet attributes;
 145: 
 146:     /**
 147:      * Creates a new <code>ElementSpec</code> with no content, length or
 148:      * offset. This is most useful for start and end tags.
 149:      *
 150:      * @param a the attributes for the element to be created
 151:      * @param type the type of the tag
 152:      */
 153:     public ElementSpec(AttributeSet a, short type)
 154:     {
 155:       this(a, type, 0);
 156:     }
 157: 
 158:     /**
 159:      * Creates a new <code>ElementSpec</code> that specifies the length but
 160:      * not the offset of an element. Such <code>ElementSpec</code>s are
 161:      * processed sequentially from a known starting point.
 162:      *
 163:      * @param a the attributes for the element to be created
 164:      * @param type the type of the tag
 165:      * @param len the length of the element
 166:      */
 167:     public ElementSpec(AttributeSet a, short type, int len)
 168:     {
 169:       this(a, type, null, 0, len);
 170:     }
 171:  
 172:     /**
 173:      * Creates a new <code>ElementSpec</code> with document content.
 174:      *
 175:      * @param a the attributes for the element to be created
 176:      * @param type the type of the tag
 177:      * @param txt the actual content
 178:      * @param offs the offset into the <code>txt</code> array
 179:      * @param len the length of the element
 180:      */
 181:     public ElementSpec(AttributeSet a, short type, char[] txt, int offs,
 182:                        int len)
 183:     {
 184:       attributes = a;
 185:       this.type = type;
 186:       offset = offs;
 187:       length = len;
 188:       content = txt;
 189:       direction = OriginateDirection;
 190:     }
 191: 
 192:     /**
 193:      * Sets the type of the element.
 194:      *
 195:      * @param type the type of the element to be set
 196:      */
 197:     public void setType(short type)
 198:     {
 199:       this.type = type;
 200:     }
 201: 
 202:     /**
 203:      * Returns the type of the element.
 204:      *
 205:      * @return the type of the element
 206:      */
 207:     public short getType()
 208:     {
 209:       return type;
 210:     }
 211: 
 212:     /**
 213:      * Sets the direction of the element.
 214:      *
 215:      * @param dir the direction of the element to be set
 216:      */
 217:     public void setDirection(short dir)
 218:     {
 219:       direction = dir;
 220:     }
 221: 
 222:     /**
 223:      * Returns the direction of the element.
 224:      *
 225:      * @return the direction of the element
 226:      */
 227:     public short getDirection()
 228:     {
 229:       return direction;
 230:     }
 231: 
 232:     /**
 233:      * Returns the attributes of the element.
 234:      *
 235:      * @return the attributes of the element
 236:      */
 237:     public AttributeSet getAttributes()
 238:     {
 239:       return attributes;
 240:     }
 241: 
 242:     /**
 243:      * Returns the actual content of the element.
 244:      *
 245:      * @return the actual content of the element
 246:      */
 247:     public char[] getArray()
 248:     {
 249:       return content;
 250:     }
 251: 
 252:     /**
 253:      * Returns the offset of the content.
 254:      *
 255:      * @return the offset of the content
 256:      */
 257:     public int getOffset()
 258:     {
 259:       return offset;
 260:     }
 261: 
 262:     /**
 263:      * Returns the length of the content.
 264:      *
 265:      * @return the length of the content
 266:      */
 267:     public int getLength()
 268:     {
 269:       return length;
 270:     }
 271: 
 272:     /**
 273:      * Returns a String representation of this <code>ElementSpec</code>
 274:      * describing the type, direction and length of this
 275:      * <code>ElementSpec</code>.
 276:      *
 277:      * @return a String representation of this <code>ElementSpec</code>
 278:      */
 279:     public String toString()
 280:     {
 281:       StringBuilder b = new StringBuilder();
 282:       b.append('<');
 283:       switch (type)
 284:         {
 285:         case StartTagType:
 286:           b.append("StartTag");
 287:           break;
 288:         case EndTagType:
 289:           b.append("EndTag");
 290:           break;
 291:         case ContentType:
 292:           b.append("Content");
 293:           break;
 294:         default:
 295:           b.append("??");
 296:           break;
 297:         }
 298: 
 299:       b.append(':');
 300: 
 301:       switch (direction)
 302:         {
 303:         case JoinPreviousDirection:
 304:           b.append("JoinPrevious");
 305:           break;
 306:         case JoinNextDirection:
 307:           b.append("JoinNext");
 308:           break;
 309:         case OriginateDirection:
 310:           b.append("Originate");
 311:           break;
 312:         case JoinFractureDirection:
 313:           b.append("Fracture");
 314:           break;
 315:         default:
 316:           b.append("??");
 317:           break;
 318:         }
 319: 
 320:       b.append(':');
 321:       b.append(length);
 322: 
 323:       return b.toString();
 324:     }
 325:   }
 326: 
 327:   /**
 328:    * Performs all <em>structural</code> changes to the <code>Element</code>
 329:    * hierarchy.
 330:    */
 331:   public class ElementBuffer implements Serializable
 332:   {
 333:     /** The serialization UID (compatible with JDK1.5). */
 334:     private static final long serialVersionUID = 1688745877691146623L;
 335: 
 336:     /** The root element of the hierarchy. */
 337:     private Element root;
 338: 
 339:     /** Holds the offset for structural changes. */
 340:     private int offset;
 341: 
 342:     /** Holds the length of structural changes. */
 343:     private int length;
 344: 
 345:     /**
 346:      * Holds fractured elements during insertion of end and start tags.
 347:      * Inserting an end tag may lead to fracturing of the current paragraph
 348:      * element. The elements that have been cut off may be added to the
 349:      * next paragraph that is created in the next start tag.
 350:      */
 351:     Element[] fracture;
 352: 
 353:     /**
 354:      * The ElementChange that describes the latest changes.
 355:      */
 356:     DefaultDocumentEvent documentEvent;
 357: 
 358:     /**
 359:      * Creates a new <code>ElementBuffer</code> for the specified
 360:      * <code>root</code> element.
 361:      *
 362:      * @param root the root element for this <code>ElementBuffer</code>
 363:      */
 364:     public ElementBuffer(Element root)
 365:     {
 366:       this.root = root;
 367:     }
 368: 
 369:     /**
 370:      * Returns the root element of this <code>ElementBuffer</code>.
 371:      *
 372:      * @return the root element of this <code>ElementBuffer</code>
 373:      */
 374:     public Element getRootElement()
 375:     {
 376:       return root;
 377:     }
 378: 
 379:     /**
 380:      * Modifies the element structure so that the specified interval starts
 381:      * and ends at an element boundary. Content and paragraph elements
 382:      * are split and created as necessary.
 383:      *
 384:      * This also updates the <code>DefaultDocumentEvent</code> to reflect the
 385:      * structural changes.
 386:      *
 387:      * The bulk work is delegated to {@link #changeUpdate()}.
 388:      *
 389:      * @param offset the start index of the interval to be changed
 390:      * @param length the length of the interval to be changed
 391:      * @param ev the <code>DefaultDocumentEvent</code> describing the change
 392:      */
 393:     public void change(int offset, int length, DefaultDocumentEvent ev)
 394:     {
 395:       this.offset = offset;
 396:       this.length = length;
 397:       documentEvent = ev;
 398:       changeUpdate();
 399:     }
 400: 
 401:     /**
 402:      * Performs the actual work for {@link #change}.
 403:      * The elements at the interval boundaries are split up (if necessary)
 404:      * so that the interval boundaries are located at element boundaries.
 405:      */
 406:     protected void changeUpdate()
 407:     {
 408:       // Split up the element at the start offset if necessary.
 409:       Element el = getCharacterElement(offset);
 410:       split(el, offset);
 411: 
 412:       int endOffset = offset + length;
 413:       el = getCharacterElement(endOffset);
 414:       split(el, endOffset);
 415:     }
 416: 
 417:     /**
 418:      * Splits an element if <code>offset</code> is not alread at its boundary.
 419:      *
 420:      * @param el the Element to possibly split
 421:      * @param offset the offset at which to possibly split
 422:      */
 423:     void split(Element el, int offset)
 424:     {
 425:       if (el instanceof AbstractElement)
 426:     {
 427:       AbstractElement ael = (AbstractElement) el;
 428:       int startOffset = ael.getStartOffset();
 429:       int endOffset = ael.getEndOffset();
 430:       int len = endOffset - startOffset;
 431:       if (startOffset != offset && endOffset != offset)
 432:         {
 433:           Element paragraph = ael.getParentElement();
 434:           if (paragraph instanceof BranchElement)
 435:         {
 436:           BranchElement par = (BranchElement) paragraph;
 437:           Element child1 = createLeafElement(par, ael, startOffset,
 438:                              offset);
 439:           Element child2 = createLeafElement(par, ael, offset,
 440:                              endOffset);
 441:           int index = par.getElementIndex(startOffset);
 442:           Element[] add = new Element[]{ child1, child2 };
 443:           par.replace(index, 1, add);
 444:           documentEvent.addEdit(new ElementEdit(par, index,
 445:                                                 new Element[]{ el },
 446:                                                 add));
 447:         }
 448:               else
 449:                 throw new AssertionError("paragraph elements are expected to "
 450:                                          + "be instances of "
 451:               + "javax.swing.text.AbstractDocument.BranchElement");
 452:         }
 453:     }
 454:       else
 455:     throw new AssertionError("content elements are expected to be "
 456:                  + "instances of "
 457:             + "javax.swing.text.AbstractDocument.AbstractElement");
 458:     }
 459: 
 460:     /**
 461:      * Inserts new <code>Element</code> in the document at the specified
 462:      * position.
 463:      *
 464:      * Most of the work is done by {@link #insertUpdate}, after some fields
 465:      * have been prepared for it.
 466:      *
 467:      * @param offset the location in the document at which the content is
 468:      *        inserted
 469:      * @param length the length of the inserted content
 470:      * @param data the element specifications for the content to be inserted
 471:      * @param ev the document event that is updated to reflect the structural
 472:      *        changes
 473:      */
 474:     public void insert(int offset, int length, ElementSpec[] data,
 475:                        DefaultDocumentEvent ev)
 476:     {
 477:       this.offset = offset;
 478:       this.length = length;
 479:       documentEvent = ev;
 480:       insertUpdate(data);
 481:     }
 482: 
 483:     /**
 484:      * Performs the actual structural change for {@link #insert}. This
 485:      * creates a bunch of {@link Element}s as specified by <code>data</code>
 486:      * and inserts it into the document as specified in the arguments to
 487:      * {@link #insert}.
 488:      *
 489:      * @param data the element specifications for the elements to be inserte
 490:      */
 491:     protected void insertUpdate(ElementSpec[] data)
 492:     {
 493:       for (int i = 0; i < data.length; i++)
 494:         {
 495:           switch (data[i].getType())
 496:             {
 497:             case ElementSpec.StartTagType:
 498:               insertStartTag(data[i]);
 499:               break;
 500:             case ElementSpec.EndTagType:
 501:               insertEndTag(data[i]);
 502:               break;
 503:             default:
 504:               insertContentTag(data[i]);
 505:               break;
 506:             }
 507:         }
 508:     }
 509: 
 510:     /**
 511:      * Insert a new paragraph after the paragraph at the current position.
 512:      *
 513:      * @param tag the element spec that describes the element to be inserted
 514:      */
 515:     void insertStartTag(ElementSpec tag)
 516:     {
 517:       BranchElement root = (BranchElement) getDefaultRootElement();
 518:       int index = root.getElementIndex(offset);
 519:       if (index == -1)
 520:         index = 0;
 521: 
 522:       BranchElement newParagraph =
 523:         (BranchElement) createBranchElement(root, tag.getAttributes());
 524:       newParagraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE));
 525: 
 526:       // Add new paragraph into document structure.
 527:       Element[] added = new Element[]{newParagraph};
 528:       root.replace(index + 1, 0, added);
 529:       ElementEdit edit = new ElementEdit(root, index + 1, new Element[0],
 530:                                          added);
 531:       documentEvent.addEdit(edit);
 532: 
 533:       // Maybe add fractured elements.
 534:       if (tag.getDirection() == ElementSpec.JoinFractureDirection)
 535:         {
 536:           Element[] newFracture = new Element[fracture.length];
 537:           for (int i = 0; i < fracture.length; i++)
 538:             {
 539:               Element oldLeaf = fracture[i];
 540:               Element newLeaf = createLeafElement(newParagraph,
 541:                                                   oldLeaf.getAttributes(),
 542:                                                   oldLeaf.getStartOffset(),
 543:                                                   oldLeaf.getEndOffset());
 544:               newFracture[i] = newLeaf;
 545:             }
 546:           newParagraph.replace(0, 0, newFracture);
 547:           edit = new ElementEdit(newParagraph, 0, new Element[0],
 548:                                  fracture);
 549:           documentEvent.addEdit(edit);
 550:           fracture = new Element[0];
 551:         }
 552:     }
 553: 
 554:     /**
 555:      * Inserts an end tag into the document structure. This cuts of the
 556:      * current paragraph element, possibly fracturing it's child elements.
 557:      * The fractured elements are saved so that they can be joined later
 558:      * with a new paragraph element.
 559:      */
 560:     void insertEndTag(ElementSpec tag)
 561:     {
 562:       BranchElement root = (BranchElement) getDefaultRootElement();
 563:       int parIndex = root.getElementIndex(offset);
 564:       BranchElement paragraph = (BranchElement) root.getElement(parIndex);
 565: 
 566:       int index = paragraph.getElementIndex(offset);
 567:       LeafElement content = (LeafElement) paragraph.getElement(index);
 568:       // We might have to split the element at offset.
 569:       split(content, offset);
 570:       index = paragraph.getElementIndex(offset);
 571: 
 572:       int count = paragraph.getElementCount();
 573:       // Store fractured elements.
 574:       fracture = new Element[count - index];
 575:       for (int i = index; i < count; ++i)
 576:         fracture[i - index] = paragraph.getElement(i);
 577: 
 578:       // Delete fractured elements.
 579:       paragraph.replace(index, count - index, new Element[0]);
 580: 
 581:       // Add this action to the document event.
 582:       ElementEdit edit = new ElementEdit(paragraph, index, fracture,
 583:                                          new Element[0]);
 584:       documentEvent.addEdit(edit);
 585:     }
 586: 
 587:     /**
 588:      * Inserts a content element into the document structure.
 589:      *
 590:      * @param tag the element spec
 591:      */
 592:     void insertContentTag(ElementSpec tag)
 593:     {
 594:       int len = tag.getLength();
 595:       int dir = tag.getDirection();
 596:       if (dir == ElementSpec.JoinPreviousDirection)
 597:         {
 598:           Element prev = getCharacterElement(offset);
 599:           BranchElement prevParent = (BranchElement) prev.getParentElement();
 600:           Element join = createLeafElement(prevParent, tag.getAttributes(),
 601:                                            prev.getStartOffset(),
 602:                                            Math.max(prev.getEndOffset(),
 603:                                                     offset + len));
 604:           int ind = prevParent.getElementIndex(offset);
 605:           if (ind == -1)
 606:             ind = 0;
 607:           Element[] add = new Element[]{join};
 608:           prevParent.replace(ind, 1, add);
 609: 
 610:           // Add this action to the document event.
 611:           ElementEdit edit = new ElementEdit(prevParent, ind,
 612:                                              new Element[]{prev}, add);
 613:           documentEvent.addEdit(edit);
 614:         }
 615:       else if (dir == ElementSpec.JoinNextDirection)
 616:         {
 617:           Element next = getCharacterElement(offset + len);
 618:           BranchElement nextParent = (BranchElement) next.getParentElement();
 619:           Element join = createLeafElement(nextParent, tag.getAttributes(),
 620:                                            offset,
 621:                                            next.getEndOffset());
 622:           int ind = nextParent.getElementIndex(offset + len);
 623:           if (ind == -1)
 624:             ind = 0;
 625:           Element[] add = new Element[]{join};
 626:           nextParent.replace(ind, 1, add);
 627: 
 628:           // Add this action to the document event.
 629:           ElementEdit edit = new ElementEdit(nextParent, ind,
 630:                                              new Element[]{next}, add);
 631:           documentEvent.addEdit(edit);
 632:         }
 633:       else
 634:         {
 635:           BranchElement par = (BranchElement) getParagraphElement(offset);
 636: 
 637:           int ind = par.getElementIndex(offset);
 638: 
 639:           // Make room for the element.
 640:           // Cut previous element.
 641:           Element prev = par.getElement(ind);
 642:           if (prev != null && prev.getStartOffset() < offset)
 643:             {
 644:               Element cutPrev = createLeafElement(par, prev.getAttributes(),
 645:                                                   prev.getStartOffset(),
 646:                                                   offset);
 647:               Element[] remove = new Element[]{prev};
 648:               Element[] add = new Element[]{cutPrev};
 649:               if (prev.getEndOffset() > offset + len)
 650:                 {
 651:                   Element rem = createLeafElement(par, prev.getAttributes(),
 652:                                                   offset + len,
 653:                                                   prev.getEndOffset());
 654:                   add = new Element[]{cutPrev, rem};
 655:                 }
 656: 
 657:               par.replace(ind, 1, add);
 658:               documentEvent.addEdit(new ElementEdit(par, ind, remove, add));
 659:               ind++;
 660:             }
 661:           // ind now points to the next element.
 662: 
 663:           // Cut next element if necessary.
 664:           Element next = par.getElement(ind);
 665:           if (next != null && next.getStartOffset() < offset + len)
 666:             {
 667:               Element cutNext = createLeafElement(par, next.getAttributes(),
 668:                                                   offset + len,
 669:                                                   next.getEndOffset());
 670:               Element[] remove = new Element[]{next};
 671:               Element[] add = new Element[]{cutNext};
 672:               par.replace(ind, 1, add);
 673:               documentEvent.addEdit(new ElementEdit(par, ind, remove,
 674:                                                     add));
 675:             }
 676: 
 677:           // Insert new element.
 678:           Element newEl = createLeafElement(par, tag.getAttributes(),
 679:                                             offset, offset + len);
 680:           Element[] added = new Element[]{newEl};
 681:           par.replace(ind, 0, added);
 682:           // Add this action to the document event.
 683:           ElementEdit edit = new ElementEdit(par, ind, new Element[0],
 684:                                              added);
 685:           documentEvent.addEdit(edit);
 686:         }
 687:       offset += len;
 688:     }
 689:   }
 690: 
 691:   /**
 692:    * An element type for sections. This is a simple BranchElement with
 693:    * a unique name.
 694:    */
 695:   protected class SectionElement extends BranchElement
 696:   {
 697:     /**
 698:      * Creates a new SectionElement.
 699:      */
 700:     public SectionElement()
 701:     {
 702:       super(null, null);
 703:     }
 704: 
 705:     /**
 706:      * Returns the name of the element. This method always returns
 707:      * &quot;section&quot;.
 708:      *
 709:      * @return the name of the element
 710:      */
 711:     public String getName()
 712:     {
 713:       return "section";
 714:     }
 715:   }
 716: 
 717:   /** The serialization UID (compatible with JDK1.5). */
 718:   private static final long serialVersionUID = 940485415728614849L;
 719: 
 720:   /**
 721:    * The default size to use for new content buffers.
 722:    */
 723:   public static final int BUFFER_SIZE_DEFAULT = 4096;
 724: 
 725:   /**
 726:    * The <code>EditorBuffer</code> that is used to manage to
 727:    * <code>Element</code> hierarchy.
 728:    */
 729:   protected DefaultStyledDocument.ElementBuffer buffer;
 730: 
 731:   /**
 732:    * Creates a new <code>DefaultStyledDocument</code>.
 733:    */
 734:   public DefaultStyledDocument()
 735:   {
 736:     this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
 737:   }
 738: 
 739:   /**
 740:    * Creates a new <code>DefaultStyledDocument</code> that uses the
 741:    * specified {@link StyleContext}.
 742:    *
 743:    * @param context the <code>StyleContext</code> to use
 744:    */
 745:   public DefaultStyledDocument(StyleContext context)
 746:   {
 747:     this(new GapContent(BUFFER_SIZE_DEFAULT), context);
 748:   }
 749: 
 750:   /**
 751:    * Creates a new <code>DefaultStyledDocument</code> that uses the
 752:    * specified {@link StyleContext} and {@link Content} buffer.
 753:    *
 754:    * @param content the <code>Content</code> buffer to use
 755:    * @param context the <code>StyleContext</code> to use
 756:    */
 757:   public DefaultStyledDocument(AbstractDocument.Content content,
 758:                    StyleContext context)
 759:   {
 760:     super(content, context);
 761:     buffer = new ElementBuffer(createDefaultRoot());
 762:     setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
 763:   }
 764: 
 765:   /**
 766:    * Adds a style into the style hierarchy. Unspecified style attributes
 767:    * can be resolved in the <code>parent</code> style, if one is specified.
 768:    *
 769:    * While it is legal to add nameless styles (<code>nm == null</code),
 770:    * you must be aware that the client application is then responsible
 771:    * for managing the style hierarchy, since unnamed styles cannot be
 772:    * looked up by their name.
 773:    *
 774:    * @param nm the name of the style or <code>null</code> if the style should
 775:    *           be unnamed
 776:    * @param parent the parent in which unspecified style attributes are
 777:    *           resolved, or <code>null</code> if that is not necessary
 778:    *
 779:    * @return the newly created <code>Style</code>
 780:    */
 781:   public Style addStyle(String nm, Style parent)
 782:   {
 783:     StyleContext context = (StyleContext) getAttributeContext();
 784:     return context.addStyle(nm, parent);
 785:   }
 786: 
 787:   /**
 788:    * Create the default root element for this kind of <code>Document</code>.
 789:    *
 790:    * @return the default root element for this kind of <code>Document</code>
 791:    */
 792:   protected AbstractDocument.AbstractElement createDefaultRoot()
 793:   {
 794:     Element[] tmp;
 795:     // FIXME: Create a SecionElement here instead of a BranchElement.
 796:     // Use createBranchElement() and createLeafElement instead.
 797:     SectionElement section = new SectionElement();
 798: 
 799:     BranchElement paragraph =
 800:       (BranchElement) createBranchElement(section, null);
 801:     paragraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE));
 802:     tmp = new Element[1];
 803:     tmp[0] = paragraph;
 804:     section.replace(0, 0, tmp);
 805: 
 806:     LeafElement leaf = new LeafElement(paragraph, null, 0, 1);
 807:     tmp = new Element[1];
 808:     tmp[0] = leaf;
 809:     paragraph.replace(0, 0, tmp);
 810: 
 811:     return section;
 812:   }
 813: 
 814:   /**
 815:    * Returns the <code>Element</code> that corresponds to the character
 816:    * at the specified position.
 817:    *
 818:    * @param position the position of which we query the corresponding
 819:    *        <code>Element</code>
 820:    *
 821:    * @return the <code>Element</code> that corresponds to the character
 822:    *         at the specified position
 823:    */
 824:   public Element getCharacterElement(int position)
 825:   {
 826:     Element element = getDefaultRootElement();
 827: 
 828:     while (! element.isLeaf())
 829:       {
 830:     int index = element.getElementIndex(position);
 831:     element = element.getElement(index);
 832:       }
 833:     
 834:     return element;
 835:   }
 836: 
 837:   /**
 838:    * Extracts a background color from a set of attributes.
 839:    *
 840:    * @param attributes the attributes from which to get a background color
 841:    *
 842:    * @return the background color that correspond to the attributes
 843:    */
 844:   public Color getBackground(AttributeSet attributes)
 845:   {
 846:     StyleContext context = (StyleContext) getAttributeContext();
 847:     return context.getBackground(attributes);
 848:   }
 849: 
 850:   /**
 851:    * Returns the default root element.
 852:    *
 853:    * @return the default root element
 854:    */
 855:   public Element getDefaultRootElement()
 856:   {
 857:     return buffer.getRootElement();
 858:   }
 859: 
 860:   /**
 861:    * Extracts a font from a set of attributes.
 862:    *
 863:    * @param attributes the attributes from which to get a font
 864:    *
 865:    * @return the font that correspond to the attributes
 866:    */
 867:   public Font getFont(AttributeSet attributes)
 868:   {
 869:     StyleContext context = (StyleContext) getAttributeContext();
 870:     return context.getFont(attributes);
 871:   }
 872:   
 873:   /**
 874:    * Extracts a foreground color from a set of attributes.
 875:    *
 876:    * @param attributes the attributes from which to get a foreground color
 877:    *
 878:    * @return the foreground color that correspond to the attributes
 879:    */
 880:   public Color getForeground(AttributeSet attributes)
 881:   {
 882:     StyleContext context = (StyleContext) getAttributeContext();
 883:     return context.getForeground(attributes);
 884:   }
 885: 
 886:   /**
 887:    * Returns the logical <code>Style</code> for the specified position.
 888:    *
 889:    * @param position the position from which to query to logical style
 890:    *
 891:    * @return the logical <code>Style</code> for the specified position
 892:    */
 893:   public Style getLogicalStyle(int position)
 894:   {
 895:     Element paragraph = getParagraphElement(position);
 896:     AttributeSet attributes = paragraph.getAttributes();
 897:     return (Style) attributes.getResolveParent();
 898:   }
 899: 
 900:   /**
 901:    * Returns the paragraph element for the specified position.
 902:    * If the position is outside the bounds of the document's root element,
 903:    * then the closest element is returned. That is the last paragraph if
 904:    * <code>position >= endIndex</code> or the first paragraph if
 905:    * <code>position < startIndex</code>.
 906:    *
 907:    * @param position the position for which to query the paragraph element
 908:    *
 909:    * @return the paragraph element for the specified position
 910:    */
 911:   public Element getParagraphElement(int position)
 912:   {
 913:     BranchElement root = (BranchElement) getDefaultRootElement();
 914:     int start = root.getStartOffset();
 915:     int end = root.getEndOffset();
 916:     if (position >= end)
 917:       position = end - 1;
 918:     else if (position < start)
 919:       position = start;
 920: 
 921:     Element par = root.positionToElement(position);
 922: 
 923:     assert par != null : "The paragraph element must not be null";
 924:     return par;
 925:   }
 926: 
 927:   /**
 928:    * Looks up and returns a named <code>Style</code>.
 929:    *
 930:    * @param nm the name of the <code>Style</code>
 931:    *
 932:    * @return the found <code>Style</code> of <code>null</code> if no such
 933:    *         <code>Style</code> exists
 934:    */
 935:   public Style getStyle(String nm)
 936:   {
 937:     StyleContext context = (StyleContext) getAttributeContext();
 938:     return context.getStyle(nm);
 939:   }
 940: 
 941:   /**
 942:    * Removes a named <code>Style</code> from the style hierarchy.
 943:    *
 944:    * @param nm the name of the <code>Style</code> to be removed
 945:    */
 946:   public void removeStyle(String nm)
 947:   {
 948:     StyleContext context = (StyleContext) getAttributeContext();
 949:     context.removeStyle(nm);
 950:   }
 951: 
 952:   /**
 953:    * Sets text attributes for the fragment specified by <code>offset</code>
 954:    * and <code>length</code>.
 955:    *
 956:    * @param offset the start offset of the fragment
 957:    * @param length the length of the fragment
 958:    * @param attributes the text attributes to set
 959:    * @param replace if <code>true</code>, the attributes of the current
 960:    *     selection are overridden, otherwise they are merged
 961:    */
 962:   public void setCharacterAttributes(int offset, int length,
 963:                      AttributeSet attributes,
 964:                      boolean replace)
 965:   {
 966:     DefaultDocumentEvent ev =
 967:       new DefaultDocumentEvent(offset, length,
 968:                    DocumentEvent.EventType.CHANGE);
 969: 
 970:     // Modify the element structure so that the interval begins at an element
 971:     // start and ends at an element end.
 972:     buffer.change(offset, length, ev);
 973: 
 974:     Element root = getDefaultRootElement();
 975:     // Visit all paragraph elements within the specified interval
 976:     int paragraphCount =  root.getElementCount();
 977:     for (int pindex = 0; pindex < paragraphCount; pindex++)
 978:       {
 979:     Element paragraph = root.getElement(pindex);
 980:     // Skip paragraphs that lie outside the interval.
 981:     if ((paragraph.getStartOffset() > offset + length)
 982:         || (paragraph.getEndOffset() < offset))
 983:       continue;
 984: 
 985:     // Visit content elements within this paragraph
 986:     int contentCount = paragraph.getElementCount();
 987:     for (int cindex = 0; cindex < contentCount; cindex++)
 988:       {
 989:         Element content = paragraph.getElement(cindex);
 990:         // Skip content that lies outside the interval.
 991:         if ((content.getStartOffset() > offset + length)
 992:         || (content.getEndOffset() < offset))
 993:           continue;
 994: 
 995:         if (content instanceof AbstractElement)
 996:           {
 997:         AbstractElement el = (AbstractElement) content;
 998:         if (replace)
 999:           el.removeAttributes(el);
1000:         el.addAttributes(attributes);
1001:           }
1002:         else
1003:           throw new AssertionError("content elements are expected to be"
1004:                        + "instances of "
1005:                + "javax.swing.text.AbstractDocument.AbstractElement");
1006:       }
1007:       }
1008: 
1009:     fireChangedUpdate(ev);
1010:   }
1011:   
1012:   /**
1013:    * Sets the logical style for the paragraph at the specified position.
1014:    *
1015:    * @param position the position at which the logical style is added
1016:    * @param style the style to set for the current paragraph
1017:    */
1018:   public void setLogicalStyle(int position, Style style)
1019:   {
1020:     Element el = getParagraphElement(position);
1021:     if (el instanceof AbstractElement)
1022:       {
1023:         AbstractElement ael = (AbstractElement) el;
1024:         ael.setResolveParent(style);
1025:       }
1026:     else
1027:       throw new AssertionError("paragraph elements are expected to be"
1028:          + "instances of javax.swing.text.AbstractDocument.AbstractElement");
1029:   }
1030: 
1031:   /**
1032:    * Sets text attributes for the paragraph at the specified fragment.
1033:    *
1034:    * @param offset the beginning of the fragment
1035:    * @param length the length of the fragment
1036:    * @param attributes the text attributes to set
1037:    * @param replace if <code>true</code>, the attributes of the current
1038:    *     selection are overridden, otherwise they are merged
1039:    */
1040:   public void setParagraphAttributes(int offset, int length,
1041:                                      AttributeSet attributes,
1042:                                      boolean replace)
1043:   {
1044:     int index = offset;
1045:     while (index < offset + length)
1046:       {
1047:         AbstractElement par = (AbstractElement) getParagraphElement(index);
1048:         AttributeContext ctx = getAttributeContext();
1049:         if (replace)
1050:           par.removeAttributes(par);
1051:         par.addAttributes(attributes);
1052:         index = par.getElementCount();
1053:       }
1054:   }
1055: 
1056:   /**
1057:    * Called in response to content insert actions. This is used to
1058:    * update the element structure.
1059:    *
1060:    * @param ev the <code>DocumentEvent</code> describing the change
1061:    * @param attr the attributes for the change
1062:    */
1063:   protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
1064:   {
1065:     super.insertUpdate(ev, attr);
1066:     int offset = ev.getOffset();
1067:     int length = ev.getLength();
1068:     int endOffset = offset + length;
1069:     Segment txt = new Segment();
1070:     try
1071:       {
1072:         getText(offset, length, txt);
1073:       }
1074:     catch (BadLocationException ex)
1075:       {
1076:         AssertionError ae = new AssertionError("Unexpected bad location");
1077:     ae.initCause(ex);
1078:     throw ae;
1079:       }
1080: 
1081:     int len = 0;
1082:     Vector specs = new Vector();
1083: 
1084:     Element prev = getCharacterElement(offset);
1085:     Element next = getCharacterElement(endOffset);
1086: 
1087:     for (int i = offset; i < endOffset; ++i)
1088:       {
1089:         len++;
1090:         if (txt.array[i] == '\n')
1091:           {
1092:             ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType,
1093:                                                len);
1094: 
1095:             // If we are at the last index, then check if we could probably be
1096:             // joined with the next element.
1097:             if (i == endOffset - 1)
1098:               {
1099:                 if (next.getAttributes().isEqual(attr))
1100:                   spec.setDirection(ElementSpec.JoinNextDirection);
1101:               }
1102:             // If we are at the first new element, then check if it could be
1103:             // joined with the previous element.
1104:             else if (specs.size() == 0)
1105:               {
1106:                 if (prev.getAttributes().isEqual(attr))
1107:                     spec.setDirection(ElementSpec.JoinPreviousDirection);
1108:               }
1109: 
1110:             specs.add(spec);
1111: 
1112:             // Add ElementSpecs for the newline.
1113:             ElementSpec endTag = new ElementSpec(null, ElementSpec.EndTagType);
1114:             specs.add(endTag);
1115:             ElementSpec startTag = new ElementSpec(null,
1116:                                                    ElementSpec.StartTagType);
1117:             startTag.setDirection(ElementSpec.JoinFractureDirection);
1118:             specs.add(startTag);
1119: 
1120:             len = 0;
1121:             offset += len;
1122:           }
1123:       }
1124: 
1125:     // Create last element if last character hasn't been a newline.
1126:     if (len > 0)
1127:       {
1128:         ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType, len);
1129:         // If we are at the first new element, then check if it could be
1130:         // joined with the previous element.
1131:         if (specs.size() == 0)
1132:           {
1133:             if (prev.getAttributes().isEqual(attr))
1134:               spec.setDirection(ElementSpec.JoinPreviousDirection);
1135:           }
1136:         // Check if we could probably be joined with the next element.
1137:         else if (next.getAttributes().isEqual(attr))
1138:           spec.setDirection(ElementSpec.JoinNextDirection);
1139: 
1140:         specs.add(spec);
1141:       }
1142: 
1143:     ElementSpec[] elSpecs =
1144:       (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]);
1145: 
1146:     buffer.insert(offset, length, elSpecs, ev);
1147:   }  
1148: }