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: // 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: 472: protected int getColumnWidth() 473: { 474: FontMetrics metrics = getToolkit().getFontMetrics(getFont()); 475: return metrics.charWidth('m'); 476: } 477: 478: public int getLineCount() 479: { 480: return getDocument().getDefaultRootElement().getElementCount(); 481: } 482: 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: 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: 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: 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 < 0 or greater 533: * than the length of the current text. 534: */ 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: 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: */ 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: */ 601: public AccessibleContext getAccessibleContext() 602: { 603: if (accessibleContext == null) 604: accessibleContext = new AccessibleJTextArea(); 605: return accessibleContext; 606: } 607: }