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