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 }