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