Frames | No Frames |
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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 217: public JTextArea(Document doc, String text, int rows, int columns) 218: { 219: setDocument(doc == null ? createDefaultModel() : doc); 220: setText(text); 221: setRows(rows); 222: setColumns(columns); 223: } 224: 225: /** 226: * Appends the supplied text to the current contents 227: * of the document model. 228: * 229: * @param toAppend the text to append 230: */ 231: public void append(String toAppend) 232: { 233: try 234: { 235: getDocument().insertString(getText().length(), toAppend, null); 236: } 237: catch (BadLocationException exception) 238: { 239: /* This shouldn't happen in theory -- but, if it does... */ 240: throw new RuntimeException("Unexpected exception occurred.", exception); 241: } 242: if (toAppend != null && toAppend.length() > 0) 243: revalidate(); 244: } 245: 246: /** 247: * Creates the default document model. 248: * 249: * @return a new default model 250: */ 251: protected Document createDefaultModel() 252: { 253: return new PlainDocument(); 254: } 255: 256: /** 257: * Returns true if the width of this component should be forced 258: * to match the width of a surrounding view port. When line wrapping 259: * is turned on, this method returns true. 260: * 261: * @return true if lines are wrapped. 262: */ 263: public boolean getScrollableTracksViewportWidth() 264: { 265: return lineWrap ? true : super.getScrollableTracksViewportWidth(); 266: } 267: 268: /** 269: * Returns the increment that is needed to expose exactly one new line 270: * of text. This is implemented here to return the values of 271: * {@link #getRowHeight} and {@link #getColumnWidth}, depending on 272: * the value of the argument <code>direction</code>. 273: * 274: * @param visibleRect the view area that is visible in the viewport 275: * @param orientation either {@link SwingConstants#VERTICAL} or 276: * {@link SwingConstants#HORIZONTAL} 277: * @param direction less than zero for up/left scrolling, greater 278: * than zero for down/right scrolling 279: * 280: * @return the increment that is needed to expose exactly one new row 281: * or column of text 282: * 283: * @throws IllegalArgumentException if <code>orientation</code> is invalid 284: */ 285: public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, 286: int direction) 287: { 288: if (orientation == SwingConstants.VERTICAL) 289: return getRowHeight(); 290: else if (orientation == SwingConstants.HORIZONTAL) 291: return getColumnWidth(); 292: else 293: throw new IllegalArgumentException("orientation must be either " 294: + "javax.swing.SwingConstants.VERTICAL " 295: + "or " 296: + "javax.swing.SwingConstants.HORIZONTAL" 297: ); 298: } 299: 300: /** 301: * Returns the preferred size of that text component in the case 302: * it is embedded within a JScrollPane. This uses the column and 303: * row settings if they are explicitly set, or fall back to 304: * the superclass's behaviour. 305: * 306: * @return the preferred size of that text component in the case 307: * it is embedded within a JScrollPane 308: */ 309: public Dimension getPreferredScrollableViewportSize() 310: { 311: if ((rows > 0) && (columns > 0)) 312: return new Dimension(columns * getColumnWidth(), rows * getRowHeight()); 313: else 314: return super.getPreferredScrollableViewportSize(); 315: } 316: 317: /** 318: * Returns the UI class ID string. 319: * 320: * @return the string "TextAreaUI" 321: */ 322: public String getUIClassID() 323: { 324: return "TextAreaUI"; 325: } 326: 327: /** 328: * Returns the current number of columns. 329: * 330: * @return number of columns 331: */ 332: public int getColumns() 333: { 334: return columns; 335: } 336: 337: /** 338: * Sets the number of rows. 339: * 340: * @param columns number of columns 341: * 342: * @exception IllegalArgumentException if columns is negative 343: */ 344: public void setColumns(int columns) 345: { 346: if (columns < 0) 347: throw new IllegalArgumentException(); 348: 349: if (columns != this.columns) 350: { 351: this.columns = columns; 352: revalidate(); 353: } 354: } 355: 356: /** 357: * Returns the current number of rows. 358: * 359: * @return number of rows 360: */ 361: public int getRows() 362: { 363: return rows; 364: } 365: 366: /** 367: * Sets the number of rows. 368: * 369: * @param rows number of rows 370: * 371: * @exception IllegalArgumentException if rows is negative 372: */ 373: public void setRows(int rows) 374: { 375: if (rows < 0) 376: throw new IllegalArgumentException(); 377: 378: if (rows != this.rows) 379: { 380: this.rows = rows; 381: revalidate(); 382: } 383: } 384: 385: /** 386: * Checks whether line wrapping is enabled. 387: * 388: * @return <code>true</code> if line wrapping is enabled, 389: * <code>false</code> otherwise 390: */ 391: public boolean getLineWrap() 392: { 393: return lineWrap; 394: } 395: 396: /** 397: * Enables/disables line wrapping. 398: * 399: * @param flag <code>true</code> to enable line wrapping, 400: * <code>false</code> otherwise 401: */ 402: public void setLineWrap(boolean flag) 403: { 404: if (lineWrap == flag) 405: return; 406: 407: boolean oldValue = lineWrap; 408: lineWrap = flag; 409: firePropertyChange("lineWrap", oldValue, lineWrap); 410: } 411: 412: /** 413: * Checks whether word style wrapping is enabled. 414: * 415: * @return <code>true</code> if word style wrapping is enabled, 416: * <code>false</code> otherwise 417: */ 418: public boolean getWrapStyleWord() 419: { 420: return wrapStyleWord; 421: } 422: 423: /** 424: * Enables/Disables word style wrapping. 425: * 426: * @param flag <code>true</code> to enable word style wrapping, 427: * <code>false</code> otherwise 428: */ 429: public void setWrapStyleWord(boolean flag) 430: { 431: if (wrapStyleWord == flag) 432: return; 433: 434: boolean oldValue = wrapStyleWord; 435: wrapStyleWord = flag; 436: firePropertyChange("wrapStyleWord", oldValue, wrapStyleWord); 437: } 438: 439: /** 440: * Returns the number of characters used for a tab. 441: * This defaults to 8. 442: * 443: * @return the current number of spaces used for a tab. 444: */ 445: public int getTabSize() 446: { 447: return tabSize; 448: } 449: 450: /** 451: * Sets the number of characters used for a tab to the 452: * supplied value. If a change to the tab size property 453: * occurs (i.e. newSize != tabSize), a property change event 454: * is fired. 455: * 456: * @param newSize The new number of characters to use for a tab. 457: */ 458: public void setTabSize(int newSize) 459: { 460: if (tabSize == newSize) 461: return; 462: 463: int oldValue = tabSize; 464: tabSize = newSize; 465: firePropertyChange("tabSize", oldValue, tabSize); 466: } 467: 468: protected int getColumnWidth() 469: { 470: FontMetrics metrics = getToolkit().getFontMetrics(getFont()); 471: return metrics.charWidth('m'); 472: } 473: 474: public int getLineCount() 475: { 476: return getDocument().getDefaultRootElement().getElementCount(); 477: } 478: 479: public int getLineStartOffset(int line) 480: throws BadLocationException 481: { 482: int lineCount = getLineCount(); 483: 484: if (line < 0 || line > lineCount) 485: throw new BadLocationException("Non-existing line number", line); 486: 487: Element lineElem = getDocument().getDefaultRootElement().getElement(line); 488: return lineElem.getStartOffset(); 489: } 490: 491: public int getLineEndOffset(int line) 492: throws BadLocationException 493: { 494: int lineCount = getLineCount(); 495: 496: if (line < 0 || line > lineCount) 497: throw new BadLocationException("Non-existing line number", line); 498: 499: Element lineElem = getDocument().getDefaultRootElement().getElement(line); 500: return lineElem.getEndOffset(); 501: } 502: 503: public int getLineOfOffset(int offset) 504: throws BadLocationException 505: { 506: Document doc = getDocument(); 507: 508: if (offset < doc.getStartPosition().getOffset() 509: || offset >= doc.getEndPosition().getOffset()) 510: throw new BadLocationException("offset outside of document", offset); 511: 512: return doc.getDefaultRootElement().getElementIndex(offset); 513: } 514: 515: protected int getRowHeight() 516: { 517: FontMetrics metrics = getToolkit().getFontMetrics(getFont()); 518: return metrics.getHeight(); 519: } 520: 521: /** 522: * Inserts the supplied text at the specified position. Nothing 523: * happens in the case that the model or the supplied string is null 524: * or of zero length. 525: * 526: * @param string The string of text to insert. 527: * @param position The position at which to insert the supplied text. 528: * @throws IllegalArgumentException if the position is < 0 or greater 529: * than the length of the current text. 530: */ 531: public void insert(String string, int position) 532: { 533: // Retrieve the document model. 534: Document doc = getDocument(); 535: 536: // Check the model and string for validity. 537: if (doc == null 538: || string == null 539: || string.length() == 0) 540: return; 541: 542: // Insert the text into the model. 543: try 544: { 545: doc.insertString(position, string, null); 546: } 547: catch (BadLocationException e) 548: { 549: throw new IllegalArgumentException("The supplied position, " 550: + position + ", was invalid."); 551: } 552: } 553: 554: public void replaceRange(String text, int start, int end) 555: { 556: Document doc = getDocument(); 557: 558: if (start > end 559: || start < doc.getStartPosition().getOffset() 560: || end >= doc.getEndPosition().getOffset()) 561: throw new IllegalArgumentException(); 562: 563: try 564: { 565: doc.remove(start, end - start); 566: doc.insertString(start, text, null); 567: } 568: catch (BadLocationException e) 569: { 570: // This cannot happen as we check offset above. 571: } 572: } 573: 574: /** 575: * Returns the preferred size for the JTextArea. This is the maximum of 576: * the size that is needed to display the content and the requested size 577: * as per {@link #getColumns} and {@link #getRows}. 578: * 579: * @return the preferred size of the JTextArea 580: */ 581: public Dimension getPreferredSize() 582: { 583: int reqWidth = getColumns() * getColumnWidth(); 584: int reqHeight = getRows() * getRowHeight(); 585: View view = getUI().getRootView(this); 586: int neededWidth = (int) view.getPreferredSpan(View.HORIZONTAL); 587: int neededHeight = (int) view.getPreferredSpan(View.VERTICAL); 588: return new Dimension(Math.max(reqWidth, neededWidth), 589: Math.max(reqHeight, neededHeight)); 590: } 591: 592: /** 593: * Returns the accessible context associated with the <code>JTextArea</code>. 594: * 595: * @return the accessible context associated with the <code>JTextArea</code> 596: */ 597: public AccessibleContext getAccessibleContext() 598: { 599: if (accessibleContext == null) 600: accessibleContext = new AccessibleJTextArea(); 601: return accessibleContext; 602: } 603: }