1 /* TextComponent.java -- Widgets for entering text 2 Copyright (C) 1999, 2002, 2003, 2006, 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 private static final long serialVersionUID = -2214773872412987419L; 67 68 /** 69 * @serial Indicates whether or not this component is editable. 70 * This is package-private to avoid an accessor method. 71 */ 72 boolean editable; 73 74 /** 75 * @serial The starting position of the selected text region. 76 * This is package-private to avoid an accessor method. 77 */ 78 int selectionStart; 79 80 /** 81 * @serial The ending position of the selected text region. 82 * This is package-private to avoid an accessor method. 83 */ 84 int selectionEnd; 85 86 /** 87 * @serial The text in the component 88 * This is package-private to avoid an accessor method. 89 */ 90 String text; 91 92 /** 93 * A list of listeners that will receive events from this object. 94 */ 95 protected transient TextListener textListener; 96 97 protected class AccessibleAWTTextComponent 98 extends AccessibleAWTComponent 99 implements AccessibleText, TextListener 100 { 101 private static final long serialVersionUID = 3631432373506317811L; 102 103 // Constructor 104 // Adds a listener for tracking caret changes AccessibleAWTTextComponent()105 public AccessibleAWTTextComponent() 106 { 107 TextComponent.this.addTextListener(this); 108 } 109 getAccessibleRole()110 public AccessibleRole getAccessibleRole() 111 { 112 return AccessibleRole.TEXT; 113 } 114 getAccessibleStateSet()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 getAccessibleText()125 public AccessibleText getAccessibleText() 126 { 127 return this; 128 } 129 130 /* (non-Javadoc) 131 * @see javax.accessibility.AccessibleText#getIndexAtPoint(java.awt.Point) 132 */ getIndexAtPoint(Point point)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 */ getCharacterBounds(int index)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 */ getCharCount()149 public int getCharCount() 150 { 151 return text.length(); 152 } 153 154 /* (non-Javadoc) 155 * @see javax.accessibility.AccessibleText#getCaretPosition() 156 */ getCaretPosition()157 public int getCaretPosition() 158 { 159 return TextComponent.this.getCaretPosition(); 160 } 161 162 /* (non-Javadoc) 163 * @see javax.accessibility.AccessibleText#getAtIndex(int, int) 164 */ getAtIndex(int part, int index)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 */ getAfterIndex(int part, int index)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 */ getBeforeIndex(int part, int index)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 */ getCharacterAttribute(int index)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 */ getSelectionStart()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 */ getSelectionEnd()285 public int getSelectionEnd() 286 { 287 return selectionEnd; 288 } 289 290 /* (non-Javadoc) 291 * @see javax.accessibility.AccessibleText#getSelectedText() 292 */ getSelectedText()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 */ textValueChanged(TextEvent event)304 public void textValueChanged(TextEvent event) 305 { 306 // TODO Auto-generated method stub 307 308 } 309 310 } 311 312 TextComponent(String text)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 */ getText()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 */ setText(String text)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 */ getSelectedText()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 */ getSelectionStart()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 */ setSelectionStart(int selectionStart)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 */ getSelectionEnd()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 */ setSelectionEnd(int selectionEnd)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 */ select(int selectionStart, int selectionEnd)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 */ selectAll()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 */ getCaretPosition()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 */ setCaretPosition(int caretPosition)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 */ isEditable()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 */ setEditable(boolean editable)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 */ removeNotify()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 */ addTextListener(TextListener listener)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 */ removeTextListener(TextListener listener)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 */ processEvent(AWTEvent event)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 */ processTextEvent(TextEvent event)580 protected void processTextEvent(TextEvent event) 581 { 582 if (textListener != null) 583 textListener.textValueChanged(event); 584 } 585 dispatchEventImpl(AWTEvent e)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 */ paramString()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 */ getListeners(Class<T> listenerType)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 */ getTextListeners()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 */ getAccessibleContext()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. getIndexAtPoint(Point p)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 getCharacterBounds(int i)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 */ ignoreOldMouseEvents()671 static boolean ignoreOldMouseEvents() 672 { 673 return true; 674 } 675 676 } // class TextComponent 677