FileDocCategorySizeDatePackage
BasicTextUI.javaAPI DocJava SE 6 API91700Tue Jun 10 00:26:48 BST 2008javax.swing.plaf.basic

BasicTextUI.java

/*
 * @(#)BasicTextUI.java	1.120 06/08/25
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package javax.swing.plaf.basic;

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.im.InputContext;
import java.beans.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import sun.swing.DefaultLookup;
import sun.awt.AppContext;
import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;

/**
 * <p>
 * Basis of a text components look-and-feel.  This provides the
 * basic editor view and controller services that may be useful
 * when creating a look-and-feel for an extension of
 * <code>JTextComponent</code>.
 * <p>
 * Most state is held in the associated <code>JTextComponent</code>
 * as bound properties, and the UI installs default values for the 
 * various properties.  This default will install something for
 * all of the properties.  Typically, a LAF implementation will
 * do more however.  At a minimum, a LAF would generally install
 * key bindings.
 * <p>
 * This class also provides some concurrency support if the 
 * <code>Document</code> associated with the JTextComponent is a subclass of
 * <code>AbstractDocument</code>.  Access to the View (or View hierarchy) is
 * serialized between any thread mutating the model and the Swing
 * event thread (which is expected to render, do model/view coordinate
 * translation, etc).  <em>Any access to the root view should first
 * acquire a read-lock on the AbstractDocument and release that lock
 * in a finally block.</em>
 * <p>
 * An important method to define is the {@link #getPropertyPrefix} method
 * which is used as the basis of the keys used to fetch defaults
 * from the UIManager.  The string should reflect the type of 
 * TextUI (eg. TextField, TextArea, etc) without the particular 
 * LAF part of the name (eg Metal, Motif, etc).
 * <p>
 * To build a view of the model, one of the following strategies 
 * can be employed.
 * <ol>
 * <li>
 * One strategy is to simply redefine the 
 * ViewFactory interface in the UI.  By default, this UI itself acts
 * as the factory for View implementations.  This is useful
 * for simple factories.  To do this reimplement the 
 * {@link #create} method.
 * <li>
 * A common strategy for creating more complex types of documents
 * is to have the EditorKit implementation return a factory.  Since
 * the EditorKit ties all of the pieces necessary to maintain a type
 * of document, the factory is typically an important part of that
 * and should be produced by the EditorKit implementation.
 * </ol>
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans<sup><font size="-2">TM</font></sup>
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author Timothy Prinzing
 * @author Shannon Hickey (drag and drop)
 * @version 1.120 08/25/06
 */
public abstract class BasicTextUI extends TextUI implements ViewFactory {

    /**
     * Creates a new UI.
     */
    public BasicTextUI() {
        painted = false;
    }

    /**
     * Creates the object to use for a caret.  By default an
     * instance of BasicCaret is created.  This method
     * can be redefined to provide something else that implements
     * the InputPosition interface or a subclass of JCaret.
     *
     * @return the caret object
     */
    protected Caret createCaret() {
        return new BasicCaret();
    }

    /**
     * Creates the object to use for adding highlights.  By default
     * an instance of BasicHighlighter is created.  This method
     * can be redefined to provide something else that implements
     * the Highlighter interface or a subclass of DefaultHighlighter.
     *
     * @return the highlighter
     */
    protected Highlighter createHighlighter() {
        return new BasicHighlighter();
    }

    /**
     * Fetches the name of the keymap that will be installed/used 
     * by default for this UI. This is implemented to create a
     * name based upon the classname.  The name is the the name
     * of the class with the package prefix removed.
     *
     * @return the name
     */
    protected String getKeymapName() {
	String nm = getClass().getName();
	int index = nm.lastIndexOf('.');
	if (index >= 0) {
	    nm = nm.substring(index+1, nm.length());
	}
	return nm;
    }

    /**
     * Creates the keymap to use for the text component, and installs
     * any necessary bindings into it.  By default, the keymap is
     * shared between all instances of this type of TextUI. The
     * keymap has the name defined by the getKeymapName method.  If the
     * keymap is not found, then DEFAULT_KEYMAP from JTextComponent is used.
     * <p>
     * The set of bindings used to create the keymap is fetched 
     * from the UIManager using a key formed by combining the
     * {@link #getPropertyPrefix} method
     * and the string <code>.keyBindings</code>.  The type is expected
     * to be <code>JTextComponent.KeyBinding[]</code>.
     *
     * @return the keymap
     * @see #getKeymapName
     * @see javax.swing.text.JTextComponent
     */
    protected Keymap createKeymap() {
	String nm = getKeymapName();
	Keymap map = JTextComponent.getKeymap(nm);
	if (map == null) {
	    Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
	    map = JTextComponent.addKeymap(nm, parent);
	    String prefix = getPropertyPrefix();
	    Object o = DefaultLookup.get(editor, this,
                prefix + ".keyBindings");
	    if ((o != null) && (o instanceof JTextComponent.KeyBinding[])) {
		JTextComponent.KeyBinding[] bindings = (JTextComponent.KeyBinding[]) o;
		JTextComponent.loadKeymap(map, bindings, getComponent().getActions());
	    }
	}
	return map;
    }

