blob: 85ea6c8d26208b788ec6977e993a81c1fe7be879 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing.text;
26
27import java.util.Vector;
28import javax.swing.event.*;
29
30/**
31 * A plain document that maintains no character attributes. The
32 * default element structure for this document is a map of the lines in
33 * the text. The Element returned by getDefaultRootElement is
34 * a map of the lines, and each child element represents a line.
35 * This model does not maintain any character level attributes,
36 * but each line can be tagged with an arbitrary set of attributes.
37 * Line to offset, and offset to line translations can be quickly
38 * performed using the default root element. The structure information
39 * of the DocumentEvent's fired by edits will indicate the line
40 * structure changes.
41 * <p>
42 * The default content storage management is performed by a
43 * gapped buffer implementation (GapContent). It supports
44 * editing reasonably large documents with good efficiency when
45 * the edits are contiguous or clustered, as is typical.
46 * <p>
47 * <strong>Warning:</strong>
48 * Serialized objects of this class will not be compatible with
49 * future Swing releases. The current serialization support is
50 * appropriate for short term storage or RMI between applications running
51 * the same version of Swing. As of 1.4, support for long term storage
52 * of all JavaBeans<sup><font size="-2">TM</font></sup>
53 * has been added to the <code>java.beans</code> package.
54 * Please see {@link java.beans.XMLEncoder}.
55 *
56 * @author Timothy Prinzing
57 * @see Document
58 * @see AbstractDocument
59 */
60public class PlainDocument extends AbstractDocument {
61
62 /**
63 * Name of the attribute that specifies the tab
64 * size for tabs contained in the content. The
65 * type for the value is Integer.
66 */
67 public static final String tabSizeAttribute = "tabSize";
68
69 /**
70 * Name of the attribute that specifies the maximum
71 * length of a line, if there is a maximum length.
72 * The type for the value is Integer.
73 */
74 public static final String lineLimitAttribute = "lineLimit";
75
76 /**
77 * Constructs a plain text document. A default model using
78 * <code>GapContent</code> is constructed and set.
79 */
80 public PlainDocument() {
81 this(new GapContent());
82 }
83
84 /**
85 * Constructs a plain text document. A default root element is created,
86 * and the tab size set to 8.
87 *
88 * @param c the container for the content
89 */
90 public PlainDocument(Content c) {
91 super(c);
92 putProperty(tabSizeAttribute, new Integer(8));
93 defaultRoot = createDefaultRoot();
94 }
95
96 /**
97 * Inserts some content into the document.
98 * Inserting content causes a write lock to be held while the
99 * actual changes are taking place, followed by notification
100 * to the observers on the thread that grabbed the write lock.
101 * <p>
102 * This method is thread safe, although most Swing methods
103 * are not. Please see
104 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
105 * to Use Threads</A> for more information.
106 *
107 * @param offs the starting offset >= 0
108 * @param str the string to insert; does nothing with null/empty strings
109 * @param a the attributes for the inserted content
110 * @exception BadLocationException the given insert position is not a valid
111 * position within the document
112 * @see Document#insertString
113 */
114 public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
115 // fields don't want to have multiple lines. We may provide a field-specific
116 // model in the future in which case the filtering logic here will no longer
117 // be needed.
118 Object filterNewlines = getProperty("filterNewlines");
119 if ((filterNewlines instanceof Boolean) && filterNewlines.equals(Boolean.TRUE)) {
120 if ((str != null) && (str.indexOf('\n') >= 0)) {
121 StringBuffer filtered = new StringBuffer(str);
122 int n = filtered.length();
123 for (int i = 0; i < n; i++) {
124 if (filtered.charAt(i) == '\n') {
125 filtered.setCharAt(i, ' ');
126 }
127 }
128 str = filtered.toString();
129 }
130 }
131 super.insertString(offs, str, a);
132 }
133
134 /**
135 * Gets the default root element for the document model.
136 *
137 * @return the root
138 * @see Document#getDefaultRootElement
139 */
140 public Element getDefaultRootElement() {
141 return defaultRoot;
142 }
143
144 /**
145 * Creates the root element to be used to represent the
146 * default document structure.
147 *
148 * @return the element base
149 */
150 protected AbstractElement createDefaultRoot() {
151 BranchElement map = (BranchElement) createBranchElement(null, null);
152 Element line = createLeafElement(map, null, 0, 1);
153 Element[] lines = new Element[1];
154 lines[0] = line;
155 map.replace(0, 0, lines);
156 return map;
157 }
158
159 /**
160 * Get the paragraph element containing the given position. Since this
161 * document only models lines, it returns the line instead.
162 */
163 public Element getParagraphElement(int pos){
164 Element lineMap = getDefaultRootElement();
165 return lineMap.getElement( lineMap.getElementIndex( pos ) );
166 }
167
168 /**
169 * Updates document structure as a result of text insertion. This
170 * will happen within a write lock. Since this document simply
171 * maps out lines, we refresh the line map.
172 *
173 * @param chng the change event describing the dit
174 * @param attr the set of attributes for the inserted text
175 */
176 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
177 removed.removeAllElements();
178 added.removeAllElements();
179 BranchElement lineMap = (BranchElement) getDefaultRootElement();
180 int offset = chng.getOffset();
181 int length = chng.getLength();
182 if (offset > 0) {
183 offset -= 1;
184 length += 1;
185 }
186 int index = lineMap.getElementIndex(offset);
187 Element rmCandidate = lineMap.getElement(index);
188 int rmOffs0 = rmCandidate.getStartOffset();
189 int rmOffs1 = rmCandidate.getEndOffset();
190 int lastOffset = rmOffs0;
191 try {
192 if (s == null) {
193 s = new Segment();
194 }
195 getContent().getChars(offset, length, s);
196 boolean hasBreaks = false;
197 for (int i = 0; i < length; i++) {
198 char c = s.array[s.offset + i];
199 if (c == '\n') {
200 int breakOffset = offset + i + 1;
201 added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset));
202 lastOffset = breakOffset;
203 hasBreaks = true;
204 }
205 }
206 if (hasBreaks) {
207 int rmCount = 1;
208 removed.addElement(rmCandidate);
209 if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) &&
210 ((index+1) < lineMap.getElementCount())) {
211 rmCount += 1;
212 Element e = lineMap.getElement(index+1);
213 removed.addElement(e);
214 rmOffs1 = e.getEndOffset();
215 }
216 if (lastOffset < rmOffs1) {
217 added.addElement(createLeafElement(lineMap, null, lastOffset, rmOffs1));
218 }
219
220 Element[] aelems = new Element[added.size()];
221 added.copyInto(aelems);
222 Element[] relems = new Element[removed.size()];
223 removed.copyInto(relems);
224 ElementEdit ee = new ElementEdit(lineMap, index, relems, aelems);
225 chng.addEdit(ee);
226 lineMap.replace(index, relems.length, aelems);
227 }
228 if (Utilities.isComposedTextAttributeDefined(attr)) {
229 insertComposedTextUpdate(chng, attr);
230 }
231 } catch (BadLocationException e) {
232 throw new Error("Internal error: " + e.toString());
233 }
234 super.insertUpdate(chng, attr);
235 }
236
237 /**
238 * Updates any document structure as a result of text removal.
239 * This will happen within a write lock. Since the structure
240 * represents a line map, this just checks to see if the
241 * removal spans lines. If it does, the two lines outside
242 * of the removal area are joined together.
243 *
244 * @param chng the change event describing the edit
245 */
246 protected void removeUpdate(DefaultDocumentEvent chng) {
247 removed.removeAllElements();
248 BranchElement map = (BranchElement) getDefaultRootElement();
249 int offset = chng.getOffset();
250 int length = chng.getLength();
251 int line0 = map.getElementIndex(offset);
252 int line1 = map.getElementIndex(offset + length);
253 if (line0 != line1) {
254 // a line was removed
255 for (int i = line0; i <= line1; i++) {
256 removed.addElement(map.getElement(i));
257 }
258 int p0 = map.getElement(line0).getStartOffset();
259 int p1 = map.getElement(line1).getEndOffset();
260 Element[] aelems = new Element[1];
261 aelems[0] = createLeafElement(map, null, p0, p1);
262 Element[] relems = new Element[removed.size()];
263 removed.copyInto(relems);
264 ElementEdit ee = new ElementEdit(map, line0, relems, aelems);
265 chng.addEdit(ee);
266 map.replace(line0, relems.length, aelems);
267 } else {
268 //Check for the composed text element
269 Element line = map.getElement(line0);
270 if (!line.isLeaf()) {
271 Element leaf = line.getElement(line.getElementIndex(offset));
272 if (Utilities.isComposedTextElement(leaf)) {
273 Element[] aelem = new Element[1];
274 aelem[0] = createLeafElement(map, null,
275 line.getStartOffset(), line.getEndOffset());
276 Element[] relem = new Element[1];
277 relem[0] = line;
278 ElementEdit ee = new ElementEdit(map, line0, relem, aelem);
279 chng.addEdit(ee);
280 map.replace(line0, 1, aelem);
281 }
282 }
283 }
284 super.removeUpdate(chng);
285 }
286
287 //
288 // Inserts the composed text of an input method. The line element
289 // where the composed text is inserted into becomes an branch element
290 // which contains leaf elements of the composed text and the text
291 // backing store.
292 //
293 private void insertComposedTextUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
294 added.removeAllElements();
295 BranchElement lineMap = (BranchElement) getDefaultRootElement();
296 int offset = chng.getOffset();
297 int length = chng.getLength();
298 int index = lineMap.getElementIndex(offset);
299 Element elem = lineMap.getElement(index);
300 int elemStart = elem.getStartOffset();
301 int elemEnd = elem.getEndOffset();
302 BranchElement[] abelem = new BranchElement[1];
303 abelem[0] = (BranchElement) createBranchElement(lineMap, null);
304 Element[] relem = new Element[1];
305 relem[0] = elem;
306 if (elemStart != offset)
307 added.addElement(createLeafElement(abelem[0], null, elemStart, offset));
308 added.addElement(createLeafElement(abelem[0], attr, offset, offset+length));
309 if (elemEnd != offset+length)
310 added.addElement(createLeafElement(abelem[0], null, offset+length, elemEnd));
311 Element[] alelem = new Element[added.size()];
312 added.copyInto(alelem);
313 ElementEdit ee = new ElementEdit(lineMap, index, relem, abelem);
314 chng.addEdit(ee);
315
316 abelem[0].replace(0, 0, alelem);
317 lineMap.replace(index, 1, abelem);
318 }
319
320 private AbstractElement defaultRoot;
321 private Vector added = new Vector(); // Vector<Element>
322 private Vector removed = new Vector(); // Vector<Element>
323 private transient Segment s;
324}