001/* JTextComponent.java --
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import gnu.java.lang.CPStringBuilder;
042
043import java.awt.AWTEvent;
044import java.awt.Color;
045import java.awt.Container;
046import java.awt.Dimension;
047import java.awt.Insets;
048import java.awt.Point;
049import java.awt.Rectangle;
050import java.awt.Shape;
051import java.awt.datatransfer.Clipboard;
052import java.awt.datatransfer.DataFlavor;
053import java.awt.datatransfer.StringSelection;
054import java.awt.datatransfer.Transferable;
055import java.awt.datatransfer.UnsupportedFlavorException;
056import java.awt.event.ActionEvent;
057import java.awt.event.InputMethodListener;
058import java.awt.event.KeyEvent;
059import java.awt.event.MouseEvent;
060import java.io.IOException;
061import java.io.Reader;
062import java.io.Writer;
063import java.text.BreakIterator;
064import java.util.Enumeration;
065import java.util.Hashtable;
066
067import javax.accessibility.Accessible;
068import javax.accessibility.AccessibleAction;
069import javax.accessibility.AccessibleContext;
070import javax.accessibility.AccessibleEditableText;
071import javax.accessibility.AccessibleRole;
072import javax.accessibility.AccessibleState;
073import javax.accessibility.AccessibleStateSet;
074import javax.accessibility.AccessibleText;
075import javax.swing.Action;
076import javax.swing.ActionMap;
077import javax.swing.InputMap;
078import javax.swing.JComponent;
079import javax.swing.JViewport;
080import javax.swing.KeyStroke;
081import javax.swing.Scrollable;
082import javax.swing.SwingConstants;
083import javax.swing.TransferHandler;
084import javax.swing.UIManager;
085import javax.swing.event.CaretEvent;
086import javax.swing.event.CaretListener;
087import javax.swing.event.DocumentEvent;
088import javax.swing.event.DocumentListener;
089import javax.swing.plaf.ActionMapUIResource;
090import javax.swing.plaf.InputMapUIResource;
091import javax.swing.plaf.TextUI;
092
093public 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}