Source for javax.swing.JTextArea

   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 &lt; 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: }