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.swing.text.BadLocationException;
  46: import javax.swing.text.Document;
  47: import javax.swing.text.Element;
  48: import javax.swing.text.JTextComponent;
  49: import javax.swing.text.PlainDocument;
  50: import javax.swing.text.View;
  51: 
  52: /**
  53:  * The <code>JTextArea</code> component provides a multi-line area for displaying
  54:  * and editing plain text.  The component is designed to act as a lightweight
  55:  * replacement for the heavyweight <code>java.awt.TextArea</code> component,
  56:  * which provides similar functionality using native widgets.
  57:  * <p>
  58:  *
  59:  * This component has additional functionality to the AWT class.  It follows
  60:  * the same design pattern as seen in other text components, such as
  61:  * <code>JTextField</code>, <code>JTextPane</code> and <code>JEditorPane</code>,
  62:  * and embodied in <code>JTextComponent</code>.  These classes separate the text
  63:  * (the model) from its appearance within the onscreen component (the view).  The
  64:  * text is held within a <code>javax.swing.text.Document</code> object, which can
  65:  * also maintain relevant style information where necessary.  As a result, it is the
  66:  * document that should be monitored for textual changes, via
  67:  * <code>DocumentEvent</code>s delivered to registered
  68:  * <code>DocumentListener</code>s, rather than this component.
  69:  * <p>
  70:  *
  71:  * Unlike <code>java.awt.TextArea</code>, <code>JTextArea</code> does not
  72:  * handle scrolling.  Instead, this functionality is delegated to a
  73:  * <code>JScrollPane</code>, which can contain the text area and handle
  74:  * scrolling when required.  Likewise, the word wrapping functionality
  75:  * of the AWT component is converted to a property of this component
  76:  * and the <code>rows</code> and <code>columns</code> properties
  77:  * are used in calculating the preferred size of the scroll pane's
  78:  * view port.
  79:  *
  80:  * @author Michael Koch  (konqueror@gmx.de)
  81:  * @author Andrew John Hughes  (gnu_andrew@member.fsf.org)
  82:  * @see java.awt.TextArea
  83:  * @see javax.swing.text.JTextComponent
  84:  * @see javax.swing.JTextField
  85:  * @see javax.swing.JTextPane
  86:  * @see javax.swing.JEditorPane
  87:  * @see javax.swing.text.Document
  88:  * @see javax.swing.event.DocumentEvent
  89:  * @see javax.swing.event.DocumentListener
  90:  */
  91: 
  92: public class JTextArea extends JTextComponent
  93: {
  94:   /**
  95:    * Compatible with Sun's JDK
  96:    */
  97:   private static final long serialVersionUID = -6141680179310439825L;
  98:   
  99:   /**
 100:    * The number of rows used by the component.
 101:    */
 102:   private int rows;
 103: 
 104:   /**
 105:    * The number of columns used by the component.
 106:    */
 107:   private int columns;
 108: 
 109:   /**
 110:    * Whether line wrapping is enabled or not.
 111:    */
 112:   private boolean lineWrap;
 113: 
 114:   /**
 115:    * The number of characters equal to a tab within the text.
 116:    */
 117:   private int tabSize = 8;
 118: 
 119:   private boolean wrapStyleWord;
 120: 
 121:   /**
 122:    * Creates a new <code>JTextArea</code> object.
 123:    */
 124:   public JTextArea()
 125:   {
 126:     this(null, null, 0, 0);
 127:   }
 128: 
 129:   /**
 130:    * Creates a new <code>JTextArea</code> object.
 131:    *
 132:    * @param text the initial text
 133:    */
 134:   public JTextArea(String text)
 135:   {
 136:     this(null, text, 0, 0);
 137:   }
 138: 
 139:   /**
 140:    * Creates a new <code>JTextArea</code> object.
 141:    *
 142:    * @param rows the number of rows
 143:    * @param columns the number of cols
 144:    *
 145:    * @exception IllegalArgumentException if rows or columns are negative
 146:    */
 147:   public JTextArea(int rows, int columns)
 148:   {
 149:     this(null, null, rows, columns);
 150:   }
 151: 
 152:   /**
 153:    * Creates a new <code>JTextArea</code> object.
 154:    *
 155:    * @param text the initial text
 156:    * @param rows the number of rows
 157:    * @param columns the number of cols
 158:    *
 159:    * @exception IllegalArgumentException if rows or columns are negative
 160:    */
 161:   public JTextArea(String text, int rows, int columns)
 162:   {
 163:     this(null, text, rows, columns);
 164:   }
 165: 
 166:   /**
 167:    * Creates a new <code>JTextArea</code> object.
 168:    *
 169:    * @param doc the document model to use
 170:    */
 171:   public JTextArea(Document doc)
 172:   {
 173:     this(doc, null, 0, 0);
 174:   }
 175: 
 176:   /**
 177:    * Creates a new <code>JTextArea</code> object.
 178:    *
 179:    * @param doc the document model to use
 180:    * @param text the initial text
 181:    * @param rows the number of rows
 182:    * @param columns the number of cols
 183:    *
 184:    * @exception IllegalArgumentException if rows or columns are negative
 185:    */
 186:   public JTextArea(Document doc, String text, int rows, int columns)
 187:   {
 188:     setDocument(doc == null ? createDefaultModel() : doc);
 189:     setText(text);
 190:     setRows(rows);
 191:     setColumns(columns);
 192:   }
 193: 
 194:   /**
 195:    * Appends the supplied text to the current contents
 196:    * of the document model.
 197:    *
 198:    * @param toAppend the text to append
 199:    */
 200:   public void append(String toAppend)
 201:   {
 202:       try
 203:       {
 204:           getDocument().insertString(getText().length(), toAppend, null);
 205:       }
 206:       catch (BadLocationException exception)
 207:       {
 208:           /* This shouldn't happen in theory -- but, if it does...  */
 209:           throw new RuntimeException("Unexpected exception occurred.", exception);
 210:       }
 211:   }
 212: 
 213:   /**
 214:    * Creates the default document model.
 215:    *
 216:    * @return a new default model
 217:    */
 218:   protected Document createDefaultModel()
 219:   {
 220:     return new PlainDocument();
 221:   }
 222: 
 223:   /**
 224:    * Returns true if the width of this component should be forced
 225:    * to match the width of a surrounding view port.  When line wrapping
 226:    * is turned on, this method returns true.
 227:    *
 228:    * @return true if lines are wrapped.
 229:    */
 230:   public boolean getScrollableTracksViewportWidth()
 231:   {
 232:     return lineWrap ? true : super.getScrollableTracksViewportWidth();
 233:   }
 234: 
 235:   /**
 236:    * Returns the increment that is needed to expose exactly one new line
 237:    * of text. This is implemented here to return the values of
 238:    * {@link #getRowHeight} and {@link #getColumnWidth}, depending on
 239:    * the value of the argument <code>direction</code>.
 240:    *
 241:    * @param visibleRect the view area that is visible in the viewport
 242:    * @param orientation either {@link SwingConstants#VERTICAL} or
 243:    *     {@link SwingConstants#HORIZONTAL}
 244:    * @param direction less than zero for up/left scrolling, greater
 245:    *     than zero for down/right scrolling
 246:    *
 247:    * @return the increment that is needed to expose exactly one new row
 248:    *     or column of text
 249:    *
 250:    * @throws IllegalArgumentException if <code>orientation</code> is invalid
 251:    */
 252:   public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
 253:                                         int direction)
 254:   {
 255:     if (orientation == SwingConstants.VERTICAL)
 256:       return getRowHeight();
 257:     else if (orientation == SwingConstants.HORIZONTAL)
 258:       return getColumnWidth();
 259:     else
 260:       throw new IllegalArgumentException("orientation must be either "
 261:                                      + "javax.swing.SwingConstants.VERTICAL "
 262:                                      + "or "
 263:                                      + "javax.swing.SwingConstants.HORIZONTAL"
 264:                                      );
 265:   }
 266: 
 267:   /**
 268:    * Returns the preferred size of that text component in the case
 269:    * it is embedded within a JScrollPane. This uses the column and
 270:    * row settings if they are explicitly set, or fall back to
 271:    * the superclass's behaviour.
 272:    *
 273:    * @return the preferred size of that text component in the case
 274:    *     it is embedded within a JScrollPane
 275:    */
 276:   public Dimension getPreferredScrollableViewportSize()
 277:   {
 278:     if ((rows > 0) && (columns > 0))
 279:       return new Dimension(columns * getColumnWidth(), rows * getRowHeight());
 280:     else
 281:       return super.getPreferredScrollableViewportSize();
 282:   }
 283: 
 284:   /**
 285:    * Returns the UI class ID string.
 286:    *
 287:    * @return the string "TextAreaUI"
 288:    */
 289:   public String getUIClassID()
 290:   {
 291:     return "TextAreaUI";
 292:   }
 293: 
 294:   /**
 295:    * Returns the current number of columns.
 296:    *
 297:    * @return number of columns
 298:    */
 299:   public int getColumns()
 300:   {
 301:     return columns;
 302:   }
 303:   
 304:   /**
 305:    * Sets the number of rows.
 306:    *
 307:    * @param columns number of columns
 308:    *
 309:    * @exception IllegalArgumentException if columns is negative
 310:    */
 311:   public void setColumns(int columns)
 312:   {
 313:     if (columns < 0)
 314:       throw new IllegalArgumentException();
 315: 
 316:     this.columns = columns;
 317:   }
 318: 
 319:   /**
 320:    * Returns the current number of rows.
 321:    *
 322:    * @return number of rows
 323:    */
 324:   public int getRows()
 325:   {
 326:     return rows;
 327:   }
 328: 
 329:   /**
 330:    * Sets the number of rows.
 331:    *
 332:    * @param rows number of rows
 333:    *
 334:    * @exception IllegalArgumentException if rows is negative
 335:    */
 336:   public void setRows(int rows)
 337:   {
 338:     if (rows < 0)
 339:       throw new IllegalArgumentException();
 340: 
 341:     this.rows = rows;
 342:   }
 343: 
 344:   /**
 345:    * Checks whether line wrapping is enabled.
 346:    *
 347:    * @return <code>true</code> if line wrapping is enabled,
 348:    * <code>false</code> otherwise
 349:    */
 350:   public boolean getLineWrap()
 351:   {
 352:     return lineWrap;
 353:   }
 354: 
 355:   /**
 356:    * Enables/disables line wrapping.
 357:    *
 358:    * @param flag <code>true</code> to enable line wrapping,
 359:    * <code>false</code> otherwise
 360:    */
 361:   public void setLineWrap(boolean flag)
 362:   {
 363:     if (lineWrap == flag)
 364:       return;
 365: 
 366:     boolean oldValue = lineWrap;
 367:     lineWrap = flag;
 368:     firePropertyChange("lineWrap", oldValue, lineWrap);
 369:   }
 370: 
 371:   /**
 372:    * Checks whether word style wrapping is enabled.
 373:    *
 374:    * @return <code>true</code> if word style wrapping is enabled,
 375:    * <code>false</code> otherwise
 376:    */
 377:   public boolean getWrapStyleWord()
 378:   {
 379:     return wrapStyleWord;
 380:   }
 381:   
 382:   /**
 383:    * Enables/Disables word style wrapping.
 384:    *
 385:    * @param flag <code>true</code> to enable word style wrapping,
 386:    * <code>false</code> otherwise
 387:    */
 388:   public void setWrapStyleWord(boolean flag)
 389:   {
 390:     if (wrapStyleWord == flag)
 391:       return;
 392:     
 393:     boolean oldValue = wrapStyleWord;
 394:     wrapStyleWord = flag;
 395:     firePropertyChange("wrapStyleWord", oldValue, wrapStyleWord);
 396:   }
 397:   
 398:   /**
 399:    * Returns the number of characters used for a tab.
 400:    * This defaults to 8.
 401:    *
 402:    * @return the current number of spaces used for a tab.
 403:    */
 404:   public int getTabSize()
 405:   {
 406:     return tabSize;
 407:   }
 408: 
 409:   /**
 410:    * Sets the number of characters used for a tab to the
 411:    * supplied value.  If a change to the tab size property
 412:    * occurs (i.e. newSize != tabSize), a property change event
 413:    * is fired.
 414:    * 
 415:    * @param newSize The new number of characters to use for a tab.
 416:    */
 417:   public void setTabSize(int newSize)
 418:   {
 419:     if (tabSize == newSize)
 420:       return;
 421:     
 422:     int oldValue = tabSize;
 423:     tabSize = newSize;
 424:     firePropertyChange("tabSize", oldValue, tabSize);
 425:   }
 426: 
 427:   protected int getColumnWidth()
 428:   {
 429:     FontMetrics metrics = getToolkit().getFontMetrics(getFont());
 430:     return metrics.charWidth('m');
 431:   }
 432: 
 433:   public int getLineCount()
 434:   {
 435:     return getDocument().getDefaultRootElement().getElementCount();
 436:   }
 437: 
 438:   public int getLineStartOffset(int line)
 439:      throws BadLocationException
 440:   {
 441:     int lineCount = getLineCount();
 442:     
 443:     if (line < 0 || line > lineCount)
 444:       throw new BadLocationException("Non-existing line number", line);
 445: 
 446:     Element lineElem = getDocument().getDefaultRootElement().getElement(line);
 447:     return lineElem.getStartOffset();
 448:   }
 449: 
 450:   public int getLineEndOffset(int line)
 451:      throws BadLocationException
 452:   {
 453:     int lineCount = getLineCount();
 454:     
 455:     if (line < 0 || line > lineCount)
 456:       throw new BadLocationException("Non-existing line number", line);
 457: 
 458:     Element lineElem = getDocument().getDefaultRootElement().getElement(line);
 459:     return lineElem.getEndOffset();
 460:   }
 461: 
 462:   public int getLineOfOffset(int offset)
 463:     throws BadLocationException
 464:   {
 465:     Document doc = getDocument();
 466: 
 467:     if (offset < doc.getStartPosition().getOffset()
 468:     || offset >= doc.getEndPosition().getOffset())
 469:       throw new BadLocationException("offset outside of document", offset);
 470: 
 471:     return doc.getDefaultRootElement().getElementIndex(offset);
 472:   }
 473: 
 474:   protected int getRowHeight()
 475:   {
 476:     FontMetrics metrics = getToolkit().getFontMetrics(getFont());
 477:     return metrics.getHeight();
 478:   }
 479: 
 480:   /**
 481:    * Inserts the supplied text at the specified position.  Nothing
 482:    * happens in the case that the model or the supplied string is null
 483:    * or of zero length.
 484:    *
 485:    * @param string The string of text to insert.
 486:    * @param position The position at which to insert the supplied text.
 487:    * @throws IllegalArgumentException if the position is &lt; 0 or greater
 488:    * than the length of the current text.
 489:    */
 490:   public void insert(String string, int position)
 491:   {
 492:     // Retrieve the document model.
 493:     Document doc = getDocument();
 494:       
 495:     // Check the model and string for validity.
 496:     if (doc == null
 497:     || string == null
 498:     || string.length() == 0)
 499:       return;
 500: 
 501:     // Insert the text into the model.
 502:     try
 503:       {
 504:     doc.insertString(position, string, null);
 505:       }
 506:     catch (BadLocationException e)
 507:       {
 508:     throw new IllegalArgumentException("The supplied position, "
 509:                        + position + ", was invalid.");
 510:       }
 511:   }
 512: 
 513:   public void replaceRange(String text, int start, int end)
 514:   {
 515:     Document doc = getDocument();
 516:     
 517:     if (start > end
 518:     || start < doc.getStartPosition().getOffset()
 519:     || end >= doc.getEndPosition().getOffset())
 520:       throw new IllegalArgumentException();
 521: 
 522:     try
 523:       {
 524:         doc.remove(start, end - start);
 525:         doc.insertString(start, text, null);
 526:       }
 527:     catch (BadLocationException e)
 528:       {
 529:     // This cannot happen as we check offset above.
 530:       }
 531:   }
 532: 
 533:   /**
 534:    * Returns the preferred size for the JTextArea. This is the maximum of
 535:    * the size that is needed to display the content and the requested size
 536:    * as per {@link #getColumns} and {@link #getRows}.
 537:    *
 538:    * @return the preferred size of the JTextArea
 539:    */
 540:   public Dimension getPreferredSize()
 541:   {
 542:     int reqWidth = getColumns() * getColumnWidth();
 543:     int reqHeight = getRows() * getRowHeight();
 544:     View view = getUI().getRootView(this);
 545:     int neededWidth = (int) view.getPreferredSpan(View.HORIZONTAL);
 546:     int neededHeight = (int) view.getPreferredSpan(View.VERTICAL);
 547:     return new Dimension(Math.max(reqWidth, neededWidth),
 548:                           Math.max(reqHeight, neededHeight));
 549:   }
 550: }