1 /* JTextArea.java --
2    Copyright (C) 2004, 2005  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing;
40 
41 import java.awt.Dimension;
42 import java.awt.FontMetrics;
43 import java.awt.Rectangle;
44 
45 import javax.accessibility.AccessibleContext;
46 import javax.accessibility.AccessibleStateSet;
47 import javax.swing.text.BadLocationException;
48 import javax.swing.text.Document;
49 import javax.swing.text.Element;
50 import javax.swing.text.JTextComponent;
51 import javax.swing.text.PlainDocument;
52 import javax.swing.text.View;
53 
54 /**
55  * The <code>JTextArea</code> component provides a multi-line area for displaying
56  * and editing plain text.  The component is designed to act as a lightweight
57  * replacement for the heavyweight <code>java.awt.TextArea</code> component,
58  * which provides similar functionality using native widgets.
59  * <p>
60  *
61  * This component has additional functionality to the AWT class.  It follows
62  * the same design pattern as seen in other text components, such as
63  * <code>JTextField</code>, <code>JTextPane</code> and <code>JEditorPane</code>,
64  * and embodied in <code>JTextComponent</code>.  These classes separate the text
65  * (the model) from its appearance within the onscreen component (the view).  The
66  * text is held within a <code>javax.swing.text.Document</code> object, which can
67  * also maintain relevant style information where necessary.  As a result, it is the
68  * document that should be monitored for textual changes, via
69  * <code>DocumentEvent</code>s delivered to registered
70  * <code>DocumentListener</code>s, rather than this component.
71  * <p>
72  *
73  * Unlike <code>java.awt.TextArea</code>, <code>JTextArea</code> does not
74  * handle scrolling.  Instead, this functionality is delegated to a
75  * <code>JScrollPane</code>, which can contain the text area and handle
76  * scrolling when required.  Likewise, the word wrapping functionality
77  * of the AWT component is converted to a property of this component
78  * and the <code>rows</code> and <code>columns</code> properties
79  * are used in calculating the preferred size of the scroll pane's
80  * view port.
81  *
82  * @author Michael Koch  (konqueror@gmx.de)
83  * @author Andrew John Hughes  (gnu_andrew@member.fsf.org)
84  * @see java.awt.TextArea
85  * @see javax.swing.text.JTextComponent
86  * @see javax.swing.JTextField
87  * @see javax.swing.JTextPane
88  * @see javax.swing.JEditorPane
89  * @see javax.swing.text.Document
90  * @see javax.swing.event.DocumentEvent
91  * @see javax.swing.event.DocumentListener
92  */
93 
94 public class JTextArea extends JTextComponent
95 {
96   /**
97    * Provides accessibility support for <code>JTextArea</code>.
98    *
99    * @author Roman Kennke (kennke@aicas.com)
100    */
101   protected class AccessibleJTextArea extends AccessibleJTextComponent
102   {
103 
104     /**
105      * Creates a new <code>AccessibleJTextArea</code> object.
106      */
AccessibleJTextArea()107     protected AccessibleJTextArea()
108     {
109       super();
110     }
111 
112     /**
113      * Returns the accessible state of this <code>AccessibleJTextArea</code>.
114      *
115      * @return  the accessible state of this <code>AccessibleJTextArea</code>
116      */
getAccessibleStateSet()117     public AccessibleStateSet getAccessibleStateSet()
118     {
119       AccessibleStateSet state = super.getAccessibleStateSet();
120       // TODO: Figure out what state must be added here to the super's state.
121       return state;
122     }
123   }
124 
125   /**
126    * Compatible with Sun's JDK
127    */
128   private static final long serialVersionUID = -6141680179310439825L;
129 
130   /**
131    * The number of rows used by the component.
132    */
133   private int rows;
134 
135   /**
136    * The number of columns used by the component.
137    */
138   private int columns;
139 
140   /**
141    * Whether line wrapping is enabled or not.
142    */
143   private boolean lineWrap;
144 
145   /**
146    * The number of characters equal to a tab within the text.
147    */
148   private int tabSize = 8;
149 
150   private boolean wrapStyleWord;
151 
152   /**
153    * Creates a new <code>JTextArea</code> object.
154    */
JTextArea()155   public JTextArea()
156   {
157     this(null, null, 0, 0);
158   }
159 
160   /**
161    * Creates a new <code>JTextArea</code> object.
162    *
163    * @param text the initial text
164    */
JTextArea(String text)165   public JTextArea(String text)
166   {
167     this(null, text, 0, 0);
168   }
169 
170   /**
171    * Creates a new <code>JTextArea</code> object.
172    *
173    * @param rows the number of rows
174    * @param columns the number of cols
175    *
176    * @exception IllegalArgumentException if rows or columns are negative
177    */
JTextArea(int rows, int columns)178   public JTextArea(int rows, int columns)
179   {
180     this(null, null, rows, columns);
181   }
182 
183   /**
184    * Creates a new <code>JTextArea</code> object.
185    *
186    * @param text the initial text
187    * @param rows the number of rows
188    * @param columns the number of cols
189    *
190    * @exception IllegalArgumentException if rows or columns are negative
191    */
JTextArea(String text, int rows, int columns)192   public JTextArea(String text, int rows, int columns)
193   {
194     this(null, text, rows, columns);
195   }
196 
197   /**
198    * Creates a new <code>JTextArea</code> object.
199    *
200    * @param doc the document model to use
201    */
JTextArea(Document doc)202   public JTextArea(Document doc)
203   {
204     this(doc, null, 0, 0);
205   }
206 
207   /**
208    * Creates a new <code>JTextArea</code> object.
209    *
210    * @param doc the document model to use
211    * @param text the initial text
212    * @param rows the number of rows
213    * @param columns the number of cols
214    *
215    * @exception IllegalArgumentException if rows or columns are negative
216    */
JTextArea(Document doc, String text, int rows, int columns)217   public JTextArea(Document doc, String text, int rows, int columns)
218   {
219     setDocument(doc == null ? createDefaultModel() : doc);
220     // Only explicitly setText() when there is actual text since
221     // setText() might be overridden and not expected to be called
222     // from the constructor (as in JEdit).
223     if (text != null)
224       setText(text);
225     setRows(rows);
226     setColumns(columns);
227   }
228 
229   /**
230    * Appends the supplied text to the current contents
231    * of the document model.
232    *
233    * @param toAppend the text to append
234    */
append(String toAppend)235   public void append(String toAppend)
236   {
237       try
238           {
239               getDocument().insertString(getText().length(), toAppend, null);
240           }
241       catch (BadLocationException exception)
242           {
243               /* This shouldn't happen in theory -- but, if it does...  */
244               throw new RuntimeException("Unexpected exception occurred.", exception);
245           }
246       if (toAppend != null && toAppend.length() > 0)
247         revalidate();
248   }
249 
250   /**
251    * Creates the default document model.
252    *
253    * @return a new default model
254    */
createDefaultModel()255   protected Document createDefaultModel()
256   {
257     return new PlainDocument();
258   }
259 
260   /**
261    * Returns true if the width of this component should be forced
262    * to match the width of a surrounding view port.  When line wrapping
263    * is turned on, this method returns true.
264    *
265    * @return true if lines are wrapped.
266    */
getScrollableTracksViewportWidth()267   public boolean getScrollableTracksViewportWidth()
268   {
269     return lineWrap ? true : super.getScrollableTracksViewportWidth();
270   }
271 
272   /**
273    * Returns the increment that is needed to expose exactly one new line
274    * of text. This is implemented here to return the values of
275    * {@link #getRowHeight} and {@link #getColumnWidth}, depending on
276    * the value of the argument <code>direction</code>.
277    *
278    * @param visibleRect the view area that is visible in the viewport
279    * @param orientation either {@link SwingConstants#VERTICAL} or
280    *     {@link SwingConstants#HORIZONTAL}
281    * @param direction less than zero for up/left scrolling, greater
282    *     than zero for down/right scrolling
283    *
284    * @return the increment that is needed to expose exactly one new row
285    *     or column of text
286    *
287    * @throws IllegalArgumentException if <code>orientation</code> is invalid
288    */
getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)289   public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
290                                         int direction)
291   {
292     if (orientation == SwingConstants.VERTICAL)
293       return getRowHeight();
294     else if (orientation == SwingConstants.HORIZONTAL)
295       return getColumnWidth();
296     else
297       throw new IllegalArgumentException("orientation must be either "
298                                      + "javax.swing.SwingConstants.VERTICAL "
299                                      + "or "
300                                      + "javax.swing.SwingConstants.HORIZONTAL"
301                                      );
302   }
303 
304   /**
305    * Returns the preferred size of that text component in the case
306    * it is embedded within a JScrollPane. This uses the column and
307    * row settings if they are explicitly set, or fall back to
308    * the superclass's behaviour.
309    *
310    * @return the preferred size of that text component in the case
311    *     it is embedded within a JScrollPane
312    */
getPreferredScrollableViewportSize()313   public Dimension getPreferredScrollableViewportSize()
314   {
315     if ((rows > 0) && (columns > 0))
316       return new Dimension(columns * getColumnWidth(), rows * getRowHeight());
317     else
318       return super.getPreferredScrollableViewportSize();
319   }
320 
321   /**
322    * Returns the UI class ID string.
323    *
324    * @return the string "TextAreaUI"
325    */
getUIClassID()326   public String getUIClassID()
327   {
328     return "TextAreaUI";
329   }
330 
331   /**
332    * Returns the current number of columns.
333    *
334    * @return number of columns
335    */
getColumns()336   public int getColumns()
337   {
338     return columns;
339   }
340 
341   /**
342    * Sets the number of rows.
343    *
344    * @param columns number of columns
345    *
346    * @exception IllegalArgumentException if columns is negative
347    */
setColumns(int columns)348   public void setColumns(int columns)
349   {
350     if (columns < 0)
351       throw new IllegalArgumentException();
352 
353     if (columns != this.columns)
354       {
355         this.columns = columns;
356         revalidate();
357       }
358   }
359 
360   /**
361    * Returns the current number of rows.
362    *
363    * @return number of rows
364    */
getRows()365   public int getRows()
366   {
367     return rows;
368   }
369 
370   /**
371    * Sets the number of rows.
372    *
373    * @param rows number of rows
374    *
375    * @exception IllegalArgumentException if rows is negative
376    */
setRows(int rows)377   public void setRows(int rows)
378   {
379     if (rows < 0)
380       throw new IllegalArgumentException();
381 
382     if (rows != this.rows)
383       {
384         this.rows = rows;
385         revalidate();
386       }
387   }
388 
389   /**
390    * Checks whether line wrapping is enabled.
391    *
392    * @return <code>true</code> if line wrapping is enabled,
393    * <code>false</code> otherwise
394    */
getLineWrap()395   public boolean getLineWrap()
396   {
397     return lineWrap;
398   }
399 
400   /**
401    * Enables/disables line wrapping.
402    *
403    * @param flag <code>true</code> to enable line wrapping,
404    * <code>false</code> otherwise
405    */
setLineWrap(boolean flag)406   public void setLineWrap(boolean flag)
407   {
408     if (lineWrap == flag)
409       return;
410 
411     boolean oldValue = lineWrap;
412     lineWrap = flag;
413     firePropertyChange("lineWrap", oldValue, lineWrap);
414   }
415 
416   /**
417    * Checks whether word style wrapping is enabled.
418    *
419    * @return <code>true</code> if word style wrapping is enabled,
420    * <code>false</code> otherwise
421    */
getWrapStyleWord()422   public boolean getWrapStyleWord()
423   {
424     return wrapStyleWord;
425   }
426 
427   /**
428    * Enables/Disables word style wrapping.
429    *
430    * @param flag <code>true</code> to enable word style wrapping,
431    * <code>false</code> otherwise
432    */
setWrapStyleWord(boolean flag)433   public void setWrapStyleWord(boolean flag)
434   {
435     if (wrapStyleWord == flag)
436       return;
437 
438     boolean oldValue = wrapStyleWord;
439     wrapStyleWord = flag;
440     firePropertyChange("wrapStyleWord", oldValue, wrapStyleWord);
441   }
442 
443   /**
444    * Returns the number of characters used for a tab.
445    * This defaults to 8.
446    *
447    * @return the current number of spaces used for a tab.
448    */
getTabSize()449   public int getTabSize()
450   {
451     return tabSize;
452   }
453 
454   /**
455    * Sets the number of characters used for a tab to the
456    * supplied value.  If a change to the tab size property
457    * occurs (i.e. newSize != tabSize), a property change event
458    * is fired.
459    *
460    * @param newSize The new number of characters to use for a tab.
461    */
setTabSize(int newSize)462   public void setTabSize(int newSize)
463   {
464     if (tabSize == newSize)
465       return;
466 
467     int oldValue = tabSize;
468     tabSize = newSize;
469     firePropertyChange("tabSize", oldValue, tabSize);
470   }
471 
getColumnWidth()472   protected int getColumnWidth()
473   {
474     FontMetrics metrics = getToolkit().getFontMetrics(getFont());
475     return metrics.charWidth('m');
476   }
477 
getLineCount()478   public int getLineCount()
479   {
480     return getDocument().getDefaultRootElement().getElementCount();
481   }
482 
getLineStartOffset(int line)483   public int getLineStartOffset(int line)
484      throws BadLocationException
485   {
486     int lineCount = getLineCount();
487 
488     if (line < 0 || line > lineCount)
489       throw new BadLocationException("Non-existing line number", line);
490 
491     Element lineElem = getDocument().getDefaultRootElement().getElement(line);
492     return lineElem.getStartOffset();
493   }
494 
getLineEndOffset(int line)495   public int getLineEndOffset(int line)
496      throws BadLocationException
497   {
498     int lineCount = getLineCount();
499 
500     if (line < 0 || line > lineCount)
501       throw new BadLocationException("Non-existing line number", line);
502 
503     Element lineElem = getDocument().getDefaultRootElement().getElement(line);
504     return lineElem.getEndOffset();
505   }
506 
getLineOfOffset(int offset)507   public int getLineOfOffset(int offset)
508     throws BadLocationException
509   {
510     Document doc = getDocument();
511 
512     if (offset < doc.getStartPosition().getOffset()
513         || offset >= doc.getEndPosition().getOffset())
514       throw new BadLocationException("offset outside of document", offset);
515 
516     return doc.getDefaultRootElement().getElementIndex(offset);
517   }
518 
getRowHeight()519   protected int getRowHeight()
520   {
521     FontMetrics metrics = getToolkit().getFontMetrics(getFont());
522     return metrics.getHeight();
523   }
524 
525   /**
526    * Inserts the supplied text at the specified position.  Nothing
527    * happens in the case that the model or the supplied string is null
528    * or of zero length.
529    *
530    * @param string The string of text to insert.
531    * @param position The position at which to insert the supplied text.
532    * @throws IllegalArgumentException if the position is &lt; 0 or greater
533    * than the length of the current text.
534    */
insert(String string, int position)535   public void insert(String string, int position)
536   {
537     // Retrieve the document model.
538     Document doc = getDocument();
539 
540     // Check the model and string for validity.
541     if (doc == null
542         || string == null
543         || string.length() == 0)
544       return;
545 
546     // Insert the text into the model.
547     try
548       {
549         doc.insertString(position, string, null);
550       }
551     catch (BadLocationException e)
552       {
553         throw new IllegalArgumentException("The supplied position, "
554                                            + position + ", was invalid.");
555       }
556   }
557 
replaceRange(String text, int start, int end)558   public void replaceRange(String text, int start, int end)
559   {
560     Document doc = getDocument();
561 
562     if (start > end
563         || start < doc.getStartPosition().getOffset()
564         || end >= doc.getEndPosition().getOffset())
565       throw new IllegalArgumentException();
566 
567     try
568       {
569         doc.remove(start, end - start);
570         doc.insertString(start, text, null);
571       }
572     catch (BadLocationException e)
573       {
574         // This cannot happen as we check offset above.
575       }
576   }
577 
578   /**
579    * Returns the preferred size for the JTextArea. This is the maximum of
580    * the size that is needed to display the content and the requested size
581    * as per {@link #getColumns} and {@link #getRows}.
582    *
583    * @return the preferred size of the JTextArea
584    */
getPreferredSize()585   public Dimension getPreferredSize()
586   {
587     int reqWidth = getColumns() * getColumnWidth();
588     int reqHeight = getRows() * getRowHeight();
589     View view = getUI().getRootView(this);
590     int neededWidth = (int) view.getPreferredSpan(View.HORIZONTAL);
591     int neededHeight = (int) view.getPreferredSpan(View.VERTICAL);
592     return new Dimension(Math.max(reqWidth, neededWidth),
593                           Math.max(reqHeight, neededHeight));
594   }
595 
596   /**
597    * Returns the accessible context associated with the <code>JTextArea</code>.
598    *
599    * @return the accessible context associated with the <code>JTextArea</code>
600    */
getAccessibleContext()601   public AccessibleContext getAccessibleContext()
602   {
603     if (accessibleContext == null)
604       accessibleContext = new AccessibleJTextArea();
605     return accessibleContext;
606   }
607 }
608