GNU Classpath (0.18) | ||
Frames | No Frames |
1: /* DefaultStyledDocument.java -- 2: Copyright (C) 2004 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: 45: import javax.swing.event.DocumentEvent; 46: 47: /** 48: * The default implementation of {@link StyledDocument}. 49: * 50: * The document is modeled as an {@link Element} tree, which has 51: * a {@link SectionElement} as single root, which has one or more 52: * {@link AbstractDocument.BranchElement}s as paragraph nodes 53: * and each paragraph node having one or more 54: * {@link AbstractDocument.LeafElement}s as content nodes. 55: * 56: * @author Michael Koch (konqueror@gmx.de) 57: * @author Roman Kennke (roman@kennke.org) 58: */ 59: public class DefaultStyledDocument extends AbstractDocument 60: implements StyledDocument 61: { 62: /** 63: * Performs all <em>structural</code> changes to the <code>Element</code> 64: * hierarchy. 65: */ 66: public class ElementBuffer 67: implements Serializable 68: { 69: /** The root element of the hierarchy. */ 70: private Element root; 71: 72: /** Holds the offset for structural changes. */ 73: private int offset; 74: 75: /** Holds the length of structural changes. */ 76: private int length; 77: 78: /** 79: * Creates a new <code>ElementBuffer</code> for the specified 80: * <code>root</code> element. 81: * 82: * @param root the root element for this <code>ElementBuffer</code> 83: */ 84: public ElementBuffer(Element root) 85: { 86: this.root = root; 87: } 88: 89: /** 90: * Returns the root element of this <code>ElementBuffer</code>. 91: * 92: * @return the root element of this <code>ElementBuffer</code> 93: */ 94: public Element getRootElement() 95: { 96: return root; 97: } 98: 99: /** 100: * Modifies the element structure so that the specified interval starts 101: * and ends at an element boundary. Content and paragraph elements 102: * are split and created as necessary. 103: * 104: * This also updates the <code>DefaultDocumentEvent</code> to reflect the 105: * structural changes. 106: * 107: * The bulk work is delegated to {@link #changeUpdate()}. 108: * 109: * @param offset the start index of the interval to be changed 110: * @param length the length of the interval to be changed 111: * @param ev the <code>DefaultDocumentEvent</code> describing the change 112: */ 113: public void change(int offset, int length, DefaultDocumentEvent ev) 114: { 115: this.offset = offset; 116: this.length = length; 117: changeUpdate(); 118: } 119: 120: /** 121: * Performs the actual work for {@link #change}. 122: * The elements at the interval boundaries are split up (if necessary) 123: * so that the interval boundaries are located at element boundaries. 124: */ 125: protected void changeUpdate() 126: { 127: // Split up the element at the start offset if necessary. 128: Element el = getCharacterElement(offset); 129: split(el, offset); 130: 131: int endOffset = offset + length; 132: el = getCharacterElement(endOffset); 133: split(el, endOffset); 134: } 135: 136: /** 137: * Splits an element if <code>offset</code> is not alread at its boundary. 138: * 139: * @param el the Element to possibly split 140: * @param offset the offset at which to possibly split 141: */ 142: void split(Element el, int offset) 143: { 144: if (el instanceof AbstractElement) 145: { 146: AbstractElement ael = (AbstractElement) el; 147: int startOffset = ael.getStartOffset(); 148: int endOffset = ael.getEndOffset(); 149: int len = endOffset - startOffset; 150: if (startOffset != offset && endOffset != offset) 151: { 152: Element paragraph = ael.getParentElement(); 153: if (paragraph instanceof BranchElement) 154: { 155: BranchElement par = (BranchElement) paragraph; 156: Element child1 = createLeafElement(par, ael, startOffset, 157: offset); 158: Element child2 = createLeafElement(par, ael, offset, 159: endOffset); 160: int index = par.getElementIndex(startOffset); 161: par.replace(index, 1, new Element[]{ child1, child2 }); 162: } 163: else 164: throw new AssertionError("paragraph elements are expected to " 165: + "be instances of " 166: + "javax.swing.text.AbstractDocument.BranchElement"); 167: } 168: } 169: else 170: throw new AssertionError("content elements are expected to be " 171: + "instances of " 172: + "javax.swing.text.AbstractDocument.AbstractElement"); 173: } 174: } 175: 176: /** 177: * The default size to use for new content buffers. 178: */ 179: public static final int BUFFER_SIZE_DEFAULT = 4096; 180: 181: /** 182: * The <code>EditorBuffer</code> that is used to manage to 183: * <code>Element</code> hierarchy. 184: */ 185: protected DefaultStyledDocument.ElementBuffer buffer; 186: 187: /** 188: * Creates a new <code>DefaultStyledDocument</code>. 189: */ 190: public DefaultStyledDocument() 191: { 192: this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext()); 193: } 194: 195: /** 196: * Creates a new <code>DefaultStyledDocument</code> that uses the 197: * specified {@link StyleContext}. 198: * 199: * @param context the <code>StyleContext</code> to use 200: */ 201: public DefaultStyledDocument(StyleContext context) 202: { 203: this(new GapContent(BUFFER_SIZE_DEFAULT), context); 204: } 205: 206: /** 207: * Creates a new <code>DefaultStyledDocument</code> that uses the 208: * specified {@link StyleContext} and {@link Content} buffer. 209: * 210: * @param content the <code>Content</code> buffer to use 211: * @param context the <code>StyleContext</code> to use 212: */ 213: public DefaultStyledDocument(AbstractDocument.Content content, 214: StyleContext context) 215: { 216: super(content, context); 217: buffer = new ElementBuffer(createDefaultRoot()); 218: setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE)); 219: } 220: 221: /** 222: * Adds a style into the style hierarchy. Unspecified style attributes 223: * can be resolved in the <code>parent</code> style, if one is specified. 224: * 225: * While it is legal to add nameless styles (<code>nm == null</code), 226: * you must be aware that the client application is then responsible 227: * for managing the style hierarchy, since unnamed styles cannot be 228: * looked up by their name. 229: * 230: * @param nm the name of the style or <code>null</code> if the style should 231: * be unnamed 232: * @param parent the parent in which unspecified style attributes are 233: * resolved, or <code>null</code> if that is not necessary 234: * 235: * @return the newly created <code>Style</code> 236: */ 237: public Style addStyle(String nm, Style parent) 238: { 239: StyleContext context = (StyleContext) getAttributeContext(); 240: return context.addStyle(nm, parent); 241: } 242: 243: /** 244: * Create the default root element for this kind of <code>Document</code>. 245: * 246: * @return the default root element for this kind of <code>Document</code> 247: */ 248: protected AbstractDocument.AbstractElement createDefaultRoot() 249: { 250: Element[] tmp; 251: // FIXME: Create a SecionElement here instead of a BranchElement. 252: // Use createBranchElement() and createLeafElement instead. 253: BranchElement section = new BranchElement(null, null); 254: 255: BranchElement paragraph = new BranchElement(section, null); 256: tmp = new Element[1]; 257: tmp[0] = paragraph; 258: section.replace(0, 0, tmp); 259: 260: LeafElement leaf = new LeafElement(paragraph, null, 0, 1); 261: tmp = new Element[1]; 262: tmp[0] = leaf; 263: paragraph.replace(0, 0, tmp); 264: 265: return section; 266: } 267: 268: /** 269: * Returns the <code>Element</code> that corresponds to the character 270: * at the specified position. 271: * 272: * @param position the position of which we query the corresponding 273: * <code>Element</code> 274: * 275: * @return the <code>Element</code> that corresponds to the character 276: * at the specified position 277: */ 278: public Element getCharacterElement(int position) 279: { 280: Element element = getDefaultRootElement(); 281: 282: while (! element.isLeaf()) 283: { 284: int index = element.getElementIndex(position); 285: element = element.getElement(index); 286: } 287: 288: return element; 289: } 290: 291: /** 292: * Extracts a background color from a set of attributes. 293: * 294: * @param attributes the attributes from which to get a background color 295: * 296: * @return the background color that correspond to the attributes 297: */ 298: public Color getBackground(AttributeSet attributes) 299: { 300: StyleContext context = (StyleContext) getAttributeContext(); 301: return context.getBackground(attributes); 302: } 303: 304: /** 305: * Returns the default root element. 306: * 307: * @return the default root element 308: */ 309: public Element getDefaultRootElement() 310: { 311: return buffer.getRootElement(); 312: } 313: 314: /** 315: * Extracts a font from a set of attributes. 316: * 317: * @param attributes the attributes from which to get a font 318: * 319: * @return the font that correspond to the attributes 320: */ 321: public Font getFont(AttributeSet attributes) 322: { 323: StyleContext context = (StyleContext) getAttributeContext(); 324: return context.getFont(attributes); 325: } 326: 327: /** 328: * Extracts a foreground color from a set of attributes. 329: * 330: * @param attributes the attributes from which to get a foreground color 331: * 332: * @return the foreground color that correspond to the attributes 333: */ 334: public Color getForeground(AttributeSet attributes) 335: { 336: StyleContext context = (StyleContext) getAttributeContext(); 337: return context.getForeground(attributes); 338: } 339: 340: /** 341: * Returns the logical <code>Style</code> for the specified position. 342: * 343: * @param position the position from which to query to logical style 344: * 345: * @return the logical <code>Style</code> for the specified position 346: */ 347: public Style getLogicalStyle(int position) 348: { 349: Element paragraph = getParagraphElement(position); 350: AttributeSet attributes = paragraph.getAttributes(); 351: return (Style) attributes.getResolveParent(); 352: } 353: 354: /** 355: * Returns the paragraph element for the specified position. 356: * 357: * @param position the position for which to query the paragraph element 358: * 359: * @return the paragraph element for the specified position 360: */ 361: public Element getParagraphElement(int position) 362: { 363: Element element = getCharacterElement(position); 364: return element.getParentElement(); 365: } 366: 367: /** 368: * Looks up and returns a named <code>Style</code>. 369: * 370: * @param nm the name of the <code>Style</code> 371: * 372: * @return the found <code>Style</code> of <code>null</code> if no such 373: * <code>Style</code> exists 374: */ 375: public Style getStyle(String nm) 376: { 377: StyleContext context = (StyleContext) getAttributeContext(); 378: return context.getStyle(nm); 379: } 380: 381: /** 382: * Removes a named <code>Style</code> from the style hierarchy. 383: * 384: * @param nm the name of the <code>Style</code> to be removed 385: */ 386: public void removeStyle(String nm) 387: { 388: StyleContext context = (StyleContext) getAttributeContext(); 389: context.removeStyle(nm); 390: } 391: 392: /** 393: * Sets text attributes for the fragment specified by <code>offset</code> 394: * and <code>length</code>. 395: * 396: * @param offset the start offset of the fragment 397: * @param length the length of the fragment 398: * @param attributes the text attributes to set 399: * @param replace if <code>true</code>, the attributes of the current 400: * selection are overridden, otherwise they are merged 401: */ 402: public void setCharacterAttributes(int offset, int length, 403: AttributeSet attributes, 404: boolean replace) 405: { 406: DefaultDocumentEvent ev = 407: new DefaultDocumentEvent(offset, length, 408: DocumentEvent.EventType.CHANGE); 409: 410: // Modify the element structure so that the interval begins at an element 411: // start and ends at an element end. 412: buffer.change(offset, length, ev); 413: 414: Element root = getDefaultRootElement(); 415: // Visit all paragraph elements within the specified interval 416: int paragraphCount = root.getElementCount(); 417: for (int pindex = 0; pindex < paragraphCount; pindex++) 418: { 419: Element paragraph = root.getElement(pindex); 420: // Skip paragraphs that lie outside the interval. 421: if ((paragraph.getStartOffset() > offset + length) 422: || (paragraph.getEndOffset() < offset)) 423: continue; 424: 425: // Visit content elements within this paragraph 426: int contentCount = paragraph.getElementCount(); 427: for (int cindex = 0; cindex < contentCount; cindex++) 428: { 429: Element content = paragraph.getElement(cindex); 430: // Skip content that lies outside the interval. 431: if ((content.getStartOffset() > offset + length) 432: || (content.getEndOffset() < offset)) 433: continue; 434: 435: if (content instanceof AbstractElement) 436: { 437: AbstractElement el = (AbstractElement) content; 438: if (replace) 439: el.removeAttributes(el); 440: el.addAttributes(attributes); 441: } 442: else 443: throw new AssertionError("content elements are expected to be" 444: + "instances of " 445: + "javax.swing.text.AbstractDocument.AbstractElement"); 446: } 447: } 448: } 449: 450: /** 451: * Sets the logical style for the paragraph at the specified position. 452: * 453: * @param position the position at which the logical style is added 454: * @param style the style to set for the current paragraph 455: */ 456: public void setLogicalStyle(int position, Style style) 457: { 458: Element el = getParagraphElement(position); 459: if (el instanceof AbstractElement) 460: { 461: AbstractElement ael = (AbstractElement) el; 462: ael.setResolveParent(style); 463: } 464: else 465: throw new AssertionError("paragraph elements are expected to be" 466: + "instances of javax.swing.text.AbstractDocument.AbstractElement"); 467: } 468: 469: /** 470: * Sets text attributes for the paragraph at the specified fragment. 471: * 472: * @param offset the beginning of the fragment 473: * @param length the length of the fragment 474: * @param attributes the text attributes to set 475: * @param replace if <code>true</code>, the attributes of the current 476: * selection are overridden, otherwise they are merged 477: */ 478: public void setParagraphAttributes(int offset, int length, 479: AttributeSet attributes, 480: boolean replace) 481: { 482: // FIXME: Implement me. 483: throw new Error("not implemented"); 484: } 485: }
GNU Classpath (0.18) |