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, getText());
 274:     ActionListener[] listeners = getActionListeners();
 275: 
 276:     for (int index = 0; index < listeners.length; ++index)
 277:       listeners[index].actionPerformed(event);
 278:   }
 279: 
 280:   /**
 281:    * Returns the number of columns of this text field.
 282:    *
 283:    * @return the number of columns
 284:    */
 285:   public int getColumns()
 286:   {
 287:     return columns;
 288:   }
 289: 
 290:   /**
 291:    * Sets the number of columns and then invalidates the layout.
 292:    * @param columns the number of columns
 293:    * @throws IllegalArgumentException if columns < 0
 294:    */
 295:   public void setColumns(int columns)
 296:   {
 297:     if (columns < 0)
 298:       throw new IllegalArgumentException();
 299: 
 300:     this.columns = columns;
 301:     invalidate();
 302:     //FIXME: do we need this repaint call?
 303:     repaint();
 304:   }
 305: 
 306:   /**
 307:    * Returns the horizontal alignment, which is one of: JTextField.LEFT, 
 308:    * JTextField.CENTER, JTextField.RIGHT, JTextField.LEADING, 
 309:    * JTextField.TRAILING.
 310:    * @return the horizontal alignment
 311:    */
 312:   public int getHorizontalAlignment()
 313:   {
 314:     return align;
 315:   }
 316: 
 317:   /**
 318:    * Sets the horizontal alignment of the text.  Calls invalidate and repaint
 319:    * and fires a property change event.
 320:    * @param newAlign must be one of: JTextField.LEFT, JTextField.CENTER,
 321:    * JTextField.RIGHT, JTextField.LEADING, JTextField.TRAILING.
 322:    * @throws IllegalArgumentException if newAlign is not one of the above.
 323:    */
 324:   public void setHorizontalAlignment(int newAlign)
 325:   {
 326:     //FIXME: should throw an IllegalArgumentException if newAlign is invalid
 327:     if (align == newAlign)
 328:       return;
 329: 
 330:     int oldAlign = align;
 331:     align = newAlign;
 332:     firePropertyChange("horizontalAlignment", oldAlign, newAlign);
 333:     invalidate();
 334:     repaint();
 335:   }
 336: 
 337:   /**
 338:    * Sets the current font and revalidates so the font will take effect.
 339:    */
 340:   public void setFont(Font newFont)
 341:   {
 342:     super.setFont(newFont);
 343:     revalidate();
 344:   }
 345: 
 346:   /**
 347:    * Returns the preferred size.  If there is a non-zero number of columns, 
 348:    * this is the number of columns multiplied by the column width, otherwise
 349:    * it returns super.getPreferredSize().
 350:    */
 351:   public Dimension getPreferredSize()
 352:   {
 353:     Dimension size = super.getPreferredSize();
 354: 
 355:     if (columns != 0)
 356:       {
 357:         Insets i = getInsets();
 358:         size.width = columns * getColumnWidth() + i.left + i.right;
 359:       }
 360: 
 361:     return size;
 362:   }
 363: 
 364:   /**
 365:    * Returns the scroll offset in pixels.
 366:    *
 367:    * @return the scroll offset
 368:    */
 369:   public int getScrollOffset()
 370:   {
 371:     return horizontalVisibility.getValue();
 372:   }
 373: 
 374:   /**
 375:    * Sets the scroll offset in pixels.
 376:    * 
 377:    * @param offset the scroll offset
 378:    */
 379:   public void setScrollOffset(int offset)
 380:   {
 381:     // Automatically sets to the highest possible value if
 382:     // offset is bigger than that.
 383:     horizontalVisibility.setValue(
 384:                                   Math.min(horizontalVisibility.getMaximum()
 385:                                            - horizontalVisibility.getExtent(),
 386:                                            offset));
 387:     
 388:   }
 389: 
 390:   /**
 391:    * Returns the set of Actions that are commands for the editor.
 392:    * This is the actions supported by this editor plus the actions
 393:    * of the UI (returned by JTextComponent.getActions()).
 394:    */
 395:   public Action[] getActions()
 396:   {
 397:     return TextAction.augmentList(super.getActions(), actions);
 398:   }
 399: 
 400:   public void postActionEvent()
 401:   {
 402:     String command = actionCommand != null ? actionCommand : getText();
 403:     ActionEvent event = new ActionEvent(this, 0, command);
 404:     ActionListener[] listeners = getActionListeners();
 405: 
 406:     for (int index = 0; index < listeners.length; ++index)
 407:       listeners[index].actionPerformed(event);
 408:   }
 409:   
 410:   /**
 411:    * @since 1.3
 412:    */
 413:   public Action getAction()
 414:   {
 415:     return action;
 416:   }
 417: 
 418:   /**
 419:    * @since 1.3
 420:    */
 421:   public void setAction(Action newAction)
 422:   {
 423:     if (action == newAction)
 424:       return;
 425: 
 426:     if (action != null)
 427:       {
 428:         removeActionListener(action);
 429:         action.removePropertyChangeListener(actionPropertyChangeListener);
 430:         actionPropertyChangeListener = null;
 431:       }
 432: 
 433:     Action oldAction = action;
 434:     action = newAction;
 435: 
 436:     if (action != null)
 437:       {
 438:         addActionListener(action);
 439:         actionPropertyChangeListener = createActionPropertyChangeListener(action);
 440:         action.addPropertyChangeListener(actionPropertyChangeListener);
 441:       }
 442: 
 443:     //FIXME: is this a hack?  The horizontal alignment hasn't changed
 444:     firePropertyChange("horizontalAlignment", oldAction, newAction);
 445:   }
 446: 
 447:   /**
 448:    * Sets the command string used in action events.
 449:    * @since 1.3
 450:    */
 451:   public void setActionCommand(String command)
 452:   {
 453:     actionCommand = command;
 454:   }
 455: 
 456:   /**
 457:    * @since 1.3
 458:    */
 459:   protected PropertyChangeListener createActionPropertyChangeListener(Action action)
 460:   {
 461:     return new PropertyChangeListener()
 462:     {
 463:       public void propertyChange(PropertyChangeEvent event)
 464:       {
 465:         // Update properties "action" and "horizontalAlignment".
 466:         String name = event.getPropertyName();
 467: 
 468:         if (name.equals("enabled"))
 469:           {
 470:             boolean enabled = ((Boolean) event.getNewValue()).booleanValue();
 471:             JTextField.this.setEnabled(enabled);
 472:           }
 473:         else if (name.equals(Action.SHORT_DESCRIPTION))
 474:           {
 475:             JTextField.this.setToolTipText((String) event.getNewValue());
 476:           }
 477:       }
 478:     };
 479:   }
 480: 
 481:   /**
 482:    * 
 483:    * @since 1.3
 484:    */
 485:   protected void configurePropertiesFromAction(Action action)
 486:   {
 487:     if (action != null)
 488:       {
 489:         setEnabled(action.isEnabled());
 490:         setToolTipText((String) action.getValue(Action.SHORT_DESCRIPTION));
 491:       }
 492:     else
 493:       {
 494:         setEnabled(true);
 495:         setToolTipText(null);
 496:       }
 497:   }
 498: 
 499:   /**
 500:    * Returns the column width, which is the width of the character m
 501:    * for the font in use.
 502:    * @return the width of the character m for the font in use.
 503:    */
 504:   protected int getColumnWidth()
 505:   {
 506:     FontMetrics metrics = getToolkit().getFontMetrics(getFont());
 507:     return metrics.charWidth('m');
 508:   }
 509: 
 510:   /**
 511:    * Returns the accessible context associated with the <code>JTextField</code>.
 512:    *
 513:    * @return the accessible context associated with the <code>JTextField</code>
 514:    */
 515:   public AccessibleContext getAccessibleContext()
 516:   {
 517:     if (accessibleContext == null)
 518:       accessibleContext = new AccessibleJTextField();
 519:     return accessibleContext;
 520:   }
 521: 
 522:   /**
 523:    * Returns the bounded range model that describes the horizontal visibility
 524:    * of the text field in the case when the text does not fit into the
 525:    * available space. The actual values of this model are managed by the look
 526:    * and feel implementation.
 527:    *
 528:    * @return the bounded range model that describes the horizontal visibility
 529:    */
 530:   public BoundedRangeModel getHorizontalVisibility()
 531:   {
 532:     return horizontalVisibility;
 533:   }
 534: 
 535:   /**
 536:    * Returns <code>true</code>, unless this is embedded in a
 537:    * <code>JViewport</code> in which case the viewport takes responsibility of
 538:    * validating.
 539:    *
 540:    * @return <code>true</code>, unless this is embedded in a
 541:    *         <code>JViewport</code> in which case the viewport takes
 542:    *         responsibility of validating
 543:    */
 544:   public boolean isValidateRoot()
 545:   {
 546:     return ! (getParent() instanceof JViewport);
 547:   }
 548:   
 549:   public void scrollRectToVisible(Rectangle r)
 550:   {
 551:     int v = horizontalVisibility.getValue();
 552:     
 553:     // The extent value is the inner width of the text field.
 554:     int e = horizontalVisibility.getExtent();
 555:     Insets i = getInsets();
 556:     
 557:     // The x value in the rectangle (usually) denotes the new location
 558:     // of the caret. We check whether the location lies inside the left or
 559:     // right border and scroll into the appropriate direction.
 560:     // The calculation has to be shifted by the BoundedRangeModel's value
 561:     // because that value was already used to calculate r.x (this happens
 562:     // as part of a modelToView() call in FieldView).
 563:     if (r.x < i.left)
 564:       setScrollOffset(v + r.x - i.left);
 565:     else if (r.x > e + i.left)
 566:       setScrollOffset(r.x + v - e - i.left);
 567:   }
 568:   
 569: }