001: /*
002: * @(#)AutosizingTextArea.java 5/11/2005
003: *
004: * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import javax.swing.*;
009: import javax.swing.event.DocumentEvent;
010: import javax.swing.event.DocumentListener;
011: import javax.swing.text.Document;
012: import javax.swing.text.Element;
013: import java.awt.*;
014:
015: /**
016: * An extended version of <code>JTextArea</code> that automatically resizes itself vertically.
017: * This component works best when used in a layout that obeys preferred height of its components.
018: * For example, you can use a <code>BorderLayout</code> and place <code>AutoResizingTextArea</code>
019: * to the north or south side. Similarly, you can use a <code>JideBoxLayout</code> and use FLEXIBLE or FIX
020: * as the constraint.
021: */
022: public class AutoResizingTextArea extends JTextArea {
023:
024: /**
025: * Default maximum height of the text area in rows.
026: */
027: public static final int DEFAULT_MAX_ROWS = 20;
028:
029: /**
030: * Default minimum height of the text area in rows.
031: */
032: public static final int DEFAULT_MIN_ROWS = 1;
033:
034: private int _maxRows;
035: private int _minRows;
036:
037: /**
038: * Creates a textarea with the default minimum and maximum number of rows.
039: */
040: public AutoResizingTextArea() {
041: this (DEFAULT_MIN_ROWS, DEFAULT_MAX_ROWS);
042: }
043:
044: /**
045: * Creates a textarea with the specified minimum number of rows.
046: *
047: * @param minRows The minimum number of rows that this textarea can have.
048: */
049: public AutoResizingTextArea(int minRows) {
050: this (minRows, DEFAULT_MAX_ROWS);
051: }
052:
053: /**
054: * Creates a textarea with the specified minimum and maximum number of rows.
055: *
056: * @param minRows The minimum number of rows that this textarea can have.
057: * @param maxRows The maximum number of rows that this textarea can have.
058: */
059: public AutoResizingTextArea(int minRows, int maxRows) {
060: super ();
061: setMinRows(minRows);
062: setMaxRows(maxRows);
063: setRows(minRows);
064: setupDocument();
065: }
066:
067: /**
068: * Creates a textarea with the default minimum and maximum row count and the provided initial text.
069: * The textarea is sized to fit the provided text.
070: *
071: * @param text The initial text to display.
072: */
073: public AutoResizingTextArea(String text) {
074: this ();
075: setText(text);
076: }
077:
078: /**
079: * Create a new <code>AutoResizingTextArea</code> with a height bounded by the provided minimum and maximum row counts and with its
080: * width dictated by the provided column count.
081: *
082: * @param minRows The minimum number of rows that this textarea can have
083: * @param maxRows The maximum number of rows that this textarea can have.
084: * @param columns The number of columns that this textarea has.
085: */
086: public AutoResizingTextArea(int minRows, int maxRows, int columns) {
087: this (minRows, maxRows);
088: setMinRows(minRows);
089: setMaxRows(maxRows);
090: setColumns(columns);
091: }
092:
093: /**
094: * Create a new <code>AutoResizingTextArea</code> with a height bounded by the provided minimum and maximum row counts and with its
095: * width dictated by the provided column count. The textarea is sized to fit the provided text.
096: *
097: * @param text The initial text to display in the textarea.
098: * @param minRows The minimum number of rows that this textarea can have
099: * @param maxRows The maximum number of rows that this textarea can have.
100: * @param columns The number of columns that this textarea has.
101: * @throws IllegalArgumentException if the rows or columns
102: * arguments are negative.
103: */
104: public AutoResizingTextArea(String text, int minRows, int maxRows,
105: int columns) {
106: this (minRows, maxRows, columns);
107: setText(text);
108: }
109:
110: /**
111: * Create a new <code>AutoResizingTextArea</code> using a <code>Document</code>.
112: * The document will be set to the text area using {@link #setDocument(javax.swing.text.Document)}.
113: *
114: * @param doc the document.
115: */
116: public AutoResizingTextArea(Document doc) {
117: this ();
118: setDocument(doc);
119: }
120:
121: /**
122: * Constructs a new <code>AutoResizingTextArea</code> 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 minRows the minimum number of rows >= 0
129: * @param maxRows the maximum number of rows >= 0
130: * @param columns the number of columns >= 0
131: * @throws IllegalArgumentException if the rows or columns
132: * arguments are negative.
133: */
134:
135: public AutoResizingTextArea(Document doc, String text, int minRows,
136: int maxRows, int columns) {
137: super (doc, text, minRows, columns);
138: setMaxRows(maxRows);
139: setMinRows(minRows);
140: setupDocument();
141: }
142:
143: /**
144: * Sets the number of visible rows. The row value will be forced to the boundaries of the range
145: * [minRows ... maxRows] if it is outside that range.
146: *
147: * @param rows The number of rows to show
148: */
149: @Override
150: public void setRows(int rows) {
151: int oldRow = super .getRows();
152: int newRow = clipRowCount(rows);
153: super .setRows(newRow);
154:
155: numberOfRowsUpdated(oldRow, newRow);
156: }
157:
158: /**
159: * Called when the number of rows is updated. By default, it will get the parent scroll pane
160: * and call revalidate. Subclass can override it to customize the behavior when number of rows
161: * is updated.
162: *
163: * @param oldRow the previous row count.
164: * @param newRow the new row count.
165: */
166: protected void numberOfRowsUpdated(int oldRow, int newRow) {
167: // look for a parent scrollpane and revalidate its container
168: // otherwise revalidate the text area's container
169: JScrollPane scroll = getParentScrollPane();
170: if (scroll != null) {
171: Container parent = scroll.getParent();
172: if (parent != null && parent instanceof JComponent) {
173: JComponent component = (JComponent) parent;
174: component.revalidate();
175: }
176: }
177: }
178:
179: /**
180: * Gets the maximum number of rows that will be displayed. You can set it using {@link #setMaxRows(int)}
181: * or passed in using constructor such as {@link #AutoResizingTextArea(int,int)}.
182: *
183: * @return the maximum number of rows that will be displayed.
184: */
185: public int getMaxRows() {
186: return _maxRows;
187: }
188:
189: /**
190: * Sets the maximum number of rows that will be displayed.
191: *
192: * @param maxRows The maximum number of rows.
193: */
194: public void setMaxRows(int maxRows) {
195: _maxRows = maxRows;
196: setRows(clipRowCount(getRows()));
197: }
198:
199: /**
200: * Gets the minimum number of rows that will be displayed. You can set it using {@link #setMinRows(int)}
201: * or passed in using constructor such as {@link #AutoResizingTextArea(int,int)}.
202: *
203: * @return the minimum number of rows that will be displayed.
204: */
205: public int getMinRows() {
206: return _minRows;
207: }
208:
209: /**
210: * Sets the minimum number of rows that will be displayed
211: *
212: * @param minRows The minimum number of rows.
213: */
214: public void setMinRows(int minRows) {
215: _minRows = minRows;
216: setRows(clipRowCount(getRows()));
217: }
218:
219: private void setupDocument() {
220: getDocument().addDocumentListener(
221: new ResizingDocumentListener());
222: }
223:
224: /**
225: * Clips the given row count to fall within the range [m_minRows .. m_maxRows] (inclusive).
226: *
227: * @param rows The row count to clip.
228: * @return a row count clipped to the above range
229: */
230: private int clipRowCount(int rows) {
231: int r = Math.min(_maxRows, rows); // clip to upper bounds
232: r = Math.max(_minRows, r); // clip to lower bounds
233: return r;
234: }
235:
236: /**
237: * Listens to document change events and updates the row count appropriately.
238: */
239: private class ResizingDocumentListener implements DocumentListener {
240: public void insertUpdate(DocumentEvent e) {
241: updateSize(e);
242: }
243:
244: public void removeUpdate(DocumentEvent e) {
245: updateSize(e);
246: }
247:
248: public void changedUpdate(DocumentEvent e) {
249: updateSize(e);
250: }
251:
252: }
253:
254: /**
255: * Updates the row count depending on the number of lines in the underlying Document object.
256: *
257: * @param e the <code>DocumentEvent</code>.
258: */
259: private void updateSize(DocumentEvent e) {
260:
261: Element[] roots = e.getDocument().getRootElements();
262: Element root = roots[0];
263:
264: // ASSUMPTION: each child element of the first root element represents one line of text
265: // NB: This may not be valid for document types other than the default.
266: int rowCount = root.getElementCount();
267: setRows(clipRowCount(rowCount));
268:
269: }
270:
271: /**
272: * Gets the parent scroll pane if any.
273: *
274: * @return the parent scroll pane. If not found, null will be returned.
275: */
276: private JScrollPane getParentScrollPane() {
277: Component parent = getParent();
278: if (parent != null && parent instanceof JViewport) {
279: return (JScrollPane) parent.getParent();
280: }
281: return null;
282: }
283: }
|