1. /*
  2. * @(#)JTextArea.java 1.59 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;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import javax.swing.text.*;
  11. import javax.swing.plaf.*;
  12. import javax.accessibility.*;
  13. import java.io.ObjectOutputStream;
  14. import java.io.ObjectInputStream;
  15. import java.io.IOException;
  16. /**
  17. * A TextArea is a multi-line area that displays plain text.
  18. * It is intended to be a lightweight component that provides source
  19. * compatibility with the java.awt.TextArea class where it can
  20. * reasonably do so. This component has capabilities not found in
  21. * the java.awt.TextArea class. The superclass should be consulted for
  22. * additional capabilities. Alternative multi-line text classes with
  23. * more capabilitites are JTextPane and JEditorPane.
  24. * <p>
  25. * The java.awt.TextArea internally handles scrolling. JTextArea
  26. * is different in that it doesn't manage scrolling, but implements
  27. * the swing Scrollable interface. This allows it to be placed
  28. * inside a JScrollPane if scrolling behavior is desired, and used
  29. * directly if scrolling is not desired.
  30. * <p>
  31. * The java.awt.TextArea has the ability to do line wrapping.
  32. * This was controlled by the horizontal scrolling policy. Since
  33. * scrolling is not done by JTextArea directly, backward
  34. * compatibility must be provided another way. JTextArea has
  35. * a bound property for line wrapping that controls whether or
  36. * not it will wrap lines.
  37. * <p>
  38. * The java.awt.TextArea could be monitored for changes by adding
  39. * a TextListener for TextEvent's. In the JTextComponent based
  40. * components, changes are broadcasted from the model via a
  41. * DocumentEvent to DocumentListeners. The DocumentEvent gives
  42. * the location of the change and the kind of change if desired.
  43. * The code fragment might look something like:
  44. * <pre><code>
  45. * DocumentListener myListener = ??;
  46. * JTextArea myArea = ??;
  47. * myArea.getDocument().addDocumentListener(myListener);
  48. * </code></pre>
  49. * <p>
  50. * For the keyboard keys used by this component in the standard Look and
  51. * Feel (L&F) renditions, see the
  52. * <a href="doc-files/Key-Index.html#JTextArea">JTextArea</a> key assignments.
  53. * <p>
  54. * <strong>Warning:</strong>
  55. * Serialized objects of this class will not be compatible with
  56. * future Swing releases. The current serialization support is appropriate
  57. * for short term storage or RMI between applications running the same
  58. * version of Swing. A future release of Swing will provide support for
  59. * long term persistence.
  60. *
  61. * @beaninfo
  62. * attribute: isContainer false
  63. * @author Timothy Prinzing
  64. * @version 1.59 11/29/01
  65. * @see JTextPane
  66. * @see JEditorPane
  67. */
  68. public class JTextArea extends JTextComponent {
  69. /**
  70. * @see #getUIClassID
  71. * @see #readObject
  72. */
  73. private static final String uiClassID = "TextAreaUI";
  74. /**
  75. * Constructs a new TextArea. A default model is set, the initial string
  76. * is null, and rows/columns are set to 0.
  77. */
  78. public JTextArea() {
  79. this(null, null, 0, 0);
  80. }
  81. /**
  82. * Constructs a new TextArea with the specified text displayed.
  83. * A default model is created and rows/columns are set to 0.
  84. *
  85. * @param text the text to be displayed, or null
  86. */
  87. public JTextArea(String text) {
  88. this(null, text, 0, 0);
  89. }
  90. /**
  91. * Constructs a new empty TextArea with the specified number of
  92. * rows and columns. A default model is created, and the initial
  93. * string is null.
  94. *
  95. * @param rows the number of rows >= 0
  96. * @param columns the number of columns >= 0
  97. */
  98. public JTextArea(int rows, int columns) {
  99. this(null, null, rows, columns);
  100. }
  101. /**
  102. * Constructs a new TextArea with the specified text and number
  103. * of rows and columns. A default model is created.
  104. *
  105. * @param text the text to be displayed, or null
  106. * @param rows the number of rows >= 0
  107. * @param columns the number of columns >= 0
  108. */
  109. public JTextArea(String text, int rows, int columns) {
  110. this(null, text, rows, columns);
  111. }
  112. /**
  113. * Constructs a new JTextArea with the given document model, and defaults
  114. * for all of the other arguments (null, 0, 0).
  115. *
  116. * @param doc the model to use
  117. */
  118. public JTextArea(Document doc) {
  119. this(doc, null, 0, 0);
  120. }
  121. /**
  122. * Constructs a new JTextArea with the specified number of rows
  123. * and columns, and the given model. All of the constructors
  124. * feed through this constructor.
  125. *
  126. * @param doc the model to use, or create a default one if null
  127. * @param text the text to be displayed, null if none
  128. * @param rows the number of rows >= 0
  129. * @param columns the number of columns >= 0
  130. */
  131. public JTextArea(Document doc, String text, int rows, int columns) {
  132. super();
  133. this.rows = rows;
  134. this.columns = columns;
  135. if (doc == null) {
  136. doc = createDefaultModel();
  137. }
  138. setDocument(doc);
  139. if (text != null) {
  140. setText(text);
  141. }
  142. }
  143. /**
  144. * Returns the class ID for the UI.
  145. *
  146. * @return the ID ("TextAreaUI")
  147. * @see JComponent#getUIClassID
  148. * @see UIDefaults#getUI
  149. */
  150. public String getUIClassID() {
  151. return uiClassID;
  152. }
  153. /**
  154. * Creates the default implementation of the model
  155. * to be used at construction if one isn't explicitly
  156. * given. A new instance of PlainDocument is returned.
  157. *
  158. * @return the default document model
  159. */
  160. protected Document createDefaultModel() {
  161. return new PlainDocument();
  162. }
  163. /**
  164. * Sets the number of characters to expand tabs to.
  165. * This will be multiplied by the maximum advance for
  166. * variable width fonts. A PropertyChange event ("tabSize") is fired
  167. * when the tab size changes.
  168. *
  169. * @param size number of characters to expand to
  170. * @see #getTabSize
  171. * @beaninfo
  172. * preferred: true
  173. * bound: true
  174. * description: the number of characters to expand tabs to
  175. */
  176. public void setTabSize(int size) {
  177. Document doc = getDocument();
  178. if (doc != null) {
  179. int old = getTabSize();
  180. doc.putProperty(PlainDocument.tabSizeAttribute, new Integer(size));
  181. firePropertyChange("tabSize", old, size);
  182. }
  183. }
  184. /**
  185. * Gets the number of characters used to expand tabs. If the document is
  186. * null or doesn't have a tab setting, return a default of 8.
  187. *
  188. * @return the number of characters
  189. */
  190. public int getTabSize() {
  191. int size = 8;
  192. Document doc = getDocument();
  193. if (doc != null) {
  194. Integer i = (Integer) doc.getProperty(PlainDocument.tabSizeAttribute);
  195. if (i != null) {
  196. size = i.intValue();
  197. }
  198. }
  199. return size;
  200. }
  201. /**
  202. * Sets the line-wrapping policy of the text area. If set
  203. * to true the lines will be wrapped if they are too long
  204. * to fit within the allocated width. If set to false,
  205. * the lines will always be unwrapped. A PropertyChange event ("lineWrap")
  206. * is fired when the policy is changed.
  207. *
  208. * @param wrap indicates if lines should be wrapped.
  209. * @see #getLineWrap
  210. * @beaninfo
  211. * preferred: true
  212. * bound: true
  213. * description: should lines be wrapped
  214. */
  215. public void setLineWrap(boolean wrap) {
  216. boolean old = this.wrap;
  217. this.wrap = wrap;
  218. firePropertyChange("lineWrap", old, wrap);
  219. }
  220. /**
  221. * Gets the line-wrapping policy of the text area. If set
  222. * to true the lines will be wrapped if they are too long
  223. * to fit within the allocated width. If set to false,
  224. * the lines will always be unwrapped.
  225. *
  226. * @returns if lines will be wrapped.
  227. */
  228. public boolean getLineWrap() {
  229. return wrap;
  230. }
  231. /**
  232. * Set the style of wrapping used if the text area is wrapping
  233. * lines. If set to true the lines will be wrapped at word
  234. * boundries (ie whitespace) if they are too long
  235. * to fit within the allocated width. If set to false,
  236. * the lines will be wrapped at character boundries.
  237. *
  238. * @param word indicates if word boundries should be used
  239. * for line wrapping.
  240. * @see #getWrapStyleWord
  241. * @beaninfo
  242. * preferred: false
  243. * bound: true
  244. * description: should wrapping occur at word boundries
  245. */
  246. public void setWrapStyleWord(boolean word) {
  247. boolean old = this.word;
  248. this.word = word;
  249. firePropertyChange("wrapStyleWord", old, word);
  250. }
  251. /**
  252. * Get the style of wrapping used if the text area is wrapping
  253. * lines. If set to true the lines will be wrapped at word
  254. * boundries (ie whitespace) if they are too long
  255. * to fit within the allocated width. If set to false,
  256. * the lines will be wrapped at character boundries.
  257. *
  258. * @returns if the wrap style should be word boundries
  259. * instead of character boundries.
  260. * @see #setWrapStyleWord
  261. */
  262. public boolean getWrapStyleWord() {
  263. return word;
  264. }
  265. /**
  266. * Translates an offset into the components text to a
  267. * line number.
  268. *
  269. * @param offset the offset >= 0
  270. * @return the line number >= 0
  271. * @exception BadLocationException thrown if the offset is
  272. * less than zero or greater than the document length.
  273. */
  274. public int getLineOfOffset(int offset) throws BadLocationException {
  275. Document doc = getDocument();
  276. if (offset < 0) {
  277. throw new BadLocationException("Can't translate offset to line", -1);
  278. } else if (offset > doc.getLength()) {
  279. throw new BadLocationException("Can't translate offset to line", doc.getLength()+1);
  280. } else {
  281. Element map = getDocument().getDefaultRootElement();
  282. return map.getElementIndex(offset);
  283. }
  284. }
  285. /**
  286. * Determines the number of lines contained in the area.
  287. *
  288. * @return the number of lines >= 0
  289. */
  290. public int getLineCount() {
  291. // There is an implicit break being modeled at the end of the
  292. // document to deal with boundry conditions at the end. This
  293. // is not desired in the line count, so we detect it and remove
  294. // its effect if throwing off the count.
  295. Element map = getDocument().getDefaultRootElement();
  296. int n = map.getElementCount();
  297. Element lastLine = map.getElement(n-1);
  298. if ((lastLine.getEndOffset() - lastLine.getStartOffset()) > 1) {
  299. return n;
  300. }
  301. return n - 1;
  302. }
  303. /**
  304. * Determines the offset of the start of the given line.
  305. *
  306. * @param line the line number to translate >= 0
  307. * @return the offset >= 0
  308. * @exception BadLocationException thrown if the line is
  309. * less than zero or greater or equal to the number of
  310. * lines contained in the document (as reported by
  311. * getLineCount).
  312. */
  313. public int getLineStartOffset(int line) throws BadLocationException {
  314. Element map = getDocument().getDefaultRootElement();
  315. if (line < 0) {
  316. throw new BadLocationException("Negative line", -1);
  317. } else if (line >= map.getElementCount()) {
  318. throw new BadLocationException("No such line", getDocument().getLength()+1);
  319. } else {
  320. Element lineElem = map.getElement(line);
  321. return lineElem.getStartOffset();
  322. }
  323. }
  324. /**
  325. * Determines the offset of the end of the given line.
  326. *
  327. * @param line the line >= 0
  328. * @return the offset >= 0
  329. * @exception BadLocationException Thrown if the line is
  330. * less than zero or greater or equal to the number of
  331. * lines contained in the document (as reported by
  332. * getLineCount).
  333. */
  334. public int getLineEndOffset(int line) throws BadLocationException {
  335. Element map = getDocument().getDefaultRootElement();
  336. if (line < 0) {
  337. throw new BadLocationException("Negative line", -1);
  338. } else if (line >= map.getElementCount()) {
  339. throw new BadLocationException("No such line", getDocument().getLength()+1);
  340. } else {
  341. Element lineElem = map.getElement(line);
  342. return lineElem.getEndOffset();
  343. }
  344. }
  345. // --- java.awt.TextArea methods ---------------------------------
  346. /**
  347. * Inserts the specified text at the specified position. Does nothing
  348. * if the model is null or if the text is null or empty.
  349. * <p>
  350. * This method is thread safe, although most Swing methods
  351. * are not. Please see
  352. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  353. * and Swing</A> for more information.
  354. *
  355. * @param str the text to insert
  356. * @param pos the position at which to insert >= 0
  357. * @exception IllegalArgumentException if pos is an
  358. * invalid position in the model
  359. * @see TextComponent#setText
  360. * @see #replaceRange
  361. */
  362. public void insert(String str, int pos) {
  363. Document doc = getDocument();
  364. if (doc != null) {
  365. try {
  366. doc.insertString(pos, str, null);
  367. } catch (BadLocationException e) {
  368. throw new IllegalArgumentException(e.getMessage());
  369. }
  370. }
  371. }
  372. /**
  373. * Appends the given text to the end of the document. Does nothing if
  374. * the model is null or the string is null or empty.
  375. * <p>
  376. * This method is thread safe, although most Swing methods
  377. * are not. Please see
  378. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  379. * and Swing</A> for more information.
  380. *
  381. * @param str the text to insert
  382. * @see #insert
  383. */
  384. public void append(String str) {
  385. Document doc = getDocument();
  386. if (doc != null) {
  387. try {
  388. doc.insertString(doc.getLength(), str, null);
  389. } catch (BadLocationException e) {
  390. }
  391. }
  392. }
  393. /**
  394. * Replaces text from the indicated start to end position with the
  395. * new text specified. Does nothing if the model is null. Simply
  396. * does a delete if the new string is null or empty.
  397. * <p>
  398. * This method is thread safe, although most Swing methods
  399. * are not. Please see
  400. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  401. * and Swing</A> for more information.
  402. *
  403. * @param str the text to use as the replacement
  404. * @param start the start position >= 0
  405. * @param end the end position >= start
  406. * @exception IllegalArgumentException if part of the range is an
  407. * invalid position in the model
  408. * @see #insert
  409. * @see #replaceRange
  410. */
  411. public void replaceRange(String str, int start, int end) {
  412. if (end < start) {
  413. throw new IllegalArgumentException("end before start");
  414. }
  415. Document doc = getDocument();
  416. if (doc != null) {
  417. try {
  418. doc.remove(start, end - start);
  419. doc.insertString(start, str, null);
  420. } catch (BadLocationException e) {
  421. throw new IllegalArgumentException(e.getMessage());
  422. }
  423. }
  424. }
  425. /**
  426. * Turns off tab traversal once focus gained.
  427. *
  428. * @return true, to indicate that the focus is being managed
  429. */
  430. public boolean isManagingFocus() {
  431. return true;
  432. }
  433. /**
  434. * Make sure that TAB and Shift-TAB events get consumed, so that
  435. * awt doesn't attempt focus traversal.
  436. *
  437. */
  438. protected void processComponentKeyEvent(KeyEvent e) {
  439. super.processComponentKeyEvent(e);
  440. // tab consumption
  441. // We are actually consuming any TABs modified in any way, because
  442. // we don't want awt to get anything it can use for focus traversal.
  443. if (isManagingFocus()) {
  444. if ((e.getKeyCode() == KeyEvent.VK_TAB || e.getKeyChar() == '\t')) {
  445. e.consume();
  446. }
  447. }
  448. }
  449. /**
  450. * Returns the number of rows in the TextArea.
  451. *
  452. * @return the number of rows >= 0
  453. */
  454. public int getRows() {
  455. return rows;
  456. }
  457. /**
  458. * Sets the number of rows for this TextArea. Calls invalidate() after
  459. * setting the new value.
  460. *
  461. * @param rows the number of rows >= 0
  462. * @exception IllegalArgumentException if rows is less than 0
  463. * @see #getRows
  464. * @beaninfo
  465. * description: the number of rows preferred for display
  466. */
  467. public void setRows(int rows) {
  468. int oldVal = this.rows;
  469. if (rows < 0) {
  470. throw new IllegalArgumentException("rows less than zero.");
  471. }
  472. if (rows != oldVal) {
  473. this.rows = rows;
  474. invalidate();
  475. }
  476. }
  477. /**
  478. * Defines the meaning of the height of a row. This defaults to
  479. * the height of the font.
  480. *
  481. * @return the height >= 1
  482. */
  483. protected int getRowHeight() {
  484. if (rowHeight == 0) {
  485. FontMetrics metrics = getFontMetrics(getFont());
  486. rowHeight = metrics.getHeight();
  487. }
  488. return rowHeight;
  489. }
  490. /**
  491. * Returns the number of columns in the TextArea.
  492. *
  493. * @return number of columns >= 0
  494. */
  495. public int getColumns() {
  496. return columns;
  497. }
  498. /**
  499. * Sets the number of columns for this TextArea. Does an invalidate()
  500. * after setting the new value.
  501. *
  502. * @param columns the number of columns >= 0
  503. * @exception IllegalArgumentException if columns is less than 0
  504. * @see #getColumns
  505. * @beaninfo
  506. * description: the number of columns preferred for display
  507. */
  508. public void setColumns(int columns) {
  509. int oldVal = this.columns;
  510. if (columns < 0) {
  511. throw new IllegalArgumentException("columns less than zero.");
  512. }
  513. if (columns != oldVal) {
  514. this.columns = columns;
  515. invalidate();
  516. }
  517. }
  518. /**
  519. * Gets column width.
  520. * The meaning of what a column is can be considered a fairly weak
  521. * notion for some fonts. This method is used to define the width
  522. * of a column. By default this is defined to be the width of the
  523. * character <em>m</em> for the font used. This method can be
  524. * redefined to be some alternative amount.
  525. *
  526. * @return the column width >= 1
  527. */
  528. protected int getColumnWidth() {
  529. if (columnWidth == 0) {
  530. FontMetrics metrics = getFontMetrics(getFont());
  531. columnWidth = metrics.charWidth('m');
  532. }
  533. return columnWidth;
  534. }
  535. // --- Component methods -----------------------------------------
  536. /**
  537. * Returns the preferred size of the TextArea. This is the
  538. * maximum of the size needed to display the text and the
  539. * size requested for the viewport.
  540. *
  541. * @return the size
  542. */
  543. public Dimension getPreferredSize() {
  544. Dimension d = super.getPreferredSize();
  545. d = (d == null) ? new Dimension(400,400) : d;
  546. if (columns != 0) {
  547. d.width = Math.max(d.width, columns * getColumnWidth());
  548. }
  549. if (rows != 0) {
  550. d.height = Math.max(d.height, rows * getRowHeight());
  551. }
  552. return d;
  553. }
  554. /**
  555. * Sets the current font. This removes cached row height and column
  556. * width so the new font will be reflected, and calls revalidate().
  557. *
  558. * @param f the font to use as the current font
  559. */
  560. public void setFont(Font f) {
  561. super.setFont(f);
  562. rowHeight = 0;
  563. columnWidth = 0;
  564. }
  565. /**
  566. * Returns a string representation of this JTextArea. This method
  567. * is intended to be used only for debugging purposes, and the
  568. * content and format of the returned string may vary between
  569. * implementations. The returned string may be empty but may not
  570. * be <code>null</code>.
  571. *
  572. * @return a string representation of this JTextArea.
  573. */
  574. protected String paramString() {
  575. String wrapString = (wrap ?
  576. "true" : "false");
  577. String wordString = (word ?
  578. "true" : "false");
  579. return super.paramString() +
  580. ",colums=" + columns +
  581. ",columWidth=" + columnWidth +
  582. ",rows=" + rows +
  583. ",rowHeight=" + rowHeight +
  584. ",word=" + wordString +
  585. ",wrap=" + wrapString;
  586. }
  587. // --- Scrollable methods ----------------------------------------
  588. /**
  589. * Returns true if a viewport should always force the width of this
  590. * Scrollable to match the width of the viewport. This is implemented
  591. * to return true if the line wrapping policy is true, and false
  592. * if lines are not being wrapped.
  593. *
  594. * @return true if a viewport should force the Scrollables width
  595. * to match its own.
  596. */
  597. public boolean getScrollableTracksViewportWidth() {
  598. return (wrap) ? true : super.getScrollableTracksViewportWidth();
  599. }
  600. /**
  601. * Returns the preferred size of the viewport if this component
  602. * is embedded in a JScrollPane. This uses the desired column
  603. * and row settings if they have been set, otherwise the superclass
  604. * behavior is used.
  605. *
  606. * @return The preferredSize of a JViewport whose view is this Scrollable.
  607. * @see JViewport#getPreferredSize
  608. */
  609. public Dimension getPreferredScrollableViewportSize() {
  610. Dimension size = super.getPreferredScrollableViewportSize();
  611. size = (size == null) ? new Dimension(400,400) : size;
  612. size.width = (columns == 0) ? size.width : columns * getColumnWidth();
  613. size.height = (rows == 0) ? size.height : rows * getRowHeight();
  614. return size;
  615. }
  616. /**
  617. * Components that display logical rows or columns should compute
  618. * the scroll increment that will completely expose one new row
  619. * or column, depending on the value of orientation. This is implemented
  620. * to use the vaules returned by the <code>getRowHeight</code> and
  621. * <code>getColumnWidth</code> methods.
  622. * <p>
  623. * Scrolling containers, like JScrollPane, will use this method
  624. * each time the user requests a unit scroll.
  625. *
  626. * @param visibleRect the view area visible within the viewport
  627. * @param orientation Either SwingConstants.VERTICAL or
  628. * SwingConstants.HORIZONTAL.
  629. * @param direction Less than zero to scroll up/left,
  630. * greater than zero for down/right.
  631. * @return The "unit" increment for scrolling in the specified direction
  632. * @exception IllegalArgumentException for an invalid orientation
  633. * @see JScrollBar#setUnitIncrement
  634. * @see #getRowHeight
  635. * @see #getColumnWidth
  636. */
  637. public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
  638. switch (orientation) {
  639. case SwingConstants.VERTICAL:
  640. return getRowHeight();
  641. case SwingConstants.HORIZONTAL:
  642. return getColumnWidth();
  643. default:
  644. throw new IllegalArgumentException("Invalid orientation: " + orientation);
  645. }
  646. }
  647. /**
  648. * See readObject() and writeObject() in JComponent for more
  649. * information about serialization in Swing.
  650. */
  651. private void writeObject(ObjectOutputStream s) throws IOException {
  652. s.defaultWriteObject();
  653. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  654. ui.installUI(this);
  655. }
  656. }
  657. /////////////////
  658. // Accessibility support
  659. ////////////////
  660. /**
  661. * Get the AccessibleContext associated with this JTextArea.
  662. * Creates a new context if necessary.
  663. *
  664. * @return the AccessibleContext of this JTextArea
  665. */
  666. public AccessibleContext getAccessibleContext() {
  667. if (accessibleContext == null) {
  668. accessibleContext = new AccessibleJTextArea();
  669. }
  670. return accessibleContext;
  671. }
  672. /**
  673. * The class used to obtain the accessible role for this object.
  674. * <p>
  675. * <strong>Warning:</strong>
  676. * Serialized objects of this class will not be compatible with
  677. * future Swing releases. The current serialization support is appropriate
  678. * for short term storage or RMI between applications running the same
  679. * version of Swing. A future release of Swing will provide support for
  680. * long term persistence.
  681. */
  682. protected class AccessibleJTextArea extends AccessibleJTextComponent {
  683. /**
  684. * Gets the state set of this object.
  685. *
  686. * @return an instance of AccessibleStateSet describing the states
  687. * of the object
  688. * @see AccessibleStateSet
  689. */
  690. public AccessibleStateSet getAccessibleStateSet() {
  691. AccessibleStateSet states = super.getAccessibleStateSet();
  692. states.add(AccessibleState.MULTI_LINE);
  693. return states;
  694. }
  695. }
  696. // --- variables -------------------------------------------------
  697. private int rows;
  698. private int columns;
  699. private int columnWidth;
  700. private int rowHeight;
  701. private boolean wrap;
  702. private boolean word;
  703. }