001    /* JTextField.java --
002       Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import java.awt.Dimension;
042    import java.awt.Font;
043    import java.awt.FontMetrics;
044    import java.awt.Insets;
045    import java.awt.Rectangle;
046    import java.awt.event.ActionEvent;
047    import java.awt.event.ActionListener;
048    import java.beans.PropertyChangeEvent;
049    import java.beans.PropertyChangeListener;
050    
051    import javax.accessibility.AccessibleContext;
052    import javax.accessibility.AccessibleStateSet;
053    import javax.swing.text.Document;
054    import javax.swing.text.JTextComponent;
055    import javax.swing.text.PlainDocument;
056    import javax.swing.text.TextAction;
057    
058    public 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    }