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