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