001    /* BasicTextUI.java --
002       Copyright (C) 2002, 2003, 2004, 2005, 2006  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.plaf.basic;
040    
041    import gnu.classpath.SystemProperties;
042    
043    import java.awt.Color;
044    import java.awt.Container;
045    import java.awt.Dimension;
046    import java.awt.Graphics;
047    import java.awt.HeadlessException;
048    import java.awt.Insets;
049    import java.awt.Point;
050    import java.awt.Rectangle;
051    import java.awt.Shape;
052    import java.awt.Toolkit;
053    import java.awt.datatransfer.Clipboard;
054    import java.awt.datatransfer.StringSelection;
055    import java.awt.event.FocusEvent;
056    import java.awt.event.FocusListener;
057    import java.beans.PropertyChangeEvent;
058    import java.beans.PropertyChangeListener;
059    
060    import javax.swing.Action;
061    import javax.swing.ActionMap;
062    import javax.swing.InputMap;
063    import javax.swing.JComponent;
064    import javax.swing.LookAndFeel;
065    import javax.swing.SwingConstants;
066    import javax.swing.SwingUtilities;
067    import javax.swing.TransferHandler;
068    import javax.swing.UIManager;
069    import javax.swing.event.DocumentEvent;
070    import javax.swing.event.DocumentListener;
071    import javax.swing.plaf.ActionMapUIResource;
072    import javax.swing.plaf.InputMapUIResource;
073    import javax.swing.plaf.TextUI;
074    import javax.swing.plaf.UIResource;
075    import javax.swing.text.AbstractDocument;
076    import javax.swing.text.AttributeSet;
077    import javax.swing.text.BadLocationException;
078    import javax.swing.text.Caret;
079    import javax.swing.text.DefaultCaret;
080    import javax.swing.text.DefaultEditorKit;
081    import javax.swing.text.DefaultHighlighter;
082    import javax.swing.text.Document;
083    import javax.swing.text.EditorKit;
084    import javax.swing.text.Element;
085    import javax.swing.text.Highlighter;
086    import javax.swing.text.JTextComponent;
087    import javax.swing.text.Keymap;
088    import javax.swing.text.Position;
089    import javax.swing.text.View;
090    import javax.swing.text.ViewFactory;
091    
092    /**
093     * The abstract base class from which the UI classes for Swings text
094     * components are derived. This provides most of the functionality for
095     * the UI classes.
096     *
097     * @author original author unknown
098     * @author Roman Kennke (roman@kennke.org)
099     */
100    public abstract class BasicTextUI extends TextUI
101      implements ViewFactory
102    {
103      /**
104       * A {@link DefaultCaret} that implements {@link UIResource}.
105       */
106      public static class BasicCaret extends DefaultCaret implements UIResource
107      {
108        public BasicCaret()
109        {
110          // Nothing to do here.
111        }
112      }
113    
114      /**
115       * A {@link DefaultHighlighter} that implements {@link UIResource}.
116       */
117      public static class BasicHighlighter extends DefaultHighlighter
118        implements UIResource
119      {
120        public BasicHighlighter()
121        {
122          // Nothing to do here.
123        }
124      }
125    
126      private static class FocusHandler
127        implements FocusListener
128      {
129        public void focusGained(FocusEvent e) 
130        {
131          // Nothing to do here.
132        }
133        public void focusLost(FocusEvent e)
134        {
135          JTextComponent textComponent = (JTextComponent) e.getComponent();
136          // Integrates Swing text components with the system clipboard:
137          // The idea is that if one wants to copy text around X11-style
138          // (select text and middle-click in the target component) the focus
139          // will move to the new component which gives the old focus owner the
140          // possibility to paste its selection into the clipboard.
141          if (!e.isTemporary()
142              && textComponent.getSelectionStart()
143                 != textComponent.getSelectionEnd())
144            {
145              SecurityManager sm = System.getSecurityManager();
146              try
147                {
148                  if (sm != null)
149                    sm.checkSystemClipboardAccess();
150                  
151                  Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection();
152                  if (cb != null)
153                    {
154                      StringSelection selection = new StringSelection(
155                          textComponent.getSelectedText());
156                      cb.setContents(selection, selection);
157                    }
158                }
159              catch (SecurityException se)
160                {
161                  // Not allowed to access the clipboard: Ignore and
162                  // do not access it.
163                }
164              catch (HeadlessException he)
165                {
166                  // There is no AWT: Ignore and do not access the
167                  // clipboard.
168                }
169              catch (IllegalStateException ise)
170              {
171                  // Clipboard is currently unavaible.
172              }
173            }
174        }
175      }
176    
177      /**
178       * This FocusListener triggers repaints on focus shift.
179       */
180      private static FocusListener focusListener;
181    
182      /**
183       * Receives notifications when properties of the text component change.
184       */
185      private class Handler
186        implements PropertyChangeListener, DocumentListener
187      {
188        /**
189         * Notifies when a property of the text component changes.
190         *
191         * @param event the PropertyChangeEvent describing the change
192         */
193        public void propertyChange(PropertyChangeEvent event)
194        {
195          if (event.getPropertyName().equals("document"))
196            {
197              // Document changed.
198              Object oldValue = event.getOldValue();
199              if (oldValue != null)
200                {
201                  Document oldDoc = (Document) oldValue;
202                  oldDoc.removeDocumentListener(handler);
203                }
204              Object newValue = event.getNewValue();
205              if (newValue != null)
206                {
207                  Document newDoc = (Document) newValue;
208                  newDoc.addDocumentListener(handler);
209                }
210              modelChanged();
211            }
212    
213          BasicTextUI.this.propertyChange(event);
214        }
215    
216        /**
217         * Notification about a document change event.
218         *
219         * @param ev the DocumentEvent describing the change
220         */
221        public void changedUpdate(DocumentEvent ev)
222        {
223          // Updates are forwarded to the View even if 'getVisibleEditorRect'
224          // method returns null. This means the View classes have to be
225          // aware of that possibility.
226          rootView.changedUpdate(ev, getVisibleEditorRect(),
227                                 rootView.getViewFactory());
228        }
229    
230        /**
231         * Notification about a document insert event.
232         *
233         * @param ev the DocumentEvent describing the insertion
234         */
235        public void insertUpdate(DocumentEvent ev)
236        {
237          // Updates are forwarded to the View even if 'getVisibleEditorRect'
238          // method returns null. This means the View classes have to be
239          // aware of that possibility.
240          rootView.insertUpdate(ev, getVisibleEditorRect(),
241                                rootView.getViewFactory());
242        }
243    
244        /**
245         * Notification about a document removal event.
246         *
247         * @param ev the DocumentEvent describing the removal
248         */
249        public void removeUpdate(DocumentEvent ev)
250        {
251          // Updates are forwarded to the View even if 'getVisibleEditorRect'
252          // method returns null. This means the View classes have to be
253          // aware of that possibility.
254          rootView.removeUpdate(ev, getVisibleEditorRect(),
255                                rootView.getViewFactory());
256        }
257    
258      }
259    
260      /**
261       * This view forms the root of the View hierarchy. However, it delegates
262       * most calls to another View which is the real root of the hierarchy.
263       * The purpose is to make sure that all Views in the hierarchy, including
264       * the (real) root have a well-defined parent to which they can delegate
265       * calls like {@link #preferenceChanged}, {@link #getViewFactory} and
266       * {@link #getContainer}.
267       */
268      private class RootView extends View
269      {
270        /** The real root view. */
271        private View view;
272    
273        /**
274         * Creates a new RootView.
275         */
276        public RootView()
277        {
278          super(null);
279        }
280    
281        /**
282         * Returns the ViewFactory for this RootView. If the current EditorKit
283         * provides a ViewFactory, this is used. Otherwise the TextUI itself
284         * is returned as a ViewFactory.
285         *
286         * @return the ViewFactory for this RootView
287         */
288        public ViewFactory getViewFactory()
289        {
290          ViewFactory factory = null;
291          EditorKit editorKit = BasicTextUI.this.getEditorKit(getComponent());
292          factory = editorKit.getViewFactory();
293          if (factory == null)
294            factory = BasicTextUI.this;
295          return factory;
296        }
297    
298        /**
299         * Indicates that the preferences of one of the child view has changed.
300         * This calls revalidate on the text component.
301         *
302         * @param v the child view which's preference has changed
303         * @param width <code>true</code> if the width preference has changed
304         * @param height <code>true</code> if the height preference has changed
305         */
306        public void preferenceChanged(View v, boolean width, boolean height)
307        {
308          textComponent.revalidate();
309        }
310    
311        /**
312         * Sets the real root view.
313         *
314         * @param v the root view to set
315         */
316        public void setView(View v)
317        {
318          if (view != null)
319            view.setParent(null);
320          
321          if (v != null)
322            v.setParent(this);
323    
324          view = v;
325        }
326    
327        /**
328         * Returns the real root view, regardless of the index.
329         *
330         * @param index not used here
331         *
332         * @return the real root view, regardless of the index.
333         */
334        public View getView(int index)
335        {
336          return view;
337        }
338    
339        /**
340         * Returns <code>1</code> since the RootView always contains one
341         * child, that is the real root of the View hierarchy.
342         *
343         * @return <code>1</code> since the RootView always contains one
344         *         child, that is the real root of the View hierarchy
345         */
346        public int getViewCount()
347        {
348          int count = 0;
349          if (view != null)
350            count = 1;
351          return count;
352        }
353    
354        /**
355         * Returns the <code>Container</code> that contains this view. This
356         * normally will be the text component that is managed by this TextUI.
357         *
358         * @return the <code>Container</code> that contains this view
359         */
360        public Container getContainer()
361        {
362          return textComponent;
363        }
364    
365        /**
366         * Sets the size of the renderer. This is synchronized because that
367         * potentially triggers layout and we don't want more than one thread
368         * playing with the layout information.
369         */
370        public synchronized void setSize(float w, float h)
371        {
372          if (view != null)
373            view.setSize(w, h);
374        }
375    
376        /**
377         * Paints the view. This is delegated to the real root view.
378         *
379         * @param g the <code>Graphics</code> context to paint to
380         * @param s the allocation for the View
381         */
382        public void paint(Graphics g, Shape s)
383        {
384          if (view != null)
385            {
386              Rectangle b = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
387              setSize(b.width, b.height);
388              view.paint(g, s);
389            }
390        }
391    
392    
393        /**
394         * Maps a position in the document into the coordinate space of the View.
395         * The output rectangle usually reflects the font height but has a width
396         * of zero.
397         *
398         * This is delegated to the real root view.
399         *
400         * @param position the position of the character in the model
401         * @param a the area that is occupied by the view
402         * @param bias either {@link Position.Bias#Forward} or
403         *        {@link Position.Bias#Backward} depending on the preferred
404         *        direction bias. If <code>null</code> this defaults to
405         *        <code>Position.Bias.Forward</code>
406         *
407         * @return a rectangle that gives the location of the document position
408         *         inside the view coordinate space
409         *
410         * @throws BadLocationException if <code>pos</code> is invalid
411         * @throws IllegalArgumentException if b is not one of the above listed
412         *         valid values
413         */
414        public Shape modelToView(int position, Shape a, Position.Bias bias)
415          throws BadLocationException
416        {
417          return view.modelToView(position, a, bias);
418        }
419    
420        /**
421         * Maps coordinates from the <code>View</code>'s space into a position
422         * in the document model.
423         *
424         * @param x the x coordinate in the view space
425         * @param y the y coordinate in the view space
426         * @param a the allocation of this <code>View</code>
427         * @param b the bias to use
428         *
429         * @return the position in the document that corresponds to the screen
430         *         coordinates <code>x, y</code>
431         */
432        public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
433        {
434          return view.viewToModel(x, y, a, b);
435        }
436    
437        /**
438         * Notification about text insertions. These are forwarded to the
439         * real root view.
440         *
441         * @param ev the DocumentEvent describing the change
442         * @param shape the current allocation of the view's display
443         * @param vf the ViewFactory to use for creating new Views
444         */
445        public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
446        {
447          if (view != null)
448            view.insertUpdate(ev, shape, vf);
449        }
450    
451        /**
452         * Notification about text removals. These are forwarded to the
453         * real root view.
454         *
455         * @param ev the DocumentEvent describing the change
456         * @param shape the current allocation of the view's display
457         * @param vf the ViewFactory to use for creating new Views
458         */
459        public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
460        {
461          if (view != null)
462            view.removeUpdate(ev, shape, vf);
463        }
464    
465        /**
466         * Notification about text changes. These are forwarded to the
467         * real root view.
468         *
469         * @param ev the DocumentEvent describing the change
470         * @param shape the current allocation of the view's display
471         * @param vf the ViewFactory to use for creating new Views
472         */
473        public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
474        {
475          if (view != null)
476            view.changedUpdate(ev, shape, vf);
477        }
478    
479        /**
480         * Returns the document position that is (visually) nearest to the given
481         * document position <code>pos</code> in the given direction <code>d</code>.
482         *
483         * @param pos the document position
484         * @param b the bias for <code>pos</code>
485         * @param a the allocation for the view
486         * @param d the direction, must be either {@link SwingConstants#NORTH},
487         *        {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
488         *        {@link SwingConstants#EAST}
489         * @param biasRet an array of {@link Position.Bias} that can hold at least
490         *        one element, which is filled with the bias of the return position
491         *        on method exit
492         *
493         * @return the document position that is (visually) nearest to the given
494         *         document position <code>pos</code> in the given direction
495         *         <code>d</code>
496         *
497         * @throws BadLocationException if <code>pos</code> is not a valid offset in
498         *         the document model
499         */
500        public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
501                                             int d, Position.Bias[] biasRet)
502          throws BadLocationException
503        {
504          return view.getNextVisualPositionFrom(pos, b, a, d, biasRet);
505        }
506    
507        /**
508         * Returns the startOffset of this view, which is always the beginning
509         * of the document.
510         *
511         * @return the startOffset of this view
512         */
513        public int getStartOffset()
514        {
515          return 0;
516        }
517    
518        /**
519         * Returns the endOffset of this view, which is always the end
520         * of the document.
521         *
522         * @return the endOffset of this view
523         */
524        public int getEndOffset()
525        {
526          return getDocument().getLength();
527        }
528    
529        /**
530         * Returns the document associated with this view.
531         *
532         * @return the document associated with this view
533         */
534        public Document getDocument()
535        {
536          return textComponent.getDocument();
537        }
538    
539        /**
540         * Returns the attributes, which is null for the RootView.
541         */
542        public AttributeSet getAttributes()
543        {
544          return null;
545        }
546    
547        /**
548         * Overridden to forward to the view.
549         */
550        public float getPreferredSpan(int axis)
551        {
552          // The RI returns 10 in the degenerate case.
553          float span = 10;
554          if (view != null)
555            span = view.getPreferredSpan(axis);
556          return span;
557        }
558    
559        /**
560         * Overridden to forward to the real view.
561         */
562        public float getMinimumSpan(int axis)
563        {
564          // The RI returns 10 in the degenerate case.
565          float span = 10;
566          if (view != null)
567            span = view.getMinimumSpan(axis);
568          return span;
569        }
570    
571        /**
572         * Overridden to return Integer.MAX_VALUE.
573         */
574        public float getMaximumSpan(int axis)
575        {
576          // The RI returns Integer.MAX_VALUE here, regardless of the real view's
577          // maximum size.
578          return Integer.MAX_VALUE;
579        }
580      }
581    
582      /**
583       * The EditorKit used by this TextUI.
584       */
585      private static EditorKit kit;
586    
587      /**
588       * The combined event handler for text components.
589       *
590       * This is package private to avoid accessor methods.
591       */
592      Handler handler;
593    
594      /**
595       * The root view.
596       *
597       * This is package private to avoid accessor methods.
598       */
599      RootView rootView;
600    
601      /**
602       * The text component that we handle.
603       */
604      JTextComponent textComponent;
605    
606      /**
607       * Creates a new <code>BasicTextUI</code> instance.
608       */
609      public BasicTextUI()
610      {
611        // Nothing to do here.
612      }
613    
614      /**
615       * Creates a {@link Caret} that should be installed into the text component.
616       *
617       * @return a caret that should be installed into the text component
618       */
619      protected Caret createCaret()
620      {
621        return new BasicCaret();
622      }
623    
624      /**
625       * Creates a {@link Highlighter} that should be installed into the text
626       * component.
627       *
628       * @return a <code>Highlighter</code> for the text component
629       */
630      protected Highlighter createHighlighter()
631      {
632        return new BasicHighlighter();
633      }
634    
635      /**
636       * The text component that is managed by this UI.
637       *
638       * @return the text component that is managed by this UI
639       */
640      protected final JTextComponent getComponent()
641      {
642        return textComponent;
643      }
644    
645      /**
646       * Installs this UI on the text component.
647       *
648       * @param c the text component on which to install the UI
649       */
650      public void installUI(final JComponent c)
651      {
652        textComponent = (JTextComponent) c;
653    
654        if (rootView == null)
655          rootView = new RootView();
656    
657        installDefaults();
658        installFixedDefaults();
659    
660        // These listeners must be installed outside of installListeners(),
661        // because overriding installListeners() doesn't prevent installing
662        // these in the RI, but overriding isntallUI() does.
663        if (handler == null)
664          handler = new Handler();
665        textComponent.addPropertyChangeListener(handler);
666        Document doc = textComponent.getDocument();
667        if (doc == null)
668          {
669            // The Handler takes care of installing the necessary listeners
670            // on the document here.
671            doc = getEditorKit(textComponent).createDefaultDocument();
672            textComponent.setDocument(doc);
673          }
674        else
675          {
676            // Must install the document listener.
677            doc.addDocumentListener(handler);
678            modelChanged();
679          }
680    
681        installListeners();
682        installKeyboardActions();
683      }
684    
685      /**
686       * Installs UI defaults on the text components.
687       */
688      protected void installDefaults()
689      {
690        String prefix = getPropertyPrefix();
691        // Install the standard properties.
692        LookAndFeel.installColorsAndFont(textComponent, prefix + ".background",
693                                         prefix + ".foreground", prefix + ".font");
694        LookAndFeel.installBorder(textComponent, prefix + ".border");
695    
696        // Some additional text component only properties.
697        Color color = textComponent.getCaretColor();
698        if (color == null || color instanceof UIResource)
699          {
700            color = UIManager.getColor(prefix + ".caretForeground");
701            textComponent.setCaretColor(color);
702          }
703    
704        // Fetch the colors for enabled/disabled text components.
705        color = textComponent.getDisabledTextColor();
706        if (color == null || color instanceof UIResource)
707          {
708            color = UIManager.getColor(prefix + ".inactiveForeground");
709            textComponent.setDisabledTextColor(color);
710          }
711        color = textComponent.getSelectedTextColor();
712        if (color == null || color instanceof UIResource)
713          {
714            color = UIManager.getColor(prefix  + ".selectionForeground");
715            textComponent.setSelectedTextColor(color);
716          }
717        color = textComponent.getSelectionColor();
718        if (color == null || color instanceof UIResource)
719          {
720            color = UIManager.getColor(prefix  + ".selectionBackground");
721            textComponent.setSelectionColor(color);    
722          }
723    
724        Insets margin = textComponent.getMargin();
725        if (margin == null || margin instanceof UIResource)
726          {
727            margin = UIManager.getInsets(prefix + ".margin");
728            textComponent.setMargin(margin);
729          }
730    
731      }
732    
733      /**
734       * Installs defaults that can't be overridden by overriding
735       * installDefaults().
736       */
737      private void installFixedDefaults()
738      {
739        String prefix = getPropertyPrefix();
740        Caret caret = textComponent.getCaret();
741        if (caret == null || caret instanceof UIResource)
742          {
743            caret = createCaret();
744            textComponent.setCaret(caret);
745            caret.setBlinkRate(UIManager.getInt(prefix + ".caretBlinkRate"));
746          }
747    
748        Highlighter highlighter = textComponent.getHighlighter();
749        if (highlighter == null || highlighter instanceof UIResource)
750          textComponent.setHighlighter(createHighlighter());
751    
752      }
753    
754      /**
755       * Install all listeners on the text component.
756       */
757      protected void installListeners()
758      {
759        // 
760        if (SystemProperties.getProperty("gnu.swing.text.no-xlike-clipboard")
761            == null)
762          {
763            if (focusListener == null)
764              focusListener = new FocusHandler();
765            textComponent.addFocusListener(focusListener);
766          }
767      }
768    
769      /**
770       * Returns the name of the keymap for this type of TextUI.
771       * 
772       * This is implemented so that the classname of this TextUI
773       * without the package prefix is returned. This way subclasses
774       * don't have to override this method.
775       * 
776       * @return the name of the keymap for this TextUI
777       */
778      protected String getKeymapName()
779      {
780        String fullClassName = getClass().getName();
781        int index = fullClassName.lastIndexOf('.');
782        String className = fullClassName.substring(index + 1);
783        return className;
784      }
785    
786      /**
787       * Creates the {@link Keymap} that is installed on the text component.
788       *
789       * @return the {@link Keymap} that is installed on the text component
790       */
791      protected Keymap createKeymap()
792      {
793        String keymapName = getKeymapName();
794        Keymap keymap = JTextComponent.getKeymap(keymapName);
795        if (keymap == null)
796          {
797            Keymap parentMap =
798              JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
799            keymap = JTextComponent.addKeymap(keymapName, parentMap);
800            Object val = UIManager.get(getPropertyPrefix() + ".keyBindings");
801            if (val != null && val instanceof JTextComponent.KeyBinding[])
802              {
803                JTextComponent.KeyBinding[] bindings =
804                  (JTextComponent.KeyBinding[]) val;
805                JTextComponent.loadKeymap(keymap, bindings,
806                                          getComponent().getActions());
807              }
808          }
809        return keymap;
810      }
811    
812      /**
813       * Installs the keyboard actions on the text components.
814       */
815      protected void installKeyboardActions()
816      {
817        // This is only there for backwards compatibility.
818        textComponent.setKeymap(createKeymap());
819    
820        // load any bindings for the newer InputMap / ActionMap interface
821        SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED,
822                                         getInputMap());
823        SwingUtilities.replaceUIActionMap(textComponent, getActionMap());
824      }
825      
826      /**
827       * Creates an ActionMap to be installed on the text component.
828       * 
829       * @return an ActionMap to be installed on the text component
830       */
831      private ActionMap getActionMap()
832      {
833        // Note: There are no .actionMap entries in the standard L&Fs. However,
834        // with the RI it is possible to install action maps via such keys, so
835        // we must load them too. It can be observed that when there is no
836        // .actionMap entry in the UIManager, one gets installed after a text
837        // component of that type has been loaded.
838        String prefix = getPropertyPrefix();
839        String amName = prefix + ".actionMap";
840        ActionMap am = (ActionMap) UIManager.get(amName);
841        if (am == null)
842          {
843            am = createActionMap();
844            UIManager.put(amName, am);
845          }
846    
847        ActionMap map = new ActionMapUIResource();
848        map.setParent(am);
849    
850        return map;
851      }
852    
853      /**
854       * Creates a default ActionMap for text components that have no UI default
855       * for this (the standard for the built-in L&Fs). The ActionMap is copied
856       * from the text component's getActions() method.
857       *
858       * @returna default ActionMap
859       */
860      private ActionMap createActionMap()
861      {
862        ActionMap am = new ActionMapUIResource();
863        Action[] actions = textComponent.getActions();
864        for (int i = actions.length - 1; i >= 0; i--)
865          {
866            Action action = actions[i];
867            am.put(action.getValue(Action.NAME), action);
868          }
869        // Add TransferHandler's actions here. They don't seem to be in the
870        // JTextComponent's default actions, and I can't make up a better place
871        // to add them.
872        Action copyAction = TransferHandler.getCopyAction();
873        am.put(copyAction.getValue(Action.NAME), copyAction);
874        Action cutAction = TransferHandler.getCutAction();
875        am.put(cutAction.getValue(Action.NAME), cutAction);
876        Action pasteAction = TransferHandler.getPasteAction();
877        am.put(pasteAction.getValue(Action.NAME), pasteAction);
878    
879        return am;
880      }
881    
882      /**
883       * Gets the input map for the specified <code>condition</code>.
884       *
885       * @return the InputMap for the specified condition
886       */
887      private InputMap getInputMap()
888      {
889        InputMap im = new InputMapUIResource();
890        String prefix = getPropertyPrefix();
891        InputMap shared =
892          (InputMap) SharedUIDefaults.get(prefix + ".focusInputMap");
893        if (shared != null)
894          im.setParent(shared);
895        return im;
896      }
897    
898      /**
899       * Uninstalls this TextUI from the text component.
900       *
901       * @param component the text component to uninstall the UI from
902       */
903      public void uninstallUI(final JComponent component)
904      {
905        textComponent.removePropertyChangeListener(handler);
906        textComponent.getDocument().removeDocumentListener(handler);
907        rootView.setView(null);
908    
909        uninstallDefaults();
910        uninstallFixedDefaults();
911        uninstallListeners();
912        uninstallKeyboardActions();
913    
914        textComponent = null;
915      }
916    
917      /**
918       * Uninstalls all default properties that have previously been installed by
919       * this UI.
920       */
921      protected void uninstallDefaults()
922      {
923        if (textComponent.getCaretColor() instanceof UIResource)
924          textComponent.setCaretColor(null);
925        if (textComponent.getSelectionColor() instanceof UIResource)
926          textComponent.setSelectionColor(null);
927        if (textComponent.getDisabledTextColor() instanceof UIResource)
928          textComponent.setDisabledTextColor(null);
929        if (textComponent.getSelectedTextColor() instanceof UIResource)
930          textComponent.setSelectedTextColor(null);
931        LookAndFeel.uninstallBorder(textComponent);
932        if (textComponent.getMargin() instanceof UIResource)
933          textComponent.setMargin(null);
934      }
935    
936      /**
937       * Uninstalls additional fixed defaults that were installed
938       * by installFixedDefaults().
939       */
940      private void uninstallFixedDefaults()
941      {
942        if (textComponent.getCaret() instanceof UIResource)
943          textComponent.setCaret(null);
944        if (textComponent.getHighlighter() instanceof UIResource)
945          textComponent.setHighlighter(null);
946      }
947    
948      /**
949       * Uninstalls all listeners that have previously been installed by
950       * this UI.
951       */
952      protected void uninstallListeners()
953      {
954        // Don't nullify the focusListener field, as it is static and shared
955        // between components.
956        if (focusListener != null)
957          textComponent.removeFocusListener(focusListener);
958      }
959    
960      /**
961       * Uninstalls all keyboard actions that have previously been installed by
962       * this UI.
963       */
964      protected void uninstallKeyboardActions()
965      {
966        SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED, 
967                                         null);
968        SwingUtilities.replaceUIActionMap(textComponent, null);
969      }
970    
971      /**
972       * Returns the property prefix by which the text component's UIDefaults
973       * are looked up.
974       *
975       * @return the property prefix by which the text component's UIDefaults
976       *     are looked up
977       */
978      protected abstract String getPropertyPrefix();
979    
980      /**
981       * Returns the preferred size of the text component.
982       *
983       * @param c not used here
984       *
985       * @return the preferred size of the text component
986       */
987      public Dimension getPreferredSize(JComponent c)
988      {
989        Dimension d = c.getSize();
990        Insets i = c.getInsets();
991        // We need to lock here, since we require the view hierarchy to _not_
992        // change in between.
993        float w;
994        float h;
995        Document doc = textComponent.getDocument();
996        if (doc instanceof AbstractDocument)
997          ((AbstractDocument) doc).readLock();
998        try
999          {
1000            if (d.width > (i.left + i.right) && d.height > (i.top + i.bottom))
1001              {
1002                rootView.setSize(d.width - i.left - i.right,
1003                                 d.height - i.top - i.bottom);
1004              }
1005            else
1006              {
1007                // Not laid out yet. Force some pseudo size.
1008                rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
1009              }
1010            w = rootView.getPreferredSpan(View.X_AXIS);
1011            h = rootView.getPreferredSpan(View.Y_AXIS);
1012          }
1013        finally
1014          {
1015            if (doc instanceof AbstractDocument)
1016              ((AbstractDocument) doc).readUnlock();
1017          }
1018        Dimension size =  new Dimension((int) w + i.left + i.right,
1019                             (int) h + i.top + i.bottom);
1020        return size;
1021      }
1022    
1023      /**
1024       * Returns the maximum size for text components that use this UI.
1025       *
1026       * This returns (Integer.MAX_VALUE, Integer.MAX_VALUE).
1027       *
1028       * @param c not used here
1029       *
1030       * @return the maximum size for text components that use this UI
1031       */
1032      public Dimension getMaximumSize(JComponent c)
1033      {
1034        Dimension d = new Dimension();
1035        Insets i = c.getInsets();
1036        Document doc = textComponent.getDocument();
1037        // We need to lock here, since we require the view hierarchy to _not_
1038        // change in between.
1039        if (doc instanceof AbstractDocument)
1040          ((AbstractDocument) doc).readLock();
1041        try
1042          {
1043            // Check for overflow here.
1044            d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS)
1045                                     + i.left + i.right, Integer.MAX_VALUE);
1046            d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS)
1047                                      + i.top + i.bottom, Integer.MAX_VALUE);
1048          }
1049        finally
1050          {
1051            if (doc instanceof AbstractDocument)
1052              ((AbstractDocument) doc).readUnlock();
1053          }
1054        return d;
1055      }
1056    
1057      /**
1058       * Returns the minimum size for text components. This returns the size
1059       * of the component's insets.
1060       *
1061       * @return the minimum size for text components
1062       */
1063      public Dimension getMinimumSize(JComponent c)
1064      {
1065        Dimension d = new Dimension();
1066        Document doc = textComponent.getDocument();
1067        // We need to lock here, since we require the view hierarchy to _not_
1068        // change in between.
1069        if (doc instanceof AbstractDocument)
1070          ((AbstractDocument) doc).readLock();
1071        try
1072          {
1073            d.width = (int) rootView.getMinimumSpan(View.X_AXIS);
1074            d.height = (int) rootView.getMinimumSpan(View.Y_AXIS);
1075          }
1076        finally
1077          {
1078            if (doc instanceof AbstractDocument)
1079              ((AbstractDocument) doc).readUnlock();
1080          }
1081        Insets i = c.getInsets();
1082        d.width += i.left + i.right;
1083        d.height += i.top + i.bottom;
1084        return d;
1085      }
1086    
1087      /**
1088       * Paints the text component. This acquires a read lock on the model and then
1089       * calls {@link #paintSafely(Graphics)} in order to actually perform the
1090       * painting.
1091       *
1092       * @param g the <code>Graphics</code> context to paint to
1093       * @param c not used here
1094       */
1095      public final void paint(Graphics g, JComponent c)
1096      {
1097        try
1098          {
1099            Document doc = textComponent.getDocument();
1100            if (doc instanceof AbstractDocument)
1101              {
1102                AbstractDocument aDoc = (AbstractDocument) doc;
1103                aDoc.readLock();
1104              }
1105            paintSafely(g);
1106          }
1107        finally
1108          {
1109            Document doc = textComponent.getDocument();
1110            if (doc instanceof AbstractDocument)
1111              {
1112                AbstractDocument aDoc = (AbstractDocument) doc;
1113                aDoc.readUnlock();
1114              }
1115          }
1116      }
1117    
1118      /**
1119       * This paints the text component while beeing sure that the model is not
1120       * modified while painting.
1121       *
1122       * The following is performed in this order:
1123       * <ol>
1124       * <li>If the text component is opaque, the background is painted by
1125       * calling {@link #paintBackground(Graphics)}.</li>
1126       * <li>If there is a highlighter, the highlighter is painted.</li>
1127       * <li>The view hierarchy is painted.</li>
1128       * <li>The Caret is painter.</li>
1129       * </ol>
1130       *
1131       * @param g the <code>Graphics</code> context to paint to
1132       */
1133      protected void paintSafely(Graphics g)
1134      {
1135        Caret caret = textComponent.getCaret();
1136        Highlighter highlighter = textComponent.getHighlighter();
1137    
1138        if (textComponent.isOpaque())
1139          paintBackground(g);
1140    
1141        // Try painting with the highlighter without checking whether there
1142        // is a selection because a highlighter can be used to do more than
1143        // marking selected text.
1144        if (highlighter != null)
1145          {
1146            // Handle restoring of the color here to prevent
1147            // drawing problems when the Highlighter implementor
1148            // forgets to restore it.
1149            Color oldColor = g.getColor();
1150            highlighter.paint(g);
1151            g.setColor(oldColor);
1152          }
1153          
1154        rootView.paint(g, getVisibleEditorRect());
1155    
1156        if (caret != null && textComponent.hasFocus())
1157          caret.paint(g);
1158      }
1159    
1160      /**
1161       * Paints the background of the text component.
1162       *
1163       * @param g the <code>Graphics</code> context to paint to
1164       */
1165      protected void paintBackground(Graphics g)
1166      {
1167        Color old = g.getColor();
1168        g.setColor(textComponent.getBackground());
1169        g.fillRect(0, 0, textComponent.getWidth(), textComponent.getHeight());
1170        g.setColor(old);
1171      }
1172    
1173      /**
1174       * Overridden for better control over background painting. This now simply
1175       * calls {@link #paint} and this delegates the background painting to
1176       * {@link #paintBackground}.
1177       *
1178       * @param g the graphics to use
1179       * @param c the component to be painted
1180       */
1181      public void update(Graphics g, JComponent c)
1182      {
1183        paint(g, c);
1184      }
1185    
1186      /**
1187       * Marks the specified range inside the text component's model as
1188       * damaged and queues a repaint request.
1189       *
1190       * @param t the text component
1191       * @param p0 the start location inside the document model of the range that
1192       *        is damaged
1193       * @param p1 the end location inside the document model of the range that
1194       *        is damaged
1195       */
1196      public void damageRange(JTextComponent t, int p0, int p1)
1197      {
1198        damageRange(t, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
1199      }
1200    
1201      /**
1202       * Marks the specified range inside the text component's model as
1203       * damaged and queues a repaint request. This variant of this method
1204       * allows a {@link Position.Bias} object to be specified for the start
1205       * and end location of the range.
1206       *
1207       * @param t the text component
1208       * @param p0 the start location inside the document model of the range that
1209       *        is damaged
1210       * @param p1 the end location inside the document model of the range that
1211       *        is damaged
1212       * @param firstBias the bias for the start location
1213       * @param secondBias the bias for the end location
1214       */
1215      public void damageRange(JTextComponent t, int p0, int p1,
1216                              Position.Bias firstBias, Position.Bias secondBias)
1217      {
1218        Rectangle alloc = getVisibleEditorRect();
1219        if (alloc != null)
1220          {
1221            Document doc = t.getDocument();
1222    
1223            // Acquire lock here to avoid structural changes in between.
1224            if (doc instanceof AbstractDocument)
1225              ((AbstractDocument) doc).readLock();
1226            try
1227              {
1228                rootView.setSize(alloc.width, alloc.height);
1229                Shape damage = rootView.modelToView(p0, firstBias, p1, secondBias,
1230                                                    alloc);
1231                Rectangle r = damage instanceof Rectangle ? (Rectangle) damage
1232                                                          : damage.getBounds();
1233                textComponent.repaint(r.x, r.y, r.width, r.height);
1234              }
1235            catch (BadLocationException ex)
1236              {
1237                // Lets ignore this as it causes no serious problems.
1238                // For debugging, comment this out.
1239                // ex.printStackTrace();
1240              }
1241            finally
1242              {
1243                // Release lock.
1244                if (doc instanceof AbstractDocument)
1245                  ((AbstractDocument) doc).readUnlock();
1246              }
1247          }
1248      }
1249    
1250      /**
1251       * Returns the {@link EditorKit} used for the text component that is managed
1252       * by this UI.
1253       *
1254       * @param t the text component
1255       *
1256       * @return the {@link EditorKit} used for the text component that is managed
1257       *         by this UI
1258       */
1259      public EditorKit getEditorKit(JTextComponent t)
1260      {
1261        if (kit == null)
1262          kit = new DefaultEditorKit();
1263        return kit;
1264      }
1265    
1266      /**
1267       * Gets the next position inside the document model that is visible on
1268       * screen, starting from <code>pos</code>.
1269       *
1270       * @param t the text component
1271       * @param pos the start positionn
1272       * @param b the bias for pos
1273       * @param direction the search direction
1274       * @param biasRet filled by the method to indicate the bias of the return
1275       *        value
1276       *
1277       * @return the next position inside the document model that is visible on
1278       *         screen
1279       */
1280      public int getNextVisualPositionFrom(JTextComponent t, int pos,
1281                                           Position.Bias b, int direction,
1282                                           Position.Bias[] biasRet)
1283        throws BadLocationException
1284      {
1285        int offset = -1;
1286        Document doc = textComponent.getDocument();
1287        if (doc instanceof AbstractDocument)
1288          ((AbstractDocument) doc).readLock();
1289        try
1290          {
1291            Rectangle alloc = getVisibleEditorRect();
1292            if (alloc != null)
1293              {
1294                rootView.setSize(alloc.width, alloc.height);
1295                offset = rootView.getNextVisualPositionFrom(pos, b, alloc,
1296                                                            direction, biasRet);
1297              }
1298          }
1299        finally
1300          {
1301            if (doc instanceof AbstractDocument)
1302              ((AbstractDocument) doc).readUnlock();
1303          }
1304        return offset;
1305      }
1306    
1307      /**
1308       * Returns the root {@link View} of a text component.
1309       *
1310       * @return the root {@link View} of a text component
1311       */
1312      public View getRootView(JTextComponent t)
1313      {
1314        return rootView;
1315      }
1316    
1317      /**
1318       * Maps a position in the document into the coordinate space of the View.
1319       * The output rectangle usually reflects the font height but has a width
1320       * of zero. A bias of {@link Position.Bias#Forward} is used in this method.
1321       *
1322       * @param t the text component
1323       * @param pos the position of the character in the model
1324       *
1325       * @return a rectangle that gives the location of the document position
1326       *         inside the view coordinate space
1327       *
1328       * @throws BadLocationException if <code>pos</code> is invalid
1329       * @throws IllegalArgumentException if b is not one of the above listed
1330       *         valid values
1331       */
1332      public Rectangle modelToView(JTextComponent t, int pos)
1333        throws BadLocationException
1334      {
1335        return modelToView(t, pos, Position.Bias.Forward);
1336      }
1337    
1338      /**
1339       * Maps a position in the document into the coordinate space of the View.
1340       * The output rectangle usually reflects the font height but has a width
1341       * of zero.
1342       *
1343       * @param t the text component
1344       * @param pos the position of the character in the model
1345       * @param bias either {@link Position.Bias#Forward} or
1346       *        {@link Position.Bias#Backward} depending on the preferred
1347       *        direction bias. If <code>null</code> this defaults to
1348       *        <code>Position.Bias.Forward</code>
1349       *
1350       * @return a rectangle that gives the location of the document position
1351       *         inside the view coordinate space
1352       *
1353       * @throws BadLocationException if <code>pos</code> is invalid
1354       * @throws IllegalArgumentException if b is not one of the above listed
1355       *         valid values
1356       */
1357      public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias)
1358        throws BadLocationException
1359      {
1360        // We need to read-lock here because we depend on the document
1361        // structure not beeing changed in between.
1362        Document doc = textComponent.getDocument();
1363        if (doc instanceof AbstractDocument)
1364          ((AbstractDocument) doc).readLock();
1365        Rectangle rect = null;
1366        try
1367          {
1368            Rectangle r = getVisibleEditorRect();
1369            if (r != null)
1370              {
1371                rootView.setSize(r.width, r.height);
1372                Shape s = rootView.modelToView(pos, r, bias);
1373                if (s != null)
1374                  rect = s.getBounds();
1375              }
1376          }
1377        finally
1378          {
1379            if (doc instanceof AbstractDocument)
1380              ((AbstractDocument) doc).readUnlock();
1381          }
1382        return rect;
1383      }
1384    
1385      /**
1386       * Maps a point in the <code>View</code> coordinate space to a position
1387       * inside a document model.
1388       *
1389       * @param t the text component
1390       * @param pt the point to be mapped
1391       *
1392       * @return the position inside the document model that corresponds to
1393       *     <code>pt</code>
1394       */
1395      public int viewToModel(JTextComponent t, Point pt)
1396      {
1397        return viewToModel(t, pt, new Position.Bias[1]);
1398      }
1399    
1400      /**
1401       * Maps a point in the <code>View</code> coordinate space to a position
1402       * inside a document model.
1403       *
1404       * @param t the text component
1405       * @param pt the point to be mapped
1406       * @param biasReturn filled in by the method to indicate the bias of the
1407       *        return value
1408       *
1409       * @return the position inside the document model that corresponds to
1410       *     <code>pt</code>
1411       */
1412      public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn)
1413      {
1414        int offset = -1;
1415        Document doc = textComponent.getDocument();
1416        if (doc instanceof AbstractDocument)
1417          ((AbstractDocument) doc).readLock();
1418        try
1419          {
1420            Rectangle alloc = getVisibleEditorRect();
1421            if (alloc != null)
1422              {
1423                rootView.setSize(alloc.width, alloc.height);
1424                offset = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
1425              }
1426          }
1427        finally
1428          {
1429            if (doc instanceof AbstractDocument)
1430              ((AbstractDocument) doc).readUnlock();
1431          }
1432        return offset;
1433      }
1434    
1435      /**
1436       * Creates a {@link View} for the specified {@link Element}.
1437       *
1438       * @param elem the <code>Element</code> to create a <code>View</code> for
1439       *
1440       * @see ViewFactory
1441       */
1442      public View create(Element elem)
1443      {
1444        // Subclasses have to implement this to get this functionality.
1445        return null;
1446      }
1447    
1448      /**
1449       * Creates a {@link View} for the specified {@link Element}.
1450       *
1451       * @param elem the <code>Element</code> to create a <code>View</code> for
1452       * @param p0 the start offset
1453       * @param p1 the end offset
1454       *
1455       * @see ViewFactory
1456       */
1457      public View create(Element elem, int p0, int p1)
1458      {
1459        // Subclasses have to implement this to get this functionality.
1460        return null;
1461      }
1462    
1463      /**
1464       * A cached Insets instance to be reused below.
1465       */
1466      private Insets cachedInsets;
1467    
1468      /**
1469       * Returns the allocation to give the root view.
1470       *
1471       * @return the allocation to give the root view
1472       *
1473       * @specnote The allocation has nothing to do with visibility. According
1474       *           to the specs the naming of this method is unfortunate and
1475       *           has historical reasons
1476       */
1477      protected Rectangle getVisibleEditorRect()
1478      {
1479        int width = textComponent.getWidth();
1480        int height = textComponent.getHeight();
1481    
1482        // Return null if the component has no valid size.
1483        if (width <= 0 || height <= 0)
1484          return null;
1485            
1486        Insets insets = textComponent.getInsets(cachedInsets);
1487        return new Rectangle(insets.left, insets.top,
1488                             width - insets.left - insets.right,
1489                             height - insets.top - insets.bottom);
1490      }
1491    
1492      /**
1493       * Sets the root view for the text component.
1494       *
1495       * @param view the <code>View</code> to be set as root view
1496       */
1497      protected final void setView(View view)
1498      {
1499        rootView.setView(view);
1500        textComponent.revalidate();
1501        textComponent.repaint();
1502      }
1503    
1504      /**
1505       * Indicates that the model of a text component has changed. This
1506       * triggers a rebuild of the view hierarchy.
1507       */
1508      protected void modelChanged()
1509      {
1510        if (textComponent == null || rootView == null) 
1511          return;
1512        ViewFactory factory = rootView.getViewFactory();
1513        if (factory == null) 
1514          return;
1515        Document doc = textComponent.getDocument();
1516        if (doc == null)
1517          return;
1518        Element elem = doc.getDefaultRootElement();
1519        if (elem == null)
1520          return;
1521        View view = factory.create(elem);
1522        setView(view);
1523      }
1524    
1525      /**
1526       * Receives notification whenever one of the text component's bound
1527       * properties changes. This default implementation does nothing.
1528       * It is a hook that enables subclasses to react to property changes
1529       * on the text component.
1530       *
1531       * @param ev the property change event
1532       */
1533      protected void propertyChange(PropertyChangeEvent ev)
1534      {
1535        // The default implementation does nothing.
1536      }
1537    
1538    }