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 */ AccessibleJTextField()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 */ getAccessibleStateSet()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 */ JTextField()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 */ JTextField(String text)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 */ JTextField(int columns)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 */ JTextField(String text, int columns)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 */ JTextField(Document doc, String text, int columns)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 */ createDefaultModel()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 */ setDocument(Document doc)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 */ getUIClassID()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 */ addActionListener(ActionListener listener)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 */ removeActionListener(ActionListener listener)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 */ getActionListeners()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 */ fireActionPerformed()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 */ getColumns()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 */ setColumns(int columns)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 */ getHorizontalAlignment()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 */ setHorizontalAlignment(int newAlign)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 */ setFont(Font newFont)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 */ getPreferredSize()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 */ getScrollOffset()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 */ setScrollOffset(int offset)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 */ getActions()396 public Action[] getActions() 397 { 398 return TextAction.augmentList(super.getActions(), actions); 399 } 400 postActionEvent()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 */ getAction()414 public Action getAction() 415 { 416 return action; 417 } 418 419 /** 420 * @since 1.3 421 */ setAction(Action newAction)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 */ setActionCommand(String command)452 public void setActionCommand(String command) 453 { 454 actionCommand = command; 455 } 456 457 /** 458 * @since 1.3 459 */ createActionPropertyChangeListener(Action action)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 } 571