Source for javax.swing.text.DefaultStyledDocument

   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: }