Source for javax.swing.JTextField

   1: /* JTextField.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006  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.Font;
  43: import java.awt.FontMetrics;
  44: import java.awt.Insets;
  45: import java.awt.Rectangle;
  46: import java.awt.event.ActionEvent;
  47: import java.awt.event.ActionListener;
  48: import java.beans.PropertyChangeEvent;
  49: import java.beans.PropertyChangeListener;
  50: 
  51: import javax.accessibility.AccessibleContext;
  52: import javax.accessibility.AccessibleStateSet;
  53: import javax.swing.text.Document;
  54: import javax.swing.text.JTextComponent;
  55: import javax.swing.text.PlainDocument;
  56: import javax.swing.text.TextAction;
  57: 
  58: public class JTextField extends JTextComponent
  59:   implements SwingConstants
  60: {
  61:   /**
  62:    * AccessibleJTextField
  63:    */
  64:   protected class AccessibleJTextField extends AccessibleJTextComponent
  65:   {
  66:     private static final long serialVersionUID = 8255147276740453036L;
  67: 
  68:     /**
  69:      * Constructor AccessibleJTextField
  70:      */
  71:     protected AccessibleJTextField()
  72:     {
  73:       super();
  74:     }
  75: 
  76:     /**
  77:      * Returns the accessible state of this <code>AccessibleJTextField</code>.
  78:      *
  79:      * @return the accessible state of this <code>AccessibleJTextField</code>
  80:      */
  81:     public AccessibleStateSet getAccessibleStateSet()
  82:     {
  83:       AccessibleStateSet state = super.getAccessibleStateSet();
  84:       // TODO: Figure out what state must be added here to the super's state.
  85:       return state;
  86:     }
  87:   }
  88: 
  89:   private static final long serialVersionUID = 353853209832607592L;
  90: 
  91:   private static final Action[] actions;
  92: 
  93:   /**
  94:    * Name of the action that gets sent when the content of the text field
  95:    * gets accepted.
  96:    */
  97:   public static final String notifyAction = "notify-field-accept";
  98:   
  99:   static
 100:     {
 101:       actions = new Action[1];
 102:       actions[0] = new TextAction(notifyAction)
 103:       {
 104:         public void actionPerformed(ActionEvent event)
 105:         {
 106:           JTextField textField = (JTextField) event.getSource();
 107:           textField.fireActionPerformed();
 108:         }
 109:       };
 110:     }
 111:   
 112:   private int columns;
 113:   private int align;
 114:   
 115:   /** @since 1.3 */
 116:   private Action action;
 117: 
 118:   /** @since 1.3 */
 119:   private String actionCommand;
 120:   
 121:   private PropertyChangeListener actionPropertyChangeListener;
 122: 
 123:   /**
 124:    * The horizontal visibility of the textfield.
 125:    */
 126:   private BoundedRangeModel horizontalVisibility;
 127: 
 128:   /**
 129:    * Creates a new instance of <code>JTextField</code>.
 130:    */
 131:   public JTextField()
 132:   {
 133:     this(null, null, 0);
 134:   }
 135: 
 136:   /**
 137:    * Creates a new instance of <code>JTextField</code>.
 138:    *
 139:    * @param text the initial text
 140:    */
 141:   public JTextField(String text)
 142:   {
 143:     this(null, text, 0);
 144:   }
 145:   
 146:   /**
 147:    * Creates a new instance of <code>JTextField</code>.
 148:    *
 149:    * @param columns the number of columns
 150:    *
 151:    * @exception IllegalArgumentException if columns %lt; 0
 152:    */
 153:   public JTextField(int columns)
 154:   {
 155:     this(null, null, columns);
 156:   }
 157: 
 158:   /**
 159:    * Creates a new instance of <code>JTextField</code>.
 160:    *
 161:    * @param text the initial text
 162:    * @param columns the number of columns
 163:    *
 164:    * @exception IllegalArgumentException if columns %lt; 0
 165:    */
 166:   public JTextField(String text, int columns)
 167:   {
 168:     this(null, text, columns);
 169:   }
 170: 
 171:   /**
 172:    * Creates a new instance of <code>JTextField</code>.
 173:    *
 174:    * @param doc the document to use
 175:    * @param text the initial text
 176:    * @param columns the number of columns
 177:    *
 178:    * @exception IllegalArgumentException if columns %lt; 0
 179:    */
 180:   public JTextField(Document doc, String text, int columns)
 181:   {
 182:     if (columns < 0)
 183:       throw new IllegalArgumentException();
 184: 
 185:     this.columns = columns;
 186: 
 187:     // Initialize the horizontal visibility model.
 188:     horizontalVisibility = new DefaultBoundedRangeModel();
 189: 
 190:     setDocument(doc == null ? createDefaultModel() : doc);
 191: 
 192:     if (text != null)
 193:       setText(text);
 194: 
 195:     // default value for alignment
 196:     align = LEADING;
 197:   }
 198: 
 199:   /**
 200:    * Creates the default model for this text field.
 201:    * This implementation returns an instance of <code>PlainDocument</code>.
 202:    *
 203:    * @return a new instance of the default model
 204:    */
 205:   protected Document createDefaultModel()
 206:   {
 207:     return new PlainDocument();
 208:   }
 209: 
 210:   /**
 211:    * Sets the document to be used for this JTextField.
 212:    *
 213:    * This sets the document property <code>filterNewlines</code> to
 214:    * <code>true</code> and then calls the super behaviour to setup a view and
 215:    * revalidate the text field.
 216:    *
 217:    * @param doc the document to set
 218:    */
 219:   public void setDocument(Document doc)
 220:   {
 221:     doc.putProperty("filterNewlines", Boolean.TRUE);
 222:     super.setDocument(doc);
 223:   }
 224: 
 225:   /**
 226:    * Returns the class ID for the UI.
 227:    *
 228:    * @return "TextFieldUI";
 229:    */
 230:   public String getUIClassID()
 231:   {
 232:     return "TextFieldUI";
 233:   }
 234: 
 235:   /**
 236:    * Adds a new listener object to this text field.
 237:    *
 238:    * @param listener the listener to add
 239:    */
 240:   public void addActionListener(ActionListener listener)
 241:   {
 242:     listenerList.add(ActionListener.class, listener);
 243:   }
 244: 
 245:   /**
 246:    * Removes a listener object from this text field.
 247:    *
 248:    * @param listener the listener to remove
 249:    */
 250:   public void removeActionListener(ActionListener listener)
 251:   {
 252:     listenerList.remove(ActionListener.class, listener);
 253:   }
 254: 
 255:   /**
 256:    * Returns all registered <code>ActionListener</code> objects.
 257:    *
 258:    * @return an array of listeners
 259:    *
 260:    * @since 1.4
 261:    */
 262:   public ActionListener[] getActionListeners()
 263:   {
 264:     return (ActionListener[]) getListeners(ActionListener.class);
 265:   }
 266: 
 267:   /**
 268:    * Sends an action event to all registered
 269:    * <code>ActionListener</code> objects.
 270:    */
 271:   protected void fireActionPerformed()
 272:   {
 273:     ActionEvent event = new ActionEvent(this, 0, 
 274:                           actionCommand == null ? getText() : actionCommand);
 275:     ActionListener[] listeners = getActionListeners();
 276: 
 277:     for (int index = 0; index < listeners.length; ++index)
 278:       listeners[index].actionPerformed(event);
 279:   }
 280: 
 281:   /**
 282:    * Returns the number of columns of this text field.
 283:    *
 284:    * @return the number of columns
 285:    */
 286:   public int getColumns()
 287:   {
 288:     return columns;
 289:   }
 290: 
 291:   /**
 292:    * Sets the number of columns and then invalidates the layout.
 293:    * @param columns the number of columns
 294:    * @throws IllegalArgumentException if columns < 0
 295:    */
 296:   public void setColumns(int columns)
 297:   {
 298:     if (columns < 0)
 299:       throw new IllegalArgumentException();
 300: 
 301:     this.columns = columns;
 302:     invalidate();
 303:     //FIXME: do we need this repaint call?
 304:     repaint();
 305:   }
 306: 
 307:   /**
 308:    * Returns the horizontal alignment, which is one of: JTextField.LEFT, 
 309:    * JTextField.CENTER, JTextField.RIGHT, JTextField.LEADING, 
 310:    * JTextField.TRAILING.
 311:    * @return the horizontal alignment
 312:    */
 313:   public int getHorizontalAlignment()
 314:   {
 315:     return align;
 316:   }
 317: 
 318:   /**
 319:    * Sets the horizontal alignment of the text.  Calls invalidate and repaint
 320:    * and fires a property change event.
 321:    * @param newAlign must be one of: JTextField.LEFT, JTextField.CENTER,
 322:    * JTextField.RIGHT, JTextField.LEADING, JTextField.TRAILING.
 323:    * @throws IllegalArgumentException if newAlign is not one of the above.
 324:    */
 325:   public void setHorizontalAlignment(int newAlign)
 326:   {
 327:     //FIXME: should throw an IllegalArgumentException if newAlign is invalid
 328:     if (align == newAlign)
 329:       return;
 330: 
 331:     int oldAlign = align;
 332:     align = newAlign;
 333:     firePropertyChange("horizontalAlignment", oldAlign, newAlign);
 334:     invalidate();
 335:     repaint();
 336:   }
 337: 
 338:   /**
 339:    * Sets the current font and revalidates so the font will take effect.
 340:    */
 341:   public void setFont(Font newFont)
 342:   {
 343:     super.setFont(newFont);
 344:     revalidate();
 345:   }
 346: 
 347:   /**
 348:    * Returns the preferred size.  If there is a non-zero number of columns, 
 349:    * this is the number of columns multiplied by the column width, otherwise
 350:    * it returns super.getPreferredSize().
 351:    */
 352:   public Dimension getPreferredSize()
 353:   {
 354:     Dimension size = super.getPreferredSize();
 355: 
 356:     if (columns != 0)
 357:       {
 358:         Insets i = getInsets();
 359:         size.width = columns * getColumnWidth() + i.left + i.right;
 360:       }
 361: 
 362:     return size;
 363:   }
 364: 
 365:   /**
 366:    * Returns the scroll offset in pixels.
 367:    *
 368:    * @return the scroll offset
 369:    */
 370:   public int getScrollOffset()
 371:   {
 372:     return horizontalVisibility.getValue();
 373:   }
 374: 
 375:   /**
 376:    * Sets the scroll offset in pixels.
 377:    * 
 378:    * @param offset the scroll offset
 379:    */
 380:   public void setScrollOffset(int offset)
 381:   {
 382:     // Automatically sets to the highest possible value if
 383:     // offset is bigger than that.
 384:     horizontalVisibility.setValue(
 385:                                   Math.min(horizontalVisibility.getMaximum()
 386:                                            - horizontalVisibility.getExtent(),
 387:                                            offset));
 388:     
 389:   }
 390: 
 391:   /**
 392:    * Returns the set of Actions that are commands for the editor.
 393:    * This is the actions supported by this editor plus the actions
 394:    * of the UI (returned by JTextComponent.getActions()).
 395:    */
 396:   public Action[] getActions()
 397:   {
 398:     return TextAction.augmentList(super.getActions(), actions);
 399:   }
 400: 
 401:   public void postActionEvent()
 402:   {
 403:     String command = actionCommand != null ? actionCommand : getText();
 404:     ActionEvent event = new ActionEvent(this, 0, command);
 405:     ActionListener[] listeners = getActionListeners();
 406: 
 407:     for (int index = 0; index < listeners.length; ++index)
 408:       listeners[index].actionPerformed(event);
 409:   }
 410:   
 411:   /**
 412:    * @since 1.3
 413:    */
 414:   public Action getAction()
 415:   {
 416:     return action;
 417:   }
 418: 
 419:   /**
 420:    * @since 1.3
 421:    */
 422:   public void setAction(Action newAction)
 423:   {
 424:     if (action == newAction)
 425:       return;
 426: 
 427:     if (action != null)
 428:       {
 429:         removeActionListener(action);
 430:         action.removePropertyChangeListener(actionPropertyChangeListener);
 431:         actionPropertyChangeListener = null;
 432:       }
 433: 
 434:     Action oldAction = action;
 435:     action = newAction;
 436: 
 437:     if (action != null)
 438:       {
 439:         addActionListener(action);
 440:         actionPropertyChangeListener = createActionPropertyChangeListener(action);
 441:         action.addPropertyChangeListener(actionPropertyChangeListener);
 442:       }
 443: 
 444:     //FIXME: is this a hack?  The horizontal alignment hasn't changed
 445:     firePropertyChange("horizontalAlignment", oldAction, newAction);
 446:   }
 447: 
 448:   /**
 449:    * Sets the command string used in action events.
 450:    * @since 1.3
 451:    */
 452:   public void setActionCommand(String command)
 453:   {
 454:     actionCommand = command;
 455:   }
 456: 
 457:   /**
 458:    * @since 1.3
 459:    */
 460:   protected PropertyChangeListener createActionPropertyChangeListener(Action action)
 461:   {
 462:     return new PropertyChangeListener()
 463:     {
 464:       public void propertyChange(PropertyChangeEvent event)
 465:       {
 466:         // Update properties "action" and "horizontalAlignment".
 467:         String name = event.getPropertyName();
 468: 
 469:         if (name.equals("enabled"))
 470:           {
 471:             boolean enabled = ((Boolean) event.getNewValue()).booleanValue();
 472:             JTextField.this.setEnabled(enabled);
 473:           }
 474:         else if (name.equals(Action.SHORT_DESCRIPTION))
 475:           {
 476:             JTextField.this.setToolTipText((String) event.getNewValue());
 477:           }
 478:       }
 479:     };
 480:   }
 481: 
 482:   /**
 483:    * 
 484:    * @since 1.3
 485:    */
 486:   protected void configurePropertiesFromAction(Action action)
 487:   {
 488:     if (action != null)
 489:       {
 490:         setEnabled(action.isEnabled());
 491:         setToolTipText((String) action.getValue(Action.SHORT_DESCRIPTION));
 492:       }
 493:     else
 494:       {
 495:         setEnabled(true);
 496:         setToolTipText(null);
 497:       }
 498:   }
 499: 
 500:   /**
 501:    * Returns the column width, which is the width of the character m
 502:    * for the font in use.
 503:    * @return the width of the character m for the font in use.
 504:    */
 505:   protected int getColumnWidth()
 506:   {
 507:     FontMetrics metrics = getToolkit().getFontMetrics(getFont());
 508:     return metrics.charWidth('m');
 509:   }
 510: 
 511:   /**
 512:    * Returns the accessible context associated with the <code>JTextField</code>.
 513:    *
 514:    * @return the accessible context associated with the <code>JTextField</code>
 515:    */
 516:   public AccessibleContext getAccessibleContext()
 517:   {
 518:     if (accessibleContext == null)
 519:       accessibleContext = new AccessibleJTextField();
 520:     return accessibleContext;
 521:   }
 522: 
 523:   /**
 524:    * Returns the bounded range model that describes the horizontal visibility
 525:    * of the text field in the case when the text does not fit into the
 526:    * available space. The actual values of this model are managed by the look
 527:    * and feel implementation.
 528:    *
 529:    * @return the bounded range model that describes the horizontal visibility
 530:    */
 531:   public BoundedRangeModel getHorizontalVisibility()
 532:   {
 533:     return horizontalVisibility;
 534:   }
 535: 
 536:   /**
 537:    * Returns <code>true</code>, unless this is embedded in a
 538:    * <code>JViewport</code> in which case the viewport takes responsibility of
 539:    * validating.
 540:    *
 541:    * @return <code>true</code>, unless this is embedded in a
 542:    *         <code>JViewport</code> in which case the viewport takes
 543:    *         responsibility of validating
 544:    */
 545:   public boolean isValidateRoot()
 546:   {
 547:     return ! (getParent() instanceof JViewport);
 548:   }
 549:   
 550:   public void scrollRectToVisible(Rectangle r)
 551:   {
 552:     int v = horizontalVisibility.getValue();
 553:     
 554:     // The extent value is the inner width of the text field.
 555:     int e = horizontalVisibility.getExtent();
 556:     Insets i = getInsets();
 557:     
 558:     // The x value in the rectangle (usually) denotes the new location
 559:     // of the caret. We check whether the location lies inside the left or
 560:     // right border and scroll into the appropriate direction.
 561:     // The calculation has to be shifted by the BoundedRangeModel's value
 562:     // because that value was already used to calculate r.x (this happens
 563:     // as part of a modelToView() call in FieldView).
 564:     if (r.x < i.left)
 565:       setScrollOffset(v + r.x - i.left);
 566:     else if (r.x > e + i.left)
 567:       setScrollOffset(r.x + v - e - i.left);
 568:   }
 569:   
 570: }