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    }