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