001/* JTextField.java --
002   Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import java.awt.Dimension;
042import java.awt.Font;
043import java.awt.FontMetrics;
044import java.awt.Insets;
045import java.awt.Rectangle;
046import java.awt.event.ActionEvent;
047import java.awt.event.ActionListener;
048import java.beans.PropertyChangeEvent;
049import java.beans.PropertyChangeListener;
050
051import javax.accessibility.AccessibleContext;
052import javax.accessibility.AccessibleStateSet;
053import javax.swing.text.Document;
054import javax.swing.text.JTextComponent;
055import javax.swing.text.PlainDocument;
056import javax.swing.text.TextAction;
057
058public class JTextField extends JTextComponent
059  implements SwingConstants
060{
061  /**
062   * AccessibleJTextField
063   */
064  protected class AccessibleJTextField extends AccessibleJTextComponent
065  {
066    private static final long serialVersionUID = 8255147276740453036L;
067
068    /**
069     * Constructor AccessibleJTextField
070     */
071    protected AccessibleJTextField()
072    {
073      super();
074    }
075
076    /**
077     * Returns the accessible state of this <code>AccessibleJTextField</code>.
078     *
079     * @return the accessible state of this <code>AccessibleJTextField</code>
080     */
081    public AccessibleStateSet getAccessibleStateSet()
082    {
083      AccessibleStateSet state = super.getAccessibleStateSet();
084      // TODO: Figure out what state must be added here to the super's state.
085      return state;
086    }
087  }
088
089  private static final long serialVersionUID = 353853209832607592L;
090
091  private static final Action[] actions;
092
093  /**
094   * Name of the action that gets sent when the content of the text field
095   * gets accepted.
096   */
097  public static final String notifyAction = "notify-field-accept";
098  
099  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}