    /**
     * This method gets called when a bound property is changed
     * on the associated JTextComponent.  This is a hook
     * which UI implementations may change to reflect how the
     * UI displays bound properties of JTextComponent subclasses.
     * This is implemented to do nothing (i.e. the response to
     * properties in JTextComponent itself are handled prior
     * to calling this method).
     *
     * This implementation updates the background of the text
     * component if the editable and/or enabled state changes.
     *
     * @param evt the property change event
     */
    protected void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals("editable") ||
                evt.getPropertyName().equals("enabled")) {
            
            updateBackground((JTextComponent)evt.getSource());
        }
    }

    /**
     * Updates the background of the text component based on whether the
     * text component is editable and/or enabled.
     *
     * @param c the JTextComponent that needs its background color updated
     */
    private void updateBackground(JTextComponent c) {
        // This is a temporary workaround.
        // This code does not correctly deal with Synth (Synth doesn't use
        // properties like this), nor does it deal with the situation where
        // the developer grabs the color from a JLabel and sets it as
        // the background for a JTextArea in all look and feels. The problem
        // scenario results if the Color obtained for the Label and TextArea
        // is ==, which is the case for the windows look and feel.
        // Until an appropriate solution is found, the code is being
        // reverted to what it was before the original fix.
        if (this instanceof sun.swing.plaf.synth.SynthUI ||
                (c instanceof JTextArea)) {
            return;
        }
        Color background = c.getBackground();
        if (background instanceof UIResource) {
            String prefix = getPropertyPrefix();

            Color disabledBG =
                DefaultLookup.getColor(c, this, prefix + ".disabledBackground", null);
            Color inactiveBG =
                DefaultLookup.getColor(c, this, prefix + ".inactiveBackground", null);
            Color bg =
                DefaultLookup.getColor(c, this, prefix + ".background", null);

            /* In an ideal situation, the following check would not be necessary
             * and we would replace the color any time the previous color was a
             * UIResouce. However, it turns out that there is existing code that
             * uses the following inadvisable pattern to turn a text area into
             * what appears to be a multi-line label:
             *
             * JLabel label = new JLabel();
             * JTextArea area = new JTextArea();
             * area.setBackground(label.getBackground());
             * area.setEditable(false);
             *
             * JLabel's default background is a UIResource. As such, just
             * checking for UIResource would have us always changing the
             * background away from what the developer wanted.
             *
             * Therefore, for JTextArea/JEditorPane, we'll additionally check
             * that the color we're about to replace matches one that was
             * installed by us from the UIDefaults.
             */
            if ((c instanceof JTextArea || c instanceof JEditorPane)
                    && background != disabledBG
                    && background != inactiveBG
                    && background != bg) {

                return;
            }

            Color newColor = null;
            if (!c.isEnabled()) {
                newColor = disabledBG;
            }
            if (newColor == null && !c.isEditable()) {
                newColor = inactiveBG;
            }
            if (newColor == null) {
                newColor = bg;
            }
            if (newColor != null && newColor != background) {
                c.setBackground(newColor);
            }
        }
    }

    /**
     * Gets the name used as a key to look up properties through the
     * UIManager.  This is used as a prefix to all the standard
     * text properties.
     *
     * @return the name
     */
    protected abstract String getPropertyPrefix();

    /**
     * Initializes component properties, e.g. font, foreground, 
     * background, caret color, selection color, selected text color,
     * disabled text color, and border color.  The font, foreground, and
     * background properties are only set if their current value is either null
     * or a UIResource, other properties are set if the current
     * value is null.
     * 
     * @see #uninstallDefaults
     * @see #installUI
     */
    protected void installDefaults() 
    {
        String prefix = getPropertyPrefix();
        Font f = editor.getFont();
        if ((f == null) || (f instanceof UIResource)) {
            editor.setFont(UIManager.getFont(prefix + ".font"));
        }

        Color bg = editor.getBackground();
        if ((bg == null) || (bg instanceof UIResource)) {
            editor.setBackground(UIManager.getColor(prefix + ".background"));
        }
        
        Color fg = editor.getForeground();
        if ((fg == null) || (fg instanceof UIResource)) {
            editor.setForeground(UIManager.getColor(prefix + ".foreground"));
        }

        Color color = editor.getCaretColor();
        if ((color == null) || (color instanceof UIResource)) {
            editor.setCaretColor(UIManager.getColor(prefix + ".caretForeground"));
        }

        Color s = editor.getSelectionColor();
        if ((s == null) || (s instanceof UIResource)) {
            editor.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground"));
        }

        Color sfg = editor.getSelectedTextColor();
        if ((sfg == null) || (sfg instanceof UIResource)) {
            editor.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground"));
        }

        Color dfg = editor.getDisabledTextColor();
        if ((dfg == null) || (dfg instanceof UIResource)) {
            editor.setDisabledTextColor(UIManager.getColor(prefix + ".inactiveForeground"));
        }

        Border b = editor.getBorder();
        if ((b == null) || (b instanceof UIResource)) {
            editor.setBorder(UIManager.getBorder(prefix + ".border"));
        }

        Insets margin = editor.getMargin();
        if (margin == null || margin instanceof UIResource) {
            editor.setMargin(UIManager.getInsets(prefix + ".margin"));
        }

        updateCursor();
    }

    private void installDefaults2() {
	editor.addMouseListener(dragListener);
	editor.addMouseMotionListener(dragListener);
	
        String prefix = getPropertyPrefix();

        Caret caret = editor.getCaret();
        if (caret == null || caret instanceof UIResource) {
            caret = createCaret();
            editor.setCaret(caret);
        
            int rate = DefaultLookup.getInt(getComponent(), this, prefix + ".caretBlinkRate", 500);
            caret.setBlinkRate(rate);
        }

        Highlighter highlighter = editor.getHighlighter();
        if (highlighter == null || highlighter instanceof UIResource) {
            editor.setHighlighter(createHighlighter());
        }

	TransferHandler th = editor.getTransferHandler();
	if (th == null || th instanceof UIResource) {
	    editor.setTransferHandler(getTransferHandler());
	}
    }

    /**
     * Sets the component properties that haven't been explicitly overridden to 
     * null.  A property is considered overridden if its current value
     * is not a UIResource.
     * 
     * @see #installDefaults
     * @see #uninstallUI
     */
    protected void uninstallDefaults() 
    {
	editor.removeMouseListener(dragListener);
	editor.removeMouseMotionListener(dragListener);

        if (editor.getCaretColor() instanceof UIResource) {
            editor.setCaretColor(null);
        }
                                                                                         
        if (editor.getSelectionColor() instanceof UIResource) {
            editor.setSelectionColor(null);
        }

        if (editor.getDisabledTextColor() instanceof UIResource) {
            editor.setDisabledTextColor(null);
        }

        if (editor.getSelectedTextColor() instanceof UIResource) {
            editor.setSelectedTextColor(null);
        }

        if (editor.getBorder() instanceof UIResource) {
            editor.setBorder(null);
        }

        if (editor.getMargin() instanceof UIResource) {
            editor.setMargin(null);
        }

        if (editor.getCaret() instanceof UIResource) {
            editor.setCaret(null);
        }

        if (editor.getHighlighter() instanceof UIResource) {
            editor.setHighlighter(null);
        }

	if (editor.getTransferHandler() instanceof UIResource) {
	    editor.setTransferHandler(null);
	}

        if (editor.getCursor() instanceof UIResource) {
            editor.setCursor(null);
        }
    }

    /**
     * Installs listeners for the UI.
     */
    protected void installListeners() {
    }

    /**
     * Uninstalls listeners for the UI.
     */
    protected void uninstallListeners() {
    }

    protected void installKeyboardActions() {
	// backward compatibility support... keymaps for the UI
	// are now installed in the more friendly input map.
        editor.setKeymap(createKeymap()); 

        InputMap km = getInputMap();
	if (km != null) {
	    SwingUtilities.replaceUIInputMap(editor, JComponent.WHEN_FOCUSED,
					     km);
	}
	
	ActionMap map = getActionMap();
	if (map != null) {
	    SwingUtilities.replaceUIActionMap(editor, map);
	}

	updateFocusAcceleratorBinding(false);
    }

    /**
     * Get the InputMap to use for the UI.  
     */
    InputMap getInputMap() {
	InputMap map = new InputMapUIResource();

	InputMap shared = 
	    (InputMap)DefaultLookup.get(editor, this,
            getPropertyPrefix() + ".focusInputMap");
	if (shared != null) {
	    map.setParent(shared);
	}
	return map;
    }

    /**
     * Invoked when the focus accelerator changes, this will update the
     * key bindings as necessary.
     */
    void updateFocusAcceleratorBinding(boolean changed) {
	char accelerator = editor.getFocusAccelerator();

	if (changed || accelerator != '\0') {
	    InputMap km = SwingUtilities.getUIInputMap
		        (editor, JComponent.WHEN_IN_FOCUSED_WINDOW);

	    if (km == null && accelerator != '\0') {
		km = new ComponentInputMapUIResource(editor);
		SwingUtilities.replaceUIInputMap(editor, JComponent.
						 WHEN_IN_FOCUSED_WINDOW, km);
		ActionMap am = getActionMap();
		SwingUtilities.replaceUIActionMap(editor, am);
	    }
	    if (km != null) {
		km.clear();
		if (accelerator != '\0') {
		    km.put(KeyStroke.getKeyStroke(accelerator,
						  ActionEvent.ALT_MASK),
			   "requestFocus");
		}
	    }
	}
    }


    /**
     * Invoked when editable property is changed.
     *
     * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case 
     * editor is editable
     * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case 
     * editor is non editable
     */ 

    void updateFocusTraversalKeys() {
	/*
	 * Fix for 4514331 Non-editable JTextArea and similar 
	 * should allow Tab to keyboard - accessibility 
	 */
	EditorKit editorKit = getEditorKit(editor);
	if ( editorKit != null
	     && editorKit instanceof DefaultEditorKit) {
	    Set storedForwardTraversalKeys = editor.
		getFocusTraversalKeys(KeyboardFocusManager.
				      FORWARD_TRAVERSAL_KEYS);
	    Set storedBackwardTraversalKeys = editor.
		getFocusTraversalKeys(KeyboardFocusManager.
				      BACKWARD_TRAVERSAL_KEYS);
	    Set forwardTraversalKeys = 
		new HashSet(storedForwardTraversalKeys);
	    Set backwardTraversalKeys = 
		new HashSet(storedBackwardTraversalKeys);
	    if (editor.isEditable()) {
		forwardTraversalKeys.
		    remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
		backwardTraversalKeys.
		    remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 
						  InputEvent.SHIFT_MASK));
	    } else {
		forwardTraversalKeys.add(KeyStroke.
					 getKeyStroke(KeyEvent.VK_TAB, 0));
		backwardTraversalKeys.
		    add(KeyStroke.
			getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
	    }
            LookAndFeel.installProperty(editor,
                                        "focusTraversalKeysForward",
					 forwardTraversalKeys);
            LookAndFeel.installProperty(editor,
                                        "focusTraversalKeysBackward",
					 backwardTraversalKeys);
	}

    }

    /**
     * As needed updates cursor for the target editor.
     */
    private void updateCursor() {
        if ((! editor.isCursorSet())
               || editor.getCursor() instanceof UIResource) {
            Cursor cursor = (editor.isEditable()) ? textCursor : null;
            editor.setCursor(cursor);
        }
    }

    /**
     * Returns the <code>TransferHandler</code> that will be installed if
     * their isn't one installed on the <code>JTextComponent</code>.
     */
    TransferHandler getTransferHandler() {
        return defaultTransferHandler;
    }

    /**
     * Fetch an action map to use.
     */
    ActionMap getActionMap() {
	String mapName = getPropertyPrefix() + ".actionMap";
	ActionMap map = (ActionMap)UIManager.get(mapName);

	if (map == null) {
	    map = createActionMap();
	    if (map != null) {
		UIManager.getLookAndFeelDefaults().put(mapName, map);
	    }
	}
        ActionMap componentMap = new ActionMapUIResource();
        componentMap.put("requestFocus", new FocusAction());
	/* 
	 * fix for bug 4515750 
	 * JTextField & non-editable JTextArea bind return key - default btn not accessible
	 *
	 * Wrap the return action so that it is only enabled when the
	 * component is editable. This allows the default button to be
	 * processed when the text component has focus and isn't editable.
	 * 
	 */
	if (getEditorKit(editor) instanceof DefaultEditorKit) {
	    if (map != null) {
		Object obj = map.get(DefaultEditorKit.insertBreakAction);
		if (obj != null  
		    && obj instanceof DefaultEditorKit.InsertBreakAction) {
		    Action action =  new TextActionWrapper((TextAction)obj);
		    componentMap.put(action.getValue(Action.NAME),action);
		}
	    }
	}
        if (map != null) {
            componentMap.setParent(map);
        }
	return componentMap;
    }

    /**
     * Create a default action map.  This is basically the
     * set of actions found exported by the component.
     */
    ActionMap createActionMap() {
	ActionMap map = new ActionMapUIResource();
	Action[] actions = editor.getActions();
	//System.out.println("building map for UI: " + getPropertyPrefix());
	int n = actions.length;
	for (int i = 0; i < n; i++) {
	    Action a = actions[i];
	    map.put(a.getValue(Action.NAME), a);
	    //System.out.println("  " + a.getValue(Action.NAME));
	}
        map.put(TransferHandler.getCutAction().getValue(Action.NAME),
                TransferHandler.getCutAction());
        map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
                TransferHandler.getCopyAction());
        map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
                TransferHandler.getPasteAction());
	return map;
    }

    protected void uninstallKeyboardActions() {
        editor.setKeymap(null);
	SwingUtilities.replaceUIInputMap(editor, JComponent.
					 WHEN_IN_FOCUSED_WINDOW, null);
	SwingUtilities.replaceUIActionMap(editor, null);
    }
    
    /**
     * Paints a background for the view.  This will only be
     * called if isOpaque() on the associated component is
     * true.  The default is to paint the background color 
     * of the component.
     *
     * @param g the graphics context
     */
    protected void paintBackground(Graphics g) {
        g.setColor(editor.getBackground());
        g.fillRect(0, 0, editor.getWidth(), editor.getHeight());
    }

    /**
     * Fetches the text component associated with this
     * UI implementation.  This will be null until
     * the ui has been installed.
     *
     * @return the editor component
     */
    protected final JTextComponent getComponent() {
        return editor;
    }

    /**
     * Flags model changes.
     * This is called whenever the model has changed.
     * It is implemented to rebuild the view hierarchy
     * to represent the default root element of the
     * associated model.
     */
    protected void modelChanged() {
        // create a view hierarchy
        ViewFactory f = rootView.getViewFactory();
        Document doc = editor.getDocument();
        Element elem = doc.getDefaultRootElement();
        setView(f.create(elem));
    }

    /**
     * Sets the current root of the view hierarchy and calls invalidate().
     * If there were any child components, they will be removed (i.e.
     * there are assumed to have come from components embedded in views).
     *
     * @param v the root view
     */
    protected final void setView(View v) {
        rootView.setView(v);
        painted = false;
        editor.revalidate();
        editor.repaint();
    }

    /**
     * Paints the interface safely with a guarantee that
     * the model won't change from the view of this thread.  
     * This does the following things, rendering from 
     * back to front.
     * <ol>
     * <li>
     * If the component is marked as opaque, the background
     * is painted in the current background color of the
     * component.
     * <li>
     * The highlights (if any) are painted.
     * <li>
     * The view hierarchy is painted.
     * <li>
     * The caret is painted.
     * </ol>
     *
     * @param g the graphics context
     */
    protected void paintSafely(Graphics g) {
	painted = true;
	Highlighter highlighter = editor.getHighlighter();
	Caret caret = editor.getCaret();
	
	// paint the background
	if (editor.isOpaque()) {
	    paintBackground(g);
	}
	
	// paint the highlights
	if (highlighter != null) {
	    highlighter.paint(g);
	}

	// paint the view hierarchy
	Rectangle alloc = getVisibleEditorRect();
        if (alloc != null) {
            rootView.paint(g, alloc);
        }
        
	// paint the caret
	if (caret != null) {
	    caret.paint(g);
	}

        if (dropCaret != null) {
            dropCaret.paint(g);
        }
    }

    // --- ComponentUI methods --------------------------------------------

    /**
     * Installs the UI for a component.  This does the following
     * things.
     * <ol>
     * <li>
     * Set the associated component to opaque (can be changed
     * easily by a subclass or on JTextComponent directly),
     * which is the most common case.  This will cause the
     * component's background color to be painted.
     * <li>
     * Install the default caret and highlighter into the 
     * associated component.
     * <li>
     * Attach to the editor and model.  If there is no 
     * model, a default one is created.
     * <li>
     * create the view factory and the view hierarchy used
     * to represent the model.
     * </ol>
     *
     * @param c the editor component
     * @see ComponentUI#installUI
     */
    public void installUI(JComponent c) {
        if (c instanceof JTextComponent) {
            editor = (JTextComponent) c;

            // install defaults
            installDefaults();
            installDefaults2();

            // common case is background painted... this can
            // easily be changed by subclasses or from outside
            // of the component.
            LookAndFeel.installProperty(editor, "opaque", Boolean.TRUE);
            LookAndFeel.installProperty(editor, "autoscrolls", Boolean.TRUE);

            // attach to the model and editor
            editor.addPropertyChangeListener(updateHandler);
            Document doc = editor.getDocument();
            if (doc == null) {
                // no model, create a default one.  This will
                // fire a notification to the updateHandler 
                // which takes care of the rest. 
                editor.setDocument(getEditorKit(editor).createDefaultDocument());
            } else {
                doc.addDocumentListener(updateHandler);
                modelChanged();
            }

            // install keymap
            installListeners();
            installKeyboardActions();

	    LayoutManager oldLayout = editor.getLayout();
	    if ((oldLayout == null) || (oldLayout instanceof UIResource)) {
		// by default, use default LayoutManger implementation that
		// will position the components associated with a View object.
		editor.setLayout(updateHandler);
	    }

            updateBackground(editor);
        } else {
            throw new Error("TextUI needs JTextComponent");
        }
    }

    /**
     * Deinstalls the UI for a component.  This removes the listeners,
     * uninstalls the highlighter, removes views, and nulls out the keymap.
     *
     * @param c the editor component
     * @see ComponentUI#uninstallUI
     */
    public void uninstallUI(JComponent c) {
        // detach from the model
        editor.removePropertyChangeListener(updateHandler);
        editor.getDocument().removeDocumentListener(updateHandler);

        // view part
        painted = false;
        uninstallDefaults();
        rootView.setView(null);
        c.removeAll();
	LayoutManager lm = c.getLayout();
	if (lm instanceof UIResource) {
	    c.setLayout(null);
	}

        // controller part
        uninstallKeyboardActions();
        uninstallListeners();

        editor = null;
    }

    /**
     * Superclass paints background in an uncontrollable way
     * (i.e. one might want an image tiled into the background).
     * To prevent this from happening twice, this method is
     * reimplemented to simply paint.
     * <p>
     * <em>NOTE:</em> Superclass is also not thread-safe in 
     * it's rendering of the background, although that's not
     * an issue with the default rendering.
     */
    public void update(Graphics g, JComponent c) {
	paint(g, c);
    }

    /**
     * Paints the interface.  This is routed to the
     * paintSafely method under the guarantee that
     * the model won't change from the view of this thread
     * while it's rendering (if the associated model is
     * derived from AbstractDocument).  This enables the 
     * model to potentially be updated asynchronously.
     *
     * @param g the graphics context
     * @param c the editor component
     */
    public final void paint(Graphics g, JComponent c) {
	if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) {
	    Document doc = editor.getDocument();
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readLock();
	    }
	    try {
		paintSafely(g);
	    } finally {
		if (doc instanceof AbstractDocument) {
		    ((AbstractDocument)doc).readUnlock();
		}
	    }
	}
    }

    /**
     * Gets the preferred size for the editor component.  If the component
     * has been given a size prior to receiving this request, it will
     * set the size of the view hierarchy to reflect the size of the component
     * before requesting the preferred size of the view hierarchy.  This
     * allows formatted views to format to the current component size before
     * answering the request.  Other views don't care about currently formatted
     * size and give the same answer either way.
     *
     * @param c the editor component
     * @return the size
     */
    public Dimension getPreferredSize(JComponent c) {
	Document doc = editor.getDocument();
	Insets i = c.getInsets();
	Dimension d = c.getSize();

	if (doc instanceof AbstractDocument) {
	    ((AbstractDocument)doc).readLock();
	}
	try {
	    if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) {
		rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom);
	    }
            else if (d.width == 0 && d.height == 0) {
                // Probably haven't been layed out yet, force some sort of
                // initial sizing.
                rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
            }
	    d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) +
				     (long) i.left + (long) i.right, Integer.MAX_VALUE);
	    d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) +
				      (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
	} finally {
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
	return d;
    }

    /**
     * Gets the minimum size for the editor component.
     *
     * @param c the editor component
     * @return the size
     */
    public Dimension getMinimumSize(JComponent c) {
	Document doc = editor.getDocument();
        Insets i = c.getInsets();
	Dimension d = new Dimension();
	if (doc instanceof AbstractDocument) {
	    ((AbstractDocument)doc).readLock();
	}
	try {
	    d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right;
	    d.height = (int)  rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom;
	} finally {
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
        return d;
    }

    /**
     * Gets the maximum size for the editor component.
     *
     * @param c the editor component
     * @return the size
     */
    public Dimension getMaximumSize(JComponent c) {
	Document doc = editor.getDocument();
        Insets i = c.getInsets();
	Dimension d = new Dimension();
	if (doc instanceof AbstractDocument) {
	    ((AbstractDocument)doc).readLock();
	}
	try {
	    d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) + 
				     (long) i.left + (long) i.right, Integer.MAX_VALUE);
	    d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) + 
				      (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
	} finally {
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
        return d;
    }

    // ---- TextUI methods -------------------------------------------


    /**
     * Gets the allocation to give the root View.  Due
     * to an unfortunate set of historical events this 
     * method is inappropriately named.  The Rectangle
     * returned has nothing to do with visibility.  
     * The component must have a non-zero positive size for 
     * this translation to be computed.
     *
     * @return the bounding box for the root view
     */
    protected Rectangle getVisibleEditorRect() {
	Rectangle alloc = editor.getBounds();
	if ((alloc.width > 0) && (alloc.height > 0)) {
	    alloc.x = alloc.y = 0;
	    Insets insets = editor.getInsets();
	    alloc.x += insets.left;
	    alloc.y += insets.top;
	    alloc.width -= insets.left + insets.right;
	    alloc.height -= insets.top + insets.bottom;
	    return alloc;
	}
	return null;
    }

    /**
     * Converts the given location in the model to a place in
     * the view coordinate system.
     * The component must have a non-zero positive size for 
     * this translation to be computed.
     *
     * @param tc the text component for which this UI is installed
     * @param pos the local location in the model to translate >= 0
     * @return the coordinates as a rectangle, null if the model is not painted
     * @exception BadLocationException  if the given position does not
     *   represent a valid location in the associated document
     * @see TextUI#modelToView
     */
    public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException {
	return modelToView(tc, pos, Position.Bias.Forward);
    }

    /**
     * Converts the given location in the model to a place in
     * the view coordinate system.
     * The component must have a non-zero positive size for 
     * this translation to be computed.
     *
     * @param tc the text component for which this UI is installed
     * @param pos the local location in the model to translate >= 0
     * @return the coordinates as a rectangle, null if the model is not painted
     * @exception BadLocationException  if the given position does not
     *   represent a valid location in the associated document
     * @see TextUI#modelToView
     */
    public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException {
	Document doc = editor.getDocument();
	if (doc instanceof AbstractDocument) {
	    ((AbstractDocument)doc).readLock();
	}
	try {
	    Rectangle alloc = getVisibleEditorRect();
	    if (alloc != null) {
		rootView.setSize(alloc.width, alloc.height);
		Shape s = rootView.modelToView(pos, alloc, bias);
		if (s != null) {
		  return s.getBounds();
		}
	    }
	} finally {
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
	return null;
    }

    /**
     * Converts the given place in the view coordinate system
     * to the nearest representative location in the model.
     * The component must have a non-zero positive size for 
     * this translation to be computed.
     *
     * @param tc the text component for which this UI is installed
     * @param pt the location in the view to translate.  This
     *  should be in the same coordinate system as the mouse events.
     * @return the offset from the start of the document >= 0,
     *   -1 if not painted
     * @see TextUI#viewToModel
     */
    public int viewToModel(JTextComponent tc, Point pt) {
	return viewToModel(tc, pt, discardBias);
    }

    /**
     * Converts the given place in the view coordinate system
     * to the nearest representative location in the model.
     * The component must have a non-zero positive size for 
     * this translation to be computed.
     *
     * @param tc the text component for which this UI is installed
     * @param pt the location in the view to translate.  This
     *  should be in the same coordinate system as the mouse events.
     * @return the offset from the start of the document >= 0,
     *   -1 if the component doesn't yet have a positive size.
     * @see TextUI#viewToModel
     */
    public int viewToModel(JTextComponent tc, Point pt,
			   Position.Bias[] biasReturn) {
	int offs = -1;
	Document doc = editor.getDocument();
	if (doc instanceof AbstractDocument) {
	    ((AbstractDocument)doc).readLock();
	}
	try {
	    Rectangle alloc = getVisibleEditorRect();
	    if (alloc != null) {
		rootView.setSize(alloc.width, alloc.height);
		offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
	    }
	} finally {
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
        return offs;
    }

    /**
     * {@inheritDoc}
     */
    public int getNextVisualPositionFrom(JTextComponent t, int pos,
		    Position.Bias b, int direction, Position.Bias[] biasRet)
	            throws BadLocationException{
	Document doc = editor.getDocument();
	if (doc instanceof AbstractDocument) {
	    ((AbstractDocument)doc).readLock();
	}
	try {
	    if (painted) {
		Rectangle alloc = getVisibleEditorRect();
                if (alloc != null) {
                    rootView.setSize(alloc.width, alloc.height);
                }
		return rootView.getNextVisualPositionFrom(pos, b, alloc, direction,
							  biasRet);
	    }
	} finally {
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
	return -1;
    }

    /**
     * Causes the portion of the view responsible for the
     * given part of the model to be repainted.  Does nothing if
     * the view is not currently painted.
     *
     * @param tc the text component for which this UI is installed
     * @param p0 the beginning of the range >= 0
     * @param p1 the end of the range >= p0
     * @see TextUI#damageRange
     */
    public void damageRange(JTextComponent tc, int p0, int p1) {
	damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
    }

    /**
     * Causes the portion of the view responsible for the 
     * given part of the model to be repainted.
     *
     * @param p0 the beginning of the range >= 0
     * @param p1 the end of the range >= p0
     */
    public void damageRange(JTextComponent t, int p0, int p1,
			    Position.Bias p0Bias, Position.Bias p1Bias) {
        if (painted) {
            Rectangle alloc = getVisibleEditorRect();
            if (alloc != null) {
                Document doc = t.getDocument();
                if (doc instanceof AbstractDocument) {
                    ((AbstractDocument)doc).readLock();
                }
                try {
                    rootView.setSize(alloc.width, alloc.height);
                    Shape toDamage = rootView.modelToView(p0, p0Bias,
                            p1, p1Bias, alloc);
                    Rectangle rect = (toDamage instanceof Rectangle) ?
                            (Rectangle)toDamage : toDamage.getBounds();
                    editor.repaint(rect.x, rect.y, rect.width, rect.height);
                } catch (BadLocationException e) {
                } finally {
                    if (doc instanceof AbstractDocument) {
                        ((AbstractDocument)doc).readUnlock();
                    }
                }
            }
        }
    }

    /**
     * Fetches the EditorKit for the UI.
     *
     * @param tc the text component for which this UI is installed
     * @return the editor capabilities
     * @see TextUI#getEditorKit
     */
    public EditorKit getEditorKit(JTextComponent tc) {
        return defaultKit;
    }

    /**
     * Fetches a View with the allocation of the associated 
     * text component (i.e. the root of the hierarchy) that 
     * can be traversed to determine how the model is being
     * represented spatially.
     * <p>
     * <font color=red><b>NOTE:</b>The View hierarchy can
     * be traversed from the root view, and other things
     * can be done as well.  Things done in this way cannot
     * be protected like simple method calls through the TextUI.
     * Therefore, proper operation in the presence of concurrency
     * must be arranged by any logic that calls this method!
     * </font>
     *
     * @param tc the text component for which this UI is installed
     * @return the view
     * @see TextUI#getRootView
     */
    public View getRootView(JTextComponent tc) {
        return rootView;
    }


    /**
     * Returns the string to be used as the tooltip at the passed in location.
     * This forwards the method onto the root View.
     *
     * @see javax.swing.text.JTextComponent#getToolTipText
     * @see javax.swing.text.View#getToolTipText
     * @since 1.4
     */
    public String getToolTipText(JTextComponent t, Point pt) {
        if (!painted) {
            return null;
        }
        Document doc = editor.getDocument();
        String tt = null;
        Rectangle alloc = getVisibleEditorRect();

        if (alloc != null) {
            if (doc instanceof AbstractDocument) {
                ((AbstractDocument)doc).readLock();
            }
            try {
                tt = rootView.getToolTipText(pt.x, pt.y, alloc);
            } finally {
                if (doc instanceof AbstractDocument) {
                    ((AbstractDocument)doc).readUnlock();
                }
            }
        }
        return tt;
    }

    // --- ViewFactory methods ------------------------------

    /**
     * Creates a view for an element.
     * If a subclass wishes to directly implement the factory
     * producing the view(s), it should reimplement this 
     * method.  By default it simply returns null indicating
     * it is unable to represent the element.
     *
     * @param elem the element
     * @return the view
     */
    public View create(Element elem) {
        return null;
    }

    /**
     * Creates a view for an element.
     * If a subclass wishes to directly implement the factory
     * producing the view(s), it should reimplement this 
     * method.  By default it simply returns null indicating
     * it is unable to represent the part of the element.
     *
     * @param elem the element
     * @param p0 the starting offset >= 0
     * @param p1 the ending offset >= p0
     * @return the view
     */
    public View create(Element elem, int p0, int p1) {
        return null;
    }

    public static class BasicCaret extends DefaultCaret implements UIResource {}

    public static class BasicHighlighter extends DefaultHighlighter implements UIResource {}

    static class BasicCursor extends Cursor implements UIResource {
    	BasicCursor(int type) {
    	    super(type);
    	}

       	BasicCursor(String name) {
    	    super(name);
    	}
    }

    private static BasicCursor textCursor = new BasicCursor(Cursor.TEXT_CURSOR);
    // ----- member variables ---------------------------------------

    private static final EditorKit defaultKit = new DefaultEditorKit();
    transient JTextComponent editor;
    transient boolean painted;
    transient RootView rootView = new RootView();
    transient UpdateHandler updateHandler = new UpdateHandler();
    private static final TransferHandler defaultTransferHandler = new TextTransferHandler();
    private final DragListener dragListener = getDragListener();
    private static final Position.Bias[] discardBias = new Position.Bias[1];
    private DefaultCaret dropCaret;

    /**
     * Root view that acts as a gateway between the component
     * and the View hierarchy.
     */
    class RootView extends View {

        RootView() {
            super(null);
        }

        void setView(View v) {
            View oldView = view;
            view = null;
            if (oldView != null) {
                // get rid of back reference so that the old
                // hierarchy can be garbage collected.
                oldView.setParent(null);
            }
            if (v != null) {
                v.setParent(this);
            }
            view = v;
        }

	/**
	 * Fetches the attributes to use when rendering.  At the root
	 * level there are no attributes.  If an attribute is resolved
	 * up the view hierarchy this is the end of the line.
	 */
        public AttributeSet getAttributes() {
	    return null;
	}

        /**
         * Determines the preferred span for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the span the view would like to be rendered into.
         *         Typically the view is told to render into the span
         *         that is returned, although there is no guarantee.
         *         The parent may choose to resize or break the view.
         */
        public float getPreferredSpan(int axis) {
            if (view != null) {
                return view.getPreferredSpan(axis);
            }
            return 10;
        }

        /**
         * Determines the minimum span for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the span the view would like to be rendered into.
         *         Typically the view is told to render into the span
         *         that is returned, although there is no guarantee.
         *         The parent may choose to resize or break the view.
         */
        public float getMinimumSpan(int axis) {
            if (view != null) {
                return view.getMinimumSpan(axis);
            }
            return 10;
        }

        /**
         * Determines the maximum span for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the span the view would like to be rendered into.
         *         Typically the view is told to render into the span
         *         that is returned, although there is no guarantee.
         *         The parent may choose to resize or break the view.
         */
        public float getMaximumSpan(int axis) {
	    return Integer.MAX_VALUE;
        }

        /**
         * Specifies that a preference has changed.
         * Child views can call this on the parent to indicate that
         * the preference has changed.  The root view routes this to
         * invalidate on the hosting component.
         * <p>
         * This can be called on a different thread from the
         * event dispatching thread and is basically unsafe to
         * propagate into the component.  To make this safe,
         * the operation is transferred over to the event dispatching 
         * thread for completion.  It is a design goal that all view
         * methods be safe to call without concern for concurrency,
         * and this behavior helps make that true.
         *
         * @param child the child view
         * @param width true if the width preference has changed
         * @param height true if the height preference has changed
         */ 
        public void preferenceChanged(View child, boolean width, boolean height) {
            editor.revalidate();
        }

        /**
         * Determines the desired alignment for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the desired alignment, where 0.0 indicates the origin
         *     and 1.0 the full span away from the origin
         */
        public float getAlignment(int axis) {
            if (view != null) {
                return view.getAlignment(axis);
            }
            return 0;
        }

        /**
         * Renders the view.
         *
         * @param g the graphics context
         * @param allocation the region to render into
         */
        public void paint(Graphics g, Shape allocation) {
            if (view != null) {
                Rectangle alloc = (allocation instanceof Rectangle) ?
		          (Rectangle)allocation : allocation.getBounds();
		setSize(alloc.width, alloc.height);
                view.paint(g, allocation);
            }
        }
        
        /**
         * Sets the view parent.
         *
         * @param parent the parent view
         */
        public void setParent(View parent) {
            throw new Error("Can't set parent on root view");
        }

        /** 
         * Returns the number of views in this view.  Since
         * this view simply wraps the root of the view hierarchy
         * it has exactly one child.
         *
         * @return the number of views
         * @see #getView
         */
        public int getViewCount() {
            return 1;
        }

        /** 
         * Gets the n-th view in this container.
         *
         * @param n the number of the view to get
         * @return the view
         */
        public View getView(int n) {
            return view;
        }

	/**
	 * Returns the child view index representing the given position in
	 * the model.  This is implemented to return the index of the only
	 * child.
	 *
	 * @param pos the position >= 0
	 * @return  index of the view representing the given position, or 
	 *   -1 if no view represents that position
	 * @since 1.3
	 */
        public int getViewIndex(int pos, Position.Bias b) {
	    return 0;
	}
    
        /**
         * Fetches the allocation for the given child view. 
         * This enables finding out where various views
         * are located, without assuming the views store
         * their location.  This returns the given allocation
         * since this view simply acts as a gateway between
         * the view hierarchy and the associated component.
         *
         * @param index the index of the child
         * @param a  the allocation to this view.
         * @return the allocation to the child
         */
        public Shape getChildAllocation(int index, Shape a) {
            return a;
        }

        /**
         * Provides a mapping from the document model coordinate space
         * to the coordinate space of the view mapped to it.
         *
         * @param pos the position to convert
         * @param a the allocated region to render into
         * @return the bounding box of the given position
         */
        public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
            if (view != null) {
                return view.modelToView(pos, a, b);
            }
            return null;
        }

	/**
	 * Provides a mapping from the document model coordinate space
	 * to the coordinate space of the view mapped to it.
	 *
	 * @param p0 the position to convert >= 0
	 * @param b0 the bias toward the previous character or the
	 *  next character represented by p0, in case the 
	 *  position is a boundary of two views. 
	 * @param p1 the position to convert >= 0
	 * @param b1 the bias toward the previous character or the
	 *  next character represented by p1, in case the 
	 *  position is a boundary of two views. 
	 * @param a the allocated region to render into
	 * @return the bounding box of the given position is returned
	 * @exception BadLocationException  if the given position does
	 *   not represent a valid location in the associated document
	 * @exception IllegalArgumentException for an invalid bias argument
	 * @see View#viewToModel
	 */
	public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
	    if (view != null) {
		return view.modelToView(p0, b0, p1, b1, a);
	    }
	    return null;
	}

        /**
         * Provides a mapping from the view coordinate space to the logical
         * coordinate space of the model.
         *
         * @param x x coordinate of the view location to convert
         * @param y y coordinate of the view location to convert
         * @param a the allocated region to render into
         * @return the location within the model that best represents the
         *    given point in the view
         */
        public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
            if (view != null) {
                int retValue = view.viewToModel(x, y, a, bias);
		return retValue;
            }
            return -1;
        }

        /**
         * Provides a way to determine the next visually represented model 
         * location that one might place a caret.  Some views may not be visible,
         * they might not be in the same order found in the model, or they just
         * might not allow access to some of the locations in the model.
         *
         * @param pos the position to convert >= 0
         * @param a the allocated region to render into
         * @param direction the direction from the current position that can
         *  be thought of as the arrow keys typically found on a keyboard.
         *  This may be SwingConstants.WEST, SwingConstants.EAST, 
         *  SwingConstants.NORTH, or SwingConstants.SOUTH.  
         * @return the location within the model that best represents the next
         *  location visual position.
         * @exception BadLocationException
         * @exception IllegalArgumentException for an invalid direction
         */
        public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 
                                             int direction,
                                             Position.Bias[] biasRet) 
            throws BadLocationException {
            if( view != null ) {
                int nextPos = view.getNextVisualPositionFrom(pos, b, a,
						     direction, biasRet);
		if(nextPos != -1) {
		    pos = nextPos;
		}
		else {
		    biasRet[0] = b;
		}
            } 
            return pos;
        }

        /**
         * Gives notification that something was inserted into the document
         * in a location that this view is responsible for.
         *
         * @param e the change information from the associated document
         * @param a the current allocation of the view
         * @param f the factory to use to rebuild if the view has children
         */
        public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
            if (view != null) {
                view.insertUpdate(e, a, f);
            }
        }
        
        /**
         * Gives notification that something was removed from the document
         * in a location that this view is responsible for.
         *
         * @param e the change information from the associated document
         * @param a the current allocation of the view
         * @param f the factory to use to rebuild if the view has children
         */
        public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
            if (view != null) {
                view.removeUpdate(e, a, f);
            }
        }

        /**
         * Gives notification from the document that attributes were changed
         * in a location that this view is responsible for.
         *
         * @param e the change information from the associated document
         * @param a the current allocation of the view
         * @param f the factory to use to rebuild if the view has children
         */
        public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
            if (view != null) {
                view.changedUpdate(e, a, f);
            }
        }

        /**
         * Returns the document model underlying the view.
         *
         * @return the model
         */
        public Document getDocument() {
            return editor.getDocument();
        }
        
        /**
         * Returns the starting offset into the model for this view.
         *
         * @return the starting offset
         */
        public int getStartOffset() {
            if (view != null) {
                return view.getStartOffset();
            }
            return getElement().getStartOffset();
        }

        /**
         * Returns the ending offset into the model for this view.
         *
         * @return the ending offset
         */
        public int getEndOffset() {
            if (view != null) {
                return view.getEndOffset();
            }
            return getElement().getEndOffset();
        }

        /**
         * Gets the element that this view is mapped to.
         *
         * @return the view
         */
        public Element getElement() {
            if (view != null) {
                return view.getElement();
            }
            return editor.getDocument().getDefaultRootElement();
        }

        /**
         * Breaks this view on the given axis at the given length.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @param len specifies where a break is desired in the span
         * @param the current allocation of the view
         * @return the fragment of the view that represents the given span
         *   if the view can be broken, otherwise null
         */
        public View breakView(int axis, float len, Shape a) {
            throw new Error("Can't break root view");
        }

        /**
         * Determines the resizability of the view along the
         * given axis.  A value of 0 or less is not resizable.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the weight
         */
        public int getResizeWeight(int axis) {
            if (view != null) {
                return view.getResizeWeight(axis);
            }
            return 0;
        }

        /**
         * Sets the view size.
         *
         * @param width the width
         * @param height the height
         */
        public void setSize(float width, float height) {
            if (view != null) {
                view.setSize(width, height);
            }
        }

        /**
         * Fetches the container hosting the view.  This is useful for
         * things like scheduling a repaint, finding out the host 
         * components font, etc.  The default implementation
         * of this is to forward the query to the parent view.
         *
         * @return the container
         */
        public Container getContainer() {
            return editor;
        }
        
        /**
         * Fetches the factory to be used for building the
         * various view fragments that make up the view that
         * represents the model.  This is what determines
         * how the model will be represented.  This is implemented
         * to fetch the factory provided by the associated
         * EditorKit unless that is null, in which case this
         * simply returns the BasicTextUI itself which allows
         * subclasses to implement a simple factory directly without
         * creating extra objects.  
         *
         * @return the factory
         */
        public ViewFactory getViewFactory() {
            EditorKit kit = getEditorKit(editor);
            ViewFactory f = kit.getViewFactory();
            if (f != null) {
                return f;
            }
            return BasicTextUI.this;
        }

        private View view;

    }

    /**
     * Handles updates from various places.  If the model is changed,
     * this class unregisters as a listener to the old model and 
     * registers with the new model.  If the document model changes,
     * the change is forwarded to the root view.  If the focus
     * accelerator changes, a new keystroke is registered to request
     * focus.
     */
    class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource {

        // --- PropertyChangeListener methods -----------------------

        /**
         * This method gets called when a bound property is changed.
         * We are looking for document changes on the editor.
         */
        public final void propertyChange(PropertyChangeEvent evt) {
            Object oldValue = evt.getOldValue();
            Object newValue = evt.getNewValue();
	    String propertyName = evt.getPropertyName();
            if ((oldValue instanceof Document) || (newValue instanceof Document)) {
                if (oldValue != null) {
                    ((Document)oldValue).removeDocumentListener(this);
		    i18nView = false;
                }
                if (newValue != null) {
                    ((Document)newValue).addDocumentListener(this);
                    if ("document" == propertyName) {
                        setView(null);
                        BasicTextUI.this.propertyChange(evt);
                        modelChanged();
                        return;
                    }
                }
                modelChanged();
            }
	    if ("focusAccelerator" == propertyName) {
		updateFocusAcceleratorBinding(true);
            } else if ("componentOrientation" == propertyName) {
                // Changes in ComponentOrientation require the views to be
                // rebuilt.
                modelChanged();
            } else if ("font" == propertyName) {
                modelChanged();
            } else if ("dropLocation" == propertyName) {
                dropIndexChanged();
	    } else if ("editable" == propertyName) {
                updateCursor();
                modelChanged();
	    }
            BasicTextUI.this.propertyChange(evt);
        }

        private void dropIndexChanged() {
            if (editor.getDropMode() == DropMode.USE_SELECTION) {
                return;
            }

            JTextComponent.DropLocation dropLocation = editor.getDropLocation();

            if (dropLocation == null) {
                if (dropCaret != null) {
                    dropCaret.deinstall(editor);
                    editor.repaint(dropCaret);
                    dropCaret = null;
                }
            } else {
                if (dropCaret == null) {
                    dropCaret = new BasicCaret();
                    dropCaret.install(editor);
                    dropCaret.setVisible(true);
                }

                dropCaret.setDot(dropLocation.getIndex(),
                                 dropLocation.getBias());
            }
        }

        // --- DocumentListener methods -----------------------

        /**
         * The insert notification.  Gets sent to the root of the view structure
         * that represents the portion of the model being represented by the
         * editor.  The factory is added as an argument to the update so that
         * the views can update themselves in a dynamic (not hardcoded) way.
         *
         * @param e  The change notification from the currently associated
         *  document.
         * @see DocumentListener#insertUpdate
         */
        public final void insertUpdate(DocumentEvent e) {
	    Document doc = e.getDocument();
	    Object o = doc.getProperty("i18n");
	    if (o instanceof Boolean) {
		Boolean i18nFlag = (Boolean) o;
		if (i18nFlag.booleanValue() != i18nView) {
		    // i18n flag changed, rebuild the view
		    i18nView = i18nFlag.booleanValue();
		    modelChanged();
		    return;
		}
	    }

	    // normal insert update
            Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
            rootView.insertUpdate(e, alloc, rootView.getViewFactory());
        }

        /**
         * The remove notification.  Gets sent to the root of the view structure
         * that represents the portion of the model being represented by the
         * editor.  The factory is added as an argument to the update so that
         * the views can update themselves in a dynamic (not hardcoded) way.
         *
         * @param e  The change notification from the currently associated
         *  document.
         * @see DocumentListener#removeUpdate
         */
        public final void removeUpdate(DocumentEvent e) {
            Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
            rootView.removeUpdate(e, alloc, rootView.getViewFactory());
	}

        /**
         * The change notification.  Gets sent to the root of the view structure
         * that represents the portion of the model being represented by the
         * editor.  The factory is added as an argument to the update so that
         * the views can update themselves in a dynamic (not hardcoded) way.
         *
         * @param e  The change notification from the currently associated
         *  document.
         * @see DocumentListener#changeUpdate
         */
        public final void changedUpdate(DocumentEvent e) {
            Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
            rootView.changedUpdate(e, alloc, rootView.getViewFactory());
        }

	// --- LayoutManager2 methods --------------------------------

	/**
	 * Adds the specified component with the specified name to
	 * the layout.
	 * @param name the component name
	 * @param comp the component to be added
	 */
	public void addLayoutComponent(String name, Component comp) {
	    // not supported
	}

	/**
	 * Removes the specified component from the layout.
	 * @param comp the component to be removed
	 */
	public void removeLayoutComponent(Component comp) {
	    if (constraints != null) {
		// remove the constraint record
		constraints.remove(comp);
	    }
	}

	/**
	 * Calculates the preferred size dimensions for the specified 
	 * panel given the components in the specified parent container.
	 * @param parent the component to be laid out
	 *  
	 * @see #minimumLayoutSize
	 */
	public Dimension preferredLayoutSize(Container parent) {
	    // should not be called (JComponent uses UI instead)
	    return null;
	}

	/** 
	 * Calculates the minimum size dimensions for the specified 
	 * panel given the components in the specified parent container.
	 * @param parent the component to be laid out
	 * @see #preferredLayoutSize
	 */
	public Dimension minimumLayoutSize(Container parent) {
	    // should not be called (JComponent uses UI instead)
	    return null;
	}

	/** 
	 * Lays out the container in the specified panel.  This is
	 * implemented to position all components that were added
	 * with a View object as a constraint.  The current allocation
	 * of the associated View is used as the location of the 
	 * component.
	 * <p>
	 * A read-lock is acquired on the document to prevent the
	 * view tree from being modified while the layout process
	 * is active.
	 *
	 * @param parent the component which needs to be laid out 
	 */
	public void layoutContainer(Container parent) {
	    if ((constraints != null) && (! constraints.isEmpty())) {
		Rectangle alloc = getVisibleEditorRect();
		if (alloc != null) {
		    Document doc = editor.getDocument();
		    if (doc instanceof AbstractDocument) {
			((AbstractDocument)doc).readLock();
		    }
		    try {
			rootView.setSize(alloc.width, alloc.height);
			Enumeration components = constraints.keys();
			while (components.hasMoreElements()) {
			    Component comp = (Component) components.nextElement();
			    View v = (View) constraints.get(comp);
			    Shape ca = calculateViewPosition(alloc, v);
			    if (ca != null) {
				Rectangle compAlloc = (ca instanceof Rectangle) ? 
				    (Rectangle) ca : ca.getBounds();
				comp.setBounds(compAlloc);
			    }
			}
		    } finally {
			if (doc instanceof AbstractDocument) {
			    ((AbstractDocument)doc).readUnlock();
			}
		    }			
		}
	    }
	}

	/**
	 * Find the Shape representing the given view.
	 */
	Shape calculateViewPosition(Shape alloc, View v) {
	    int pos = v.getStartOffset();
	    View child = null;
	    for (View parent = rootView; (parent != null) && (parent != v); parent = child) {
		int index = parent.getViewIndex(pos, Position.Bias.Forward);
		alloc = parent.getChildAllocation(index, alloc);
		child = parent.getView(index);
	    }
	    return (child != null) ? alloc : null;
	}

	/**
	 * Adds the specified component to the layout, using the specified
	 * constraint object.  We only store those components that were added
	 * with a constraint that is of type View.
	 *
	 * @param comp the component to be added
	 * @param constraint  where/how the component is added to the layout.
	 */
	public void addLayoutComponent(Component comp, Object constraint) {
	    if (constraint instanceof View) {
		if (constraints == null) {
		    constraints = new Hashtable(7);
		}
		constraints.put(comp, constraint);
	    }
	}

	/** 
	 * Returns the maximum size of this component.
	 * @see java.awt.Component#getMinimumSize()
	 * @see java.awt.Component#getPreferredSize()
	 * @see LayoutManager
	 */
        public Dimension maximumLayoutSize(Container target) {
	    // should not be called (JComponent uses UI instead)
	    return null;
	}

	/**
	 * Returns the alignment along the x axis.  This specifies how
	 * the component would like to be aligned relative to other 
	 * components.  The value should be a number between 0 and 1
	 * where 0 represents alignment along the origin, 1 is aligned
	 * the furthest away from the origin, 0.5 is centered, etc.
	 */
        public float getLayoutAlignmentX(Container target) {
	    return 0.5f;
	}

	/**
	 * Returns the alignment along the y axis.  This specifies how
	 * the component would like to be aligned relative to other 
	 * components.  The value should be a number between 0 and 1
	 * where 0 represents alignment along the origin, 1 is aligned
	 * the furthest away from the origin, 0.5 is centered, etc.
	 */
        public float getLayoutAlignmentY(Container target) {
	    return 0.5f;
	}

	/**
	 * Invalidates the layout, indicating that if the layout manager
	 * has cached information it should be discarded.
	 */
        public void invalidateLayout(Container target) {
	}

	/**
	 * The "layout constraints" for the LayoutManager2 implementation.
	 * These are View objects for those components that are represented
	 * by a View in the View tree.
	 */
	private Hashtable constraints;

	private boolean i18nView = false;
    }

    /**
     * Wrapper for text actions to return isEnabled false in case editor is non editable
     */
    class TextActionWrapper extends TextAction {
	public TextActionWrapper(TextAction action) {
	    super((String)action.getValue(Action.NAME));
	    this.action = action;
	}
	/**
         * The operation to perform when this action is triggered.
         *
         * @param e the action event
         */
        public void actionPerformed(ActionEvent e) {
	    action.actionPerformed(e);
	}
	public boolean isEnabled() { 
 	    return (editor == null || editor.isEditable()) ? action.isEnabled() : false;
 	}
	TextAction action = null;
    }


    /**
     * Registered in the ActionMap.
     */
    class FocusAction extends AbstractAction {

        public void actionPerformed(ActionEvent e) {
	    editor.requestFocus();
        }

        public boolean isEnabled() {
            return editor.isEditable();
        }
    }

    private static DragListener getDragListener() {
        synchronized(DragListener.class) {
            DragListener listener =
                (DragListener)AppContext.getAppContext().
                    get(DragListener.class);

            if (listener == null) {
                listener = new DragListener();
                AppContext.getAppContext().put(DragListener.class, listener);
            }

            return listener;
        }
    }

    /**
     * Listens for mouse events for the purposes of detecting drag gestures.
     * BasicTextUI will maintain one of these per AppContext.
     */
    static class DragListener extends MouseInputAdapter
                              implements BeforeDrag {

        private boolean dragStarted;

        public void dragStarting(MouseEvent me) {
            dragStarted = true;
        }

        public void mousePressed(MouseEvent e) {
            JTextComponent c = (JTextComponent)e.getSource();
            if (c.getDragEnabled()) {
                dragStarted = false;
                if (isDragPossible(e) && DragRecognitionSupport.mousePressed(e)) {
                    e.consume();
                }
            }
        }

        public void mouseReleased(MouseEvent e) {
            JTextComponent c = (JTextComponent)e.getSource();
            if (c.getDragEnabled()) {
                if (dragStarted) {
                    e.consume();
                }

                DragRecognitionSupport.mouseReleased(e);
            }
        }

        public void mouseDragged(MouseEvent e) {
            JTextComponent c = (JTextComponent)e.getSource();
            if (c.getDragEnabled()) {
                if (dragStarted || DragRecognitionSupport.mouseDragged(e, this)) {
                    e.consume();
                }
            }
        }

        /**
         * Determines if the following are true:
         * <ul>
         * <li>the component is enabled
         * <li>the press event is located over a selection
         * </ul>
         */
        protected boolean isDragPossible(MouseEvent e) {
            JTextComponent c = (JTextComponent)e.getSource();
            if (c.isEnabled()) {
                Caret caret = c.getCaret();
                int dot = caret.getDot();
                int mark = caret.getMark();
                if (dot != mark) {
                    Point p = new Point(e.getX(), e.getY());
                    int pos = c.viewToModel(p);

                    int p0 = Math.min(dot, mark);
                    int p1 = Math.max(dot, mark);
                    if ((pos >= p0) && (pos < p1)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    static class TextTransferHandler extends TransferHandler implements UIResource {
        
        private JTextComponent exportComp;
        private boolean shouldRemove;
        private int p0;
        private int p1;

        /**
         * Whether or not this is a drop using
         * <code>DropMode.INSERT</code>.
         */
        private boolean modeBetween = false;

        /**
         * Whether or not this is a drop.
         */
        private boolean isDrop = false;

        /**
         * The drop action.
         */
        private int dropAction = MOVE;

        /**
         * The drop bias.
         */
        private Position.Bias dropBias;

        /**
         * Try to find a flavor that can be used to import a Transferable.  
         * The set of usable flavors are tried in the following order:
         * <ol>
         *     <li>First, an attempt is made to find a flavor matching the content type
         *         of the EditorKit for the component.
         *     <li>Second, an attempt to find a text/plain flavor is made.
         *     <li>Third, an attempt to find a flavor representing a String reference
         *         in the same VM is made.
         *     <li>Lastly, DataFlavor.stringFlavor is searched for.
         * </ol>
         */
	protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
            DataFlavor plainFlavor = null;
            DataFlavor refFlavor = null;
            DataFlavor stringFlavor = null;
            
            if (c instanceof JEditorPane) {
                for (int i = 0; i < flavors.length; i++) {
                    String mime = flavors[i].getMimeType();
                    if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) {
                        return flavors[i];
                    } else if (plainFlavor == null && mime.startsWith("text/plain")) {
                        plainFlavor = flavors[i];
                    } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
                                                 && flavors[i].getRepresentationClass() == java.lang.String.class) {
                        refFlavor = flavors[i];
                    } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
                        stringFlavor = flavors[i];
                    }
                }
                if (plainFlavor != null) {
                    return plainFlavor;
                } else if (refFlavor != null) {
                    return refFlavor;
                } else if (stringFlavor != null) {
                    return stringFlavor;
                }
                return null;
            }
            
            
            for (int i = 0; i < flavors.length; i++) {
                String mime = flavors[i].getMimeType();
                if (mime.startsWith("text/plain")) {
                    return flavors[i];
                } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
                                             && flavors[i].getRepresentationClass() == java.lang.String.class) {
                    refFlavor = flavors[i];
                } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
                    stringFlavor = flavors[i];
                }
            }
            if (refFlavor != null) {
                return refFlavor;
            } else if (stringFlavor != null) {
                return stringFlavor;
            }
            return null;
	}

	/**
	 * Import the given stream data into the text component.
	 */
        protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
                                               throws BadLocationException, IOException {
            if (useRead) {
                int startPosition = c.getSelectionStart();
                int endPosition = c.getSelectionEnd();
                int length = endPosition - startPosition;
                EditorKit kit = c.getUI().getEditorKit(c);
                Document doc = c.getDocument();
                if (length > 0) {
                    doc.remove(startPosition, length);
                }
                kit.read(in, doc, startPosition);
            } else {
                char[] buff = new char[1024];
                int nch;
                boolean lastWasCR = false;
                int last;
                StringBuffer sbuff = null;
                
                // Read in a block at a time, mapping \r\n to \n, as well as single
                // \r to \n.
                while ((nch = in.read(buff, 0, buff.length)) != -1) {
                    if (sbuff == null) {
                        sbuff = new StringBuffer(nch);
                    }
                    last = 0;
                    for(int counter = 0; counter < nch; counter++) {
                        switch(buff[counter]) {
                        case '\r':
                            if (lastWasCR) {
                                if (counter == 0) {
                                    sbuff.append('\n');
                                } else {
                                    buff[counter - 1] = '\n';
                                }
                            } else {
                                lastWasCR = true;
                            }
                            break;
                        case '\n':
                            if (lastWasCR) {
                                if (counter > (last + 1)) {
                                    sbuff.append(buff, last, counter - last - 1);
                                }
                                // else nothing to do, can skip \r, next write will
                                // write \n
                                lastWasCR = false;
                                last = counter;
                            }
                            break;
                        default:
                            if (lastWasCR) {
                                if (counter == 0) {
                                    sbuff.append('\n');
                                } else {
                                    buff[counter - 1] = '\n';
                                }
                                lastWasCR = false;
                            }
                            break;
                        }
                    }
                    if (last < nch) {
                        if (lastWasCR) {
                            if (last < (nch - 1)) {
                                sbuff.append(buff, last, nch - last - 1);
                            }
                        } else {
                            sbuff.append(buff, last, nch - last);
                        }
                    }
                }
                if (lastWasCR) {
                    sbuff.append('\n');
                }
                c.replaceSelection(sbuff != null ? sbuff.toString() : "");
            }
	}

	// --- TransferHandler methods ------------------------------------

	/**
	 * This is the type of transfer actions supported by the source.  Some models are 
	 * not mutable, so a transfer operation of COPY only should
	 * be advertised in that case.
	 * 
	 * @param c  The component holding the data to be transfered.  This
	 *  argument is provided to enable sharing of TransferHandlers by
	 *  multiple components.
	 * @return  This is implemented to return NONE if the component is a JPasswordField
	 *  since exporting data via user gestures is not allowed.  If the text component is
	 *  editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
	 */
        public int getSourceActions(JComponent c) {
            if (c instanceof JPasswordField &&
                c.getClientProperty("JPasswordField.cutCopyAllowed") !=
                Boolean.TRUE) {
                return NONE;
            }

            return ((JTextComponent)c).isEditable() ? COPY_OR_MOVE : COPY;
	}

	/**
	 * Create a Transferable to use as the source for a data transfer.
	 *
	 * @param comp  The component holding the data to be transfered.  This
	 *  argument is provided to enable sharing of TransferHandlers by
	 *  multiple components.
	 * @return  The representation of the data to be transfered. 
	 *  
	 */
        protected Transferable createTransferable(JComponent comp) {
            exportComp = (JTextComponent)comp;
            shouldRemove = true;
            p0 = exportComp.getSelectionStart();
            p1 = exportComp.getSelectionEnd();
            return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null;
	}

	/**
	 * This method is called after data has been exported.  This method should remove 
	 * the data that was transfered if the action was MOVE.
	 *
	 * @param source The component that was the source of the data.
	 * @param data   The data that was transferred or possibly null
         *               if the action is <code>NONE</code>.
	 * @param action The actual action that was performed.  
	 */
        protected void exportDone(JComponent source, Transferable data, int action) {
            // only remove the text if shouldRemove has not been set to
            // false by importData and only if the action is a move
            if (shouldRemove && action == MOVE) {
		TextTransferable t = (TextTransferable)data;
		t.removeText();
	    }
            
            exportComp = null;
	}

        public boolean importData(TransferSupport support) {
            isDrop = support.isDrop();

            if (isDrop) {
                modeBetween =
                    ((JTextComponent)support.getComponent()).getDropMode() == DropMode.INSERT;

                dropBias = ((JTextComponent.DropLocation)support.getDropLocation()).getBias();

                dropAction = support.getDropAction();
            }

            try {
                return super.importData(support);
            } finally {
                isDrop = false;
                modeBetween = false;
                dropBias = null;
                dropAction = MOVE;
            }
        }
 
	/**
	 * This method causes a transfer to a component from a clipboard or a 
	 * DND drop operation.  The Transferable represents the data to be
	 * imported into the component.  
	 *
	 * @param comp  The component to receive the transfer.  This
	 *  argument is provided to enable sharing of TransferHandlers by
	 *  multiple components.
	 * @param t     The data to import
	 * @return  true if the data was inserted into the component, false otherwise.
	 */
        public boolean importData(JComponent comp, Transferable t) {
            JTextComponent c = (JTextComponent)comp;

            int pos = modeBetween
                      ? ((JTextComponent.DropLocation)c.getDropLocation()).getIndex()
                      : c.getCaretPosition();

	    // if we are importing to the same component that we exported from
            // then don't actually do anything if the drop location is inside
            // the drag location and set shouldRemove to false so that exportDone
            // knows not to remove any data
            if (dropAction == MOVE && c == exportComp && pos >= p0 && pos <= p1) {
                shouldRemove = false;
                return true;
            }

	    boolean imported = false;
	    DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
	    if (importFlavor != null) {
		try {
                    boolean useRead = false;
                    if (comp instanceof JEditorPane) {
                        JEditorPane ep = (JEditorPane)comp;
                        if (!ep.getContentType().startsWith("text/plain") &&
                                importFlavor.getMimeType().startsWith(ep.getContentType())) {
                            useRead = true;
                        }
                    }
		    InputContext ic = c.getInputContext();
		    if (ic != null) {
			ic.endComposition();
		    }
                    Reader r = importFlavor.getReaderForText(t);

                    if (modeBetween) {
                        Caret caret = c.getCaret();
                        if (caret instanceof DefaultCaret) {
                            ((DefaultCaret)caret).setDot(pos, dropBias);
                        } else {
                            c.setCaretPosition(pos);
                        }
                    }

                    handleReaderImport(r, c, useRead);

                    if (isDrop) {
                        c.requestFocus();
                        Caret caret = c.getCaret();
                        if (caret instanceof DefaultCaret) {
                            int newPos = caret.getDot();
                            Position.Bias newBias = ((DefaultCaret)caret).getDotBias();

                            ((DefaultCaret)caret).setDot(pos, dropBias);
                            ((DefaultCaret)caret).moveDot(newPos, newBias);
                        } else {
                            c.select(pos, c.getCaretPosition());
                        }
                    }

                    imported = true;
		} catch (UnsupportedFlavorException ufe) {
		} catch (BadLocationException ble) {
		} catch (IOException ioe) {
		}
	    }
	    return imported;
	}

	/**
	 * This method indicates if a component would accept an import of the given
	 * set of data flavors prior to actually attempting to import it. 
	 *
	 * @param comp  The component to receive the transfer.  This
	 *  argument is provided to enable sharing of TransferHandlers by
	 *  multiple components.
	 * @param flavors  The data formats available
	 * @return  true if the data can be inserted into the component, false otherwise.
	 */
        public boolean canImport(JComponent comp, DataFlavor[] flavors) {
            JTextComponent c = (JTextComponent)comp;
            if (!(c.isEditable() && c.isEnabled())) {
                return false;
            }
            return (getImportFlavor(flavors, c) != null);
	}

        /**
	 * A possible implementation of the Transferable interface
	 * for text components.  For a JEditorPane with a rich set
	 * of EditorKit implementations, conversions could be made
	 * giving a wider set of formats.  This is implemented to
	 * offer up only the active content type and text/plain
	 * (if that is not the active format) since that can be
	 * extracted from other formats.
	 */
	static class TextTransferable extends BasicTransferable {

	    TextTransferable(JTextComponent c, int start, int end) {
		super(null, null);
                
                this.c = c;
                
		Document doc = c.getDocument();

		try {
		    p0 = doc.createPosition(start);
		    p1 = doc.createPosition(end);

                    plainData = c.getSelectedText();

                    if (c instanceof JEditorPane) {
                        JEditorPane ep = (JEditorPane)c;
                        
                        mimeType = ep.getContentType();

                        if (mimeType.startsWith("text/plain")) {
                            return;
                        }

                        StringWriter sw = new StringWriter(p1.getOffset() - p0.getOffset());
                        ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset());
                        
                        if (mimeType.startsWith("text/html")) {
                            htmlData = sw.toString();
                        } else {
                            richText = sw.toString();
                        }
                    }
		} catch (BadLocationException ble) {
		} catch (IOException ioe) {
                }
	    }

	    void removeText() {
		if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) {
		    try {
			Document doc = c.getDocument();
			doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset());
		    } catch (BadLocationException e) {
		    }
		}
	    }

	    // ---- EditorKit other than plain or HTML text -----------------------

	    /** 
	     * If the EditorKit is not for text/plain or text/html, that format
	     * is supported through the "richer flavors" part of BasicTransferable.
	     */
            protected DataFlavor[] getRicherFlavors() {
		if (richText == null) {
		    return null;
		}

		try {
		    DataFlavor[] flavors = new DataFlavor[3];
                    flavors[0] = new DataFlavor(mimeType + ";class=java.lang.String");
                    flavors[1] = new DataFlavor(mimeType + ";class=java.io.Reader");
                    flavors[2] = new DataFlavor(mimeType + ";class=java.io.InputStream;charset=unicode");
		    return flavors;
		} catch (ClassNotFoundException cle) {
		    // fall through to unsupported (should not happen)
		}

		return null;
	    }

	    /**
	     * The only richer format supported is the file list flavor
	     */
            protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException {
		if (richText == null) {
		    return null;
		}

		if (String.class.equals(flavor.getRepresentationClass())) {
		    return richText;
		} else if (Reader.class.equals(flavor.getRepresentationClass())) {
		    return new StringReader(richText);
		} else if (InputStream.class.equals(flavor.getRepresentationClass())) {
		    return new StringBufferInputStream(richText);
		}
                throw new UnsupportedFlavorException(flavor);
	    }

	    Position p0;
	    Position p1;
            String mimeType;
            String richText;
            JTextComponent c;
	}

    }

}