diff options
Diffstat (limited to 'src/share/classes/javax/swing/text/PlainDocument.java')
-rw-r--r-- | src/share/classes/javax/swing/text/PlainDocument.java | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/share/classes/javax/swing/text/PlainDocument.java b/src/share/classes/javax/swing/text/PlainDocument.java new file mode 100644 index 000000000..85ea6c8d2 --- /dev/null +++ b/src/share/classes/javax/swing/text/PlainDocument.java @@ -0,0 +1,324 @@ +/* + * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package javax.swing.text; + +import java.util.Vector; +import javax.swing.event.*; + +/** + * A plain document that maintains no character attributes. The + * default element structure for this document is a map of the lines in + * the text. The Element returned by getDefaultRootElement is + * a map of the lines, and each child element represents a line. + * This model does not maintain any character level attributes, + * but each line can be tagged with an arbitrary set of attributes. + * Line to offset, and offset to line translations can be quickly + * performed using the default root element. The structure information + * of the DocumentEvent's fired by edits will indicate the line + * structure changes. + * <p> + * The default content storage management is performed by a + * gapped buffer implementation (GapContent). It supports + * editing reasonably large documents with good efficiency when + * the edits are contiguous or clustered, as is typical. + * <p> + * <strong>Warning:</strong> + * Serialized objects of this class will not be compatible with + * future Swing releases. The current serialization support is + * appropriate for short term storage or RMI between applications running + * the same version of Swing. As of 1.4, support for long term storage + * of all JavaBeans<sup><font size="-2">TM</font></sup> + * has been added to the <code>java.beans</code> package. + * Please see {@link java.beans.XMLEncoder}. + * + * @author Timothy Prinzing + * @see Document + * @see AbstractDocument + */ +public class PlainDocument extends AbstractDocument { + + /** + * Name of the attribute that specifies the tab + * size for tabs contained in the content. The + * type for the value is Integer. + */ + public static final String tabSizeAttribute = "tabSize"; + + /** + * Name of the attribute that specifies the maximum + * length of a line, if there is a maximum length. + * The type for the value is Integer. + */ + public static final String lineLimitAttribute = "lineLimit"; + + /** + * Constructs a plain text document. A default model using + * <code>GapContent</code> is constructed and set. + */ + public PlainDocument() { + this(new GapContent()); + } + + /** + * Constructs a plain text document. A default root element is created, + * and the tab size set to 8. + * + * @param c the container for the content + */ + public PlainDocument(Content c) { + super(c); + putProperty(tabSizeAttribute, new Integer(8)); + defaultRoot = createDefaultRoot(); + } + + /** + * Inserts some content into the document. + * Inserting content causes a write lock to be held while the + * actual changes are taking place, followed by notification + * to the observers on the thread that grabbed the write lock. + * <p> + * This method is thread safe, although most Swing methods + * are not. Please see + * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How + * to Use Threads</A> for more information. + * + * @param offs the starting offset >= 0 + * @param str the string to insert; does nothing with null/empty strings + * @param a the attributes for the inserted content + * @exception BadLocationException the given insert position is not a valid + * position within the document + * @see Document#insertString + */ + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { + // fields don't want to have multiple lines. We may provide a field-specific + // model in the future in which case the filtering logic here will no longer + // be needed. + Object filterNewlines = getProperty("filterNewlines"); + if ((filterNewlines instanceof Boolean) && filterNewlines.equals(Boolean.TRUE)) { + if ((str != null) && (str.indexOf('\n') >= 0)) { + StringBuffer filtered = new StringBuffer(str); + int n = filtered.length(); + for (int i = 0; i < n; i++) { + if (filtered.charAt(i) == '\n') { + filtered.setCharAt(i, ' '); + } + } + str = filtered.toString(); + } + } + super.insertString(offs, str, a); + } + + /** + * Gets the default root element for the document model. + * + * @return the root + * @see Document#getDefaultRootElement + */ + public Element getDefaultRootElement() { + return defaultRoot; + } + + /** + * Creates the root element to be used to represent the + * default document structure. + * + * @return the element base + */ + protected AbstractElement createDefaultRoot() { + BranchElement map = (BranchElement) createBranchElement(null, null); + Element line = createLeafElement(map, null, 0, 1); + Element[] lines = new Element[1]; + lines[0] = line; + map.replace(0, 0, lines); + return map; + } + + /** + * Get the paragraph element containing the given position. Since this + * document only models lines, it returns the line instead. + */ + public Element getParagraphElement(int pos){ + Element lineMap = getDefaultRootElement(); + return lineMap.getElement( lineMap.getElementIndex( pos ) ); + } + + /** + * Updates document structure as a result of text insertion. This + * will happen within a write lock. Since this document simply + * maps out lines, we refresh the line map. + * + * @param chng the change event describing the dit + * @param attr the set of attributes for the inserted text + */ + protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { + removed.removeAllElements(); + added.removeAllElements(); + BranchElement lineMap = (BranchElement) getDefaultRootElement(); + int offset = chng.getOffset(); + int length = chng.getLength(); + if (offset > 0) { + offset -= 1; + length += 1; + } + int index = lineMap.getElementIndex(offset); + Element rmCandidate = lineMap.getElement(index); + int rmOffs0 = rmCandidate.getStartOffset(); + int rmOffs1 = rmCandidate.getEndOffset(); + int lastOffset = rmOffs0; + try { + if (s == null) { + s = new Segment(); + } + getContent().getChars(offset, length, s); + boolean hasBreaks = false; + for (int i = 0; i < length; i++) { + char c = s.array[s.offset + i]; + if (c == '\n') { + int breakOffset = offset + i + 1; + added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset)); + lastOffset = breakOffset; + hasBreaks = true; + } + } + if (hasBreaks) { + int rmCount = 1; + removed.addElement(rmCandidate); + if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) && + ((index+1) < lineMap.getElementCount())) { + rmCount += 1; + Element e = lineMap.getElement(index+1); + removed.addElement(e); + rmOffs1 = e.getEndOffset(); + } + if (lastOffset < rmOffs1) { + added.addElement(createLeafElement(lineMap, null, lastOffset, rmOffs1)); + } + + Element[] aelems = new Element[added.size()]; + added.copyInto(aelems); + Element[] relems = new Element[removed.size()]; + removed.copyInto(relems); + ElementEdit ee = new ElementEdit(lineMap, index, relems, aelems); + chng.addEdit(ee); + lineMap.replace(index, relems.length, aelems); + } + if (Utilities.isComposedTextAttributeDefined(attr)) { + insertComposedTextUpdate(chng, attr); + } + } catch (BadLocationException e) { + throw new Error("Internal error: " + e.toString()); + } + super.insertUpdate(chng, attr); + } + + /** + * Updates any document structure as a result of text removal. + * This will happen within a write lock. Since the structure + * represents a line map, this just checks to see if the + * removal spans lines. If it does, the two lines outside + * of the removal area are joined together. + * + * @param chng the change event describing the edit + */ + protected void removeUpdate(DefaultDocumentEvent chng) { + removed.removeAllElements(); + BranchElement map = (BranchElement) getDefaultRootElement(); + int offset = chng.getOffset(); + int length = chng.getLength(); + int line0 = map.getElementIndex(offset); + int line1 = map.getElementIndex(offset + length); + if (line0 != line1) { + // a line was removed + for (int i = line0; i <= line1; i++) { + removed.addElement(map.getElement(i)); + } + int p0 = map.getElement(line0).getStartOffset(); + int p1 = map.getElement(line1).getEndOffset(); + Element[] aelems = new Element[1]; + aelems[0] = createLeafElement(map, null, p0, p1); + Element[] relems = new Element[removed.size()]; + removed.copyInto(relems); + ElementEdit ee = new ElementEdit(map, line0, relems, aelems); + chng.addEdit(ee); + map.replace(line0, relems.length, aelems); + } else { + //Check for the composed text element + Element line = map.getElement(line0); + if (!line.isLeaf()) { + Element leaf = line.getElement(line.getElementIndex(offset)); + if (Utilities.isComposedTextElement(leaf)) { + Element[] aelem = new Element[1]; + aelem[0] = createLeafElement(map, null, + line.getStartOffset(), line.getEndOffset()); + Element[] relem = new Element[1]; + relem[0] = line; + ElementEdit ee = new ElementEdit(map, line0, relem, aelem); + chng.addEdit(ee); + map.replace(line0, 1, aelem); + } + } + } + super.removeUpdate(chng); + } + + // + // Inserts the composed text of an input method. The line element + // where the composed text is inserted into becomes an branch element + // which contains leaf elements of the composed text and the text + // backing store. + // + private void insertComposedTextUpdate(DefaultDocumentEvent chng, AttributeSet attr) { + added.removeAllElements(); + BranchElement lineMap = (BranchElement) getDefaultRootElement(); + int offset = chng.getOffset(); + int length = chng.getLength(); + int index = lineMap.getElementIndex(offset); + Element elem = lineMap.getElement(index); + int elemStart = elem.getStartOffset(); + int elemEnd = elem.getEndOffset(); + BranchElement[] abelem = new BranchElement[1]; + abelem[0] = (BranchElement) createBranchElement(lineMap, null); + Element[] relem = new Element[1]; + relem[0] = elem; + if (elemStart != offset) + added.addElement(createLeafElement(abelem[0], null, elemStart, offset)); + added.addElement(createLeafElement(abelem[0], attr, offset, offset+length)); + if (elemEnd != offset+length) + added.addElement(createLeafElement(abelem[0], null, offset+length, elemEnd)); + Element[] alelem = new Element[added.size()]; + added.copyInto(alelem); + ElementEdit ee = new ElementEdit(lineMap, index, relem, abelem); + chng.addEdit(ee); + + abelem[0].replace(0, 0, alelem); + lineMap.replace(index, 1, abelem); + } + + private AbstractElement defaultRoot; + private Vector added = new Vector(); // Vector<Element> + private Vector removed = new Vector(); // Vector<Element> + private transient Segment s; +} |