| /* JTextArea.java -- |
| Copyright (C) 2004, 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing; |
| |
| import java.awt.Dimension; |
| import java.awt.FontMetrics; |
| import java.awt.Rectangle; |
| |
| import javax.accessibility.AccessibleContext; |
| import javax.accessibility.AccessibleStateSet; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.Element; |
| import javax.swing.text.JTextComponent; |
| import javax.swing.text.PlainDocument; |
| import javax.swing.text.View; |
| |
| /** |
| * The <code>JTextArea</code> component provides a multi-line area for displaying |
| * and editing plain text. The component is designed to act as a lightweight |
| * replacement for the heavyweight <code>java.awt.TextArea</code> component, |
| * which provides similar functionality using native widgets. |
| * <p> |
| * |
| * This component has additional functionality to the AWT class. It follows |
| * the same design pattern as seen in other text components, such as |
| * <code>JTextField</code>, <code>JTextPane</code> and <code>JEditorPane</code>, |
| * and embodied in <code>JTextComponent</code>. These classes separate the text |
| * (the model) from its appearance within the onscreen component (the view). The |
| * text is held within a <code>javax.swing.text.Document</code> object, which can |
| * also maintain relevant style information where necessary. As a result, it is the |
| * document that should be monitored for textual changes, via |
| * <code>DocumentEvent</code>s delivered to registered |
| * <code>DocumentListener</code>s, rather than this component. |
| * <p> |
| * |
| * Unlike <code>java.awt.TextArea</code>, <code>JTextArea</code> does not |
| * handle scrolling. Instead, this functionality is delegated to a |
| * <code>JScrollPane</code>, which can contain the text area and handle |
| * scrolling when required. Likewise, the word wrapping functionality |
| * of the AWT component is converted to a property of this component |
| * and the <code>rows</code> and <code>columns</code> properties |
| * are used in calculating the preferred size of the scroll pane's |
| * view port. |
| * |
| * @author Michael Koch (konqueror@gmx.de) |
| * @author Andrew John Hughes (gnu_andrew@member.fsf.org) |
| * @see java.awt.TextArea |
| * @see javax.swing.text.JTextComponent |
| * @see javax.swing.JTextField |
| * @see javax.swing.JTextPane |
| * @see javax.swing.JEditorPane |
| * @see javax.swing.text.Document |
| * @see javax.swing.event.DocumentEvent |
| * @see javax.swing.event.DocumentListener |
| */ |
| |
| public class JTextArea extends JTextComponent |
| { |
| /** |
| * Provides accessibility support for <code>JTextArea</code>. |
| * |
| * @author Roman Kennke (kennke@aicas.com) |
| */ |
| protected class AccessibleJTextArea extends AccessibleJTextComponent |
| { |
| |
| /** |
| * Creates a new <code>AccessibleJTextArea</code> object. |
| */ |
| protected AccessibleJTextArea() |
| { |
| super(); |
| } |
| |
| /** |
| * Returns the accessible state of this <code>AccessibleJTextArea</code>. |
| * |
| * @return the accessible state of this <code>AccessibleJTextArea</code> |
| */ |
| public AccessibleStateSet getAccessibleStateSet() |
| { |
| AccessibleStateSet state = super.getAccessibleStateSet(); |
| // TODO: Figure out what state must be added here to the super's state. |
| return state; |
| } |
| } |
| |
| /** |
| * Compatible with Sun's JDK |
| */ |
| private static final long serialVersionUID = -6141680179310439825L; |
| |
| /** |
| * The number of rows used by the component. |
| */ |
| private int rows; |
| |
| /** |
| * The number of columns used by the component. |
| */ |
| private int columns; |
| |
| /** |
| * Whether line wrapping is enabled or not. |
| */ |
| private boolean lineWrap; |
| |
| /** |
| * The number of characters equal to a tab within the text. |
| */ |
| private int tabSize = 8; |
| |
| private boolean wrapStyleWord; |
| |
| /** |
| * Creates a new <code>JTextArea</code> object. |
| */ |
| public JTextArea() |
| { |
| this(null, null, 0, 0); |
| } |
| |
| /** |
| * Creates a new <code>JTextArea</code> object. |
| * |
| * @param text the initial text |
| */ |
| public JTextArea(String text) |
| { |
| this(null, text, 0, 0); |
| } |
| |
| /** |
| * Creates a new <code>JTextArea</code> object. |
| * |
| * @param rows the number of rows |
| * @param columns the number of cols |
| * |
| * @exception IllegalArgumentException if rows or columns are negative |
| */ |
| public JTextArea(int rows, int columns) |
| { |
| this(null, null, rows, columns); |
| } |
| |
| /** |
| * Creates a new <code>JTextArea</code> object. |
| * |
| * @param text the initial text |
| * @param rows the number of rows |
| * @param columns the number of cols |
| * |
| * @exception IllegalArgumentException if rows or columns are negative |
| */ |
| public JTextArea(String text, int rows, int columns) |
| { |
| this(null, text, rows, columns); |
| } |
| |
| /** |
| * Creates a new <code>JTextArea</code> object. |
| * |
| * @param doc the document model to use |
| */ |
| public JTextArea(Document doc) |
| { |
| this(doc, null, 0, 0); |
| } |
| |
| /** |
| * Creates a new <code>JTextArea</code> object. |
| * |
| * @param doc the document model to use |
| * @param text the initial text |
| * @param rows the number of rows |
| * @param columns the number of cols |
| * |
| * @exception IllegalArgumentException if rows or columns are negative |
| */ |
| public JTextArea(Document doc, String text, int rows, int columns) |
| { |
| setDocument(doc == null ? createDefaultModel() : doc); |
| // Only explicitly setText() when there is actual text since |
| // setText() might be overridden and not expected to be called |
| // from the constructor (as in JEdit). |
| if (text != null) |
| setText(text); |
| setRows(rows); |
| setColumns(columns); |
| } |
| |
| /** |
| * Appends the supplied text to the current contents |
| * of the document model. |
| * |
| * @param toAppend the text to append |
| */ |
| public void append(String toAppend) |
| { |
| try |
| { |
| getDocument().insertString(getText().length(), toAppend, null); |
| } |
| catch (BadLocationException exception) |
| { |
| /* This shouldn't happen in theory -- but, if it does... */ |
| throw new RuntimeException("Unexpected exception occurred.", exception); |
| } |
| if (toAppend != null && toAppend.length() > 0) |
| revalidate(); |
| } |
| |
| /** |
| * Creates the default document model. |
| * |
| * @return a new default model |
| */ |
| protected Document createDefaultModel() |
| { |
| return new PlainDocument(); |
| } |
| |
| /** |
| * Returns true if the width of this component should be forced |
| * to match the width of a surrounding view port. When line wrapping |
| * is turned on, this method returns true. |
| * |
| * @return true if lines are wrapped. |
| */ |
| public boolean getScrollableTracksViewportWidth() |
| { |
| return lineWrap ? true : super.getScrollableTracksViewportWidth(); |
| } |
| |
| /** |
| * Returns the increment that is needed to expose exactly one new line |
| * of text. This is implemented here to return the values of |
| * {@link #getRowHeight} and {@link #getColumnWidth}, depending on |
| * the value of the argument <code>direction</code>. |
| * |
| * @param visibleRect the view area that is visible in the viewport |
| * @param orientation either {@link SwingConstants#VERTICAL} or |
| * {@link SwingConstants#HORIZONTAL} |
| * @param direction less than zero for up/left scrolling, greater |
| * than zero for down/right scrolling |
| * |
| * @return the increment that is needed to expose exactly one new row |
| * or column of text |
| * |
| * @throws IllegalArgumentException if <code>orientation</code> is invalid |
| */ |
| public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, |
| int direction) |
| { |
| if (orientation == SwingConstants.VERTICAL) |
| return getRowHeight(); |
| else if (orientation == SwingConstants.HORIZONTAL) |
| return getColumnWidth(); |
| else |
| throw new IllegalArgumentException("orientation must be either " |
| + "javax.swing.SwingConstants.VERTICAL " |
| + "or " |
| + "javax.swing.SwingConstants.HORIZONTAL" |
| ); |
| } |
| |
| /** |
| * Returns the preferred size of that text component in the case |
| * it is embedded within a JScrollPane. This uses the column and |
| * row settings if they are explicitly set, or fall back to |
| * the superclass's behaviour. |
| * |
| * @return the preferred size of that text component in the case |
| * it is embedded within a JScrollPane |
| */ |
| public Dimension getPreferredScrollableViewportSize() |
| { |
| if ((rows > 0) && (columns > 0)) |
| return new Dimension(columns * getColumnWidth(), rows * getRowHeight()); |
| else |
| return super.getPreferredScrollableViewportSize(); |
| } |
| |
| /** |
| * Returns the UI class ID string. |
| * |
| * @return the string "TextAreaUI" |
| */ |
| public String getUIClassID() |
| { |
| return "TextAreaUI"; |
| } |
| |
| /** |
| * Returns the current number of columns. |
| * |
| * @return number of columns |
| */ |
| public int getColumns() |
| { |
| return columns; |
| } |
| |
| /** |
| * Sets the number of rows. |
| * |
| * @param columns number of columns |
| * |
| * @exception IllegalArgumentException if columns is negative |
| */ |
| public void setColumns(int columns) |
| { |
| if (columns < 0) |
| throw new IllegalArgumentException(); |
| |
| if (columns != this.columns) |
| { |
| this.columns = columns; |
| revalidate(); |
| } |
| } |
| |
| /** |
| * Returns the current number of rows. |
| * |
| * @return number of rows |
| */ |
| public int getRows() |
| { |
| return rows; |
| } |
| |
| /** |
| * Sets the number of rows. |
| * |
| * @param rows number of rows |
| * |
| * @exception IllegalArgumentException if rows is negative |
| */ |
| public void setRows(int rows) |
| { |
| if (rows < 0) |
| throw new IllegalArgumentException(); |
| |
| if (rows != this.rows) |
| { |
| this.rows = rows; |
| revalidate(); |
| } |
| } |
| |
| /** |
| * Checks whether line wrapping is enabled. |
| * |
| * @return <code>true</code> if line wrapping is enabled, |
| * <code>false</code> otherwise |
| */ |
| public boolean getLineWrap() |
| { |
| return lineWrap; |
| } |
| |
| /** |
| * Enables/disables line wrapping. |
| * |
| * @param flag <code>true</code> to enable line wrapping, |
| * <code>false</code> otherwise |
| */ |
| public void setLineWrap(boolean flag) |
| { |
| if (lineWrap == flag) |
| return; |
| |
| boolean oldValue = lineWrap; |
| lineWrap = flag; |
| firePropertyChange("lineWrap", oldValue, lineWrap); |
| } |
| |
| /** |
| * Checks whether word style wrapping is enabled. |
| * |
| * @return <code>true</code> if word style wrapping is enabled, |
| * <code>false</code> otherwise |
| */ |
| public boolean getWrapStyleWord() |
| { |
| return wrapStyleWord; |
| } |
| |
| /** |
| * Enables/Disables word style wrapping. |
| * |
| * @param flag <code>true</code> to enable word style wrapping, |
| * <code>false</code> otherwise |
| */ |
| public void setWrapStyleWord(boolean flag) |
| { |
| if (wrapStyleWord == flag) |
| return; |
| |
| boolean oldValue = wrapStyleWord; |
| wrapStyleWord = flag; |
| firePropertyChange("wrapStyleWord", oldValue, wrapStyleWord); |
| } |
| |
| /** |
| * Returns the number of characters used for a tab. |
| * This defaults to 8. |
| * |
| * @return the current number of spaces used for a tab. |
| */ |
| public int getTabSize() |
| { |
| return tabSize; |
| } |
| |
| /** |
| * Sets the number of characters used for a tab to the |
| * supplied value. If a change to the tab size property |
| * occurs (i.e. newSize != tabSize), a property change event |
| * is fired. |
| * |
| * @param newSize The new number of characters to use for a tab. |
| */ |
| public void setTabSize(int newSize) |
| { |
| if (tabSize == newSize) |
| return; |
| |
| int oldValue = tabSize; |
| tabSize = newSize; |
| firePropertyChange("tabSize", oldValue, tabSize); |
| } |
| |
| protected int getColumnWidth() |
| { |
| FontMetrics metrics = getToolkit().getFontMetrics(getFont()); |
| return metrics.charWidth('m'); |
| } |
| |
| public int getLineCount() |
| { |
| return getDocument().getDefaultRootElement().getElementCount(); |
| } |
| |
| public int getLineStartOffset(int line) |
| throws BadLocationException |
| { |
| int lineCount = getLineCount(); |
| |
| if (line < 0 || line > lineCount) |
| throw new BadLocationException("Non-existing line number", line); |
| |
| Element lineElem = getDocument().getDefaultRootElement().getElement(line); |
| return lineElem.getStartOffset(); |
| } |
| |
| public int getLineEndOffset(int line) |
| throws BadLocationException |
| { |
| int lineCount = getLineCount(); |
| |
| if (line < 0 || line > lineCount) |
| throw new BadLocationException("Non-existing line number", line); |
| |
| Element lineElem = getDocument().getDefaultRootElement().getElement(line); |
| return lineElem.getEndOffset(); |
| } |
| |
| public int getLineOfOffset(int offset) |
| throws BadLocationException |
| { |
| Document doc = getDocument(); |
| |
| if (offset < doc.getStartPosition().getOffset() |
| || offset >= doc.getEndPosition().getOffset()) |
| throw new BadLocationException("offset outside of document", offset); |
| |
| return doc.getDefaultRootElement().getElementIndex(offset); |
| } |
| |
| protected int getRowHeight() |
| { |
| FontMetrics metrics = getToolkit().getFontMetrics(getFont()); |
| return metrics.getHeight(); |
| } |
| |
| /** |
| * Inserts the supplied text at the specified position. Nothing |
| * happens in the case that the model or the supplied string is null |
| * or of zero length. |
| * |
| * @param string The string of text to insert. |
| * @param position The position at which to insert the supplied text. |
| * @throws IllegalArgumentException if the position is < 0 or greater |
| * than the length of the current text. |
| */ |
| public void insert(String string, int position) |
| { |
| // Retrieve the document model. |
| Document doc = getDocument(); |
| |
| // Check the model and string for validity. |
| if (doc == null |
| || string == null |
| || string.length() == 0) |
| return; |
| |
| // Insert the text into the model. |
| try |
| { |
| doc.insertString(position, string, null); |
| } |
| catch (BadLocationException e) |
| { |
| throw new IllegalArgumentException("The supplied position, " |
| + position + ", was invalid."); |
| } |
| } |
| |
| public void replaceRange(String text, int start, int end) |
| { |
| Document doc = getDocument(); |
| |
| if (start > end |
| || start < doc.getStartPosition().getOffset() |
| || end >= doc.getEndPosition().getOffset()) |
| throw new IllegalArgumentException(); |
| |
| try |
| { |
| doc.remove(start, end - start); |
| doc.insertString(start, text, null); |
| } |
| catch (BadLocationException e) |
| { |
| // This cannot happen as we check offset above. |
| } |
| } |
| |
| /** |
| * Returns the preferred size for the JTextArea. This is the maximum of |
| * the size that is needed to display the content and the requested size |
| * as per {@link #getColumns} and {@link #getRows}. |
| * |
| * @return the preferred size of the JTextArea |
| */ |
| public Dimension getPreferredSize() |
| { |
| int reqWidth = getColumns() * getColumnWidth(); |
| int reqHeight = getRows() * getRowHeight(); |
| View view = getUI().getRootView(this); |
| int neededWidth = (int) view.getPreferredSpan(View.HORIZONTAL); |
| int neededHeight = (int) view.getPreferredSpan(View.VERTICAL); |
| return new Dimension(Math.max(reqWidth, neededWidth), |
| Math.max(reqHeight, neededHeight)); |
| } |
| |
| /** |
| * Returns the accessible context associated with the <code>JTextArea</code>. |
| * |
| * @return the accessible context associated with the <code>JTextArea</code> |
| */ |
| public AccessibleContext getAccessibleContext() |
| { |
| if (accessibleContext == null) |
| accessibleContext = new AccessibleJTextArea(); |
| return accessibleContext; |
| } |
| } |