001    /* TextComponent.java -- Widgets for entering text
002       Copyright (C) 1999, 2002, 2003, 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 java.awt;
040    
041    import java.awt.event.TextEvent;
042    import java.awt.event.TextListener;
043    import java.awt.peer.TextComponentPeer;
044    import java.io.Serializable;
045    import java.text.BreakIterator;
046    import java.util.EventListener;
047    
048    import javax.accessibility.Accessible;
049    import javax.accessibility.AccessibleContext;
050    import javax.accessibility.AccessibleRole;
051    import javax.accessibility.AccessibleState;
052    import javax.accessibility.AccessibleStateSet;
053    import javax.accessibility.AccessibleText;
054    import 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     */
062    public 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