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