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