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