You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
12 KiB
322 lines
12 KiB
/*
|
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package javax.swing.text;
|
|
|
|
import java.util.Vector;
|
|
|
|
/**
|
|
* 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™
|
|
* 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, Integer.valueOf(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="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
|
|
* in Swing</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)) {
|
|
StringBuilder filtered = new StringBuilder(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) {
|
|
removed.addElement(rmCandidate);
|
|
if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) &&
|
|
((index+1) < lineMap.getElementCount())) {
|
|
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<Element> added = new Vector<Element>();
|
|
private Vector<Element> removed = new Vector<Element>();
|
|
private transient Segment s;
|
|
}
|