001 /* JTextComponent.java -- 002 Copyright (C) 2002, 2004, 2005 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 javax.swing.text; 040 041 import java.awt.AWTEvent; 042 import java.awt.Color; 043 import java.awt.Container; 044 import java.awt.Dimension; 045 import java.awt.Insets; 046 import java.awt.Point; 047 import java.awt.Rectangle; 048 import java.awt.Shape; 049 import java.awt.datatransfer.Clipboard; 050 import java.awt.datatransfer.DataFlavor; 051 import java.awt.datatransfer.StringSelection; 052 import java.awt.datatransfer.Transferable; 053 import java.awt.datatransfer.UnsupportedFlavorException; 054 import java.awt.event.ActionEvent; 055 import java.awt.event.InputMethodListener; 056 import java.awt.event.KeyEvent; 057 import java.awt.event.MouseEvent; 058 import java.io.IOException; 059 import java.io.Reader; 060 import java.io.Writer; 061 import java.text.BreakIterator; 062 import java.util.Enumeration; 063 import java.util.Hashtable; 064 065 import javax.accessibility.Accessible; 066 import javax.accessibility.AccessibleAction; 067 import javax.accessibility.AccessibleContext; 068 import javax.accessibility.AccessibleEditableText; 069 import javax.accessibility.AccessibleRole; 070 import javax.accessibility.AccessibleState; 071 import javax.accessibility.AccessibleStateSet; 072 import javax.accessibility.AccessibleText; 073 import javax.swing.Action; 074 import javax.swing.ActionMap; 075 import javax.swing.InputMap; 076 import javax.swing.JComponent; 077 import javax.swing.JViewport; 078 import javax.swing.KeyStroke; 079 import javax.swing.Scrollable; 080 import javax.swing.SwingConstants; 081 import javax.swing.TransferHandler; 082 import javax.swing.UIManager; 083 import javax.swing.event.CaretEvent; 084 import javax.swing.event.CaretListener; 085 import javax.swing.event.DocumentEvent; 086 import javax.swing.event.DocumentListener; 087 import javax.swing.plaf.ActionMapUIResource; 088 import javax.swing.plaf.InputMapUIResource; 089 import javax.swing.plaf.TextUI; 090 091 public abstract class JTextComponent extends JComponent 092 implements Scrollable, Accessible 093 { 094 /** 095 * AccessibleJTextComponent implements accessibility hooks for 096 * JTextComponent. It allows an accessibility driver to read and 097 * manipulate the text component's contents as well as update UI 098 * elements such as the caret. 099 */ 100 public class AccessibleJTextComponent extends AccessibleJComponent implements 101 AccessibleText, CaretListener, DocumentListener, AccessibleAction, 102 AccessibleEditableText 103 { 104 private static final long serialVersionUID = 7664188944091413696L; 105 106 /** 107 * The caret's offset. 108 */ 109 private int caretDot; 110 111 /** 112 * Construct an AccessibleJTextComponent. 113 */ 114 public AccessibleJTextComponent() 115 { 116 super(); 117 JTextComponent.this.addCaretListener(this); 118 caretDot = getCaretPosition(); 119 } 120 121 /** 122 * Retrieve the current caret position. The index of the first 123 * caret position is 0. 124 * 125 * @return caret position 126 */ 127 public int getCaretPosition() 128 { 129 return JTextComponent.this.getCaretPosition(); 130 } 131 132 /** 133 * Retrieve the current text selection. If no text is selected 134 * this method returns null. 135 * 136 * @return the currently selected text or null 137 */ 138 public String getSelectedText() 139 { 140 return JTextComponent.this.getSelectedText(); 141 } 142 143 /** 144 * Retrieve the index of the first character in the current text 145 * selection. If there is no text in the text component, this 146 * method returns 0. If there is text in the text component, but 147 * there is no selection, this method returns the current caret 148 * position. 149 * 150 * @return the index of the first character in the selection, the 151 * current caret position or 0 152 */ 153 public int getSelectionStart() 154 { 155 if (getSelectedText() == null 156 || (JTextComponent.this.getText().equals(""))) 157 return 0; 158 return JTextComponent.this.getSelectionStart(); 159 } 160 161 /** 162 * Retrieve the index of the last character in the current text 163 * selection. If there is no text in the text component, this 164 * method returns 0. If there is text in the text component, but 165 * there is no selection, this method returns the current caret 166 * position. 167 * 168 * @return the index of the last character in the selection, the 169 * current caret position or 0 170 */ 171 public int getSelectionEnd() 172 { 173 return JTextComponent.this.getSelectionEnd(); 174 } 175 176 /** 177 * Handle a change in the caret position and fire any applicable 178 * property change events. 179 * 180 * @param e - the caret update event 181 */ 182 public void caretUpdate(CaretEvent e) 183 { 184 int dot = e.getDot(); 185 int mark = e.getMark(); 186 if (caretDot != dot) 187 { 188 firePropertyChange(ACCESSIBLE_CARET_PROPERTY, new Integer(caretDot), 189 new Integer(dot)); 190 caretDot = dot; 191 } 192 if (mark != dot) 193 { 194 firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null, 195 getSelectedText()); 196 } 197 } 198 199 /** 200 * Retreive the accessible state set of this component. 201 * 202 * @return the accessible state set of this component 203 */ 204 public AccessibleStateSet getAccessibleStateSet() 205 { 206 AccessibleStateSet state = super.getAccessibleStateSet(); 207 if (isEditable()) 208 state.add(AccessibleState.EDITABLE); 209 return state; 210 } 211 212 /** 213 * Retrieve the accessible role of this component. 214 * 215 * @return the accessible role of this component 216 * 217 * @see AccessibleRole 218 */ 219 public AccessibleRole getAccessibleRole() 220 { 221 return AccessibleRole.TEXT; 222 } 223 224 /** 225 * Retrieve an AccessibleEditableText object that controls this 226 * text component. 227 * 228 * @return this 229 */ 230 public AccessibleEditableText getAccessibleEditableText() 231 { 232 return this; 233 } 234 235 /** 236 * Retrieve an AccessibleText object that controls this text 237 * component. 238 * 239 * @return this 240 * 241 * @see AccessibleText 242 */ 243 public AccessibleText getAccessibleText() 244 { 245 return this; 246 } 247 248 /** 249 * Handle a text insertion event and fire an 250 * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change 251 * event. 252 * 253 * @param e - the insertion event 254 */ 255 public void insertUpdate(DocumentEvent e) 256 { 257 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, 258 new Integer(e.getOffset())); 259 } 260 261 /** 262 * Handle a text removal event and fire an 263 * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change 264 * event. 265 * 266 * @param e - the removal event 267 */ 268 public void removeUpdate(DocumentEvent e) 269 { 270 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, 271 new Integer(e.getOffset())); 272 } 273 274 /** 275 * Handle a text change event and fire an 276 * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change 277 * event. 278 * 279 * @param e - text change event 280 */ 281 public void changedUpdate(DocumentEvent e) 282 { 283 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, 284 new Integer(e.getOffset())); 285 } 286 287 /** 288 * Get the index of the character at the given point, in component 289 * pixel co-ordinates. If the point argument is invalid this 290 * method returns -1. 291 * 292 * @param p - a point in component pixel co-ordinates 293 * 294 * @return a character index, or -1 295 */ 296 public int getIndexAtPoint(Point p) 297 { 298 return viewToModel(p); 299 } 300 301 /** 302 * Calculate the bounding box of the character at the given index. 303 * The returned x and y co-ordinates are relative to this text 304 * component's top-left corner. If the index is invalid this 305 * method returns null. 306 * 307 * @param index - the character index 308 * 309 * @return a character's bounding box, or null 310 */ 311 public Rectangle getCharacterBounds(int index) 312 { 313 // This is basically the same as BasicTextUI.modelToView(). 314 315 Rectangle bounds = null; 316 if (index >= 0 && index < doc.getLength() - 1) 317 { 318 if (doc instanceof AbstractDocument) 319 ((AbstractDocument) doc).readLock(); 320 try 321 { 322 TextUI ui = getUI(); 323 if (ui != null) 324 { 325 // Get editor rectangle. 326 Rectangle rect = new Rectangle(); 327 Insets insets = getInsets(); 328 rect.x = insets.left; 329 rect.y = insets.top; 330 rect.width = getWidth() - insets.left - insets.right; 331 rect.height = getHeight() - insets.top - insets.bottom; 332 View rootView = ui.getRootView(JTextComponent.this); 333 if (rootView != null) 334 { 335 rootView.setSize(rect.width, rect.height); 336 Shape s = rootView.modelToView(index, 337 Position.Bias.Forward, 338 index + 1, 339 Position.Bias.Backward, 340 rect); 341 if (s != null) 342 bounds = s.getBounds(); 343 } 344 } 345 } 346 catch (BadLocationException ex) 347 { 348 // Ignore (return null). 349 } 350 finally 351 { 352 if (doc instanceof AbstractDocument) 353 ((AbstractDocument) doc).readUnlock(); 354 } 355 } 356 return bounds; 357 } 358 359 /** 360 * Return the length of the text in this text component. 361 * 362 * @return a character length 363 */ 364 public int getCharCount() 365 { 366 return JTextComponent.this.getText().length(); 367 } 368 369 /** 370 * Gets the character attributes of the character at index. If 371 * the index is out of bounds, null is returned. 372 * 373 * @param index - index of the character 374 * 375 * @return the character's attributes 376 */ 377 public AttributeSet getCharacterAttribute(int index) 378 { 379 AttributeSet atts; 380 if (doc instanceof AbstractDocument) 381 ((AbstractDocument) doc).readLock(); 382 try 383 { 384 Element el = doc.getDefaultRootElement(); 385 while (! el.isLeaf()) 386 { 387 int i = el.getElementIndex(index); 388 el = el.getElement(i); 389 } 390 atts = el.getAttributes(); 391 } 392 finally 393 { 394 if (doc instanceof AbstractDocument) 395 ((AbstractDocument) doc).readUnlock(); 396 } 397 return atts; 398 } 399 400 /** 401 * Gets the text located at index. null is returned if the index 402 * or part is invalid. 403 * 404 * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} 405 * @param index - index of the part 406 * 407 * @return the part of text at that index, or null 408 */ 409 public String getAtIndex(int part, int index) 410 { 411 return getAtIndexImpl(part, index, 0); 412 } 413 414 /** 415 * Gets the text located after index. null is returned if the index 416 * or part is invalid. 417 * 418 * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} 419 * @param index - index after the part 420 * 421 * @return the part of text after that index, or null 422 */ 423 public String getAfterIndex(int part, int index) 424 { 425 return getAtIndexImpl(part, index, 1); 426 } 427 428 /** 429 * Gets the text located before index. null is returned if the index 430 * or part is invalid. 431 * 432 * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} 433 * @param index - index before the part 434 * 435 * @return the part of text before that index, or null 436 */ 437 public String getBeforeIndex(int part, int index) 438 { 439 return getAtIndexImpl(part, index, -1); 440 } 441 442 /** 443 * Implements getAtIndex(), getBeforeIndex() and getAfterIndex(). 444 * 445 * @param part the part to return, either CHARACTER, WORD or SENTENCE 446 * @param index the index 447 * @param dir the direction, -1 for backwards, 0 for here, +1 for forwards 448 * 449 * @return the resulting string 450 */ 451 private String getAtIndexImpl(int part, int index, int dir) 452 { 453 String ret = null; 454 if (doc instanceof AbstractDocument) 455 ((AbstractDocument) doc).readLock(); 456 try 457 { 458 BreakIterator iter = null; 459 switch (part) 460 { 461 case CHARACTER: 462 iter = BreakIterator.getCharacterInstance(getLocale()); 463 break; 464 case WORD: 465 iter = BreakIterator.getWordInstance(getLocale()); 466 break; 467 case SENTENCE: 468 iter = BreakIterator.getSentenceInstance(getLocale()); 469 break; 470 default: 471 break; 472 } 473 String text = doc.getText(0, doc.getLength() - 1); 474 iter.setText(text); 475 int start = index; 476 int end = index; 477 switch (dir) 478 { 479 case 0: 480 if (iter.isBoundary(index)) 481 { 482 start = index; 483 end = iter.following(index); 484 } 485 else 486 { 487 start = iter.preceding(index); 488 end = iter.next(); 489 } 490 break; 491 case 1: 492 start = iter.following(index); 493 end = iter.next(); 494 break; 495 case -1: 496 end = iter.preceding(index); 497 start = iter.previous(); 498 break; 499 default: 500 assert false; 501 } 502 ret = text.substring(start, end); 503 } 504 catch (BadLocationException ex) 505 { 506 // Ignore (return null). 507 } 508 finally 509 { 510 if (doc instanceof AbstractDocument) 511 ((AbstractDocument) doc).readUnlock(); 512 } 513 return ret; 514 } 515 516 /** 517 * Returns the number of actions for this object. The zero-th 518 * object represents the default action. 519 * 520 * @return the number of actions (0-based). 521 */ 522 public int getAccessibleActionCount() 523 { 524 return getActions().length; 525 } 526 527 /** 528 * Returns the description of the i-th action. Null is returned if 529 * i is out of bounds. 530 * 531 * @param i - the action to get the description for 532 * 533 * @return description of the i-th action 534 */ 535 public String getAccessibleActionDescription(int i) 536 { 537 String desc = null; 538 Action[] actions = getActions(); 539 if (i >= 0 && i < actions.length) 540 desc = (String) actions[i].getValue(Action.NAME); 541 return desc; 542 } 543 544 /** 545 * Performs the i-th action. Nothing happens if i is 546 * out of bounds. 547 * 548 * @param i - the action to perform 549 * 550 * @return true if the action was performed successfully 551 */ 552 public boolean doAccessibleAction(int i) 553 { 554 boolean ret = false; 555 Action[] actions = getActions(); 556 if (i >= 0 && i < actions.length) 557 { 558 ActionEvent ev = new ActionEvent(JTextComponent.this, 559 ActionEvent.ACTION_PERFORMED, null); 560 actions[i].actionPerformed(ev); 561 ret = true; 562 } 563 return ret; 564 } 565 566 /** 567 * Sets the text contents. 568 * 569 * @param s - the new text contents. 570 */ 571 public void setTextContents(String s) 572 { 573 setText(s); 574 } 575 576 /** 577 * Inserts the text at the given index. 578 * 579 * @param index - the index to insert the new text at. 580 * @param s - the new text 581 */ 582 public void insertTextAtIndex(int index, String s) 583 { 584 try 585 { 586 doc.insertString(index, s, null); 587 } 588 catch (BadLocationException ex) 589 { 590 // What should we do with this? 591 ex.printStackTrace(); 592 } 593 } 594 595 /** 596 * Gets the text between two indexes. 597 * 598 * @param start - the starting index (inclusive) 599 * @param end - the ending index (exclusive) 600 */ 601 public String getTextRange(int start, int end) 602 { 603 try 604 { 605 return JTextComponent.this.getText(start, end - start); 606 } 607 catch (BadLocationException ble) 608 { 609 return ""; 610 } 611 } 612 613 /** 614 * Deletes the text between two indexes. 615 * 616 * @param start - the starting index (inclusive) 617 * @param end - the ending index (exclusive) 618 */ 619 public void delete(int start, int end) 620 { 621 replaceText(start, end, ""); 622 } 623 624 /** 625 * Cuts the text between two indexes. The text is put 626 * into the system clipboard. 627 * 628 * @param start - the starting index (inclusive) 629 * @param end - the ending index (exclusive) 630 */ 631 public void cut(int start, int end) 632 { 633 JTextComponent.this.select(start, end); 634 JTextComponent.this.cut(); 635 } 636 637 /** 638 * Pastes the text from the system clipboard to the given index. 639 * 640 * @param start - the starting index 641 */ 642 public void paste(int start) 643 { 644 JTextComponent.this.setCaretPosition(start); 645 JTextComponent.this.paste(); 646 } 647 648 /** 649 * Replaces the text between two indexes with the given text. 650 * 651 * 652 * @param start - the starting index (inclusive) 653 * @param end - the ending index (exclusive) 654 * @param s - the text to paste 655 */ 656 public void replaceText(int start, int end, String s) 657 { 658 JTextComponent.this.select(start, end); 659 JTextComponent.this.replaceSelection(s); 660 } 661 662 /** 663 * Selects the text between two indexes. 664 * 665 * @param start - the starting index (inclusive) 666 * @param end - the ending index (exclusive) 667 */ 668 public void selectText(int start, int end) 669 { 670 JTextComponent.this.select(start, end); 671 } 672 673 /** 674 * Sets the attributes of all the text between two indexes. 675 * 676 * @param start - the starting index (inclusive) 677 * @param end - the ending index (exclusive) 678 * @param s - the new attribute set for the text in the range 679 */ 680 public void setAttributes(int start, int end, AttributeSet s) 681 { 682 if (doc instanceof StyledDocument) 683 { 684 StyledDocument sdoc = (StyledDocument) doc; 685 sdoc.setCharacterAttributes(start, end - start, s, true); 686 } 687 } 688 } 689 690 public static class KeyBinding 691 { 692 public KeyStroke key; 693 public String actionName; 694 695 /** 696 * Creates a new <code>KeyBinding</code> instance. 697 * 698 * @param key a <code>KeyStroke</code> value 699 * @param actionName a <code>String</code> value 700 */ 701 public KeyBinding(KeyStroke key, String actionName) 702 { 703 this.key = key; 704 this.actionName = actionName; 705 } 706 } 707 708 /** 709 * According to <a 710 * href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">this 711 * report</a>, a pair of private classes wraps a {@link 712 * javax.swing.text.Keymap} in the new {@link InputMap} / {@link 713 * ActionMap} interfaces, such that old Keymap-using code can make use of 714 * the new framework. 715 * 716 * <p>A little bit of experimentation with these classes reveals the following 717 * structure: 718 * 719 * <ul> 720 * 721 * <li>KeymapWrapper extends {@link InputMap} and holds a reference to 722 * the underlying {@link Keymap}.</li> 723 * 724 * <li>KeymapWrapper maps {@link KeyStroke} objects to {@link Action} 725 * objects, by delegation to the underlying {@link Keymap}.</li> 726 * 727 * <li>KeymapActionMap extends {@link ActionMap} also holds a reference to 728 * the underlying {@link Keymap} but only appears to use it for listing 729 * its keys. </li> 730 * 731 * <li>KeymapActionMap maps all {@link Action} objects to 732 * <em>themselves</em>, whether they exist in the underlying {@link 733 * Keymap} or not, and passes other objects to the parent {@link 734 * ActionMap} for resolving. 735 * 736 * </ul> 737 */ 738 739 private class KeymapWrapper extends InputMap 740 { 741 Keymap map; 742 743 public KeymapWrapper(Keymap k) 744 { 745 map = k; 746 } 747 748 public int size() 749 { 750 return map.getBoundKeyStrokes().length + super.size(); 751 } 752 753 public Object get(KeyStroke ks) 754 { 755 Action mapped = null; 756 Keymap m = map; 757 while(mapped == null && m != null) 758 { 759 mapped = m.getAction(ks); 760 if (mapped == null && ks.getKeyEventType() == KeyEvent.KEY_TYPED) 761 mapped = m.getDefaultAction(); 762 if (mapped == null) 763 m = m.getResolveParent(); 764 } 765 766 if (mapped == null) 767 return super.get(ks); 768 else 769 return mapped; 770 } 771 772 public KeyStroke[] keys() 773 { 774 KeyStroke[] superKeys = super.keys(); 775 KeyStroke[] mapKeys = map.getBoundKeyStrokes(); 776 KeyStroke[] bothKeys = new KeyStroke[superKeys.length + mapKeys.length]; 777 for (int i = 0; i < superKeys.length; ++i) 778 bothKeys[i] = superKeys[i]; 779 for (int i = 0; i < mapKeys.length; ++i) 780 bothKeys[i + superKeys.length] = mapKeys[i]; 781 return bothKeys; 782 } 783 784 public KeyStroke[] allKeys() 785 { 786 KeyStroke[] superKeys = super.allKeys(); 787 KeyStroke[] mapKeys = map.getBoundKeyStrokes(); 788 int skl = 0; 789 int mkl = 0; 790 if (superKeys != null) 791 skl = superKeys.length; 792 if (mapKeys != null) 793 mkl = mapKeys.length; 794 KeyStroke[] bothKeys = new KeyStroke[skl + mkl]; 795 for (int i = 0; i < skl; ++i) 796 bothKeys[i] = superKeys[i]; 797 for (int i = 0; i < mkl; ++i) 798 bothKeys[i + skl] = mapKeys[i]; 799 return bothKeys; 800 } 801 } 802 803 private class KeymapActionMap extends ActionMap 804 { 805 Keymap map; 806 807 public KeymapActionMap(Keymap k) 808 { 809 map = k; 810 } 811 812 public Action get(Object cmd) 813 { 814 if (cmd instanceof Action) 815 return (Action) cmd; 816 else 817 return super.get(cmd); 818 } 819 820 public int size() 821 { 822 return map.getBoundKeyStrokes().length + super.size(); 823 } 824 825 public Object[] keys() 826 { 827 Object[] superKeys = super.keys(); 828 Object[] mapKeys = map.getBoundKeyStrokes(); 829 Object[] bothKeys = new Object[superKeys.length + mapKeys.length]; 830 for (int i = 0; i < superKeys.length; ++i) 831 bothKeys[i] = superKeys[i]; 832 for (int i = 0; i < mapKeys.length; ++i) 833 bothKeys[i + superKeys.length] = mapKeys[i]; 834 return bothKeys; 835 } 836 837 public Object[] allKeys() 838 { 839 Object[] superKeys = super.allKeys(); 840 Object[] mapKeys = map.getBoundKeyStrokes(); 841 Object[] bothKeys = new Object[superKeys.length + mapKeys.length]; 842 for (int i = 0; i < superKeys.length; ++i) 843 bothKeys[i] = superKeys[i]; 844 for (int i = 0; i < mapKeys.length; ++i) 845 bothKeys[i + superKeys.length] = mapKeys[i]; 846 return bothKeys; 847 } 848 849 } 850 851 static class DefaultKeymap implements Keymap 852 { 853 String name; 854 Keymap parent; 855 Hashtable map; 856 Action defaultAction; 857 858 public DefaultKeymap(String name) 859 { 860 this.name = name; 861 this.map = new Hashtable(); 862 } 863 864 public void addActionForKeyStroke(KeyStroke key, Action a) 865 { 866 map.put(key, a); 867 } 868 869 /** 870 * Looks up a KeyStroke either in the current map or the parent Keymap; 871 * does <em>not</em> return the default action if lookup fails. 872 * 873 * @param key The KeyStroke to look up an Action for. 874 * 875 * @return The mapping for <code>key</code>, or <code>null</code> 876 * if no mapping exists in this Keymap or any of its parents. 877 */ 878 public Action getAction(KeyStroke key) 879 { 880 if (map.containsKey(key)) 881 return (Action) map.get(key); 882 else if (parent != null) 883 return parent.getAction(key); 884 else 885 return null; 886 } 887 888 public Action[] getBoundActions() 889 { 890 Action [] ret = new Action[map.size()]; 891 Enumeration e = map.elements(); 892 int i = 0; 893 while (e.hasMoreElements()) 894 { 895 ret[i++] = (Action) e.nextElement(); 896 } 897 return ret; 898 } 899 900 public KeyStroke[] getBoundKeyStrokes() 901 { 902 KeyStroke [] ret = new KeyStroke[map.size()]; 903 Enumeration e = map.keys(); 904 int i = 0; 905 while (e.hasMoreElements()) 906 { 907 ret[i++] = (KeyStroke) e.nextElement(); 908 } 909 return ret; 910 } 911 912 public Action getDefaultAction() 913 { 914 return defaultAction; 915 } 916 917 public KeyStroke[] getKeyStrokesForAction(Action a) 918 { 919 int i = 0; 920 Enumeration e = map.keys(); 921 while (e.hasMoreElements()) 922 { 923 if (map.get(e.nextElement()).equals(a)) 924 ++i; 925 } 926 KeyStroke [] ret = new KeyStroke[i]; 927 i = 0; 928 e = map.keys(); 929 while (e.hasMoreElements()) 930 { 931 KeyStroke k = (KeyStroke) e.nextElement(); 932 if (map.get(k).equals(a)) 933 ret[i++] = k; 934 } 935 return ret; 936 } 937 938 public String getName() 939 { 940 return name; 941 } 942 943 public Keymap getResolveParent() 944 { 945 return parent; 946 } 947 948 public boolean isLocallyDefined(KeyStroke key) 949 { 950 return map.containsKey(key); 951 } 952 953 public void removeBindings() 954 { 955 map.clear(); 956 } 957 958 public void removeKeyStrokeBinding(KeyStroke key) 959 { 960 map.remove(key); 961 } 962 963 public void setDefaultAction(Action a) 964 { 965 defaultAction = a; 966 } 967 968 public void setResolveParent(Keymap p) 969 { 970 parent = p; 971 } 972 } 973 974 class DefaultTransferHandler extends TransferHandler 975 { 976 public boolean canImport(JComponent component, DataFlavor[] flavors) 977 { 978 JTextComponent textComponent = (JTextComponent) component; 979 980 if (! (textComponent.isEnabled() 981 && textComponent.isEditable() 982 && flavors != null)) 983 return false; 984 985 for (int i = 0; i < flavors.length; ++i) 986 if (flavors[i].equals(DataFlavor.stringFlavor)) 987 return true; 988 989 return false; 990 } 991 992 public void exportToClipboard(JComponent component, Clipboard clipboard, 993 int action) 994 { 995 JTextComponent textComponent = (JTextComponent) component; 996 int start = textComponent.getSelectionStart(); 997 int end = textComponent.getSelectionEnd(); 998 999 if (start == end) 1000 return; 1001 1002 try 1003 { 1004 // Copy text to clipboard. 1005 String data = textComponent.getDocument().getText(start, end); 1006 StringSelection selection = new StringSelection(data); 1007 clipboard.setContents(selection, null); 1008 1009 // Delete selected text on cut action. 1010 if (action == MOVE) 1011 doc.remove(start, end - start); 1012 } 1013 catch (BadLocationException e) 1014 { 1015 // Ignore this and do nothing. 1016 } 1017 } 1018 1019 public int getSourceActions() 1020 { 1021 return NONE; 1022 } 1023 1024 public boolean importData(JComponent component, Transferable transferable) 1025 { 1026 DataFlavor flavor = null; 1027 DataFlavor[] flavors = transferable.getTransferDataFlavors(); 1028 1029 if (flavors == null) 1030 return false; 1031 1032 for (int i = 0; i < flavors.length; ++i) 1033 if (flavors[i].equals(DataFlavor.stringFlavor)) 1034 flavor = flavors[i]; 1035 1036 if (flavor == null) 1037 return false; 1038 1039 try 1040 { 1041 JTextComponent textComponent = (JTextComponent) component; 1042 String data = (String) transferable.getTransferData(flavor); 1043 textComponent.replaceSelection(data); 1044 return true; 1045 } 1046 catch (IOException e) 1047 { 1048 // Ignored. 1049 } 1050 catch (UnsupportedFlavorException e) 1051 { 1052 // Ignored. 1053 } 1054 1055 return false; 1056 } 1057 } 1058 1059 private static final long serialVersionUID = -8796518220218978795L; 1060 1061 public static final String DEFAULT_KEYMAP = "default"; 1062 public static final String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey"; 1063 1064 private static DefaultTransferHandler defaultTransferHandler; 1065 private static Hashtable keymaps = new Hashtable(); 1066 private Keymap keymap; 1067 private char focusAccelerator = '\0'; 1068 private NavigationFilter navigationFilter; 1069 1070 /** 1071 * Get a Keymap from the global keymap table, by name. 1072 * 1073 * @param n The name of the Keymap to look up 1074 * 1075 * @return A Keymap associated with the provided name, or 1076 * <code>null</code> if no such Keymap exists 1077 * 1078 * @see #addKeymap 1079 * @see #removeKeymap 1080 * @see #keymaps 1081 */ 1082 public static Keymap getKeymap(String n) 1083 { 1084 return (Keymap) keymaps.get(n); 1085 } 1086 1087 /** 1088 * Remove a Keymap from the global Keymap table, by name. 1089 * 1090 * @param n The name of the Keymap to remove 1091 * 1092 * @return The keymap removed from the global table 1093 * 1094 * @see #addKeymap 1095 * @see #getKeymap() 1096 * @see #keymaps 1097 */ 1098 public static Keymap removeKeymap(String n) 1099 { 1100 Keymap km = (Keymap) keymaps.get(n); 1101 keymaps.remove(n); 1102 return km; 1103 } 1104 1105 /** 1106 * Create a new Keymap with a specific name and parent, and add the new 1107 * Keymap to the global keymap table. The name may be <code>null</code>, 1108 * in which case the new Keymap will <em>not</em> be added to the global 1109 * Keymap table. The parent may also be <code>null</code>, which is 1110 * harmless. 1111 * 1112 * @param n The name of the new Keymap, or <code>null</code> 1113 * @param parent The parent of the new Keymap, or <code>null</code> 1114 * 1115 * @return The newly created Keymap 1116 * 1117 * @see #removeKeymap 1118 * @see #getKeymap() 1119 * @see #keymaps 1120 */ 1121 public static Keymap addKeymap(String n, Keymap parent) 1122 { 1123 Keymap k = new DefaultKeymap(n); 1124 k.setResolveParent(parent); 1125 if (n != null) 1126 keymaps.put(n, k); 1127 return k; 1128 } 1129 1130 /** 1131 * Get the current Keymap of this component. 1132 * 1133 * @return The component's current Keymap 1134 * 1135 * @see #setKeymap 1136 * @see #keymap 1137 */ 1138 public Keymap getKeymap() 1139 { 1140 return keymap; 1141 } 1142 1143 /** 1144 * Set the current Keymap of this component, installing appropriate 1145 * {@link KeymapWrapper} and {@link KeymapActionMap} objects in the 1146 * {@link InputMap} and {@link ActionMap} parent chains, respectively, 1147 * and fire a property change event with name <code>"keymap"</code>. 1148 * 1149 * @see #getKeymap() 1150 * @see #keymap 1151 */ 1152 public void setKeymap(Keymap k) 1153 { 1154 1155 // phase 1: replace the KeymapWrapper entry in the InputMap chain. 1156 // the goal here is to always maintain the following ordering: 1157 // 1158 // [InputMap]? -> [KeymapWrapper]? -> [InputMapUIResource]* 1159 // 1160 // that is to say, component-specific InputMaps need to remain children 1161 // of Keymaps, and Keymaps need to remain children of UI-installed 1162 // InputMaps (and the order of each group needs to be preserved, of 1163 // course). 1164 1165 KeymapWrapper kw = (k == null ? null : new KeymapWrapper(k)); 1166 InputMap childInputMap = getInputMap(JComponent.WHEN_FOCUSED); 1167 if (childInputMap == null) 1168 setInputMap(JComponent.WHEN_FOCUSED, kw); 1169 else 1170 { 1171 while (childInputMap.getParent() != null 1172 && !(childInputMap.getParent() instanceof KeymapWrapper) 1173 && !(childInputMap.getParent() instanceof InputMapUIResource)) 1174 childInputMap = childInputMap.getParent(); 1175 1176 // option 1: there is nobody to replace at the end of the chain 1177 if (childInputMap.getParent() == null) 1178 childInputMap.setParent(kw); 1179 1180 // option 2: there is already a KeymapWrapper in the chain which 1181 // needs replacing (possibly with its own parents, possibly without) 1182 else if (childInputMap.getParent() instanceof KeymapWrapper) 1183 { 1184 if (kw == null) 1185 childInputMap.setParent(childInputMap.getParent().getParent()); 1186 else 1187 { 1188 kw.setParent(childInputMap.getParent().getParent()); 1189 childInputMap.setParent(kw); 1190 } 1191 } 1192 1193 // option 3: there is an InputMapUIResource in the chain, which marks 1194 // the place where we need to stop and insert ourselves 1195 else if (childInputMap.getParent() instanceof InputMapUIResource) 1196 { 1197 if (kw != null) 1198 { 1199 kw.setParent(childInputMap.getParent()); 1200 childInputMap.setParent(kw); 1201 } 1202 } 1203 } 1204 1205 // phase 2: replace the KeymapActionMap entry in the ActionMap chain 1206 1207 KeymapActionMap kam = (k == null ? null : new KeymapActionMap(k)); 1208 ActionMap childActionMap = getActionMap(); 1209 if (childActionMap == null) 1210 setActionMap(kam); 1211 else 1212 { 1213 while (childActionMap.getParent() != null 1214 && !(childActionMap.getParent() instanceof KeymapActionMap) 1215 && !(childActionMap.getParent() instanceof ActionMapUIResource)) 1216 childActionMap = childActionMap.getParent(); 1217 1218 // option 1: there is nobody to replace at the end of the chain 1219 if (childActionMap.getParent() == null) 1220 childActionMap.setParent(kam); 1221 1222 // option 2: there is already a KeymapActionMap in the chain which 1223 // needs replacing (possibly with its own parents, possibly without) 1224 else if (childActionMap.getParent() instanceof KeymapActionMap) 1225 { 1226 if (kam == null) 1227 childActionMap.setParent(childActionMap.getParent().getParent()); 1228 else 1229 { 1230 kam.setParent(childActionMap.getParent().getParent()); 1231 childActionMap.setParent(kam); 1232 } 1233 } 1234 1235 // option 3: there is an ActionMapUIResource in the chain, which marks 1236 // the place where we need to stop and insert ourselves 1237 else if (childActionMap.getParent() instanceof ActionMapUIResource) 1238 { 1239 if (kam != null) 1240 { 1241 kam.setParent(childActionMap.getParent()); 1242 childActionMap.setParent(kam); 1243 } 1244 } 1245 } 1246 1247 // phase 3: update the explicit keymap field 1248 1249 Keymap old = keymap; 1250 keymap = k; 1251 firePropertyChange("keymap", old, k); 1252 } 1253 1254 /** 1255 * Resolves a set of bindings against a set of actions and inserts the 1256 * results into a {@link Keymap}. Specifically, for each provided binding 1257 * <code>b</code>, if there exists a provided action <code>a</code> such 1258 * that <code>a.getValue(Action.NAME) == b.ActionName</code> then an 1259 * entry is added to the Keymap mapping <code>b</code> to 1260 * <code>a</code>. 1261 * 1262 * @param map The Keymap to add new mappings to 1263 * @param bindings The set of bindings to add to the Keymap 1264 * @param actions The set of actions to resolve binding names against 1265 * 1266 * @see Action#NAME 1267 * @see Action#getValue 1268 * @see KeyBinding#actionName 1269 */ 1270 public static void loadKeymap(Keymap map, 1271 JTextComponent.KeyBinding[] bindings, 1272 Action[] actions) 1273 { 1274 Hashtable acts = new Hashtable(actions.length); 1275 for (int i = 0; i < actions.length; ++i) 1276 acts.put(actions[i].getValue(Action.NAME), actions[i]); 1277 for (int i = 0; i < bindings.length; ++i) 1278 if (acts.containsKey(bindings[i].actionName)) 1279 map.addActionForKeyStroke(bindings[i].key, (Action) acts.get(bindings[i].actionName)); 1280 } 1281 1282 /** 1283 * Returns the set of available Actions this component's associated 1284 * editor can run. Equivalent to calling 1285 * <code>getUI().getEditorKit().getActions()</code>. This set of Actions 1286 * is a reasonable value to provide as a parameter to {@link 1287 * #loadKeymap}, when resolving a set of {@link KeyBinding} objects 1288 * against this component. 1289 * 1290 * @return The set of available Actions on this component's {@link EditorKit} 1291 * 1292 * @see TextUI#getEditorKit 1293 * @see EditorKit#getActions() 1294 */ 1295 public Action[] getActions() 1296 { 1297 return getUI().getEditorKit(this).getActions(); 1298 } 1299 1300 // These are package-private to avoid an accessor method. 1301 Document doc; 1302 Caret caret; 1303 boolean editable; 1304 1305 private Highlighter highlighter; 1306 private Color caretColor; 1307 private Color disabledTextColor; 1308 private Color selectedTextColor; 1309 private Color selectionColor; 1310 private Insets margin; 1311 private boolean dragEnabled; 1312 1313 /** 1314 * Creates a new <code>JTextComponent</code> instance. 1315 */ 1316 public JTextComponent() 1317 { 1318 Keymap defkeymap = getKeymap(DEFAULT_KEYMAP); 1319 if (defkeymap == null) 1320 { 1321 defkeymap = addKeymap(DEFAULT_KEYMAP, null); 1322 defkeymap.setDefaultAction(new DefaultEditorKit.DefaultKeyTypedAction()); 1323 } 1324 1325 setFocusable(true); 1326 setEditable(true); 1327 enableEvents(AWTEvent.KEY_EVENT_MASK); 1328 setOpaque(true); 1329 updateUI(); 1330 } 1331 1332 public void setDocument(Document newDoc) 1333 { 1334 Document oldDoc = doc; 1335 try 1336 { 1337 if (oldDoc instanceof AbstractDocument) 1338 ((AbstractDocument) oldDoc).readLock(); 1339 1340 doc = newDoc; 1341 firePropertyChange("document", oldDoc, newDoc); 1342 } 1343 finally 1344 { 1345 if (oldDoc instanceof AbstractDocument) 1346 ((AbstractDocument) oldDoc).readUnlock(); 1347 } 1348 revalidate(); 1349 repaint(); 1350 } 1351 1352 public Document getDocument() 1353 { 1354 return doc; 1355 } 1356 1357 /** 1358 * Get the <code>AccessibleContext</code> of this object. 1359 * 1360 * @return an <code>AccessibleContext</code> object 1361 */ 1362 public AccessibleContext getAccessibleContext() 1363 { 1364 return new AccessibleJTextComponent(); 1365 } 1366 1367 public void setMargin(Insets m) 1368 { 1369 margin = m; 1370 } 1371 1372 public Insets getMargin() 1373 { 1374 return margin; 1375 } 1376 1377 public void setText(String text) 1378 { 1379 try 1380 { 1381 if (doc instanceof AbstractDocument) 1382 ((AbstractDocument) doc).replace(0, doc.getLength(), text, null); 1383 else 1384 { 1385 doc.remove(0, doc.getLength()); 1386 doc.insertString(0, text, null); 1387 } 1388 } 1389 catch (BadLocationException e) 1390 { 1391 // This can never happen. 1392 throw (InternalError) new InternalError().initCause(e); 1393 } 1394 } 1395 1396 /** 1397 * Retrieves the current text in this text document. 1398 * 1399 * @return the text 1400 * 1401 * @exception NullPointerException if the underlaying document is null 1402 */ 1403 public String getText() 1404 { 1405 if (doc == null) 1406 return null; 1407 1408 try 1409 { 1410 return doc.getText(0, doc.getLength()); 1411 } 1412 catch (BadLocationException e) 1413 { 1414 // This should never happen. 1415 return ""; 1416 } 1417 } 1418 1419 /** 1420 * Retrieves a part of the current text in this document. 1421 * 1422 * @param offset the postion of the first character 1423 * @param length the length of the text to retrieve 1424 * 1425 * @return the text 1426 * 1427 * @exception BadLocationException if arguments do not hold pre-conditions 1428 */ 1429 public String getText(int offset, int length) 1430 throws BadLocationException 1431 { 1432 return getDocument().getText(offset, length); 1433 } 1434 1435 /** 1436 * Retrieves the currently selected text in this text document. 1437 * 1438 * @return the selected text 1439 * 1440 * @exception NullPointerException if the underlaying document is null 1441 */ 1442 public String getSelectedText() 1443 { 1444 int start = getSelectionStart(); 1445 int offset = getSelectionEnd() - start; 1446 1447 if (offset <= 0) 1448 return null; 1449 1450 try 1451 { 1452 return doc.getText(start, offset); 1453 } 1454 catch (BadLocationException e) 1455 { 1456 // This should never happen. 1457 return null; 1458 } 1459 } 1460 1461 /** 1462 * Returns a string that specifies the name of the Look and Feel class 1463 * that renders this component. 1464 * 1465 * @return the string "TextComponentUI" 1466 */ 1467 public String getUIClassID() 1468 { 1469 return "TextComponentUI"; 1470 } 1471 1472 /** 1473 * Returns a string representation of this JTextComponent. 1474 */ 1475 protected String paramString() 1476 { 1477 // TODO: Do something useful here. 1478 return super.paramString(); 1479 } 1480 1481 /** 1482 * This method returns the label's UI delegate. 1483 * 1484 * @return The label's UI delegate. 1485 */ 1486 public TextUI getUI() 1487 { 1488 return (TextUI) ui; 1489 } 1490 1491 /** 1492 * This method sets the label's UI delegate. 1493 * 1494 * @param newUI The label's UI delegate. 1495 */ 1496 public void setUI(TextUI newUI) 1497 { 1498 super.setUI(newUI); 1499 } 1500 1501 /** 1502 * This method resets the label's UI delegate to the default UI for the 1503 * current look and feel. 1504 */ 1505 public void updateUI() 1506 { 1507 setUI((TextUI) UIManager.getUI(this)); 1508 } 1509 1510 public Dimension getPreferredScrollableViewportSize() 1511 { 1512 return getPreferredSize(); 1513 } 1514 1515 public int getScrollableUnitIncrement(Rectangle visible, int orientation, 1516 int direction) 1517 { 1518 // We return 1/10 of the visible area as documented in Sun's API docs. 1519 if (orientation == SwingConstants.HORIZONTAL) 1520 return visible.width / 10; 1521 else if (orientation == SwingConstants.VERTICAL) 1522 return visible.height / 10; 1523 else 1524 throw new IllegalArgumentException("orientation must be either " 1525 + "javax.swing.SwingConstants.VERTICAL " 1526 + "or " 1527 + "javax.swing.SwingConstants.HORIZONTAL" 1528 ); 1529 } 1530 1531 public int getScrollableBlockIncrement(Rectangle visible, int orientation, 1532 int direction) 1533 { 1534 // We return the whole visible area as documented in Sun's API docs. 1535 if (orientation == SwingConstants.HORIZONTAL) 1536 return visible.width; 1537 else if (orientation == SwingConstants.VERTICAL) 1538 return visible.height; 1539 else 1540 throw new IllegalArgumentException("orientation must be either " 1541 + "javax.swing.SwingConstants.VERTICAL " 1542 + "or " 1543 + "javax.swing.SwingConstants.HORIZONTAL" 1544 ); 1545 } 1546 1547 /** 1548 * Checks whether this text component it editable. 1549 * 1550 * @return true if editable, false otherwise 1551 */ 1552 public boolean isEditable() 1553 { 1554 return editable; 1555 } 1556 1557 /** 1558 * Enables/disabled this text component's editability. 1559 * 1560 * @param newValue true to make it editable, false otherwise. 1561 */ 1562 public void setEditable(boolean newValue) 1563 { 1564 if (editable == newValue) 1565 return; 1566 1567 boolean oldValue = editable; 1568 editable = newValue; 1569 firePropertyChange("editable", oldValue, newValue); 1570 } 1571 1572 /** 1573 * The <code>Caret</code> object used in this text component. 1574 * 1575 * @return the caret object 1576 */ 1577 public Caret getCaret() 1578 { 1579 return caret; 1580 } 1581 1582 /** 1583 * Sets a new <code>Caret</code> for this text component. 1584 * 1585 * @param newCaret the new <code>Caret</code> to set 1586 */ 1587 public void setCaret(Caret newCaret) 1588 { 1589 if (caret != null) 1590 caret.deinstall(this); 1591 1592 Caret oldCaret = caret; 1593 caret = newCaret; 1594 1595 if (caret != null) 1596 caret.install(this); 1597 1598 firePropertyChange("caret", oldCaret, newCaret); 1599 } 1600 1601 public Color getCaretColor() 1602 { 1603 return caretColor; 1604 } 1605 1606 public void setCaretColor(Color newColor) 1607 { 1608 Color oldCaretColor = caretColor; 1609 caretColor = newColor; 1610 firePropertyChange("caretColor", oldCaretColor, newColor); 1611 } 1612 1613 public Color getDisabledTextColor() 1614 { 1615 return disabledTextColor; 1616 } 1617 1618 public void setDisabledTextColor(Color newColor) 1619 { 1620 Color oldColor = disabledTextColor; 1621 disabledTextColor = newColor; 1622 firePropertyChange("disabledTextColor", oldColor, newColor); 1623 } 1624 1625 public Color getSelectedTextColor() 1626 { 1627 return selectedTextColor; 1628 } 1629 1630 public void setSelectedTextColor(Color newColor) 1631 { 1632 Color oldColor = selectedTextColor; 1633 selectedTextColor = newColor; 1634 firePropertyChange("selectedTextColor", oldColor, newColor); 1635 } 1636 1637 public Color getSelectionColor() 1638 { 1639 return selectionColor; 1640 } 1641 1642 public void setSelectionColor(Color newColor) 1643 { 1644 Color oldColor = selectionColor; 1645 selectionColor = newColor; 1646 firePropertyChange("selectionColor", oldColor, newColor); 1647 } 1648 1649 /** 1650 * Retrisves the current caret position. 1651 * 1652 * @return the current position 1653 */ 1654 public int getCaretPosition() 1655 { 1656 return caret.getDot(); 1657 } 1658 1659 /** 1660 * Sets the caret to a new position. 1661 * 1662 * @param position the new position 1663 */ 1664 public void setCaretPosition(int position) 1665 { 1666 if (doc == null) 1667 return; 1668 1669 if (position < 0 || position > doc.getLength()) 1670 throw new IllegalArgumentException(); 1671 1672 caret.setDot(position); 1673 } 1674 1675 /** 1676 * Moves the caret to a given position. This selects the text between 1677 * the old and the new position of the caret. 1678 */ 1679 public void moveCaretPosition(int position) 1680 { 1681 if (doc == null) 1682 return; 1683 1684 if (position < 0 || position > doc.getLength()) 1685 throw new IllegalArgumentException(); 1686 1687 caret.moveDot(position); 1688 } 1689 1690 public Highlighter getHighlighter() 1691 { 1692 return highlighter; 1693 } 1694 1695 public void setHighlighter(Highlighter newHighlighter) 1696 { 1697 if (highlighter != null) 1698 highlighter.deinstall(this); 1699 1700 Highlighter oldHighlighter = highlighter; 1701 highlighter = newHighlighter; 1702 1703 if (highlighter != null) 1704 highlighter.install(this); 1705 1706 firePropertyChange("highlighter", oldHighlighter, newHighlighter); 1707 } 1708 1709 /** 1710 * Returns the start postion of the currently selected text. 1711 * 1712 * @return the start postion 1713 */ 1714 public int getSelectionStart() 1715 { 1716 return Math.min(caret.getDot(), caret.getMark()); 1717 } 1718 1719 /** 1720 * Selects the text from the given postion to the selection end position. 1721 * 1722 * @param start the start positon of the selected text. 1723 */ 1724 public void setSelectionStart(int start) 1725 { 1726 select(start, getSelectionEnd()); 1727 } 1728 1729 /** 1730 * Returns the end postion of the currently selected text. 1731 * 1732 * @return the end postion 1733 */ 1734 public int getSelectionEnd() 1735 { 1736 return Math.max(caret.getDot(), caret.getMark()); 1737 } 1738 1739 /** 1740 * Selects the text from the selection start postion to the given position. 1741 * 1742 * @param end the end positon of the selected text. 1743 */ 1744 public void setSelectionEnd(int end) 1745 { 1746 select(getSelectionStart(), end); 1747 } 1748 1749 /** 1750 * Selects a part of the content of the text component. 1751 * 1752 * @param start the start position of the selected text 1753 * @param end the end position of the selected text 1754 */ 1755 public void select(int start, int end) 1756 { 1757 int length = doc.getLength(); 1758 1759 start = Math.max(start, 0); 1760 start = Math.min(start, length); 1761 1762 end = Math.max(end, start); 1763 end = Math.min(end, length); 1764 1765 setCaretPosition(start); 1766 moveCaretPosition(end); 1767 } 1768 1769 /** 1770 * Selects the whole content of the text component. 1771 */ 1772 public void selectAll() 1773 { 1774 select(0, doc.getLength()); 1775 } 1776 1777 public synchronized void replaceSelection(String content) 1778 { 1779 int dot = caret.getDot(); 1780 int mark = caret.getMark(); 1781 1782 // If content is empty delete selection. 1783 if (content == null) 1784 { 1785 caret.setDot(dot); 1786 return; 1787 } 1788 1789 try 1790 { 1791 int start = getSelectionStart(); 1792 int end = getSelectionEnd(); 1793 1794 // Remove selected text. 1795 if (dot != mark) 1796 doc.remove(start, end - start); 1797 1798 // Insert new text. 1799 doc.insertString(start, content, null); 1800 1801 // Set dot to new position, 1802 dot = start + content.length(); 1803 setCaretPosition(dot); 1804 1805 // and update it's magic position. 1806 caret.setMagicCaretPosition(modelToView(dot).getLocation()); 1807 } 1808 catch (BadLocationException e) 1809 { 1810 // This should never happen. 1811 } 1812 } 1813 1814 public boolean getScrollableTracksViewportHeight() 1815 { 1816 if (getParent() instanceof JViewport) 1817 return getParent().getHeight() > getPreferredSize().height; 1818 1819 return false; 1820 } 1821 1822 public boolean getScrollableTracksViewportWidth() 1823 { 1824 boolean res = false; 1825 Container c = getParent(); 1826 if (c instanceof JViewport) 1827 res = ((JViewport) c).getExtentSize().width > getPreferredSize().width; 1828 1829 return res; 1830 } 1831 1832 /** 1833 * Adds a <code>CaretListener</code> object to this text component. 1834 * 1835 * @param listener the listener to add 1836 */ 1837 public void addCaretListener(CaretListener listener) 1838 { 1839 listenerList.add(CaretListener.class, listener); 1840 } 1841 1842 /** 1843 * Removed a <code>CaretListener</code> object from this text component. 1844 * 1845 * @param listener the listener to remove 1846 */ 1847 public void removeCaretListener(CaretListener listener) 1848 { 1849 listenerList.remove(CaretListener.class, listener); 1850 } 1851 1852 /** 1853 * Returns all added <code>CaretListener</code> objects. 1854 * 1855 * @return an array of listeners 1856 */ 1857 public CaretListener[] getCaretListeners() 1858 { 1859 return (CaretListener[]) getListeners(CaretListener.class); 1860 } 1861 1862 /** 1863 * Notifies all registered <code>CaretListener</code> objects that the caret 1864 * was updated. 1865 * 1866 * @param event the event to send 1867 */ 1868 protected void fireCaretUpdate(CaretEvent event) 1869 { 1870 CaretListener[] listeners = getCaretListeners(); 1871 1872 for (int index = 0; index < listeners.length; ++index) 1873 listeners[index].caretUpdate(event); 1874 } 1875 1876 /** 1877 * Adds an <code>InputListener</code> object to this text component. 1878 * 1879 * @param listener the listener to add 1880 */ 1881 public void addInputMethodListener(InputMethodListener listener) 1882 { 1883 listenerList.add(InputMethodListener.class, listener); 1884 } 1885 1886 /** 1887 * Removes an <code>InputListener</code> object from this text component. 1888 * 1889 * @param listener the listener to remove 1890 */ 1891 public void removeInputMethodListener(InputMethodListener listener) 1892 { 1893 listenerList.remove(InputMethodListener.class, listener); 1894 } 1895 1896 /** 1897 * Returns all added <code>InputMethodListener</code> objects. 1898 * 1899 * @return an array of listeners 1900 */ 1901 public InputMethodListener[] getInputMethodListeners() 1902 { 1903 return (InputMethodListener[]) getListeners(InputMethodListener.class); 1904 } 1905 1906 public Rectangle modelToView(int position) throws BadLocationException 1907 { 1908 return getUI().modelToView(this, position); 1909 } 1910 1911 public boolean getDragEnabled() 1912 { 1913 return dragEnabled; 1914 } 1915 1916 public void setDragEnabled(boolean enabled) 1917 { 1918 dragEnabled = enabled; 1919 } 1920 1921 public int viewToModel(Point pt) 1922 { 1923 return getUI().viewToModel(this, pt); 1924 } 1925 1926 public void copy() 1927 { 1928 if (isEnabled()) 1929 doTransferAction("copy", TransferHandler.getCopyAction()); 1930 } 1931 1932 public void cut() 1933 { 1934 if (editable && isEnabled()) 1935 doTransferAction("cut", TransferHandler.getCutAction()); 1936 } 1937 1938 public void paste() 1939 { 1940 if (editable && isEnabled()) 1941 doTransferAction("paste", TransferHandler.getPasteAction()); 1942 } 1943 1944 private void doTransferAction(String name, Action action) 1945 { 1946 // Install default TransferHandler if none set. 1947 if (getTransferHandler() == null) 1948 { 1949 if (defaultTransferHandler == null) 1950 defaultTransferHandler = new DefaultTransferHandler(); 1951 1952 setTransferHandler(defaultTransferHandler); 1953 } 1954 1955 // Perform action. 1956 ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, 1957 action.getValue(Action.NAME).toString()); 1958 action.actionPerformed(event); 1959 } 1960 1961 public void setFocusAccelerator(char newKey) 1962 { 1963 if (focusAccelerator == newKey) 1964 return; 1965 1966 char oldKey = focusAccelerator; 1967 focusAccelerator = newKey; 1968 firePropertyChange(FOCUS_ACCELERATOR_KEY, oldKey, newKey); 1969 } 1970 1971 public char getFocusAccelerator() 1972 { 1973 return focusAccelerator; 1974 } 1975 1976 /** 1977 * @since 1.4 1978 */ 1979 public NavigationFilter getNavigationFilter() 1980 { 1981 return navigationFilter; 1982 } 1983 1984 /** 1985 * @since 1.4 1986 */ 1987 public void setNavigationFilter(NavigationFilter filter) 1988 { 1989 navigationFilter = filter; 1990 } 1991 1992 /** 1993 * Read and set the content this component. If not overridden, the 1994 * method reads the component content as a plain text. 1995 * 1996 * The second parameter of this method describes the input stream. It can 1997 * be String, URL, File and so on. If not null, this object is added to 1998 * the properties of the associated document under the key 1999 * {@link Document#StreamDescriptionProperty}. 2000 * 2001 * @param input an input stream to read from. 2002 * @param streamDescription an object, describing the stream. 2003 * 2004 * @throws IOException if the reader throws it. 2005 * 2006 * @see #getDocument() 2007 * @see Document#getProperty(Object) 2008 */ 2009 public void read(Reader input, Object streamDescription) 2010 throws IOException 2011 { 2012 if (streamDescription != null) 2013 { 2014 Document d = getDocument(); 2015 if (d != null) 2016 d.putProperty(Document.StreamDescriptionProperty, streamDescription); 2017 } 2018 2019 StringBuffer b = new StringBuffer(); 2020 int c; 2021 2022 // Read till -1 (EOF). 2023 while ((c = input.read()) >= 0) 2024 b.append((char) c); 2025 2026 setText(b.toString()); 2027 } 2028 2029 /** 2030 * Write the content of this component to the given stream. If not 2031 * overridden, the method writes the component content as a plain text. 2032 * 2033 * @param output the writer to write into. 2034 * 2035 * @throws IOException if the writer throws it. 2036 */ 2037 public void write(Writer output) 2038 throws IOException 2039 { 2040 output.write(getText()); 2041 } 2042 2043 /** 2044 * Returns the tooltip text for this text component for the given mouse 2045 * event. This forwards the call to 2046 * {@link TextUI#getToolTipText(JTextComponent, Point)}. 2047 * 2048 * @param ev the mouse event 2049 * 2050 * @return the tooltip text for this text component for the given mouse 2051 * event 2052 */ 2053 public String getToolTipText(MouseEvent ev) 2054 { 2055 return getUI().getToolTipText(this, ev.getPoint()); 2056 } 2057 }