001/* TextComponent.java -- Widgets for entering text
002   Copyright (C) 1999, 2002, 2003, 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 java.awt;
040
041import java.awt.event.TextEvent;
042import java.awt.event.TextListener;
043import java.awt.peer.TextComponentPeer;
044import java.io.Serializable;
045import java.text.BreakIterator;
046import java.util.EventListener;
047
048import javax.accessibility.Accessible;
049import javax.accessibility.AccessibleContext;
050import javax.accessibility.AccessibleRole;
051import javax.accessibility.AccessibleState;
052import javax.accessibility.AccessibleStateSet;
053import javax.accessibility.AccessibleText;
054import javax.swing.text.AttributeSet;
055
056/**
057 * This class provides common functionality for widgets than 
058 * contain text.
059 *
060 * @author Aaron M. Renn (arenn@urbanophile.com)
061 */
062public class TextComponent extends Component
063  implements Serializable, Accessible
064{
065
066  private static final long serialVersionUID = -2214773872412987419L;
067
068  /**
069   * @serial Indicates whether or not this component is editable.
070   * This is package-private to avoid an accessor method.
071   */
072  boolean editable;
073
074  /**
075   * @serial The starting position of the selected text region.
076   * This is package-private to avoid an accessor method.
077   */
078  int selectionStart;
079
080  /**
081   * @serial The ending position of the selected text region.
082   * This is package-private to avoid an accessor method.
083   */
084  int selectionEnd;
085
086  /**
087   * @serial The text in the component
088   * This is package-private to avoid an accessor method.
089   */
090  String text;
091
092  /**
093   * A list of listeners that will receive events from this object.
094   */
095  protected transient TextListener textListener;
096
097  protected class AccessibleAWTTextComponent
098    extends AccessibleAWTComponent
099    implements AccessibleText, TextListener
100  {
101    private static final long serialVersionUID = 3631432373506317811L;
102
103    // Constructor
104    // Adds a listener for tracking caret changes
105    public AccessibleAWTTextComponent()
106    {
107      TextComponent.this.addTextListener(this);
108    }
109    
110    public AccessibleRole getAccessibleRole()
111    {
112      return AccessibleRole.TEXT;
113    }
114    
115    public AccessibleStateSet getAccessibleStateSet()
116    {
117      // TODO: Docs say PropertyChangeEvent will fire if this state changes.
118      // That means that the event has to fire when editable changes.
119      AccessibleStateSet ss = super.getAccessibleStateSet();
120      if (editable)
121        ss.add(AccessibleState.EDITABLE);
122      return ss;
123    }
124    
125    public AccessibleText getAccessibleText()
126    {
127      return this;
128    }
129    
130    /* (non-Javadoc)
131     * @see javax.accessibility.AccessibleText#getIndexAtPoint(java.awt.Point)
132     */
133    public int getIndexAtPoint(Point point)
134    {
135      return TextComponent.this.getIndexAtPoint(point);
136    }
137
138    /* (non-Javadoc)
139     * @see javax.accessibility.AccessibleText#getCharacterBounds(int)
140     */
141    public Rectangle getCharacterBounds(int index)
142    {
143      return TextComponent.this.getCharacterBounds(index);
144    }
145
146    /* (non-Javadoc)
147     * @see javax.accessibility.AccessibleText#getCharCount()
148     */
149    public int getCharCount()
150    {
151      return text.length();
152    }
153
154    /* (non-Javadoc)
155     * @see javax.accessibility.AccessibleText#getCaretPosition()
156     */
157    public int getCaretPosition()
158    {
159      return TextComponent.this.getCaretPosition();
160    }
161
162    /* (non-Javadoc)
163     * @see javax.accessibility.AccessibleText#getAtIndex(int, int)
164     */
165    public String getAtIndex(int part, int index)
166    {
167      if (index < 0 || index >= text.length())
168        return null;
169      BreakIterator it = null;
170      switch (part)
171      {
172        case CHARACTER:
173          return text.substring(index, index + 1);
174        case WORD:
175          it = BreakIterator.getWordInstance();
176          break;
177        case SENTENCE:
178          it = BreakIterator.getSentenceInstance();
179          break;
180        default:
181          return null;
182      }
183          it.setText(text);
184          int start = index;
185          if (!it.isBoundary(index))
186            start = it.preceding(index); 
187          int end = it.following(index);
188          if (end == -1)
189            return text.substring(index);
190          else
191            return text.substring(index, end);
192    }
193
194    /* (non-Javadoc)
195     * @see javax.accessibility.AccessibleText#getAfterIndex(int, int)
196     */
197    public String getAfterIndex(int part, int index) {
198      if (index < 0 || index >= text.length())
199        return null;
200      BreakIterator it = null;
201      switch (part)
202      {
203        case CHARACTER:
204          return text.substring(index, index + 1);
205        case WORD:
206          it = BreakIterator.getWordInstance();
207          break;
208        case SENTENCE:
209          it = BreakIterator.getSentenceInstance();
210          break;
211        default:
212          return null;
213      }
214          it.setText(text);
215          int start = index;
216          if (!it.isBoundary(index))
217            start = it.following(index);
218          // Make sure there was a complete unit.  I.e. if index is in the middle
219          // of a word, return null if there is no word after the that one.
220          if (start == -1)
221            return null;
222          int end = it.following(start);
223          if (end == -1)
224            return text.substring(index);
225          else
226            return text.substring(index, end);
227    }
228
229    /* (non-Javadoc)
230     * @see javax.accessibility.AccessibleText#getBeforeIndex(int, int)
231     */
232    public String getBeforeIndex(int part, int index)
233    {
234      if (index < 1 || index >= text.length())
235        return null;
236      BreakIterator it = null;
237      switch (part)
238      {
239        case CHARACTER:
240          return text.substring(index - 1, index);
241        case WORD:
242          it = BreakIterator.getWordInstance();
243          break;
244        case SENTENCE:
245          it = BreakIterator.getSentenceInstance();
246          break;
247        default:
248          return null;
249      }
250          it.setText(text);
251          int end = index;
252          if (!it.isBoundary(index))
253            end = it.preceding(index); 
254          // Make sure there was a complete unit.  I.e. if index is in the middle
255          // of a word, return null if there is no word before that one.
256          if (end == -1)
257            return null;
258          int start = it.preceding(end);
259          if (start == -1)
260            return text.substring(0, end);
261          else
262            return text.substring(start, end);
263    }
264
265    /* (non-Javadoc)
266     * @see javax.accessibility.AccessibleText#getCharacterAttribute(int)
267     */
268    public AttributeSet getCharacterAttribute(int index)
269    {
270      // FIXME: I suspect this really gets filled in by subclasses.
271      return null;
272    }
273
274    /* (non-Javadoc)
275     * @see javax.accessibility.AccessibleText#getSelectionStart()
276     */
277    public int getSelectionStart() {
278      // TODO Auto-generated method stub
279      return selectionStart;
280    }
281
282    /* (non-Javadoc)
283     * @see javax.accessibility.AccessibleText#getSelectionEnd()
284     */
285    public int getSelectionEnd()
286    {
287      return selectionEnd;
288    }
289
290    /* (non-Javadoc)
291     * @see javax.accessibility.AccessibleText#getSelectedText()
292     */
293    public String getSelectedText()
294    {
295      if (selectionEnd - selectionStart > 0)
296        return text.substring(selectionStart, selectionEnd);
297      else
298        return null;
299    }
300
301    /* (non-Javadoc)
302     * @see java.awt.event.TextListener#textValueChanged(java.awt.event.TextEvent)
303     */
304    public void textValueChanged(TextEvent event)
305    {
306      // TODO Auto-generated method stub
307      
308    }
309    
310  }
311
312
313  TextComponent(String text)
314  {
315    if (text == null)
316      this.text = "";
317    else
318      this.text = text;
319    
320    this.editable = true;
321  }
322
323
324  /**
325   * Returns the text in this component
326   *
327   * @return The text in this component.
328   */
329  public synchronized String getText()
330  {
331    TextComponentPeer tcp = (TextComponentPeer) getPeer();
332    if (tcp != null)
333      text = tcp.getText();
334
335    return(text);
336  }
337
338  /**
339   * Sets the text in this component to the specified string.
340   *
341   * @param text The new text for this component.
342   */
343  public synchronized void setText(String text)
344  {
345    if (text == null)
346      text = "";
347
348    this.text = text;
349
350    TextComponentPeer tcp = (TextComponentPeer) getPeer();
351    if (tcp != null)
352      tcp.setText(text);
353    setCaretPosition(0);
354  }
355
356  /**
357   * Returns a string that contains the text that is currently selected.
358   *
359   * @return The currently selected text region.
360   */
361  public synchronized String getSelectedText()
362  {
363    String alltext = getText();
364    int start = getSelectionStart();
365    int end = getSelectionEnd();
366  
367    return(alltext.substring(start, end));
368  }
369
370  /**
371   * Returns the starting position of the selected text region.
372   * If the text is not selected then caret position is returned. 
373   *
374   * @return The starting position of the selected text region.
375   */
376  public synchronized int getSelectionStart()
377  {
378    TextComponentPeer tcp = (TextComponentPeer) getPeer();
379    if (tcp != null)
380      selectionStart = tcp.getSelectionStart();
381
382    return(selectionStart);
383  }
384
385  /**
386   * Sets the starting position of the selected region to the
387   * specified value.  If the specified value is out of range, then it
388   * will be silently changed to the nearest legal value.
389   *
390   * @param selectionStart The new start position for selected text.
391   */
392  public synchronized void setSelectionStart(int selectionStart)
393  {
394    select(selectionStart, 
395           (getSelectionEnd() < selectionStart) 
396                              ? selectionStart : getSelectionEnd());
397  }
398
399  /**
400   * Returns the ending position of the selected text region.
401   * If the text is not selected, then caret position is returned 
402   *
403   * @return The ending position of the selected text region.
404   */
405  public synchronized int getSelectionEnd()
406  {
407    TextComponentPeer tcp = (TextComponentPeer) getPeer();
408    if (tcp != null)
409      selectionEnd = tcp.getSelectionEnd();
410
411    return(selectionEnd);
412  }
413
414  /**
415   * Sets the ending position of the selected region to the
416   * specified value.  If the specified value is out of range, then it
417   * will be silently changed to the nearest legal value.
418   *
419   * @param selectionEnd The new start position for selected text.
420   */
421  public synchronized void setSelectionEnd(int selectionEnd)
422  {
423    select(getSelectionStart(), selectionEnd);
424  }
425
426  /**
427   * This method sets the selected text range to the text between the
428   * specified start and end positions.  Illegal values for these
429   * positions are silently fixed.
430   *
431   * @param selectionStart The new start position for the selected text.
432   * @param selectionEnd The new end position for the selected text.
433   */
434  public synchronized void select(int selectionStart, int selectionEnd)
435  {
436    if (selectionStart < 0)
437      selectionStart = 0;
438
439    if (selectionStart > getText().length())
440      selectionStart = text.length();
441
442    if (selectionEnd > text.length())
443      selectionEnd = text.length();
444
445    if (selectionStart > selectionEnd)
446      selectionStart = selectionEnd;
447
448    this.selectionStart = selectionStart;
449    this.selectionEnd = selectionEnd;
450    
451    TextComponentPeer tcp = (TextComponentPeer) getPeer();
452    if (tcp != null)
453      tcp.select(selectionStart, selectionEnd);
454  }
455
456  /**
457   * Selects all of the text in the component.
458   */
459  public synchronized void selectAll()
460  {
461    select(0, getText().length());
462  }
463
464  /**
465   * Returns the current caret position in the text.
466   *
467   * @return The caret position in the text.
468   */
469  public synchronized int getCaretPosition()
470  {
471    TextComponentPeer tcp = (TextComponentPeer) getPeer();
472    if (tcp != null)
473      return(tcp.getCaretPosition());
474    else
475      return(0);
476  }
477
478  /**
479   * Sets the caret position to the specified value.
480   *
481   * @param caretPosition The new caret position.
482   *
483   * @exception IllegalArgumentException If the value supplied for position
484   * is less than zero.
485   *
486   * @since 1.1
487   */
488  public synchronized void setCaretPosition(int caretPosition)
489  {
490    if (caretPosition < 0)
491      throw new IllegalArgumentException();
492  
493    TextComponentPeer tcp = (TextComponentPeer) getPeer();
494    if (tcp != null)
495      tcp.setCaretPosition(caretPosition);
496  }
497
498  /**
499   * Tests whether or not this component's text can be edited.
500   *
501   * @return <code>true</code> if the text can be edited, <code>false</code>
502   * otherwise.
503   */
504  public boolean isEditable()
505  {
506    return(editable);
507  }
508
509  /**
510   * Sets whether or not this component's text can be edited.
511   *
512   * @param editable <code>true</code> to enable editing of the text,
513   * <code>false</code> to disable it.
514   */
515  public synchronized void setEditable(boolean editable)
516  {
517    this.editable = editable;
518
519    TextComponentPeer tcp = (TextComponentPeer) getPeer();
520    if (tcp != null)
521      tcp.setEditable(editable);
522  }
523
524  /**
525   * Notifies the component that it should destroy its native peer.
526   */
527  public void removeNotify()
528  {
529    super.removeNotify();
530  }
531
532  /**
533   * Adds a new listener to the list of text listeners for this
534   * component.
535   *
536   * @param listener The listener to be added.
537   */
538  public synchronized void addTextListener(TextListener listener)
539  {
540    textListener = AWTEventMulticaster.add(textListener, listener);
541
542    enableEvents(AWTEvent.TEXT_EVENT_MASK);  
543  }
544
545  /**
546   * Removes the specified listener from the list of listeners
547   * for this component.
548   *
549   * @param listener The listener to remove.
550   */
551  public synchronized void removeTextListener(TextListener listener)
552  {
553    textListener = AWTEventMulticaster.remove(textListener, listener);
554  }
555
556  /**
557   * Processes the specified event for this component.  Text events are
558   * processed by calling the <code>processTextEvent()</code> method.
559   * All other events are passed to the superclass method.
560   * 
561   * @param event The event to process.
562   */
563  protected void processEvent(AWTEvent event)
564  {
565    if (event instanceof TextEvent)
566      processTextEvent((TextEvent)event);
567    else
568      super.processEvent(event);
569  }
570
571  /**
572   * Processes the specified text event by dispatching it to any listeners
573   * that are registered.  Note that this method will only be called
574   * if text event's are enabled.  This will be true if there are any
575   * registered listeners, or if the event has been specifically
576   * enabled using <code>enableEvents()</code>.
577   *
578   * @param event The text event to process.
579   */
580  protected void processTextEvent(TextEvent event)
581  {
582    if (textListener != null)
583      textListener.textValueChanged(event);
584  }
585
586  void dispatchEventImpl(AWTEvent e)
587  {
588    if (e.id <= TextEvent.TEXT_LAST 
589        && e.id >= TextEvent.TEXT_FIRST
590        && (textListener != null 
591            || (eventMask & AWTEvent.TEXT_EVENT_MASK) != 0))
592      processEvent(e);
593    else
594      super.dispatchEventImpl(e); 
595  }
596
597  /**
598   * Returns a debugging string.
599   *
600   * @return A debugging string.
601   */
602  protected String paramString()
603  {
604    return(getClass().getName() + "(text=" + getText() + ")");
605  }
606
607  /**
608   * Returns an array of all the objects currently registered as FooListeners
609   * upon this <code>TextComponent</code>. FooListeners are registered using
610   * the addFooListener method.
611   *
612   * @exception ClassCastException If listenerType doesn't specify a class or
613   * interface that implements java.util.EventListener.
614   */
615  public <T extends EventListener> T[] getListeners(Class<T> listenerType)
616  {
617    if (listenerType == TextListener.class)
618      return AWTEventMulticaster.getListeners(textListener, listenerType);
619
620    return super.getListeners(listenerType);
621  }
622
623  /**
624   * Returns all text listeners registered to this object.
625   */
626  public TextListener[] getTextListeners()
627  {
628    return (TextListener[]) getListeners(TextListener.class);
629  }
630
631  /**
632   * Gets the AccessibleContext associated with this <code>TextComponent</code>.
633   * The context is created, if necessary.
634   *
635   * @return the associated context
636   */
637  public AccessibleContext getAccessibleContext()
638  {
639    /* Create the context if this is the first request */
640    if (accessibleContext == null)
641      accessibleContext = new AccessibleAWTTextComponent();
642    return accessibleContext;
643  }
644
645  
646  // Provide AccessibleAWTTextComponent access to several peer functions that
647  // aren't publicly exposed.  This is package-private to avoid an accessor
648  // method.
649  synchronized int getIndexAtPoint(Point p)
650  {
651    TextComponentPeer tcp = (TextComponentPeer) getPeer();
652    if (tcp != null)
653      return tcp.getIndexAtPoint(p.x, p.y);
654    return -1;
655  }
656  
657  synchronized Rectangle getCharacterBounds(int i)
658  {
659    TextComponentPeer tcp = (TextComponentPeer) getPeer();
660    if (tcp != null)
661      return tcp.getCharacterBounds(i);
662    return null;
663  }
664  
665  /**
666   * All old mouse events for this component should
667   * be ignored.
668   * 
669   * @return true to ignore all old mouse events.
670   */
671  static boolean ignoreOldMouseEvents()
672  {
673    return true;
674  }
675
676} // class TextComponent
677