jdk/src/share/classes/javax/swing/plaf/basic/BasicTextUI.java
changeset 2 90ce3da70b43
child 1290 da8902cd496c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/plaf/basic/BasicTextUI.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,2650 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+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)
+ */
+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;
+        }
+
+    }
+
+}