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 < 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