001 /* DefaultStyledDocument.java -- 002 Copyright (C) 2004, 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text; 040 041 import java.awt.Color; 042 import java.awt.Font; 043 import java.io.Serializable; 044 import java.util.ArrayList; 045 import java.util.Enumeration; 046 import java.util.Iterator; 047 import java.util.Stack; 048 import java.util.Vector; 049 050 import javax.swing.event.ChangeEvent; 051 import javax.swing.event.ChangeListener; 052 import javax.swing.event.DocumentEvent; 053 import javax.swing.event.UndoableEditEvent; 054 import javax.swing.undo.AbstractUndoableEdit; 055 import javax.swing.undo.UndoableEdit; 056 057 /** 058 * The default implementation of {@link StyledDocument}. The document is 059 * modeled as an {@link Element} tree, which has a {@link SectionElement} as 060 * single root, which has one or more {@link AbstractDocument.BranchElement}s 061 * as paragraph nodes and each paragraph node having one or more 062 * {@link AbstractDocument.LeafElement}s as content nodes. 063 * 064 * @author Michael Koch (konqueror@gmx.de) 065 * @author Roman Kennke (roman@kennke.org) 066 */ 067 public class DefaultStyledDocument extends AbstractDocument implements 068 StyledDocument 069 { 070 071 /** 072 * An {@link UndoableEdit} that can undo attribute changes to an element. 073 * 074 * @author Roman Kennke (kennke@aicas.com) 075 */ 076 public static class AttributeUndoableEdit extends AbstractUndoableEdit 077 { 078 /** 079 * A copy of the old attributes. 080 */ 081 protected AttributeSet copy; 082 083 /** 084 * The new attributes. 085 */ 086 protected AttributeSet newAttributes; 087 088 /** 089 * If the new attributes replaced the old attributes or if they only were 090 * added to them. 091 */ 092 protected boolean isReplacing; 093 094 /** 095 * The element that has changed. 096 */ 097 protected Element element; 098 099 /** 100 * Creates a new <code>AttributeUndoableEdit</code>. 101 * 102 * @param el 103 * the element that changes attributes 104 * @param newAtts 105 * the new attributes 106 * @param replacing 107 * if the new attributes replace the old or only append to them 108 */ 109 public AttributeUndoableEdit(Element el, AttributeSet newAtts, 110 boolean replacing) 111 { 112 element = el; 113 newAttributes = newAtts; 114 isReplacing = replacing; 115 copy = el.getAttributes().copyAttributes(); 116 } 117 118 /** 119 * Undos the attribute change. The <code>copy</code> field is set as 120 * attributes on <code>element</code>. 121 */ 122 public void undo() 123 { 124 super.undo(); 125 AttributeSet atts = element.getAttributes(); 126 if (atts instanceof MutableAttributeSet) 127 { 128 MutableAttributeSet mutable = (MutableAttributeSet) atts; 129 mutable.removeAttributes(atts); 130 mutable.addAttributes(copy); 131 } 132 } 133 134 /** 135 * Redos an attribute change. This adds <code>newAttributes</code> to the 136 * <code>element</code>'s attribute set, possibly clearing all attributes 137 * if <code>isReplacing</code> is true. 138 */ 139 public void redo() 140 { 141 super.undo(); 142 AttributeSet atts = element.getAttributes(); 143 if (atts instanceof MutableAttributeSet) 144 { 145 MutableAttributeSet mutable = (MutableAttributeSet) atts; 146 if (isReplacing) 147 mutable.removeAttributes(atts); 148 mutable.addAttributes(newAttributes); 149 } 150 } 151 } 152 153 /** 154 * Carries specification information for new {@link Element}s that should be 155 * created in {@link ElementBuffer}. This allows the parsing process to be 156 * decoupled from the <code>Element</code> creation process. 157 */ 158 public static class ElementSpec 159 { 160 /** 161 * This indicates a start tag. This is a possible value for {@link #getType}. 162 */ 163 public static final short StartTagType = 1; 164 165 /** 166 * This indicates an end tag. This is a possible value for {@link #getType}. 167 */ 168 public static final short EndTagType = 2; 169 170 /** 171 * This indicates a content element. This is a possible value for 172 * {@link #getType}. 173 */ 174 public static final short ContentType = 3; 175 176 /** 177 * This indicates that the data associated with this spec should be joined 178 * with what precedes it. This is a possible value for {@link #getDirection}. 179 */ 180 public static final short JoinPreviousDirection = 4; 181 182 /** 183 * This indicates that the data associated with this spec should be joined 184 * with what follows it. This is a possible value for {@link #getDirection}. 185 */ 186 public static final short JoinNextDirection = 5; 187 188 /** 189 * This indicates that the data associated with this spec should be used to 190 * create a new element. This is a possible value for {@link #getDirection}. 191 */ 192 public static final short OriginateDirection = 6; 193 194 /** 195 * This indicates that the data associated with this spec should be joined 196 * to the fractured element. This is a possible value for 197 * {@link #getDirection}. 198 */ 199 public static final short JoinFractureDirection = 7; 200 201 /** 202 * The type of the tag. 203 */ 204 short type; 205 206 /** 207 * The direction of the tag. 208 */ 209 short direction; 210 211 /** 212 * The offset of the content. 213 */ 214 int offset; 215 216 /** 217 * The length of the content. 218 */ 219 int length; 220 221 /** 222 * The actual content. 223 */ 224 char[] content; 225 226 /** 227 * The attributes for the tag. 228 */ 229 AttributeSet attributes; 230 231 /** 232 * Creates a new <code>ElementSpec</code> with no content, length or 233 * offset. This is most useful for start and end tags. 234 * 235 * @param a 236 * the attributes for the element to be created 237 * @param type 238 * the type of the tag 239 */ 240 public ElementSpec(AttributeSet a, short type) 241 { 242 this(a, type, 0); 243 } 244 245 /** 246 * Creates a new <code>ElementSpec</code> that specifies the length but 247 * not the offset of an element. Such <code>ElementSpec</code>s are 248 * processed sequentially from a known starting point. 249 * 250 * @param a 251 * the attributes for the element to be created 252 * @param type 253 * the type of the tag 254 * @param len 255 * the length of the element 256 */ 257 public ElementSpec(AttributeSet a, short type, int len) 258 { 259 this(a, type, null, 0, len); 260 } 261 262 /** 263 * Creates a new <code>ElementSpec</code> with document content. 264 * 265 * @param a 266 * the attributes for the element to be created 267 * @param type 268 * the type of the tag 269 * @param txt 270 * the actual content 271 * @param offs 272 * the offset into the <code>txt</code> array 273 * @param len 274 * the length of the element 275 */ 276 public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len) 277 { 278 attributes = a; 279 this.type = type; 280 offset = offs; 281 length = len; 282 content = txt; 283 direction = OriginateDirection; 284 } 285 286 /** 287 * Sets the type of the element. 288 * 289 * @param type 290 * the type of the element to be set 291 */ 292 public void setType(short type) 293 { 294 this.type = type; 295 } 296 297 /** 298 * Returns the type of the element. 299 * 300 * @return the type of the element 301 */ 302 public short getType() 303 { 304 return type; 305 } 306 307 /** 308 * Sets the direction of the element. 309 * 310 * @param dir 311 * the direction of the element to be set 312 */ 313 public void setDirection(short dir) 314 { 315 direction = dir; 316 } 317 318 /** 319 * Returns the direction of the element. 320 * 321 * @return the direction of the element 322 */ 323 public short getDirection() 324 { 325 return direction; 326 } 327 328 /** 329 * Returns the attributes of the element. 330 * 331 * @return the attributes of the element 332 */ 333 public AttributeSet getAttributes() 334 { 335 return attributes; 336 } 337 338 /** 339 * Returns the actual content of the element. 340 * 341 * @return the actual content of the element 342 */ 343 public char[] getArray() 344 { 345 return content; 346 } 347 348 /** 349 * Returns the offset of the content. 350 * 351 * @return the offset of the content 352 */ 353 public int getOffset() 354 { 355 return offset; 356 } 357 358 /** 359 * Returns the length of the content. 360 * 361 * @return the length of the content 362 */ 363 public int getLength() 364 { 365 return length; 366 } 367 368 /** 369 * Returns a String representation of this <code>ElementSpec</code> 370 * describing the type, direction and length of this 371 * <code>ElementSpec</code>. 372 * 373 * @return a String representation of this <code>ElementSpec</code> 374 */ 375 public String toString() 376 { 377 StringBuilder b = new StringBuilder(); 378 switch (type) 379 { 380 case StartTagType: 381 b.append("StartTag"); 382 break; 383 case EndTagType: 384 b.append("EndTag"); 385 break; 386 case ContentType: 387 b.append("Content"); 388 break; 389 default: 390 b.append("??"); 391 break; 392 } 393 394 b.append(':'); 395 396 switch (direction) 397 { 398 case JoinPreviousDirection: 399 b.append("JoinPrevious"); 400 break; 401 case JoinNextDirection: 402 b.append("JoinNext"); 403 break; 404 case OriginateDirection: 405 b.append("Originate"); 406 break; 407 case JoinFractureDirection: 408 b.append("Fracture"); 409 break; 410 default: 411 b.append("??"); 412 break; 413 } 414 415 b.append(':'); 416 b.append(length); 417 418 return b.toString(); 419 } 420 } 421 422 /** 423 * Performs all <em>structural</code> changes to the <code>Element</code> 424 * hierarchy. This class was implemented with much help from the document: 425 * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html. 426 */ 427 public class ElementBuffer implements Serializable 428 { 429 /** 430 * Instance of all editing information for an object in the Vector. This class 431 * is used to add information to the DocumentEvent associated with an 432 * insertion/removal/change as well as to store the changes that need to be 433 * made so they can be made all at the same (appropriate) time. 434 */ 435 class Edit 436 { 437 /** The element to edit . */ 438 Element e; 439 440 /** The index of the change. */ 441 int index; 442 443 /** The removed elements. */ 444 ArrayList removed = new ArrayList(); 445 446 /** The added elements. */ 447 ArrayList added = new ArrayList(); 448 449 /** 450 * Indicates if this edit contains a fracture. 451 */ 452 boolean isFracture; 453 454 /** 455 * Creates a new Edit for the specified element at index i. 456 * 457 * @param el the element 458 * @param i the index 459 */ 460 Edit(Element el, int i) 461 { 462 this(el, i, false); 463 } 464 465 /** 466 * Creates a new Edit for the specified element at index i. 467 * 468 * @param el the element 469 * @param i the index 470 * @param frac if this is a fracture edit or not 471 */ 472 Edit(Element el, int i, boolean frac) 473 { 474 e = el; 475 index = i; 476 isFracture = frac; 477 } 478 479 } 480 481 /** The serialization UID (compatible with JDK1.5). */ 482 private static final long serialVersionUID = 1688745877691146623L; 483 484 /** The root element of the hierarchy. */ 485 private Element root; 486 487 /** Holds the offset for structural changes. */ 488 private int offset; 489 490 /** Holds the end offset for structural changes. */ 491 private int endOffset; 492 493 /** Holds the length of structural changes. */ 494 private int length; 495 496 /** Holds the position of the change. */ 497 private int pos; 498 499 /** 500 * The parent of the fracture. 501 */ 502 private Element fracturedParent; 503 504 /** 505 * The fractured child. 506 */ 507 private Element fracturedChild; 508 509 /** 510 * Indicates if a fracture has been created. 511 */ 512 private boolean createdFracture; 513 514 /** 515 * The current position in the element tree. This is used for bulk inserts 516 * using ElementSpecs. 517 */ 518 private Stack elementStack; 519 520 private Edit[] insertPath; 521 522 private boolean recreateLeafs; 523 524 /** 525 * Vector that contains all the edits. Maybe replace by a HashMap. 526 */ 527 private ArrayList edits; 528 529 private boolean offsetLastIndex; 530 private boolean offsetLastIndexReplace; 531 532 /** 533 * Creates a new <code>ElementBuffer</code> for the specified 534 * <code>root</code> element. 535 * 536 * @param root 537 * the root element for this <code>ElementBuffer</code> 538 */ 539 public ElementBuffer(Element root) 540 { 541 this.root = root; 542 } 543 544 /** 545 * Returns the root element of this <code>ElementBuffer</code>. 546 * 547 * @return the root element of this <code>ElementBuffer</code> 548 */ 549 public Element getRootElement() 550 { 551 return root; 552 } 553 554 /** 555 * Removes the content. This method sets some internal parameters and 556 * delegates the work to {@link #removeUpdate}. 557 * 558 * @param offs 559 * the offset from which content is remove 560 * @param len 561 * the length of the removed content 562 * @param ev 563 * the document event that records the changes 564 */ 565 public void remove(int offs, int len, DefaultDocumentEvent ev) 566 { 567 prepareEdit(offs, len); 568 removeUpdate(); 569 finishEdit(ev); 570 } 571 572 /** 573 * Updates the element structure of the document in response to removal of 574 * content. It removes the affected {@link Element}s from the document 575 * structure. 576 */ 577 protected void removeUpdate() 578 { 579 removeElements(root, offset, endOffset); 580 } 581 582 private boolean removeElements(Element elem, int rmOffs0, int rmOffs1) 583 { 584 boolean ret = false; 585 if (! elem.isLeaf()) 586 { 587 // Update stack for changes. 588 int index0 = elem.getElementIndex(rmOffs0); 589 int index1 = elem.getElementIndex(rmOffs1); 590 elementStack.push(new Edit(elem, index0)); 591 Edit ec = (Edit) elementStack.peek(); 592 593 // If the range is contained by one element, 594 // we just forward the request 595 if (index0 == index1) 596 { 597 Element child0 = elem.getElement(index0); 598 if(rmOffs0 <= child0.getStartOffset() 599 && rmOffs1 >= child0.getEndOffset()) 600 { 601 // Element totally removed. 602 ec.removed.add(child0); 603 } 604 else if (removeElements(child0, rmOffs0, rmOffs1)) 605 { 606 ec.removed.add(child0); 607 } 608 } 609 else 610 { 611 // The removal range spans elements. If we can join 612 // the two endpoints, do it. Otherwise we remove the 613 // interior and forward to the endpoints. 614 Element child0 = elem.getElement(index0); 615 Element child1 = elem.getElement(index1); 616 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset()); 617 if (containsOffs1 && canJoin(child0, child1)) 618 { 619 // Remove and join. 620 for (int i = index0; i <= index1; i++) 621 { 622 ec.removed.add(elem.getElement(i)); 623 } 624 Element e = join(elem, child0, child1, rmOffs0, rmOffs1); 625 ec.added.add(e); 626 } 627 else 628 { 629 // Remove interior and forward. 630 int rmIndex0 = index0 + 1; 631 int rmIndex1 = index1 - 1; 632 if (child0.getStartOffset() == rmOffs0 633 || (index0 == 0 && child0.getStartOffset() > rmOffs0 634 && child0.getEndOffset() <= rmOffs1)) 635 { 636 // Start element completely consumed. 637 child0 = null; 638 rmIndex0 = index0; 639 } 640 if (! containsOffs1) 641 { 642 child1 = null; 643 rmIndex1++; 644 } 645 else if (child1.getStartOffset() == rmOffs1) 646 { 647 // End element not touched. 648 child1 = null; 649 } 650 if (rmIndex0 <= rmIndex1) 651 { 652 ec.index = rmIndex0; 653 } 654 for (int i = rmIndex0; i <= rmIndex1; i++) 655 { 656 ec.removed.add(elem.getElement(i)); 657 } 658 if (child0 != null) 659 { 660 if(removeElements(child0, rmOffs0, rmOffs1)) 661 { 662 ec.removed.add(0, child0); 663 ec.index = index0; 664 } 665 } 666 if (child1 != null) 667 { 668 if(removeElements(child1, rmOffs0, rmOffs1)) 669 { 670 ec.removed.add(child1); 671 } 672 } 673 } 674 } 675 676 // Perform changes. 677 pop(); 678 679 // Return true if we no longer have any children. 680 if(elem.getElementCount() == (ec.removed.size() - ec.added.size())) 681 ret = true; 682 } 683 return ret; 684 } 685 686 /** 687 * Creates a document in response to a call to 688 * {@link DefaultStyledDocument#create(ElementSpec[])}. 689 * 690 * @param len the length of the inserted text 691 * @param data the specs for the elements 692 * @param ev the document event 693 */ 694 void create(int len, ElementSpec[] data, DefaultDocumentEvent ev) 695 { 696 prepareEdit(offset, len); 697 Element el = root; 698 int index = el.getElementIndex(0); 699 while (! el.isLeaf()) 700 { 701 Element child = el.getElement(index); 702 Edit edit = new Edit(el, index, false); 703 elementStack.push(edit); 704 el = child; 705 index = el.getElementIndex(0); 706 } 707 Edit ed = (Edit) elementStack.peek(); 708 Element child = ed.e.getElement(ed.index); 709 ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(), 710 child.getEndOffset())); 711 ed.removed.add(child); 712 while (elementStack.size() > 1) 713 pop(); 714 int n = data.length; 715 716 // Reset root element's attributes. 717 AttributeSet newAtts = null; 718 if (n > 0 && data[0].getType() == ElementSpec.StartTagType) 719 newAtts = data[0].getAttributes(); 720 if (newAtts == null) 721 newAtts = SimpleAttributeSet.EMPTY; 722 MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes(); 723 ev.addEdit(new AttributeUndoableEdit(root, newAtts, true)); 724 mAtts.removeAttributes(mAtts); 725 mAtts.addAttributes(newAtts); 726 727 // Insert the specified elements. 728 for (int i = 1; i < n; i++) 729 insertElement(data[i]); 730 731 // Pop remaining stack. 732 while (elementStack.size() > 0) 733 pop(); 734 735 finishEdit(ev); 736 } 737 738 private boolean canJoin(Element e0, Element e1) 739 { 740 boolean ret = false; 741 if ((e0 != null) && (e1 != null)) 742 { 743 // Don't join a leaf to a branch. 744 boolean isLeaf0 = e0.isLeaf(); 745 boolean isLeaf1 = e1.isLeaf(); 746 if(isLeaf0 == isLeaf1) 747 { 748 if (isLeaf0) 749 { 750 // Only join leaves if the attributes match, otherwise 751 // style information will be lost. 752 ret = e0.getAttributes().isEqual(e1.getAttributes()); 753 } 754 else 755 { 756 // Only join non-leafs if the names are equal. This may result 757 // in loss of style information, but this is typically 758 // acceptable for non-leafs. 759 String name0 = e0.getName(); 760 String name1 = e1.getName(); 761 if (name0 != null) 762 ret = name0.equals(name1); 763 else if (name1 != null) 764 ret = name1.equals(name0); 765 else // Both names null. 766 ret = true; 767 } 768 } 769 } 770 return ret; 771 } 772 773 private Element join(Element p, Element left, Element right, int rmOffs0, 774 int rmOffs1) 775 { 776 Element joined = null; 777 if (left.isLeaf() && right.isLeaf()) 778 { 779 joined = createLeafElement(p, left.getAttributes(), 780 left.getStartOffset(), 781 right.getEndOffset()); 782 } 783 else if ((! left.isLeaf()) && (! right.isLeaf())) 784 { 785 // Join two branch elements. This copies the children before 786 // the removal range on the left element, and after the removal 787 // range on the right element. The two elements on the edge 788 // are joined if possible and needed. 789 joined = createBranchElement(p, left.getAttributes()); 790 int ljIndex = left.getElementIndex(rmOffs0); 791 int rjIndex = right.getElementIndex(rmOffs1); 792 Element lj = left.getElement(ljIndex); 793 if (lj.getStartOffset() >= rmOffs0) 794 { 795 lj = null; 796 } 797 Element rj = right.getElement(rjIndex); 798 if (rj.getStartOffset() == rmOffs1) 799 { 800 rj = null; 801 } 802 ArrayList children = new ArrayList(); 803 // Transfer the left. 804 for (int i = 0; i < ljIndex; i++) 805 { 806 children.add(clone(joined, left.getElement(i))); 807 } 808 809 // Transfer the join/middle. 810 if (canJoin(lj, rj)) 811 { 812 Element e = join(joined, lj, rj, rmOffs0, rmOffs1); 813 children.add(e); 814 } 815 else 816 { 817 if (lj != null) 818 { 819 children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1)); 820 } 821 if (rj != null) 822 { 823 children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1)); 824 } 825 } 826 827 // Transfer the right. 828 int n = right.getElementCount(); 829 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) 830 { 831 children.add(clone(joined, right.getElement(i))); 832 } 833 834 // Install the children. 835 Element[] c = new Element[children.size()]; 836 c = (Element[]) children.toArray(c); 837 ((BranchElement) joined).replace(0, 0, c); 838 } 839 else 840 { 841 assert false : "Must not happen"; 842 } 843 return joined; 844 } 845 846 /** 847 * Performs the actual work for {@link #change}. The elements at the 848 * interval boundaries are split up (if necessary) so that the interval 849 * boundaries are located at element boundaries. 850 */ 851 protected void changeUpdate() 852 { 853 boolean didEnd = split(offset, length); 854 if (! didEnd) 855 { 856 // need to do the other end 857 while (elementStack.size() != 0) 858 { 859 pop(); 860 } 861 split(offset + length, 0); 862 } 863 while (elementStack.size() != 0) 864 { 865 pop(); 866 } 867 } 868 869 /** 870 * Modifies the element structure so that the specified interval starts and 871 * ends at an element boundary. Content and paragraph elements are split and 872 * created as necessary. This also updates the 873 * <code>DefaultDocumentEvent</code> to reflect the structural changes. 874 * The bulk work is delegated to {@link #changeUpdate()}. 875 * 876 * @param offset 877 * the start index of the interval to be changed 878 * @param length 879 * the length of the interval to be changed 880 * @param ev 881 * the <code>DefaultDocumentEvent</code> describing the change 882 */ 883 public void change(int offset, int length, DefaultDocumentEvent ev) 884 { 885 prepareEdit(offset, length); 886 changeUpdate(); 887 finishEdit(ev); 888 } 889 890 /** 891 * Creates and returns a deep clone of the specified <code>clonee</code> 892 * with the specified parent as new parent. 893 * 894 * This method can only clone direct instances of {@link BranchElement} 895 * or {@link LeafElement}. 896 * 897 * @param parent the new parent 898 * @param clonee the element to be cloned 899 * 900 * @return the cloned element with the new parent 901 */ 902 public Element clone(Element parent, Element clonee) 903 { 904 Element clone = clonee; 905 // We can only handle AbstractElements here. 906 if (clonee instanceof BranchElement) 907 { 908 BranchElement branchEl = (BranchElement) clonee; 909 BranchElement branchClone = 910 new BranchElement(parent, branchEl.getAttributes()); 911 // Also clone all of the children. 912 int numChildren = branchClone.getElementCount(); 913 Element[] cloneChildren = new Element[numChildren]; 914 for (int i = 0; i < numChildren; ++i) 915 { 916 cloneChildren[i] = clone(branchClone, 917 branchClone.getElement(i)); 918 } 919 branchClone.replace(0, 0, cloneChildren); 920 clone = branchClone; 921 } 922 else if (clonee instanceof LeafElement) 923 { 924 clone = new LeafElement(parent, clonee.getAttributes(), 925 clonee.getStartOffset(), 926 clonee.getEndOffset()); 927 } 928 return clone; 929 } 930 931 private Element cloneAsNecessary(Element parent, Element clonee, 932 int rmOffs0, int rmOffs1) 933 { 934 Element cloned; 935 if (clonee.isLeaf()) 936 { 937 cloned = createLeafElement(parent, clonee.getAttributes(), 938 clonee.getStartOffset(), 939 clonee.getEndOffset()); 940 } 941 else 942 { 943 Element e = createBranchElement(parent, clonee.getAttributes()); 944 int n = clonee.getElementCount(); 945 ArrayList childrenList = new ArrayList(n); 946 for (int i = 0; i < n; i++) 947 { 948 Element elem = clonee.getElement(i); 949 if (elem.getStartOffset() < rmOffs0 950 || elem.getEndOffset() > rmOffs1) 951 { 952 childrenList.add(cloneAsNecessary(e, elem, rmOffs0, 953 rmOffs1)); 954 } 955 } 956 Element[] children = new Element[childrenList.size()]; 957 children = (Element[]) childrenList.toArray(children); 958 ((BranchElement) e).replace(0, 0, children); 959 cloned = e; 960 } 961 return cloned; 962 } 963 964 /** 965 * Inserts new <code>Element</code> in the document at the specified 966 * position. Most of the work is done by {@link #insertUpdate}, after some 967 * fields have been prepared for it. 968 * 969 * @param offset 970 * the location in the document at which the content is inserted 971 * @param length 972 * the length of the inserted content 973 * @param data 974 * the element specifications for the content to be inserted 975 * @param ev 976 * the document event that is updated to reflect the structural 977 * changes 978 */ 979 public void insert(int offset, int length, ElementSpec[] data, 980 DefaultDocumentEvent ev) 981 { 982 if (length > 0) 983 { 984 prepareEdit(offset, length); 985 insertUpdate(data); 986 finishEdit(ev); 987 } 988 } 989 990 /** 991 * Prepares the state of this object for performing an insert. 992 * 993 * @param offset the offset at which is inserted 994 * @param length the length of the inserted region 995 */ 996 private void prepareEdit(int offset, int length) 997 { 998 this.offset = offset; 999 this.pos = offset; 1000 this.endOffset = offset + length; 1001 this.length = length; 1002 1003 if (edits == null) 1004 edits = new ArrayList(); 1005 else 1006 edits.clear(); 1007 1008 if (elementStack == null) 1009 elementStack = new Stack(); 1010 else 1011 elementStack.clear(); 1012 1013 fracturedParent = null; 1014 fracturedChild = null; 1015 offsetLastIndex = false; 1016 offsetLastIndexReplace = false; 1017 } 1018 1019 /** 1020 * Finishes an insert. This applies all changes and updates 1021 * the DocumentEvent. 1022 * 1023 * @param ev the document event 1024 */ 1025 private void finishEdit(DefaultDocumentEvent ev) 1026 { 1027 // This for loop applies all the changes that were made and updates the 1028 // DocumentEvent. 1029 for (Iterator i = edits.iterator(); i.hasNext();) 1030 { 1031 Edit edits = (Edit) i.next(); 1032 Element[] removed = new Element[edits.removed.size()]; 1033 removed = (Element[]) edits.removed.toArray(removed); 1034 Element[] added = new Element[edits.added.size()]; 1035 added = (Element[]) edits.added.toArray(added); 1036 int index = edits.index; 1037 BranchElement parent = (BranchElement) edits.e; 1038 parent.replace(index, removed.length, added); 1039 ElementEdit ee = new ElementEdit(parent, index, removed, added); 1040 ev.addEdit(ee); 1041 } 1042 edits.clear(); 1043 elementStack.clear(); 1044 } 1045 1046 /** 1047 * Inserts new content. 1048 * 1049 * @param data the element specifications for the elements to be inserted 1050 */ 1051 protected void insertUpdate(ElementSpec[] data) 1052 { 1053 // Push the current path to the stack. 1054 Element current = root; 1055 int index = current.getElementIndex(offset); 1056 while (! current.isLeaf()) 1057 { 1058 Element child = current.getElement(index); 1059 int editIndex = child.isLeaf() ? index : index + 1; 1060 Edit edit = new Edit(current, editIndex); 1061 elementStack.push(edit); 1062 current = child; 1063 index = current.getElementIndex(offset); 1064 } 1065 1066 // Create a copy of the original path. 1067 insertPath = new Edit[elementStack.size()]; 1068 insertPath = (Edit[]) elementStack.toArray(insertPath); 1069 1070 // No fracture yet. 1071 createdFracture = false; 1072 1073 // Insert first content tag. 1074 int i = 0; 1075 recreateLeafs = false; 1076 int type = data[0].getType(); 1077 if (type == ElementSpec.ContentType) 1078 { 1079 // If the first tag is content we must treat it separately to allow 1080 // for joining properly to previous Elements and to ensure that 1081 // no extra LeafElements are erroneously inserted. 1082 insertFirstContentTag(data); 1083 pos += data[0].length; 1084 i = 1; 1085 } 1086 else 1087 { 1088 createFracture(data); 1089 i = 0; 1090 } 1091 1092 // Handle each ElementSpec individually. 1093 for (; i < data.length; i++) 1094 { 1095 insertElement(data[i]); 1096 } 1097 1098 // Fracture if we haven't done yet. 1099 if (! createdFracture) 1100 fracture(-1); 1101 1102 // Pop the remaining stack. 1103 while (elementStack.size() != 0) 1104 pop(); 1105 1106 // Offset last index if necessary. 1107 if (offsetLastIndex && offsetLastIndexReplace) 1108 insertPath[insertPath.length - 1].index++; 1109 1110 // Make sure we havea an Edit for each path item that has a change. 1111 for (int p = insertPath.length - 1; p >= 0; p--) 1112 { 1113 Edit edit = insertPath[p]; 1114 if (edit.e == fracturedParent) 1115 edit.added.add(fracturedChild); 1116 if ((edit.added.size() > 0 || edit.removed.size() > 0) 1117 && ! edits.contains(edit)) 1118 edits.add(edit); 1119 } 1120 1121 // Remove element that would be created by an insert at 0 with 1122 // an initial end tag. 1123 if (offset == 0 && fracturedParent != null 1124 && data[0].getType() == ElementSpec.EndTagType) 1125 { 1126 int p; 1127 for (p = 0; 1128 p < data.length && data[p].getType() == ElementSpec.EndTagType; 1129 p++) 1130 ; 1131 1132 Edit edit = insertPath[insertPath.length - p - 1]; 1133 edit.index--; 1134 edit.removed.add(0, edit.e.getElement(edit.index)); 1135 } 1136 } 1137 1138 private void pop() 1139 { 1140 Edit edit = (Edit) elementStack.peek(); 1141 elementStack.pop(); 1142 if ((edit.added.size() > 0) || (edit.removed.size() > 0)) 1143 { 1144 edits.add(edit); 1145 } 1146 else if (! elementStack.isEmpty()) 1147 { 1148 Element e = edit.e; 1149 if (e.getElementCount() == 0) 1150 { 1151 // If we pushed a branch element that didn't get 1152 // used, make sure its not marked as having been added. 1153 edit = (Edit) elementStack.peek(); 1154 edit.added.remove(e); 1155 } 1156 } 1157 } 1158 1159 private void insertElement(ElementSpec spec) 1160 { 1161 Edit edit = (Edit) elementStack.peek(); 1162 switch (spec.getType()) 1163 { 1164 case ElementSpec.StartTagType: 1165 switch (spec.getDirection()) 1166 { 1167 case ElementSpec.JoinFractureDirection: 1168 // Fracture the tree and ensure the appropriate element 1169 // is on top of the stack. 1170 if (! createdFracture) 1171 { 1172 fracture(elementStack.size() - 1); 1173 } 1174 if (! edit.isFracture) 1175 { 1176 // If the parent isn't a fracture, then the fracture is 1177 // in fracturedChild. 1178 Edit newEdit = new Edit(fracturedChild, 0, true); 1179 elementStack.push(newEdit); 1180 } 1181 else 1182 { 1183 // Otherwise use the parent's first child. 1184 Element el = edit.e.getElement(0); 1185 Edit newEdit = new Edit(el, 0, true); 1186 elementStack.push(newEdit); 1187 } 1188 break; 1189 case ElementSpec.JoinNextDirection: 1190 // Push the next paragraph element onto the stack so 1191 // future insertions are added to it. 1192 Element parent = edit.e.getElement(edit.index); 1193 if (parent.isLeaf()) 1194 { 1195 if (edit.index + 1 < edit.e.getElementCount()) 1196 parent = edit.e.getElement(edit.index + 1); 1197 else 1198 assert false; // Must not happen. 1199 } 1200 elementStack.push(new Edit(parent, 0, true)); 1201 break; 1202 default: 1203 Element branch = createBranchElement(edit.e, 1204 spec.getAttributes()); 1205 edit.added.add(branch); 1206 elementStack.push(new Edit(branch, 0)); 1207 break; 1208 } 1209 break; 1210 case ElementSpec.EndTagType: 1211 pop(); 1212 break; 1213 case ElementSpec.ContentType: 1214 insertContentTag(spec, edit); 1215 break; 1216 } 1217 } 1218 1219 /** 1220 * Inserts the first tag into the document. 1221 * 1222 * @param data - 1223 * the data to be inserted. 1224 */ 1225 private void insertFirstContentTag(ElementSpec[] data) 1226 { 1227 ElementSpec first = data[0]; 1228 Edit edit = (Edit) elementStack.peek(); 1229 Element current = edit.e.getElement(edit.index); 1230 int firstEndOffset = offset + first.length; 1231 boolean onlyContent = data.length == 1; 1232 switch (first.getDirection()) 1233 { 1234 case ElementSpec.JoinPreviousDirection: 1235 if (current.getEndOffset() != firstEndOffset && ! onlyContent) 1236 { 1237 Element newEl1 = createLeafElement(edit.e, 1238 current.getAttributes(), 1239 current.getStartOffset(), 1240 firstEndOffset); 1241 edit.added.add(newEl1); 1242 edit.removed.add(current); 1243 if (current.getEndOffset() != endOffset) 1244 recreateLeafs = true; 1245 else 1246 offsetLastIndex = true; 1247 } 1248 else 1249 { 1250 offsetLastIndex = true; 1251 offsetLastIndexReplace = true; 1252 } 1253 break; 1254 case ElementSpec.JoinNextDirection: 1255 if (offset != 0) 1256 { 1257 Element newEl1 = createLeafElement(edit.e, 1258 current.getAttributes(), 1259 current.getStartOffset(), 1260 offset); 1261 edit.added.add(newEl1); 1262 Element next = edit.e.getElement(edit.index + 1); 1263 if (onlyContent) 1264 newEl1 = createLeafElement(edit.e, next.getAttributes(), 1265 offset, next.getEndOffset()); 1266 else 1267 { 1268 newEl1 = createLeafElement(edit.e, next.getAttributes(), 1269 offset, firstEndOffset); 1270 } 1271 edit.added.add(newEl1); 1272 edit.removed.add(current); 1273 edit.removed.add(next); 1274 } 1275 break; 1276 default: // OriginateDirection. 1277 if (current.getStartOffset() != offset) 1278 { 1279 Element newEl = createLeafElement(edit.e, 1280 current.getAttributes(), 1281 current.getStartOffset(), 1282 offset); 1283 edit.added.add(newEl); 1284 } 1285 edit.removed.add(current); 1286 Element newEl1 = createLeafElement(edit.e, first.getAttributes(), 1287 offset, firstEndOffset); 1288 edit.added.add(newEl1); 1289 if (current.getEndOffset() != endOffset) 1290 recreateLeafs = true; 1291 else 1292 offsetLastIndex = true; 1293 break; 1294 } 1295 } 1296 1297 /** 1298 * Inserts a content element into the document structure. 1299 * 1300 * @param tag - 1301 * the element spec 1302 */ 1303 private void insertContentTag(ElementSpec tag, Edit edit) 1304 { 1305 int len = tag.getLength(); 1306 int dir = tag.getDirection(); 1307 if (dir == ElementSpec.JoinNextDirection) 1308 { 1309 if (! edit.isFracture) 1310 { 1311 Element first = null; 1312 if (insertPath != null) 1313 { 1314 for (int p = insertPath.length - 1; p >= 0; p--) 1315 { 1316 if (insertPath[p] == edit) 1317 { 1318 if (p != insertPath.length - 1) 1319 first = edit.e.getElement(edit.index); 1320 break; 1321 } 1322 } 1323 } 1324 if (first == null) 1325 first = edit.e.getElement(edit.index + 1); 1326 Element leaf = createLeafElement(edit.e, first.getAttributes(), 1327 pos, first.getEndOffset()); 1328 edit.added.add(leaf); 1329 edit.removed.add(first); 1330 } 1331 else 1332 { 1333 Element first = edit.e.getElement(0); 1334 Element leaf = createLeafElement(edit.e, first.getAttributes(), 1335 pos, first.getEndOffset()); 1336 edit.added.add(leaf); 1337 edit.removed.add(first); 1338 } 1339 } 1340 else 1341 { 1342 Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos, 1343 pos + len); 1344 edit.added.add(leaf); 1345 } 1346 1347 pos += len; 1348 1349 } 1350 1351 /** 1352 * This method fractures bottomost leaf in the elementStack. This 1353 * happens when the first inserted tag is not content. 1354 * 1355 * @param data 1356 * the ElementSpecs used for the entire insertion 1357 */ 1358 private void createFracture(ElementSpec[] data) 1359 { 1360 Edit edit = (Edit) elementStack.peek(); 1361 Element child = edit.e.getElement(edit.index); 1362 if (offset != 0) 1363 { 1364 Element newChild = createLeafElement(edit.e, child.getAttributes(), 1365 child.getStartOffset(), offset); 1366 edit.added.add(newChild); 1367 } 1368 edit.removed.add(child); 1369 if (child.getEndOffset() != endOffset) 1370 recreateLeafs = true; 1371 else 1372 offsetLastIndex = true; 1373 } 1374 1375 private void fracture(int depth) 1376 { 1377 int len = insertPath.length; 1378 int lastIndex = -1; 1379 boolean recreate = recreateLeafs; 1380 Edit lastEdit = insertPath[len - 1]; 1381 boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount(); 1382 int deepestChangedIndex = recreate ? len : - 1; 1383 int lastChangedIndex = len - 1; 1384 createdFracture = true; 1385 for (int i = len - 2; i >= 0; i--) 1386 { 1387 Edit edit = insertPath[i]; 1388 if (edit.added.size() > 0 || i == depth) 1389 { 1390 lastIndex = i; 1391 if (! recreate && childChanged) 1392 { 1393 recreate = true; 1394 if (deepestChangedIndex == -1) 1395 deepestChangedIndex = lastChangedIndex + 1; 1396 } 1397 } 1398 if (! childChanged && edit.index < edit.e.getElementCount()) 1399 { 1400 childChanged = true; 1401 lastChangedIndex = i; 1402 } 1403 } 1404 if (recreate) 1405 { 1406 if (lastIndex == -1) 1407 lastIndex = len - 1; 1408 recreate(lastIndex, deepestChangedIndex); 1409 } 1410 } 1411 1412 private void recreate(int startIndex, int endIndex) 1413 { 1414 // Recreate the element representing the inserted index. 1415 Edit edit = insertPath[startIndex]; 1416 Element child; 1417 Element newChild; 1418 int changeLength = insertPath.length; 1419 1420 if (startIndex + 1 == changeLength) 1421 child = edit.e.getElement(edit.index); 1422 else 1423 child = edit.e.getElement(edit.index - 1); 1424 1425 if(child.isLeaf()) 1426 { 1427 newChild = createLeafElement(edit.e, child.getAttributes(), 1428 Math.max(endOffset, child.getStartOffset()), 1429 child.getEndOffset()); 1430 } 1431 else 1432 { 1433 newChild = createBranchElement(edit.e, child.getAttributes()); 1434 } 1435 fracturedParent = edit.e; 1436 fracturedChild = newChild; 1437 1438 // Recreate all the elements to the right of the insertion point. 1439 Element parent = newChild; 1440 while (++startIndex < endIndex) 1441 { 1442 boolean isEnd = (startIndex + 1) == endIndex; 1443 boolean isEndLeaf = (startIndex + 1) == changeLength; 1444 1445 // Create the newChild, a duplicate of the elment at 1446 // index. This isn't done if isEnd and offsetLastIndex are true 1447 // indicating a join previous was done. 1448 edit = insertPath[startIndex]; 1449 1450 // Determine the child to duplicate, won't have to duplicate 1451 // if at end of fracture, or offseting index. 1452 if(isEnd) 1453 { 1454 if(offsetLastIndex || ! isEndLeaf) 1455 child = null; 1456 else 1457 child = edit.e.getElement(edit.index); 1458 } 1459 else 1460 { 1461 child = edit.e.getElement(edit.index - 1); 1462 } 1463 1464 // Duplicate it. 1465 if(child != null) 1466 { 1467 if(child.isLeaf()) 1468 { 1469 newChild = createLeafElement(parent, child.getAttributes(), 1470 Math.max(endOffset, child.getStartOffset()), 1471 child.getEndOffset()); 1472 } 1473 else 1474 { 1475 newChild = createBranchElement(parent, 1476 child.getAttributes()); 1477 } 1478 } 1479 else 1480 newChild = null; 1481 1482 // Recreate the remaining children (there may be none). 1483 int childrenToMove = edit.e.getElementCount() - edit.index; 1484 Element[] children; 1485 int moveStartIndex; 1486 int childStartIndex = 1; 1487 1488 if (newChild == null) 1489 { 1490 // Last part of fracture. 1491 if (isEndLeaf) 1492 { 1493 childrenToMove--; 1494 moveStartIndex = edit.index + 1; 1495 } 1496 else 1497 { 1498 moveStartIndex = edit.index; 1499 } 1500 childStartIndex = 0; 1501 children = new Element[childrenToMove]; 1502 } 1503 else 1504 { 1505 if (! isEnd) 1506 { 1507 // Branch. 1508 childrenToMove++; 1509 moveStartIndex = edit.index; 1510 } 1511 else 1512 { 1513 // Last leaf, need to recreate part of it. 1514 moveStartIndex = edit.index + 1; 1515 } 1516 children = new Element[childrenToMove]; 1517 children[0] = newChild; 1518 } 1519 1520 for (int c = childStartIndex; c < childrenToMove; c++) 1521 { 1522 Element toMove = edit.e.getElement(moveStartIndex++); 1523 children[c] = recreateFracturedElement(parent, toMove); 1524 edit.removed.add(toMove); 1525 } 1526 ((BranchElement) parent).replace(0, 0, children); 1527 parent = newChild; 1528 } 1529 1530 } 1531 1532 private Element recreateFracturedElement(Element parent, Element toCopy) 1533 { 1534 Element recreated; 1535 if(toCopy.isLeaf()) 1536 { 1537 recreated = createLeafElement(parent, toCopy.getAttributes(), 1538 Math.max(toCopy.getStartOffset(), endOffset), 1539 toCopy.getEndOffset()); 1540 } 1541 else 1542 { 1543 Element newParent = createBranchElement(parent, 1544 toCopy.getAttributes()); 1545 int childCount = toCopy.getElementCount(); 1546 Element[] newChildren = new Element[childCount]; 1547 for (int i = 0; i < childCount; i++) 1548 { 1549 newChildren[i] = recreateFracturedElement(newParent, 1550 toCopy.getElement(i)); 1551 } 1552 ((BranchElement) newParent).replace(0, 0, newChildren); 1553 recreated = newParent; 1554 } 1555 return recreated; 1556 } 1557 1558 private boolean split(int offs, int len) 1559 { 1560 boolean splitEnd = false; 1561 // Push the path to the stack. 1562 Element e = root; 1563 int index = e.getElementIndex(offs); 1564 while (! e.isLeaf()) 1565 { 1566 elementStack.push(new Edit(e, index)); 1567 e = e.getElement(index); 1568 index = e.getElementIndex(offs); 1569 } 1570 1571 Edit ec = (Edit) elementStack.peek(); 1572 Element child = ec.e.getElement(ec.index); 1573 // Make sure there is something to do. If the 1574 // offset is already at a boundary then there is 1575 // nothing to do. 1576 if (child.getStartOffset() < offs && offs < child.getEndOffset()) 1577 { 1578 // We need to split, now see if the other end is within 1579 // the same parent. 1580 int index0 = ec.index; 1581 int index1 = index0; 1582 if (((offs + len) < ec.e.getEndOffset()) && (len != 0)) 1583 { 1584 // It's a range split in the same parent. 1585 index1 = ec.e.getElementIndex(offs+len); 1586 if (index1 == index0) 1587 { 1588 // It's a three-way split. 1589 ec.removed.add(child); 1590 e = createLeafElement(ec.e, child.getAttributes(), 1591 child.getStartOffset(), offs); 1592 ec.added.add(e); 1593 e = createLeafElement(ec.e, child.getAttributes(), 1594 offs, offs + len); 1595 ec.added.add(e); 1596 e = createLeafElement(ec.e, child.getAttributes(), 1597 offs + len, child.getEndOffset()); 1598 ec.added.add(e); 1599 return true; 1600 } 1601 else 1602 { 1603 child = ec.e.getElement(index1); 1604 if ((offs + len) == child.getStartOffset()) 1605 { 1606 // End is already on a boundary. 1607 index1 = index0; 1608 } 1609 } 1610 splitEnd = true; 1611 } 1612 1613 // Split the first location. 1614 pos = offs; 1615 child = ec.e.getElement(index0); 1616 ec.removed.add(child); 1617 e = createLeafElement(ec.e, child.getAttributes(), 1618 child.getStartOffset(), pos); 1619 ec.added.add(e); 1620 e = createLeafElement(ec.e, child.getAttributes(), 1621 pos, child.getEndOffset()); 1622 ec.added.add(e); 1623 1624 // Pick up things in the middle. 1625 for (int i = index0 + 1; i < index1; i++) 1626 { 1627 child = ec.e.getElement(i); 1628 ec.removed.add(child); 1629 ec.added.add(child); 1630 } 1631 1632 if (index1 != index0) 1633 { 1634 child = ec.e.getElement(index1); 1635 pos = offs + len; 1636 ec.removed.add(child); 1637 e = createLeafElement(ec.e, child.getAttributes(), 1638 child.getStartOffset(), pos); 1639 ec.added.add(e); 1640 e = createLeafElement(ec.e, child.getAttributes(), 1641 pos, child.getEndOffset()); 1642 1643 ec.added.add(e); 1644 } 1645 } 1646 return splitEnd; 1647 1648 } 1649 1650 } 1651 1652 1653 /** 1654 * An element type for sections. This is a simple BranchElement with a unique 1655 * name. 1656 */ 1657 protected class SectionElement extends BranchElement 1658 { 1659 /** 1660 * Creates a new SectionElement. 1661 */ 1662 public SectionElement() 1663 { 1664 super(null, null); 1665 } 1666 1667 /** 1668 * Returns the name of the element. This method always returns 1669 * "section". 1670 * 1671 * @return the name of the element 1672 */ 1673 public String getName() 1674 { 1675 return SectionElementName; 1676 } 1677 } 1678 1679 /** 1680 * Receives notification when any of the document's style changes and calls 1681 * {@link DefaultStyledDocument#styleChanged(Style)}. 1682 * 1683 * @author Roman Kennke (kennke@aicas.com) 1684 */ 1685 private class StyleChangeListener implements ChangeListener 1686 { 1687 1688 /** 1689 * Receives notification when any of the document's style changes and calls 1690 * {@link DefaultStyledDocument#styleChanged(Style)}. 1691 * 1692 * @param event 1693 * the change event 1694 */ 1695 public void stateChanged(ChangeEvent event) 1696 { 1697 Style style = (Style) event.getSource(); 1698 styleChanged(style); 1699 } 1700 } 1701 1702 /** The serialization UID (compatible with JDK1.5). */ 1703 private static final long serialVersionUID = 940485415728614849L; 1704 1705 /** 1706 * The default size to use for new content buffers. 1707 */ 1708 public static final int BUFFER_SIZE_DEFAULT = 4096; 1709 1710 /** 1711 * The <code>EditorBuffer</code> that is used to manage to 1712 * <code>Element</code> hierarchy. 1713 */ 1714 protected DefaultStyledDocument.ElementBuffer buffer; 1715 1716 /** 1717 * Listens for changes on this document's styles and notifies styleChanged(). 1718 */ 1719 private StyleChangeListener styleChangeListener; 1720 1721 /** 1722 * Creates a new <code>DefaultStyledDocument</code>. 1723 */ 1724 public DefaultStyledDocument() 1725 { 1726 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext()); 1727 } 1728 1729 /** 1730 * Creates a new <code>DefaultStyledDocument</code> that uses the specified 1731 * {@link StyleContext}. 1732 * 1733 * @param context 1734 * the <code>StyleContext</code> to use 1735 */ 1736 public DefaultStyledDocument(StyleContext context) 1737 { 1738 this(new GapContent(BUFFER_SIZE_DEFAULT), context); 1739 } 1740 1741 /** 1742 * Creates a new <code>DefaultStyledDocument</code> that uses the specified 1743 * {@link StyleContext} and {@link Content} buffer. 1744 * 1745 * @param content 1746 * the <code>Content</code> buffer to use 1747 * @param context 1748 * the <code>StyleContext</code> to use 1749 */ 1750 public DefaultStyledDocument(AbstractDocument.Content content, 1751 StyleContext context) 1752 { 1753 super(content, context); 1754 buffer = new ElementBuffer(createDefaultRoot()); 1755 setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE)); 1756 } 1757 1758 /** 1759 * Adds a style into the style hierarchy. Unspecified style attributes can be 1760 * resolved in the <code>parent</code> style, if one is specified. While it 1761 * is legal to add nameless styles (<code>nm == null</code), 1762 * you must be aware that the client application is then responsible 1763 * for managing the style hierarchy, since unnamed styles cannot be 1764 * looked up by their name. 1765 * 1766 * @param nm the name of the style or <code>null</code> if the style should 1767 * be unnamed 1768 * @param parent the parent in which unspecified style attributes are 1769 * resolved, or <code>null</code> if that is not necessary 1770 * 1771 * @return the newly created <code>Style</code> 1772 */ 1773 public Style addStyle(String nm, Style parent) 1774 { 1775 StyleContext context = (StyleContext) getAttributeContext(); 1776 Style newStyle = context.addStyle(nm, parent); 1777 1778 // Register change listener. 1779 if (styleChangeListener == null) 1780 styleChangeListener = new StyleChangeListener(); 1781 newStyle.addChangeListener(styleChangeListener); 1782 1783 return newStyle; 1784 } 1785 1786 /** 1787 * Create the default root element for this kind of <code>Document</code>. 1788 * 1789 * @return the default root element for this kind of <code>Document</code> 1790 */ 1791 protected AbstractDocument.AbstractElement createDefaultRoot() 1792 { 1793 Element[] tmp; 1794 SectionElement section = new SectionElement(); 1795 1796 BranchElement paragraph = new BranchElement(section, null); 1797 tmp = new Element[1]; 1798 tmp[0] = paragraph; 1799 section.replace(0, 0, tmp); 1800 1801 Element leaf = new LeafElement(paragraph, null, 0, 1); 1802 tmp = new Element[1]; 1803 tmp[0] = leaf; 1804 paragraph.replace(0, 0, tmp); 1805 1806 return section; 1807 } 1808 1809 /** 1810 * Returns the <code>Element</code> that corresponds to the character at the 1811 * specified position. 1812 * 1813 * @param position 1814 * the position of which we query the corresponding 1815 * <code>Element</code> 1816 * @return the <code>Element</code> that corresponds to the character at the 1817 * specified position 1818 */ 1819 public Element getCharacterElement(int position) 1820 { 1821 Element element = getDefaultRootElement(); 1822 1823 while (!element.isLeaf()) 1824 { 1825 int index = element.getElementIndex(position); 1826 element = element.getElement(index); 1827 } 1828 1829 return element; 1830 } 1831 1832 /** 1833 * Extracts a background color from a set of attributes. 1834 * 1835 * @param attributes 1836 * the attributes from which to get a background color 1837 * @return the background color that correspond to the attributes 1838 */ 1839 public Color getBackground(AttributeSet attributes) 1840 { 1841 StyleContext context = (StyleContext) getAttributeContext(); 1842 return context.getBackground(attributes); 1843 } 1844 1845 /** 1846 * Returns the default root element. 1847 * 1848 * @return the default root element 1849 */ 1850 public Element getDefaultRootElement() 1851 { 1852 return buffer.getRootElement(); 1853 } 1854 1855 /** 1856 * Extracts a font from a set of attributes. 1857 * 1858 * @param attributes 1859 * the attributes from which to get a font 1860 * @return the font that correspond to the attributes 1861 */ 1862 public Font getFont(AttributeSet attributes) 1863 { 1864 StyleContext context = (StyleContext) getAttributeContext(); 1865 return context.getFont(attributes); 1866 } 1867 1868 /** 1869 * Extracts a foreground color from a set of attributes. 1870 * 1871 * @param attributes 1872 * the attributes from which to get a foreground color 1873 * @return the foreground color that correspond to the attributes 1874 */ 1875 public Color getForeground(AttributeSet attributes) 1876 { 1877 StyleContext context = (StyleContext) getAttributeContext(); 1878 return context.getForeground(attributes); 1879 } 1880 1881 /** 1882 * Returns the logical <code>Style</code> for the specified position. 1883 * 1884 * @param position 1885 * the position from which to query to logical style 1886 * @return the logical <code>Style</code> for the specified position 1887 */ 1888 public Style getLogicalStyle(int position) 1889 { 1890 Element paragraph = getParagraphElement(position); 1891 AttributeSet attributes = paragraph.getAttributes(); 1892 AttributeSet a = attributes.getResolveParent(); 1893 // If the resolve parent is not of type Style, we return null. 1894 if (a instanceof Style) 1895 return (Style) a; 1896 return null; 1897 } 1898 1899 /** 1900 * Returns the paragraph element for the specified position. If the position 1901 * is outside the bounds of the document's root element, then the closest 1902 * element is returned. That is the last paragraph if 1903 * <code>position >= endIndex</code> or the first paragraph if 1904 * <code>position < startIndex</code>. 1905 * 1906 * @param position 1907 * the position for which to query the paragraph element 1908 * @return the paragraph element for the specified position 1909 */ 1910 public Element getParagraphElement(int position) 1911 { 1912 Element e = getDefaultRootElement(); 1913 while (!e.isLeaf()) 1914 e = e.getElement(e.getElementIndex(position)); 1915 1916 if (e != null) 1917 return e.getParentElement(); 1918 return e; 1919 } 1920 1921 /** 1922 * Looks up and returns a named <code>Style</code>. 1923 * 1924 * @param nm 1925 * the name of the <code>Style</code> 1926 * @return the found <code>Style</code> of <code>null</code> if no such 1927 * <code>Style</code> exists 1928 */ 1929 public Style getStyle(String nm) 1930 { 1931 StyleContext context = (StyleContext) getAttributeContext(); 1932 return context.getStyle(nm); 1933 } 1934 1935 /** 1936 * Removes a named <code>Style</code> from the style hierarchy. 1937 * 1938 * @param nm 1939 * the name of the <code>Style</code> to be removed 1940 */ 1941 public void removeStyle(String nm) 1942 { 1943 StyleContext context = (StyleContext) getAttributeContext(); 1944 context.removeStyle(nm); 1945 } 1946 1947 /** 1948 * Sets text attributes for the fragment specified by <code>offset</code> 1949 * and <code>length</code>. 1950 * 1951 * @param offset 1952 * the start offset of the fragment 1953 * @param length 1954 * the length of the fragment 1955 * @param attributes 1956 * the text attributes to set 1957 * @param replace 1958 * if <code>true</code>, the attributes of the current selection 1959 * are overridden, otherwise they are merged 1960 */ 1961 public void setCharacterAttributes(int offset, int length, 1962 AttributeSet attributes, boolean replace) 1963 { 1964 // Exit early if length is 0, so no DocumentEvent is created or fired. 1965 if (length == 0) 1966 return; 1967 try 1968 { 1969 // Must obtain a write lock for this method. writeLock() and 1970 // writeUnlock() should always be in try/finally block to make 1971 // sure that locking happens in a balanced manner. 1972 writeLock(); 1973 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, 1974 length, 1975 DocumentEvent.EventType.CHANGE); 1976 1977 // Modify the element structure so that the interval begins at an 1978 // element 1979 // start and ends at an element end. 1980 buffer.change(offset, length, ev); 1981 1982 // Visit all paragraph elements within the specified interval 1983 int end = offset + length; 1984 Element curr; 1985 for (int pos = offset; pos < end;) 1986 { 1987 // Get the CharacterElement at offset pos. 1988 curr = getCharacterElement(pos); 1989 if (pos == curr.getEndOffset()) 1990 break; 1991 1992 MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes(); 1993 ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace)); 1994 // If replace is true, remove all the old attributes. 1995 if (replace) 1996 a.removeAttributes(a); 1997 // Add all the new attributes. 1998 a.addAttributes(attributes); 1999 // Increment pos so we can check the next CharacterElement. 2000 pos = curr.getEndOffset(); 2001 } 2002 fireChangedUpdate(ev); 2003 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2004 } 2005 finally 2006 { 2007 writeUnlock(); 2008 } 2009 } 2010 2011 /** 2012 * Sets the logical style for the paragraph at the specified position. 2013 * 2014 * @param position 2015 * the position at which the logical style is added 2016 * @param style 2017 * the style to set for the current paragraph 2018 */ 2019 public void setLogicalStyle(int position, Style style) 2020 { 2021 Element el = getParagraphElement(position); 2022 // getParagraphElement doesn't return null but subclasses might so 2023 // we check for null here. 2024 if (el == null) 2025 return; 2026 try 2027 { 2028 writeLock(); 2029 if (el instanceof AbstractElement) 2030 { 2031 AbstractElement ael = (AbstractElement) el; 2032 ael.setResolveParent(style); 2033 int start = el.getStartOffset(); 2034 int end = el.getEndOffset(); 2035 DefaultDocumentEvent ev = new DefaultDocumentEvent(start, 2036 end - start, 2037 DocumentEvent.EventType.CHANGE); 2038 fireChangedUpdate(ev); 2039 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2040 } 2041 else 2042 throw new AssertionError( 2043 "paragraph elements are expected to be" 2044 + "instances of AbstractDocument.AbstractElement"); 2045 } 2046 finally 2047 { 2048 writeUnlock(); 2049 } 2050 } 2051 2052 /** 2053 * Sets text attributes for the paragraph at the specified fragment. 2054 * 2055 * @param offset 2056 * the beginning of the fragment 2057 * @param length 2058 * the length of the fragment 2059 * @param attributes 2060 * the text attributes to set 2061 * @param replace 2062 * if <code>true</code>, the attributes of the current selection 2063 * are overridden, otherwise they are merged 2064 */ 2065 public void setParagraphAttributes(int offset, int length, 2066 AttributeSet attributes, boolean replace) 2067 { 2068 try 2069 { 2070 // Must obtain a write lock for this method. writeLock() and 2071 // writeUnlock() should always be in try/finally blocks to make 2072 // sure that locking occurs in a balanced manner. 2073 writeLock(); 2074 2075 // Create a DocumentEvent to use for changedUpdate(). 2076 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, 2077 length, 2078 DocumentEvent.EventType.CHANGE); 2079 2080 // Have to iterate through all the _paragraph_ elements that are 2081 // contained or partially contained in the interval 2082 // (offset, offset + length). 2083 Element rootElement = getDefaultRootElement(); 2084 int startElement = rootElement.getElementIndex(offset); 2085 int endElement = rootElement.getElementIndex(offset + length - 1); 2086 if (endElement < startElement) 2087 endElement = startElement; 2088 2089 for (int i = startElement; i <= endElement; i++) 2090 { 2091 Element par = rootElement.getElement(i); 2092 MutableAttributeSet a = (MutableAttributeSet) par.getAttributes(); 2093 // Add the change to the DocumentEvent. 2094 ev.addEdit(new AttributeUndoableEdit(par, attributes, replace)); 2095 // If replace is true remove the old attributes. 2096 if (replace) 2097 a.removeAttributes(a); 2098 // Add the new attributes. 2099 a.addAttributes(attributes); 2100 } 2101 fireChangedUpdate(ev); 2102 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2103 } 2104 finally 2105 { 2106 writeUnlock(); 2107 } 2108 } 2109 2110 /** 2111 * Called in response to content insert actions. This is used to update the 2112 * element structure. 2113 * 2114 * @param ev 2115 * the <code>DocumentEvent</code> describing the change 2116 * @param attr 2117 * the attributes for the change 2118 */ 2119 protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr) 2120 { 2121 int offs = ev.getOffset(); 2122 int len = ev.getLength(); 2123 int endOffs = offs + len; 2124 if (attr == null) 2125 attr = SimpleAttributeSet.EMPTY; 2126 2127 // Paragraph attributes are fetched from the point _after_ the insertion. 2128 Element paragraph = getParagraphElement(endOffs); 2129 AttributeSet pAttr = paragraph.getAttributes(); 2130 // Character attributes are fetched from the actual insertion point. 2131 Element paragraph2 = getParagraphElement(offs); 2132 int contIndex = paragraph2.getElementIndex(offs); 2133 Element content = paragraph2.getElement(contIndex); 2134 AttributeSet cAttr = content.getAttributes(); 2135 2136 boolean insertAtBoundary = content.getEndOffset() == endOffs; 2137 try 2138 { 2139 Segment s = new Segment(); 2140 ArrayList buf = new ArrayList(); 2141 ElementSpec lastStartTag = null; 2142 boolean insertAfterNewline = false; 2143 short lastStartDir = ElementSpec.OriginateDirection; 2144 2145 // Special handle if we are inserting after a newline. 2146 if (offs > 0) 2147 { 2148 getText(offs - 1, 1, s); 2149 if (s.array[s.offset] == '\n') 2150 { 2151 insertAfterNewline = true; 2152 lastStartDir = insertAfterNewline(paragraph, paragraph2, 2153 pAttr, buf, offs, 2154 endOffs); 2155 // Search last start tag. 2156 for (int i = buf.size() - 1; i >= 0 && lastStartTag == null; 2157 i--) 2158 { 2159 ElementSpec tag = (ElementSpec) buf.get(i); 2160 if (tag.getType() == ElementSpec.StartTagType) 2161 { 2162 lastStartTag = tag; 2163 } 2164 } 2165 } 2166 2167 } 2168 2169 // If we are not inserting after a newline, the paragraph attributes 2170 // come from the paragraph under the insertion point. 2171 if (! insertAfterNewline) 2172 pAttr = paragraph2.getAttributes(); 2173 2174 // Scan text and build up the specs. 2175 getText(offs, len, s); 2176 int end = s.offset + s.count; 2177 int last = s.offset; 2178 for (int i = s.offset; i < end; i++) 2179 { 2180 if (s.array[i] == '\n') 2181 { 2182 int breakOffs = i + 1; 2183 buf.add(new ElementSpec(attr, ElementSpec.ContentType, 2184 breakOffs - last)); 2185 buf.add(new ElementSpec(null, ElementSpec.EndTagType)); 2186 lastStartTag = new ElementSpec(pAttr, 2187 ElementSpec.StartTagType); 2188 buf.add(lastStartTag); 2189 last = breakOffs; 2190 } 2191 } 2192 2193 // Need to add a tailing content tag if we didn't finish at a boundary. 2194 if (last < end) 2195 { 2196 buf.add(new ElementSpec(attr, ElementSpec.ContentType, 2197 end - last)); 2198 } 2199 2200 // Now we need to fix up the directions of the specs. 2201 ElementSpec first = (ElementSpec) buf.get(0); 2202 int doclen = getLength(); 2203 2204 // Maybe join-previous the first tag if it is content and has 2205 // the same attributes as the previous character run. 2206 if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr)) 2207 first.setDirection(ElementSpec.JoinPreviousDirection); 2208 2209 // Join-fracture or join-next the last start tag if necessary. 2210 if (lastStartTag != null) 2211 { 2212 if (insertAfterNewline) 2213 lastStartTag.setDirection(lastStartDir); 2214 else if (paragraph2.getEndOffset() != endOffs) 2215 lastStartTag.setDirection(ElementSpec.JoinFractureDirection); 2216 else 2217 { 2218 Element par = paragraph2.getParentElement(); 2219 int par2Index = par.getElementIndex(offs); 2220 if (par2Index + 1 < par.getElementCount() 2221 && ! par.getElement(par2Index + 1).isLeaf()) 2222 lastStartTag.setDirection(ElementSpec.JoinNextDirection); 2223 } 2224 } 2225 2226 // Join-next last tag if possible. 2227 if (insertAtBoundary && endOffs < doclen) 2228 { 2229 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1); 2230 if (lastTag.getType() == ElementSpec.ContentType 2231 && ((lastStartTag == null 2232 && (paragraph == paragraph2 || insertAfterNewline)) 2233 || (lastStartTag != null 2234 && lastStartTag.getDirection() != ElementSpec.OriginateDirection))) 2235 { 2236 int nextIndex = paragraph.getElementIndex(endOffs); 2237 Element nextRun = paragraph.getElement(nextIndex); 2238 if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes())) 2239 lastTag.setDirection(ElementSpec.JoinNextDirection); 2240 } 2241 } 2242 2243 else if (! insertAtBoundary && lastStartTag != null 2244 && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection) 2245 { 2246 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1); 2247 if (lastTag.getType() == ElementSpec.ContentType 2248 && lastTag.getDirection() != ElementSpec.JoinPreviousDirection 2249 && attr.isEqual(cAttr)) 2250 { 2251 lastTag.setDirection(ElementSpec.JoinNextDirection); 2252 } 2253 } 2254 2255 ElementSpec[] specs = new ElementSpec[buf.size()]; 2256 specs = (ElementSpec[]) buf.toArray(specs); 2257 buffer.insert(offs, len, specs, ev); 2258 } 2259 catch (BadLocationException ex) 2260 { 2261 // Ignore this. Comment out for debugging. 2262 ex.printStackTrace(); 2263 } 2264 super.insertUpdate(ev, attr); 2265 } 2266 2267 private short insertAfterNewline(Element par1, Element par2, 2268 AttributeSet attr, ArrayList buf, 2269 int offs, int endOffs) 2270 { 2271 short dir = 0; 2272 if (par1.getParentElement() == par2.getParentElement()) 2273 { 2274 ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType); 2275 buf.add(tag); 2276 tag = new ElementSpec(attr, ElementSpec.StartTagType); 2277 buf.add(tag); 2278 if (par2.getEndOffset() != endOffs) 2279 dir = ElementSpec.JoinFractureDirection; 2280 else 2281 { 2282 Element par = par2.getParentElement(); 2283 if (par.getElementIndex(offs) + 1 < par.getElementCount()) 2284 dir = ElementSpec.JoinNextDirection; 2285 } 2286 } 2287 else 2288 { 2289 // For text with more than 2 levels, find the common parent of 2290 // par1 and par2. 2291 ArrayList parentsLeft = new ArrayList(); 2292 ArrayList parentsRight = new ArrayList(); 2293 Element e = par2; 2294 while (e != null) 2295 { 2296 parentsLeft.add(e); 2297 e = e.getParentElement(); 2298 } 2299 e = par1; 2300 int leftIndex = -1; 2301 while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1) 2302 { 2303 parentsRight.add(e); 2304 e = e.getParentElement(); 2305 } 2306 2307 if (e != null) 2308 2309 { 2310 // e is now the common parent. 2311 // Insert the end tags. 2312 for (int c = 0; c < leftIndex; c++) 2313 { 2314 buf.add(new ElementSpec(null, ElementSpec.EndTagType)); 2315 } 2316 // Insert the start tags. 2317 for (int c = parentsRight.size() - 1; c >= 0; c--) 2318 { 2319 Element el = (Element) parentsRight.get(c); 2320 ElementSpec tag = new ElementSpec(el.getAttributes(), 2321 ElementSpec.StartTagType); 2322 if (c > 0) 2323 tag.setDirection(ElementSpec.JoinNextDirection); 2324 buf.add(tag); 2325 } 2326 if (parentsRight.size() > 0) 2327 dir = ElementSpec.JoinNextDirection; 2328 else 2329 dir = ElementSpec.JoinFractureDirection; 2330 } 2331 else 2332 assert false; 2333 } 2334 return dir; 2335 } 2336 2337 /** 2338 * A helper method to set up the ElementSpec buffer for the special case of an 2339 * insertion occurring immediately after a newline. 2340 * 2341 * @param specs 2342 * the ElementSpec buffer to initialize. 2343 */ 2344 short handleInsertAfterNewline(Vector specs, int offset, int endOffset, 2345 Element prevParagraph, Element paragraph, 2346 AttributeSet a) 2347 { 2348 if (prevParagraph.getParentElement() == paragraph.getParentElement()) 2349 { 2350 specs.add(new ElementSpec(a, ElementSpec.EndTagType)); 2351 specs.add(new ElementSpec(a, ElementSpec.StartTagType)); 2352 if (paragraph.getStartOffset() != endOffset) 2353 return ElementSpec.JoinFractureDirection; 2354 // If there is an Element after this one, use JoinNextDirection. 2355 Element parent = paragraph.getParentElement(); 2356 if (parent.getElementCount() > (parent.getElementIndex(offset) + 1)) 2357 return ElementSpec.JoinNextDirection; 2358 } 2359 return ElementSpec.OriginateDirection; 2360 } 2361 2362 /** 2363 * Updates the document structure in response to text removal. This is 2364 * forwarded to the {@link ElementBuffer} of this document. Any changes to the 2365 * document structure are added to the specified document event and sent to 2366 * registered listeners. 2367 * 2368 * @param ev 2369 * the document event that records the changes to the document 2370 */ 2371 protected void removeUpdate(DefaultDocumentEvent ev) 2372 { 2373 super.removeUpdate(ev); 2374 buffer.remove(ev.getOffset(), ev.getLength(), ev); 2375 } 2376 2377 /** 2378 * Returns an enumeration of all style names. 2379 * 2380 * @return an enumeration of all style names 2381 */ 2382 public Enumeration<?> getStyleNames() 2383 { 2384 StyleContext context = (StyleContext) getAttributeContext(); 2385 return context.getStyleNames(); 2386 } 2387 2388 /** 2389 * Called when any of this document's styles changes. 2390 * 2391 * @param style 2392 * the style that changed 2393 */ 2394 protected void styleChanged(Style style) 2395 { 2396 // Nothing to do here. This is intended to be overridden by subclasses. 2397 } 2398 2399 /** 2400 * Inserts a bulk of structured content at once. 2401 * 2402 * @param offset 2403 * the offset at which the content should be inserted 2404 * @param data 2405 * the actual content spec to be inserted 2406 */ 2407 protected void insert(int offset, ElementSpec[] data) 2408 throws BadLocationException 2409 { 2410 if (data == null || data.length == 0) 2411 return; 2412 try 2413 { 2414 // writeLock() and writeUnlock() should always be in a try/finally 2415 // block so that locking balance is guaranteed even if some 2416 // exception is thrown. 2417 writeLock(); 2418 2419 // First we collect the content to be inserted. 2420 StringBuffer contentBuffer = new StringBuffer(); 2421 for (int i = 0; i < data.length; i++) 2422 { 2423 // Collect all inserts into one so we can get the correct 2424 // ElementEdit 2425 ElementSpec spec = data[i]; 2426 if (spec.getArray() != null && spec.getLength() > 0) 2427 contentBuffer.append(spec.getArray(), spec.getOffset(), 2428 spec.getLength()); 2429 } 2430 2431 int length = contentBuffer.length(); 2432 2433 // If there was no content inserted then exit early. 2434 if (length == 0) 2435 return; 2436 2437 Content c = getContent(); 2438 UndoableEdit edit = c.insertString(offset, 2439 contentBuffer.toString()); 2440 2441 // Create the DocumentEvent with the ElementEdit added 2442 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, 2443 length, 2444 DocumentEvent.EventType.INSERT); 2445 2446 ev.addEdit(edit); 2447 2448 // Finally we must update the document structure and fire the insert 2449 // update event. 2450 buffer.insert(offset, length, data, ev); 2451 2452 super.insertUpdate(ev, null); 2453 2454 ev.end(); 2455 fireInsertUpdate(ev); 2456 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2457 } 2458 finally 2459 { 2460 writeUnlock(); 2461 } 2462 } 2463 2464 /** 2465 * Initializes the <code>DefaultStyledDocument</code> with the specified 2466 * data. 2467 * 2468 * @param data 2469 * the specification of the content with which the document is 2470 * initialized 2471 */ 2472 protected void create(ElementSpec[] data) 2473 { 2474 try 2475 { 2476 2477 // Clear content if there is some. 2478 int len = getLength(); 2479 if (len > 0) 2480 remove(0, len); 2481 2482 writeLock(); 2483 2484 // Now we insert the content. 2485 StringBuilder b = new StringBuilder(); 2486 for (int i = 0; i < data.length; ++i) 2487 { 2488 ElementSpec el = data[i]; 2489 if (el.getArray() != null && el.getLength() > 0) 2490 b.append(el.getArray(), el.getOffset(), el.getLength()); 2491 } 2492 Content content = getContent(); 2493 UndoableEdit cEdit = content.insertString(0, b.toString()); 2494 2495 len = b.length(); 2496 DefaultDocumentEvent ev = 2497 new DefaultDocumentEvent(0, b.length(), 2498 DocumentEvent.EventType.INSERT); 2499 ev.addEdit(cEdit); 2500 2501 buffer.create(len, data, ev); 2502 2503 // For the bidi update. 2504 super.insertUpdate(ev, null); 2505 2506 ev.end(); 2507 fireInsertUpdate(ev); 2508 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2509 } 2510 catch (BadLocationException ex) 2511 { 2512 AssertionError err = new AssertionError("Unexpected bad location"); 2513 err.initCause(ex); 2514 throw err; 2515 } 2516 finally 2517 { 2518 writeUnlock(); 2519 } 2520 } 2521 }