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