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