| GNU Classpath (0.95) | |
| Frames | No Frames |
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.ArrayList; 45: import java.util.Enumeration; 46: import java.util.Iterator; 47: import java.util.Stack; 48: import java.util.Vector; 49: 50: import javax.swing.event.ChangeEvent; 51: import javax.swing.event.ChangeListener; 52: import javax.swing.event.DocumentEvent; 53: import javax.swing.event.UndoableEditEvent; 54: import javax.swing.undo.AbstractUndoableEdit; 55: import javax.swing.undo.UndoableEdit; 56: 57: /** 58: * The default implementation of {@link StyledDocument}. The document is 59: * modeled as an {@link Element} tree, which has a {@link SectionElement} as 60: * single root, which has one or more {@link AbstractDocument.BranchElement}s 61: * as paragraph nodes and each paragraph node having one or more 62: * {@link AbstractDocument.LeafElement}s as content nodes. 63: * 64: * @author Michael Koch (konqueror@gmx.de) 65: * @author Roman Kennke (roman@kennke.org) 66: */ 67: public class DefaultStyledDocument extends AbstractDocument implements 68: StyledDocument 69: { 70: 71: /** 72: * An {@link UndoableEdit} that can undo attribute changes to an element. 73: * 74: * @author Roman Kennke (kennke@aicas.com) 75: */ 76: public static class AttributeUndoableEdit extends AbstractUndoableEdit 77: { 78: /** 79: * A copy of the old attributes. 80: */ 81: protected AttributeSet copy; 82: 83: /** 84: * The new attributes. 85: */ 86: protected AttributeSet newAttributes; 87: 88: /** 89: * If the new attributes replaced the old attributes or if they only were 90: * added to them. 91: */ 92: protected boolean isReplacing; 93: 94: /** 95: * The element that has changed. 96: */ 97: protected Element element; 98: 99: /** 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