1. /*
  2. * @(#)PlainDocument.java 1.32 01/11/29
  3. *
  4. * Copyright 2002 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 appropriate
  30. * for short term storage or RMI between applications running the same
  31. * version of Swing. A future release of Swing will provide support for
  32. * long term persistence.
  33. *
  34. * @author Timothy Prinzing
  35. * @version 1.32 11/29/01
  36. * @see Document
  37. * @see AbstractDocument
  38. */
  39. public class PlainDocument extends AbstractDocument {
  40. /**
  41. * Name of the attribute that specifies the tab
  42. * size for tabs contained in the content. The
  43. * type for the value is Integer.
  44. */
  45. public static final String tabSizeAttribute = "tabSize";
  46. /**
  47. * Name of the attribute that specifies the maximum
  48. * length of a line, if there is a maximum length.
  49. * The type for the value is Integer.
  50. */
  51. public static final String lineLimitAttribute = "lineLimit";
  52. /**
  53. * Constructs a plain text document. A default model using StringContent
  54. * is constructed and set.
  55. */
  56. public PlainDocument() {
  57. this(new GapContent());
  58. }
  59. /**
  60. * Constructs a plain text document. A default root element is created,
  61. * and the tab size set to 8.
  62. *
  63. * @param c the container for the content
  64. */
  65. protected PlainDocument(Content c) {
  66. super(c);
  67. putProperty(tabSizeAttribute, new Integer(8));
  68. defaultRoot = createDefaultRoot();
  69. }
  70. /**
  71. * Gets the default root element for the document model.
  72. *
  73. * @return the root
  74. * @see Document#getDefaultRootElement
  75. */
  76. public Element getDefaultRootElement() {
  77. return defaultRoot;
  78. }
  79. /**
  80. * Creates the root element to be used to represent the
  81. * default document structure.
  82. *
  83. * @return the element base
  84. */
  85. protected AbstractElement createDefaultRoot() {
  86. BranchElement map = (BranchElement) createBranchElement(null, null);
  87. Element line = createLeafElement(map, null, 0, 1);
  88. Element[] lines = new Element[1];
  89. lines[0] = line;
  90. map.replace(0, 0, lines);
  91. return map;
  92. }
  93. /**
  94. * Get the paragraph element containing the given position. Since this
  95. * document only models lines, it returns the line instead.
  96. */
  97. public Element getParagraphElement(int pos){
  98. Element lineMap = getDefaultRootElement();
  99. return lineMap.getElement( lineMap.getElementIndex( pos ) );
  100. }
  101. /**
  102. * Updates document structure as a result of text insertion. This
  103. * will happen within a write lock. Since this document simply
  104. * maps out lines, we refresh the line map.
  105. *
  106. * @param chng the change event describing the dit
  107. * @param attr the set of attributes for the inserted text
  108. */
  109. protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  110. removed.removeAllElements();
  111. added.removeAllElements();
  112. BranchElement lineMap = (BranchElement) getDefaultRootElement();
  113. int offset = chng.getOffset();
  114. int length = chng.getLength();
  115. if (offset > 0) {
  116. offset -= 1;
  117. length += 1;
  118. }
  119. int index = lineMap.getElementIndex(offset);
  120. Element rmCandidate = lineMap.getElement(index);
  121. int rmOffs0 = rmCandidate.getStartOffset();
  122. int rmOffs1 = rmCandidate.getEndOffset();
  123. int lastOffset = rmOffs0;
  124. try {
  125. String str = getText(offset, length);
  126. boolean hasBreaks = false;
  127. for (int i = 0; i < length; i++) {
  128. char c = str.charAt(i);
  129. if (c == '\n') {
  130. int breakOffset = offset + i + 1;
  131. added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset));
  132. lastOffset = breakOffset;
  133. hasBreaks = true;
  134. }
  135. }
  136. if (hasBreaks) {
  137. int rmCount = 1;
  138. removed.addElement(rmCandidate);
  139. if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) &&
  140. ((index+1) < lineMap.getElementCount())) {
  141. rmCount += 1;
  142. Element e = lineMap.getElement(index+1);
  143. removed.addElement(e);
  144. rmOffs1 = e.getEndOffset();
  145. }
  146. if (lastOffset < rmOffs1) {
  147. added.addElement(createLeafElement(lineMap, null, lastOffset, rmOffs1));
  148. }
  149. Element[] aelems = new Element[added.size()];
  150. added.copyInto(aelems);
  151. Element[] relems = new Element[removed.size()];
  152. removed.copyInto(relems);
  153. ElementEdit ee = new ElementEdit(lineMap, index, relems, aelems);
  154. chng.addEdit(ee);
  155. lineMap.replace(index, relems.length, aelems);
  156. }
  157. if (Utilities.isComposedTextAttributeDefined(attr)) {
  158. insertComposedTextUpdate(chng, attr);
  159. }
  160. } catch (BadLocationException e) {
  161. throw new Error("Internal error: " + e.toString());
  162. }
  163. }
  164. /**
  165. * Updates any document structure as a result of text removal.
  166. * This will happen within a write lock. Since the structure
  167. * represents a line map, this just checks to see if the
  168. * removal spans lines. If it does, the two lines outside
  169. * of the removal area are joined together.
  170. *
  171. * @param chng the change event describing the edit
  172. */
  173. protected void removeUpdate(DefaultDocumentEvent chng) {
  174. removed.removeAllElements();
  175. BranchElement map = (BranchElement) getDefaultRootElement();
  176. int offset = chng.getOffset();
  177. int length = chng.getLength();
  178. int line0 = map.getElementIndex(offset);
  179. int line1 = map.getElementIndex(offset + length);
  180. if (line0 != line1) {
  181. // a line was removed
  182. for (int i = line0; i <= line1; i++) {
  183. removed.addElement(map.getElement(i));
  184. }
  185. int p0 = map.getElement(line0).getStartOffset();
  186. int p1 = map.getElement(line1).getEndOffset();
  187. Element[] aelems = new Element[1];
  188. aelems[0] = createLeafElement(map, null, p0, p1);
  189. Element[] relems = new Element[removed.size()];
  190. removed.copyInto(relems);
  191. ElementEdit ee = new ElementEdit(map, line0, relems, aelems);
  192. chng.addEdit(ee);
  193. map.replace(line0, relems.length, aelems);
  194. } else {
  195. //Check for the composed text element
  196. Element line = map.getElement(line0);
  197. if (!line.isLeaf()) {
  198. Element leaf = line.getElement(line.getElementIndex(offset));
  199. if (Utilities.isComposedTextElement(leaf)) {
  200. Element[] aelem = new Element[1];
  201. aelem[0] = createLeafElement(map, null,
  202. line.getStartOffset(), line.getEndOffset());
  203. Element[] relem = new Element[1];
  204. relem[0] = line;
  205. ElementEdit ee = new ElementEdit(map, line0, relem, aelem);
  206. chng.addEdit(ee);
  207. map.replace(line0, 1, aelem);
  208. }
  209. }
  210. }
  211. }
  212. //
  213. // Inserts the composed text of an input method. The line element
  214. // where the composed text is inserted into becomes an branch element
  215. // which contains leaf elements of the composed text and the text
  216. // backing store.
  217. //
  218. private void insertComposedTextUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  219. added.removeAllElements();
  220. BranchElement lineMap = (BranchElement) getDefaultRootElement();
  221. int offset = chng.getOffset();
  222. int length = chng.getLength();
  223. int index = lineMap.getElementIndex(offset);
  224. Element elem = lineMap.getElement(index);
  225. int elemStart = elem.getStartOffset();
  226. int elemEnd = elem.getEndOffset();
  227. BranchElement[] abelem = new BranchElement[1];
  228. abelem[0] = (BranchElement) createBranchElement(lineMap, null);
  229. Element[] relem = new Element[1];
  230. relem[0] = elem;
  231. if (elemStart != offset)
  232. added.addElement(createLeafElement(abelem[0], null, elemStart, offset));
  233. added.addElement(createLeafElement(abelem[0], attr, offset, offset+length));
  234. if (elemEnd != offset+length)
  235. added.addElement(createLeafElement(abelem[0], null, offset+length, elemEnd));
  236. Element[] alelem = new Element[added.size()];
  237. added.copyInto(alelem);
  238. ElementEdit ee = new ElementEdit(lineMap, index, relem, abelem);
  239. chng.addEdit(ee);
  240. abelem[0].replace(0, 0, alelem);
  241. lineMap.replace(index, 1, abelem);
  242. }
  243. private AbstractElement defaultRoot;
  244. private Vector added = new Vector(); // Vector<Element>
  245. private Vector removed = new Vector(); // Vector<Element>
  246. }