       1 /*
       2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25 package javax.swing.plaf.basic;
      26
      27 import java.util.*;
      28 import java.awt.*;
      29 import java.awt.event.*;
      30 import java.awt.datatransfer.*;
      31 import java.awt.geom.Point2D;
      32 import java.awt.geom.Rectangle2D;
      33 import java.awt.im.InputContext;
      34 import java.beans.*;
      35 import java.io.*;
      36 import javax.swing.*;
      37 import javax.swing.plaf.*;
      38 import javax.swing.text.*;
      39 import javax.swing.event.*;
      40 import javax.swing.border.Border;
      41 import javax.swing.plaf.UIResource;
      42 import javax.swing.plaf.synth.SynthUI;
      43 import sun.swing.DefaultLookup;
      44 import sun.awt.AppContext;
      45 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
      46
      47 /**
      48  * <p>
      49  * Basis of a text components look-and-feel.  This provides the
      50  * basic editor view and controller services that may be useful
      51  * when creating a look-and-feel for an extension of
      52  * <code>JTextComponent</code>.
      53  * <p>
      54  * Most state is held in the associated <code>JTextComponent</code>
      55  * as bound properties, and the UI installs default values for the
      56  * various properties.  This default will install something for
      57  * all of the properties.  Typically, a LAF implementation will
      58  * do more however.  At a minimum, a LAF would generally install
      59  * key bindings.
      60  * <p>
      61  * This class also provides some concurrency support if the
      62  * <code>Document</code> associated with the JTextComponent is a subclass of
      63  * <code>AbstractDocument</code>.  Access to the View (or View hierarchy) is
      64  * serialized between any thread mutating the model and the Swing
      65  * event thread (which is expected to render, do model/view coordinate
      66  * translation, etc).  <em>Any access to the root view should first
      67  * acquire a read-lock on the AbstractDocument and release that lock
      68  * in a finally block.</em>
      69  * <p>
      70  * An important method to define is the {@link #getPropertyPrefix} method
      71  * which is used as the basis of the keys used to fetch defaults
      72  * from the UIManager.  The string should reflect the type of
      73  * TextUI (eg. TextField, TextArea, etc) without the particular
      74  * LAF part of the name (eg Metal, Motif, etc).
      75  * <p>
      76  * To build a view of the model, one of the following strategies
      77  * can be employed.
      78  * <ol>
      79  * <li>
      80  * One strategy is to simply redefine the
      81  * ViewFactory interface in the UI.  By default, this UI itself acts
      82  * as the factory for View implementations.  This is useful
      83  * for simple factories.  To do this reimplement the
      84  * {@link #create} method.
      85  * <li>
      86  * A common strategy for creating more complex types of documents
      87  * is to have the EditorKit implementation return a factory.  Since
      88  * the EditorKit ties all of the pieces necessary to maintain a type
      89  * of document, the factory is typically an important part of that
      90  * and should be produced by the EditorKit implementation.
      91  * </ol>
      92  * <p>
      93  * <strong>Warning:</strong>
      94  * Serialized objects of this class will not be compatible with
      95  * future Swing releases. The current serialization support is
      96  * appropriate for short term storage or RMI between applications running
      97  * the same version of Swing.  As of 1.4, support for long term storage
      98  * of all JavaBeans&trade;
      99  * has been added to the <code>java.beans</code> package.
     100  * Please see {@link java.beans.XMLEncoder}.
     101  *
     102  * @author Timothy Prinzing
     103  * @author Shannon Hickey (drag and drop)
     104  */
     105 @SuppressWarnings("serial") // Same-version serialization only
     106 public abstract class BasicTextUI extends TextUI implements ViewFactory {
     107     private static final int DEFAULT_CARET_MARGIN = 1;
     108
     109     /**
     110      * Creates a new UI.
     111      */
     112     public BasicTextUI() {
     113         painted = false;
     114     }
     115
     116     /**
     117      * Creates the object to use for a caret.  By default an
     118      * instance of BasicCaret is created.  This method
     119      * can be redefined to provide something else that implements
     120      * the InputPosition interface or a subclass of JCaret.
     121      *
     122      * @return the caret object
     123      */
     124     protected Caret createCaret() {
     125         return new BasicCaret();
     126     }
     127
     128     /**
     129      * Creates the object to use for adding highlights.  By default
     130      * an instance of BasicHighlighter is created.  This method
     131      * can be redefined to provide something else that implements
     132      * the Highlighter interface or a subclass of DefaultHighlighter.
     133      *
     134      * @return the highlighter
     135      */
     136     protected Highlighter createHighlighter() {
     137         return new BasicHighlighter();
     138     }
     139
     140     /**
     141      * Fetches the name of the keymap that will be installed/used
     142      * by default for this UI. This is implemented to create a
     143      * name based upon the classname.  The name is the name
     144      * of the class with the package prefix removed.
     145      *
     146      * @return the name
     147      */
     148     protected String getKeymapName() {
     149         String nm = getClass().getName();
     150         int index = nm.lastIndexOf('.');
     151         if (index >= 0) {
     152             nm = nm.substring(index+1, nm.length());
     153         }
     154         return nm;
     155     }
     156
     157     /**
     158      * Creates the keymap to use for the text component, and installs
     159      * any necessary bindings into it.  By default, the keymap is
     160      * shared between all instances of this type of TextUI. The
     161      * keymap has the name defined by the getKeymapName method.  If the
     162      * keymap is not found, then DEFAULT_KEYMAP from JTextComponent is used.
     163      * <p>
     164      * The set of bindings used to create the keymap is fetched
     165      * from the UIManager using a key formed by combining the
     166      * {@link #getPropertyPrefix} method
     167      * and the string <code>.keyBindings</code>.  The type is expected
     168      * to be <code>JTextComponent.KeyBinding[]</code>.
     169      *
     170      * @return the keymap
     171      * @see #getKeymapName
     172      * @see javax.swing.text.JTextComponent
     173      */
     174     protected Keymap createKeymap() {
     175         String nm = getKeymapName();
     176         Keymap map = JTextComponent.getKeymap(nm);
     177         if (map == null) {
     178             Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
     179             map = JTextComponent.addKeymap(nm, parent);
     180             String prefix = getPropertyPrefix();
     181             Object o = DefaultLookup.get(editor, this,
     182                 prefix + ".keyBindings");
     183             if ((o != null) && (o instanceof JTextComponent.KeyBinding[])) {
     184                 JTextComponent.KeyBinding[] bindings = (JTextComponent.KeyBinding[]) o;
     185                 JTextComponent.loadKeymap(map, bindings, getComponent().getActions());
     186             }
     187         }
     188         return map;
     189     }
     190
     191     /**
     192      * This method gets called when a bound property is changed
     193      * on the associated JTextComponent.  This is a hook
     194      * which UI implementations may change to reflect how the
     195      * UI displays bound properties of JTextComponent subclasses.
     196      * This is implemented to do nothing (i.e. the response to
     197      * properties in JTextComponent itself are handled prior
     198      * to calling this method).
     199      *
     200      * This implementation updates the background of the text
     201      * component if the editable and/or enabled state changes.
     202      *
     203      * @param evt the property change event
     204      */
     205     protected void propertyChange(PropertyChangeEvent evt) {
     206         if (evt.getPropertyName().equals("editable") ||
     207                 evt.getPropertyName().equals("enabled")) {
     208
     209             updateBackground((JTextComponent)evt.getSource());
     210         } else if (evt.getPropertyName().equals("caretWidth")) {
     211             Object value = evt.getNewValue();
     212             if (value instanceof Number) {
     213                 int width = ((Number) value).intValue();
     214                 if (width >= 0) caretMargin = width;
     215             }
     216         }
     217     }
     218
     219     /**
     220      * Updates the background of the text component based on whether the
     221      * text component is editable and/or enabled.
     222      *
     223      * @param c the JTextComponent that needs its background color updated
     224      */
     225     private void updateBackground(JTextComponent c) {
     226         // This is a temporary workaround.
     227         // This code does not correctly deal with Synth (Synth doesn't use
     228         // properties like this), nor does it deal with the situation where
     229         // the developer grabs the color from a JLabel and sets it as
     230         // the background for a JTextArea in all look and feels. The problem
     231         // scenario results if the Color obtained for the Label and TextArea
     232         // is ==, which is the case for the windows look and feel.
     233         // Until an appropriate solution is found, the code is being
     234         // reverted to what it was before the original fix.
     235         if (this instanceof SynthUI || (c instanceof JTextArea)) {
     236             return;
     237         }
     238         Color background = c.getBackground();
     239         if (background instanceof UIResource) {
     240             String prefix = getPropertyPrefix();
     241
     242             Color disabledBG =
     243                 DefaultLookup.getColor(c, this, prefix + ".disabledBackground", null);
     244             Color inactiveBG =
     245                 DefaultLookup.getColor(c, this, prefix + ".inactiveBackground", null);
     246             Color bg =
     247                 DefaultLookup.getColor(c, this, prefix + ".background", null);
     248
     249             /* In an ideal situation, the following check would not be necessary
     250              * and we would replace the color any time the previous color was a
     251              * UIResouce. However, it turns out that there is existing code that
     252              * uses the following inadvisable pattern to turn a text area into
     253              * what appears to be a multi-line label:
     254              *
     255              * JLabel label = new JLabel();
     256              * JTextArea area = new JTextArea();
     257              * area.setBackground(label.getBackground());
     258              * area.setEditable(false);
     259              *
     260              * JLabel's default background is a UIResource. As such, just
     261              * checking for UIResource would have us always changing the
     262              * background away from what the developer wanted.
     263              *
     264              * Therefore, for JTextArea/JEditorPane, we'll additionally check
     265              * that the color we're about to replace matches one that was
     266              * installed by us from the UIDefaults.
     267              */
     268             if ((c instanceof JTextArea || c instanceof JEditorPane)
     269                     && background != disabledBG
     270                     && background != inactiveBG
     271                     && background != bg) {
     272
     273                 return;
     274             }
     275
     276             Color newColor = null;
     277             if (!c.isEnabled()) {
     278                 newColor = disabledBG;
     279             }
     280             if (newColor == null && !c.isEditable()) {
     281                 newColor = inactiveBG;
     282             }
     283             if (newColor == null) {
     284                 newColor = bg;
     285             }
     286             if (newColor != null && newColor != background) {
     287                 c.setBackground(newColor);
     288             }
     289         }
     290     }
     291
     292     /**
     293      * Gets the name used as a key to look up properties through the
     294      * UIManager.  This is used as a prefix to all the standard
     295      * text properties.
     296      *
     297      * @return the name
     298      */
     299     protected abstract String getPropertyPrefix();
     300
     301     /**
     302      * Initializes component properties, such as font, foreground,
     303      * background, caret color, selection color, selected text color,
     304      * disabled text color, and border color.  The font, foreground, and
     305      * background properties are only set if their current value is either null
     306      * or a UIResource, other properties are set if the current
     307      * value is null.
     308      *
     309      * @see #uninstallDefaults
     310      * @see #installUI
     311      */
     312     protected void installDefaults()
     313     {
     314         String prefix = getPropertyPrefix();
     315         Font f = editor.getFont();
     316         if ((f == null) || (f instanceof UIResource)) {
     317             editor.setFont(UIManager.getFont(prefix + ".font"));
     318         }
     319
     320         Color bg = editor.getBackground();
     321         if ((bg == null) || (bg instanceof UIResource)) {
     322             editor.setBackground(UIManager.getColor(prefix + ".background"));
     323         }
     324
     325         Color fg = editor.getForeground();
     326         if ((fg == null) || (fg instanceof UIResource)) {
     327             editor.setForeground(UIManager.getColor(prefix + ".foreground"));
     328         }
     329
     330         Color color = editor.getCaretColor();
     331         if ((color == null) || (color instanceof UIResource)) {
     332             editor.setCaretColor(UIManager.getColor(prefix + ".caretForeground"));
     333         }
     334
     335         Color s = editor.getSelectionColor();
     336         if ((s == null) || (s instanceof UIResource)) {
     337             editor.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground"));
     338         }
     339
     340         Color sfg = editor.getSelectedTextColor();
     341         if ((sfg == null) || (sfg instanceof UIResource)) {
     342             editor.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground"));
     343         }
     344
     345         Color dfg = editor.getDisabledTextColor();
     346         if ((dfg == null) || (dfg instanceof UIResource)) {
     347             editor.setDisabledTextColor(UIManager.getColor(prefix + ".inactiveForeground"));
     348         }
     349
     350         Border b = editor.getBorder();
     351         if ((b == null) || (b instanceof UIResource)) {
     352             editor.setBorder(UIManager.getBorder(prefix + ".border"));
     353         }
     354
     355         Insets margin = editor.getMargin();
     356         if (margin == null || margin instanceof UIResource) {
     357             editor.setMargin(UIManager.getInsets(prefix + ".margin"));
     358         }
     359
     360         updateCursor();
     361     }
     362
     363     private void installDefaults2() {
     364         editor.addMouseListener(dragListener);
     365         editor.addMouseMotionListener(dragListener);
     366
     367         String prefix = getPropertyPrefix();
     368
     369         Caret caret = editor.getCaret();
     370         if (caret == null || caret instanceof UIResource) {
     371             caret = createCaret();
     372             editor.setCaret(caret);
     373
     374             int rate = DefaultLookup.getInt(getComponent(), this, prefix + ".caretBlinkRate", 500);
     375             caret.setBlinkRate(rate);
     376         }
     377
     378         Highlighter highlighter = editor.getHighlighter();
     379         if (highlighter == null || highlighter instanceof UIResource) {
     380             editor.setHighlighter(createHighlighter());
     381         }
     382
     383         TransferHandler th = editor.getTransferHandler();
     384         if (th == null || th instanceof UIResource) {
     385             editor.setTransferHandler(getTransferHandler());
     386         }
     387     }
     388
     389     /**
     390      * Sets the component properties that have not been explicitly overridden
     391      * to {@code null}.  A property is considered overridden if its current
     392      * value is not a {@code UIResource}.
     393      *
     394      * @see #installDefaults
     395      * @see #uninstallUI
     396      */
     397     protected void uninstallDefaults()
     398     {
     399         editor.removeMouseListener(dragListener);
     400         editor.removeMouseMotionListener(dragListener);
     401
     402         if (editor.getCaretColor() instanceof UIResource) {
     403             editor.setCaretColor(null);
     404         }
     405
     406         if (editor.getSelectionColor() instanceof UIResource) {
     407             editor.setSelectionColor(null);
     408         }
     409
     410         if (editor.getDisabledTextColor() instanceof UIResource) {
     411             editor.setDisabledTextColor(null);
     412         }
     413
     414         if (editor.getSelectedTextColor() instanceof UIResource) {
     415             editor.setSelectedTextColor(null);
     416         }
     417
     418         if (editor.getBorder() instanceof UIResource) {
     419             editor.setBorder(null);
     420         }
     421
     422         if (editor.getMargin() instanceof UIResource) {
     423             editor.setMargin(null);
     424         }
     425
     426         if (editor.getCaret() instanceof UIResource) {
     427             editor.setCaret(null);
     428         }
     429
     430         if (editor.getHighlighter() instanceof UIResource) {
     431             editor.setHighlighter(null);
     432         }
     433
     434         if (editor.getTransferHandler() instanceof UIResource) {
     435             editor.setTransferHandler(null);
     436         }
     437
     438         if (editor.getCursor() instanceof UIResource) {
     439             editor.setCursor(null);
     440         }
     441     }
     442
     443     /**
     444      * Installs listeners for the UI.
     445      */
     446     protected void installListeners() {
     447     }
     448
     449     /**
     450      * Uninstalls listeners for the UI.
     451      */
     452     protected void uninstallListeners() {
     453     }
     454
     455     /**
     456      * Registers keyboard actions.
     457      */
     458     protected void installKeyboardActions() {
     459         // backward compatibility support... keymaps for the UI
     460         // are now installed in the more friendly input map.
     461         editor.setKeymap(createKeymap());
     462
     463         InputMap km = getInputMap();
     464         if (km != null) {
     465             SwingUtilities.replaceUIInputMap(editor, JComponent.WHEN_FOCUSED,
     466                                              km);
     467         }
     468
     469         ActionMap map = getActionMap();
     470         if (map != null) {
     471             SwingUtilities.replaceUIActionMap(editor, map);
     472         }
     473
     474         updateFocusAcceleratorBinding(false);
     475     }
     476
     477     /**
     478      * Get the InputMap to use for the UI.
     479      */
     480     InputMap getInputMap() {
     481         InputMap map = new InputMapUIResource();
     482
     483         InputMap shared =
     484             (InputMap)DefaultLookup.get(editor, this,
     485             getPropertyPrefix() + ".focusInputMap");
     486         if (shared != null) {
     487             map.setParent(shared);
     488         }
     489         return map;
     490     }
     491
     492     /**
     493      * Invoked when the focus accelerator changes, this will update the
     494      * key bindings as necessary.
     495      */
     496     void updateFocusAcceleratorBinding(boolean changed) {
     497         char accelerator = editor.getFocusAccelerator();
     498
     499         if (changed || accelerator != '\0') {
     500             InputMap km = SwingUtilities.getUIInputMap
     501                         (editor, JComponent.WHEN_IN_FOCUSED_WINDOW);
     502
     503             if (km == null && accelerator != '\0') {
     504                 km = new ComponentInputMapUIResource(editor);
     505                 SwingUtilities.replaceUIInputMap(editor, JComponent.
     506                                                  WHEN_IN_FOCUSED_WINDOW, km);
     507                 ActionMap am = getActionMap();
     508                 SwingUtilities.replaceUIActionMap(editor, am);
     509             }
     510             if (km != null) {
     511                 km.clear();
     512                 if (accelerator != '\0') {
     513                     km.put(KeyStroke.getKeyStroke(accelerator, BasicLookAndFeel.getFocusAcceleratorKeyMask()), "requestFocus");
     514                     km.put(KeyStroke.getKeyStroke(accelerator,
     515                             BasicLookAndFeel.getFocusAcceleratorKeyMask() |
     516                             InputEvent.ALT_GRAPH_DOWN_MASK), "requestFocus");
     517                 }
     518             }
     519         }
     520     }
     521
     522
     523     /**
     524      * Invoked when editable property is changed.
     525      *
     526      * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case
     527      * editor is editable
     528      * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case
     529      * editor is non editable
     530      */
     531     @SuppressWarnings("deprecation")
     532     void updateFocusTraversalKeys() {
     533         /*
     534          * Fix for 4514331 Non-editable JTextArea and similar
     535          * should allow Tab to keyboard - accessibility
     536          */
     537         EditorKit editorKit = getEditorKit(editor);
     538         if ( editorKit != null
     539              && editorKit instanceof DefaultEditorKit) {
     540             Set<AWTKeyStroke> storedForwardTraversalKeys = editor.
     541                 getFocusTraversalKeys(KeyboardFocusManager.
     542                                       FORWARD_TRAVERSAL_KEYS);
     543             Set<AWTKeyStroke> storedBackwardTraversalKeys = editor.
     544                 getFocusTraversalKeys(KeyboardFocusManager.
     545                                       BACKWARD_TRAVERSAL_KEYS);
     546             Set<AWTKeyStroke> forwardTraversalKeys =
     547                 new HashSet<AWTKeyStroke>(storedForwardTraversalKeys);
     548             Set<AWTKeyStroke> backwardTraversalKeys =
     549                 new HashSet<AWTKeyStroke>(storedBackwardTraversalKeys);
     550             if (editor.isEditable()) {
     551                 forwardTraversalKeys.
     552                     remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
     553                 backwardTraversalKeys.
     554                     remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
     555                                                   InputEvent.SHIFT_MASK));
     556             } else {
     557                 forwardTraversalKeys.add(KeyStroke.
     558                                          getKeyStroke(KeyEvent.VK_TAB, 0));
     559                 backwardTraversalKeys.
     560                     add(KeyStroke.
     561                         getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
     562             }
     563             LookAndFeel.installProperty(editor,
     564                                         "focusTraversalKeysForward",
     565                                          forwardTraversalKeys);
     566             LookAndFeel.installProperty(editor,
     567                                         "focusTraversalKeysBackward",
     568                                          backwardTraversalKeys);
     569         }
     570
     571     }
     572
     573     /**
     574      * As needed updates cursor for the target editor.
     575      */
     576     private void updateCursor() {
     577         if ((! editor.isCursorSet())
     578                || editor.getCursor() instanceof UIResource) {
     579             Cursor cursor = (editor.isEditable()) ? textCursor : null;
     580             editor.setCursor(cursor);
     581         }
     582     }
     583
     584     /**
     585      * Returns the <code>TransferHandler</code> that will be installed if
     586      * their isn't one installed on the <code>JTextComponent</code>.
     587      */
     588     TransferHandler getTransferHandler() {
     589         return defaultTransferHandler;
     590     }
     591
     592     /**
     593      * Fetch an action map to use.
     594      */
     595     ActionMap getActionMap() {
     596         String mapName = getPropertyPrefix() + ".actionMap";
     597         ActionMap map = (ActionMap)UIManager.get(mapName);
     598
     599         if (map == null) {
     600             map = createActionMap();
     601             if (map != null) {
     602                 UIManager.getLookAndFeelDefaults().put(mapName, map);
     603             }
     604         }
     605         ActionMap componentMap = new ActionMapUIResource();
     606         componentMap.put("requestFocus", new FocusAction());
     607         /*
     608          * fix for bug 4515750
     609          * JTextField & non-editable JTextArea bind return key - default btn not accessible
     610          *
     611          * Wrap the return action so that it is only enabled when the
     612          * component is editable. This allows the default button to be
     613          * processed when the text component has focus and isn't editable.
     614          *
     615          */
     616         if (getEditorKit(editor) instanceof DefaultEditorKit) {
     617             if (map != null) {
     618                 Object obj = map.get(DefaultEditorKit.insertBreakAction);
     619                 if (obj != null
     620                     && obj instanceof DefaultEditorKit.InsertBreakAction) {
     621                     Action action =  new TextActionWrapper((TextAction)obj);
     622                     componentMap.put(action.getValue(Action.NAME),action);
     623                 }
     624             }
     625         }
     626         if (map != null) {
     627             componentMap.setParent(map);
     628         }
     629         return componentMap;
     630     }
     631
     632     /**
     633      * Create a default action map.  This is basically the
     634      * set of actions found exported by the component.
     635      */
     636     ActionMap createActionMap() {
     637         ActionMap map = new ActionMapUIResource();
     638         Action[] actions = editor.getActions();
     639         //System.out.println("building map for UI: " + getPropertyPrefix());
     640         int n = actions.length;
     641         for (int i = 0; i < n; i++) {
     642             Action a = actions[i];
     643             map.put(a.getValue(Action.NAME), a);
     644             //System.out.println("  " + a.getValue(Action.NAME));
     645         }
     646         map.put(TransferHandler.getCutAction().getValue(Action.NAME),
     647                 TransferHandler.getCutAction());
     648         map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
     649                 TransferHandler.getCopyAction());
     650         map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
     651                 TransferHandler.getPasteAction());
     652         return map;
     653     }
     654
     655     /**
     656      * Unregisters keyboard actions.
     657      */
     658     protected void uninstallKeyboardActions() {
     659         editor.setKeymap(null);
     660         SwingUtilities.replaceUIInputMap(editor, JComponent.
     661                                          WHEN_IN_FOCUSED_WINDOW, null);
     662         SwingUtilities.replaceUIActionMap(editor, null);
     663     }
     664
     665     /**
     666      * Paints a background for the view.  This will only be
     667      * called if isOpaque() on the associated component is
     668      * true.  The default is to paint the background color
     669      * of the component.
     670      *
     671      * @param g the graphics context
     672      */
     673     protected void paintBackground(Graphics g) {
     674         g.setColor(editor.getBackground());
     675         g.fillRect(0, 0, editor.getWidth(), editor.getHeight());
     676     }
     677
     678     /**
     679      * Fetches the text component associated with this
     680      * UI implementation.  This will be null until
     681      * the ui has been installed.
     682      *
     683      * @return the editor component
     684      */
     685     protected final JTextComponent getComponent() {
     686         return editor;
     687     }
     688
     689     /**
     690      * Flags model changes.
     691      * This is called whenever the model has changed.
     692      * It is implemented to rebuild the view hierarchy
     693      * to represent the default root element of the
     694      * associated model.
     695      */
     696     protected void modelChanged() {
     697         // create a view hierarchy
     698         ViewFactory f = rootView.getViewFactory();
     699         Document doc = editor.getDocument();
     700         Element elem = doc.getDefaultRootElement();
     701         setView(f.create(elem));
     702     }
     703
     704     /**
     705      * Sets the current root of the view hierarchy and calls invalidate().
     706      * If there were any child components, they will be removed (i.e.
     707      * there are assumed to have come from components embedded in views).
     708      *
     709      * @param v the root view
     710      */
     711     protected final void setView(View v) {
     712         rootView.setView(v);
     713         painted = false;
     714         editor.revalidate();
     715         editor.repaint();
     716     }
     717
     718     /**
     719      * Paints the interface safely with a guarantee that
     720      * the model won't change from the view of this thread.
     721      * This does the following things, rendering from
     722      * back to front.
     723      * <ol>
     724      * <li>
     725      * If the component is marked as opaque, the background
     726      * is painted in the current background color of the
     727      * component.
     728      * <li>
     729      * The highlights (if any) are painted.
     730      * <li>
     731      * The view hierarchy is painted.
     732      * <li>
     733      * The caret is painted.
     734      * </ol>
     735      *
     736      * @param g the graphics context
     737      */
     738     protected void paintSafely(Graphics g) {
     739         painted = true;
     740         Highlighter highlighter = editor.getHighlighter();
     741         Caret caret = editor.getCaret();
     742
     743         // paint the background
     744         if (editor.isOpaque()) {
     745             paintBackground(g);
     746         }
     747
     748         // paint the highlights
     749         if (highlighter != null) {
     750             highlighter.paint(g);
     751         }
     752
     753         // paint the view hierarchy
     754         Rectangle alloc = getVisibleEditorRect();
     755         if (alloc != null) {
     756             rootView.paint(g, alloc);
     757         }
     758
     759         // paint the caret
     760         if (caret != null) {
     761             caret.paint(g);
     762         }
     763
     764         if (dropCaret != null) {
     765             dropCaret.paint(g);
     766         }
     767     }
     768
     769     // --- ComponentUI methods --------------------------------------------
     770
     771     /**
     772      * Installs the UI for a component.  This does the following
     773      * things.
     774      * <ol>
     775      * <li>
     776      * Sets the associated component to opaque if the opaque property
     777      * has not already been set by the client program. This will cause the
     778      * component's background color to be painted.
     779      * <li>
     780      * Installs the default caret and highlighter into the
     781      * associated component. These properties are only set if their
     782      * current value is either {@code null} or an instance of
     783      * {@link UIResource}.
     784      * <li>
     785      * Attaches to the editor and model.  If there is no
     786      * model, a default one is created.
     787      * <li>
     788      * Creates the view factory and the view hierarchy used
     789      * to represent the model.
     790      * </ol>
     791      *
     792      * @param c the editor component
     793      * @see ComponentUI#installUI
     794      */
     795     public void installUI(JComponent c) {
     796         if (c instanceof JTextComponent) {
     797             editor = (JTextComponent) c;
     798
     799             // common case is background painted... this can
     800             // easily be changed by subclasses or from outside
     801             // of the component.
     802             LookAndFeel.installProperty(editor, "opaque", Boolean.TRUE);
     803             LookAndFeel.installProperty(editor, "autoscrolls", Boolean.TRUE);
     804
     805             // install defaults
     806             installDefaults();
     807             installDefaults2();
     808
     809             // margin required to show caret in the rightmost position
     810             caretMargin = -1;
     811             Object property = UIManager.get("Caret.width");
     812             if (property instanceof Number) {
     813                 caretMargin = ((Number) property).intValue();
     814             }
     815             property = c.getClientProperty("caretWidth");
     816             if (property instanceof Number) {
     817                 caretMargin = ((Number) property).intValue();
     818             }
     819             if (caretMargin < 0) {
     820                 caretMargin = DEFAULT_CARET_MARGIN;
     821             }
     822
     823             // attach to the model and editor
     824             editor.addPropertyChangeListener(updateHandler);
     825             Document doc = editor.getDocument();
     826             if (doc == null) {
     827                 // no model, create a default one.  This will
     828                 // fire a notification to the updateHandler
     829                 // which takes care of the rest.
     830                 editor.setDocument(getEditorKit(editor).createDefaultDocument());
     831             } else {
     832                 doc.addDocumentListener(updateHandler);
     833                 modelChanged();
     834             }
     835
     836             // install keymap
     837             installListeners();
     838             installKeyboardActions();
     839
     840             LayoutManager oldLayout = editor.getLayout();
     841             if ((oldLayout == null) || (oldLayout instanceof UIResource)) {
     842                 // by default, use default LayoutManger implementation that
     843                 // will position the components associated with a View object.
     844                 editor.setLayout(updateHandler);
     845             }
     846
     847             updateBackground(editor);
     848         } else {
     849             throw new Error("TextUI needs JTextComponent");
     850         }
     851     }
     852
     853     /**
     854      * Deinstalls the UI for a component.  This removes the listeners,
     855      * uninstalls the highlighter, removes views, and nulls out the keymap.
     856      *
     857      * @param c the editor component
     858      * @see ComponentUI#uninstallUI
     859      */
     860     public void uninstallUI(JComponent c) {
     861         // detach from the model
     862         editor.removePropertyChangeListener(updateHandler);
     863         editor.getDocument().removeDocumentListener(updateHandler);
     864
     865         // view part
     866         painted = false;
     867         uninstallDefaults();
     868         rootView.setView(null);
     869         c.removeAll();
     870         LayoutManager lm = c.getLayout();
     871         if (lm instanceof UIResource) {
     872             c.setLayout(null);
     873         }
     874
     875         // controller part
     876         uninstallKeyboardActions();
     877         uninstallListeners();
     878
     879         editor = null;
     880     }
     881
     882     /**
     883      * Superclass paints background in an uncontrollable way
     884      * (i.e. one might want an image tiled into the background).
     885      * To prevent this from happening twice, this method is
     886      * reimplemented to simply paint.
     887      * <p>
     888      * <em>NOTE:</em> NOTE: Superclass is also not thread-safe in its
     889      * rendering of the background, although that is not an issue with the
     890      * default rendering.
     891      */
     892     public void update(Graphics g, JComponent c) {
     893         paint(g, c);
     894     }
     895
     896     /**
     897      * Paints the interface.  This is routed to the
     898      * paintSafely method under the guarantee that
     899      * the model won't change from the view of this thread
     900      * while it's rendering (if the associated model is
     901      * derived from AbstractDocument).  This enables the
     902      * model to potentially be updated asynchronously.
     903      *
     904      * @param g the graphics context
     905      * @param c the editor component
     906      */
     907     public final void paint(Graphics g, JComponent c) {
     908         if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) {
     909             Document doc = editor.getDocument();
     910             if (doc instanceof AbstractDocument) {
     911                 ((AbstractDocument)doc).readLock();
     912             }
     913             try {
     914                 paintSafely(g);
     915             } finally {
     916                 if (doc instanceof AbstractDocument) {
     917                     ((AbstractDocument)doc).readUnlock();
     918                 }
     919             }
     920         }
     921     }
     922
     923     /**
     924      * Gets the preferred size for the editor component.  If the component
     925      * has been given a size prior to receiving this request, it will
     926      * set the size of the view hierarchy to reflect the size of the component
     927      * before requesting the preferred size of the view hierarchy.  This
     928      * allows formatted views to format to the current component size before
     929      * answering the request.  Other views don't care about currently formatted
     930      * size and give the same answer either way.
     931      *
     932      * @param c the editor component
     933      * @return the size
     934      */
     935     public Dimension getPreferredSize(JComponent c) {
     936         Document doc = editor.getDocument();
     937         Insets i = c.getInsets();
     938         Dimension d = c.getSize();
     939
     940         if (doc instanceof AbstractDocument) {
     941             ((AbstractDocument)doc).readLock();
     942         }
     943         try {
     944             if ((d.width > (i.left + i.right + caretMargin)) && (d.height > (i.top + i.bottom))) {
     945                 rootView.setSize(d.width - i.left - i.right -
     946                         caretMargin, d.height - i.top - i.bottom);
     947             }
     948             else if (!rootViewInitialized && (d.width <= 0 || d.height <= 0)) {
     949                 // Probably haven't been layed out yet, force some sort of
     950                 // initial sizing.
     951                 rootViewInitialized = true;
     952                 rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
     953             }
     954             d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) +
     955                          (long) i.left + (long) i.right + caretMargin, Integer.MAX_VALUE);
     956             d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) +
     957                                       (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
     958         } finally {
     959             if (doc instanceof AbstractDocument) {
     960                 ((AbstractDocument)doc).readUnlock();
     961             }
     962         }
     963         return d;
     964     }
     965
     966     /**
     967      * Gets the minimum size for the editor component.
     968      *
     969      * @param c the editor component
     970      * @return the size
     971      */
     972     public Dimension getMinimumSize(JComponent c) {
     973         Document doc = editor.getDocument();
     974         Insets i = c.getInsets();
     975         Dimension d = new Dimension();
     976         if (doc instanceof AbstractDocument) {
     977             ((AbstractDocument)doc).readLock();
     978         }
     979         try {
     980             d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right + caretMargin;
     981             d.height = (int)  rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom;
     982         } finally {
     983             if (doc instanceof AbstractDocument) {
     984                 ((AbstractDocument)doc).readUnlock();
     985             }
     986         }
     987         return d;
     988     }
     989
     990     /**
     991      * Gets the maximum size for the editor component.
     992      *
     993      * @param c the editor component
     994      * @return the size
     995      */
     996     public Dimension getMaximumSize(JComponent c) {
     997         Document doc = editor.getDocument();
     998         Insets i = c.getInsets();
     999         Dimension d = new Dimension();
    1000         if (doc instanceof AbstractDocument) {
    1001             ((AbstractDocument)doc).readLock();
    1002         }
    1003         try {
    1004             d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) +
    1005                                      (long) i.left + (long) i.right + caretMargin, Integer.MAX_VALUE);
    1006             d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) +
    1007                                       (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
    1008         } finally {
    1009             if (doc instanceof AbstractDocument) {
    1010                 ((AbstractDocument)doc).readUnlock();
    1011             }
    1012         }
    1013         return d;
    1014     }
    1015
    1016     // ---- TextUI methods -------------------------------------------
    1017
    1018
    1019     /**
    1020      * Gets the allocation to give the root View.  Due
    1021      * to an unfortunate set of historical events this
    1022      * method is inappropriately named.  The Rectangle
    1023      * returned has nothing to do with visibility.
    1024      * The component must have a non-zero positive size for
    1025      * this translation to be computed.
    1026      *
    1027      * @return the bounding box for the root view
    1028      */
    1029     protected Rectangle getVisibleEditorRect() {
    1030         Rectangle alloc = editor.getBounds();
    1031         if ((alloc.width > 0) && (alloc.height > 0)) {
    1032             alloc.x = alloc.y = 0;
    1033             Insets insets = editor.getInsets();
    1034             alloc.x += insets.left;
    1035             alloc.y += insets.top;
    1036             alloc.width -= insets.left + insets.right + caretMargin;
    1037             alloc.height -= insets.top + insets.bottom;
    1038             return alloc;
    1039         }
    1040         return null;
    1041     }
    1042
    1043     /**
    1044      * Converts the given location in the model to a place in
    1045      * the view coordinate system.
    1046      * The component must have a non-zero positive size for
    1047      * this translation to be computed.
    1048      *
    1049      * @param tc the text component for which this UI is installed
    1050      * @param pos the local location in the model to translate &gt;= 0
    1051      * @return the coordinates as a rectangle, null if the model is not painted
    1052      * @exception BadLocationException  if the given position does not
    1053      *   represent a valid location in the associated document
    1054      * @see TextUI#modelToView
    1055      *
    1056      * @deprecated replaced by
    1057      *     {@link #modelToView2D(JTextComponent, int, Position.Bias)}
    1058      */
    1059     @Deprecated(since = "9")
    1060     @Override
    1061     public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException {
    1062         return modelToView(tc, pos, Position.Bias.Forward);
    1063     }
    1064
    1065     /**
    1066      * Converts the given location in the model to a place in
    1067      * the view coordinate system.
    1068      * The component must have a non-zero positive size for
    1069      * this translation to be computed.
    1070      *
    1071      * @param tc the text component for which this UI is installed
    1072      * @param pos the local location in the model to translate &gt;= 0
    1073      * @return the coordinates as a rectangle, null if the model is not painted
    1074      * @exception BadLocationException  if the given position does not
    1075      *   represent a valid location in the associated document
    1076      * @see TextUI#modelToView
    1077      *
    1078      * @deprecated replaced by
    1079      *     {@link #modelToView2D(JTextComponent, int, Position.Bias)}
    1080      */
    1081     @Deprecated(since = "9")
    1082     @Override
    1083     public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias)
    1084             throws BadLocationException
    1085     {
    1086         return (Rectangle) modelToView(tc, pos, bias, false);
    1087     }
    1088
    1089     @Override
    1090     public Rectangle2D modelToView2D(JTextComponent tc, int pos,
    1091                                      Position.Bias bias)
    1092             throws BadLocationException
    1093     {
    1094         return modelToView(tc, pos, bias, true);
    1095     }
    1096
    1097     private Rectangle2D modelToView(JTextComponent tc, int pos,
    1098                                     Position.Bias bias, boolean useFPAPI)
    1099             throws BadLocationException
    1100     {
    1101         Document doc = editor.getDocument();
    1102         if (doc instanceof AbstractDocument) {
    1103             ((AbstractDocument)doc).readLock();
    1104         }
    1105         try {
    1106             Rectangle alloc = getVisibleEditorRect();
    1107             if (alloc != null) {
    1108                 rootView.setSize(alloc.width, alloc.height);
    1109                 Shape s = rootView.modelToView(pos, alloc, bias);
    1110                 if (s != null) {
    1111                     return useFPAPI ? s.getBounds2D() : s.getBounds();
    1112                 }
    1113             }
    1114         } finally {
    1115             if (doc instanceof AbstractDocument) {
    1116                 ((AbstractDocument)doc).readUnlock();
    1117             }
    1118         }
    1119         return null;
    1120     }
    1121
    1122     /**
    1123      * Converts the given place in the view coordinate system
    1124      * to the nearest representative location in the model.
    1125      * The component must have a non-zero positive size for
    1126      * this translation to be computed.
    1127      *
    1128      * @param tc the text component for which this UI is installed
    1129      * @param pt the location in the view to translate.  This
    1130      *  should be in the same coordinate system as the mouse events.
    1131      * @return the offset from the start of the document &gt;= 0,
    1132      *   -1 if not painted
    1133      * @see TextUI#viewToModel
    1134      *
    1135      * @deprecated replaced by
    1136      *     {@link #viewToModel2D(JTextComponent, Point2D, Position.Bias[])}
    1137      */
    1138     @Deprecated(since = "9")
    1139     @Override
    1140     public int viewToModel(JTextComponent tc, Point pt) {
    1141         return viewToModel(tc, pt, discardBias);
    1142     }
    1143
    1144     /**
    1145      * Converts the given place in the view coordinate system
    1146      * to the nearest representative location in the model.
    1147      * The component must have a non-zero positive size for
    1148      * this translation to be computed.
    1149      *
    1150      * @param tc the text component for which this UI is installed
    1151      * @param pt the location in the view to translate.  This
    1152      *  should be in the same coordinate system as the mouse events.
    1153      * @return the offset from the start of the document &gt;= 0,
    1154      *   -1 if the component doesn't yet have a positive size.
    1155      * @see TextUI#viewToModel
    1156      *
    1157      * @deprecated replaced by
    1158      *     {@link #viewToModel2D(JTextComponent, Point2D, Position.Bias[])}
    1159      */
    1160     @Deprecated(since = "9")
    1161     @Override
    1162     public int viewToModel(JTextComponent tc, Point pt,
    1163                            Position.Bias[] biasReturn) {
    1164         return viewToModel(tc, pt.x, pt.y, biasReturn);
    1165     }
    1166
    1167     @Override
    1168     public int viewToModel2D(JTextComponent tc, Point2D pt,
    1169                              Position.Bias[] biasReturn) {
    1170         return viewToModel(tc, (float) pt.getX(), (float) pt.getY(), biasReturn);
    1171     }
    1172
    1173     private int viewToModel(JTextComponent tc, float x, float y,
    1174                             Position.Bias[] biasReturn) {
    1175         int offs = -1;
    1176         Document doc = editor.getDocument();
    1177         if (doc instanceof AbstractDocument) {
    1178             ((AbstractDocument)doc).readLock();
    1179         }
    1180         try {
    1181             Rectangle alloc = getVisibleEditorRect();
    1182             if (alloc != null) {
    1183                 rootView.setSize(alloc.width, alloc.height);
    1184                 offs = rootView.viewToModel(x, y, alloc, biasReturn);
    1185             }
    1186         } finally {
    1187             if (doc instanceof AbstractDocument) {
    1188                 ((AbstractDocument)doc).readUnlock();
    1189             }
    1190         }
    1191         return offs;
    1192     }
    1193
    1194     /**
    1195      * {@inheritDoc}
    1196      */
    1197     public int getNextVisualPositionFrom(JTextComponent t, int pos,
    1198                     Position.Bias b, int direction, Position.Bias[] biasRet)
    1199                     throws BadLocationException{
    1200         Document doc = editor.getDocument();
    1201
    1202         if (pos < -1 || pos > doc.getLength()) {
    1203             throw new BadLocationException("Invalid position", pos);
    1204         }
    1205
    1206         if (doc instanceof AbstractDocument) {
    1207             ((AbstractDocument)doc).readLock();
    1208         }
    1209         try {
    1210             if (painted) {
    1211                 Rectangle alloc = getVisibleEditorRect();
    1212                 if (alloc != null) {
    1213                     rootView.setSize(alloc.width, alloc.height);
    1214                 }
    1215                 return rootView.getNextVisualPositionFrom(pos, b, alloc, direction,
    1216                                                           biasRet);
    1217             }
    1218         } finally {
    1219             if (doc instanceof AbstractDocument) {
    1220                 ((AbstractDocument)doc).readUnlock();
    1221             }
    1222         }
    1223         return -1;
    1224     }
    1225
    1226     /**
    1227      * Causes the portion of the view responsible for the
    1228      * given part of the model to be repainted.  Does nothing if
    1229      * the view is not currently painted.
    1230      *
    1231      * @param tc the text component for which this UI is installed
    1232      * @param p0 the beginning of the range &gt;= 0
    1233      * @param p1 the end of the range &gt;= p0
    1234      * @see TextUI#damageRange
    1235      */
    1236     public void damageRange(JTextComponent tc, int p0, int p1) {
    1237         damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
    1238     }
    1239
    1240     /**
    1241      * Causes the portion of the view responsible for the
    1242      * given part of the model to be repainted.
    1243      *
    1244      * @param p0 the beginning of the range &gt;= 0
    1245      * @param p1 the end of the range &gt;= p0
    1246      */
    1247     public void damageRange(JTextComponent t, int p0, int p1,
    1248                             Position.Bias p0Bias, Position.Bias p1Bias) {
    1249         if (painted) {
    1250             Rectangle alloc = getVisibleEditorRect();
    1251             if (alloc != null) {
    1252                 Document doc = t.getDocument();
    1253                 if (doc instanceof AbstractDocument) {
    1254                     ((AbstractDocument)doc).readLock();
    1255                 }
    1256                 try {
    1257                     rootView.setSize(alloc.width, alloc.height);
    1258                     Shape toDamage = rootView.modelToView(p0, p0Bias,
    1259                             p1, p1Bias, alloc);
    1260                     Rectangle rect = (toDamage instanceof Rectangle) ?
    1261                             (Rectangle)toDamage : toDamage.getBounds();
    1262                     editor.repaint(rect.x, rect.y, rect.width, rect.height);
    1263                 } catch (BadLocationException e) {
    1264                 } finally {
    1265                     if (doc instanceof AbstractDocument) {
    1266                         ((AbstractDocument)doc).readUnlock();
    1267                     }
    1268                 }
    1269             }
    1270         }
    1271     }
    1272
    1273     /**
    1274      * Fetches the EditorKit for the UI.
    1275      *
    1276      * @param tc the text component for which this UI is installed
    1277      * @return the editor capabilities
    1278      * @see TextUI#getEditorKit
    1279      */
    1280     public EditorKit getEditorKit(JTextComponent tc) {
    1281         return defaultKit;
    1282     }
    1283
    1284     /**
    1285      * Fetches a View with the allocation of the associated
    1286      * text component (i.e. the root of the hierarchy) that
    1287      * can be traversed to determine how the model is being
    1288      * represented spatially.
    1289      * <p style="color:red;">
    1290      * <b>NOTE:</b>The View hierarchy can
    1291      * be traversed from the root view, and other things
    1292      * can be done as well.  Things done in this way cannot
    1293      * be protected like simple method calls through the TextUI.
    1294      * Therefore, proper operation in the presence of concurrency
    1295      * must be arranged by any logic that calls this method!
    1296      *
    1297      * @param tc the text component for which this UI is installed
    1298      * @return the view
    1299      * @see TextUI#getRootView
    1300      */
    1301     public View getRootView(JTextComponent tc) {
    1302         return rootView;
    1303     }
    1304
    1305
    1306     /**
    1307      * Returns the string to be used as the tooltip at the passed in location.
    1308      * This forwards the method onto the root View.
    1309      *
    1310      * @see javax.swing.text.JTextComponent#getToolTipText
    1311      * @see javax.swing.text.View#getToolTipText
    1312      * @since 1.4
    1313      */
    1314     @SuppressWarnings("deprecation")
    1315     public String getToolTipText(JTextComponent t, Point pt) {
    1316         if (!painted) {
    1317             return null;
    1318         }
    1319         Document doc = editor.getDocument();
    1320         String tt = null;
    1321         Rectangle alloc = getVisibleEditorRect();
    1322
    1323         if (alloc != null) {
    1324             if (doc instanceof AbstractDocument) {
    1325                 ((AbstractDocument)doc).readLock();
    1326             }
    1327             try {
    1328                 tt = rootView.getToolTipText(pt.x, pt.y, alloc);
    1329             } finally {
    1330                 if (doc instanceof AbstractDocument) {
    1331                     ((AbstractDocument)doc).readUnlock();
    1332                 }
    1333             }
    1334         }
    1335         return tt;
    1336     }
    1337
    1338     // --- ViewFactory methods ------------------------------
    1339
    1340     /**
    1341      * Creates a view for an element.
    1342      * If a subclass wishes to directly implement the factory
    1343      * producing the view(s), it should reimplement this
    1344      * method.  By default it simply returns null indicating
    1345      * it is unable to represent the element.
    1346      *
    1347      * @param elem the element
    1348      * @return the view
    1349      */
    1350     public View create(Element elem) {
    1351         return null;
    1352     }
    1353
    1354     /**
    1355      * Creates a view for an element.
    1356      * If a subclass wishes to directly implement the factory
    1357      * producing the view(s), it should reimplement this
    1358      * method.  By default it simply returns null indicating
    1359      * it is unable to represent the part of the element.
    1360      *
    1361      * @param elem the element
    1362      * @param p0 the starting offset &gt;= 0
    1363      * @param p1 the ending offset &gt;= p0
    1364      * @return the view
    1365      */
    1366     public View create(Element elem, int p0, int p1) {
    1367         return null;
    1368     }
    1369
    1370     /**
    1371      * Default implementation of the interface {@code Caret}.
    1372      */
    1373     public static class BasicCaret extends DefaultCaret implements UIResource {}
    1374
    1375     /**
    1376      * Default implementation of the interface {@code Highlighter}.
    1377      */
    1378     public static class BasicHighlighter extends DefaultHighlighter implements UIResource {}
    1379
    1380     static class BasicCursor extends Cursor implements UIResource {
    1381         BasicCursor(int type) {
    1382             super(type);
    1383         }
    1384
    1385         BasicCursor(String name) {
    1386             super(name);
    1387         }
    1388     }
    1389
    1390     private static BasicCursor textCursor = new BasicCursor(Cursor.TEXT_CURSOR);
    1391     // ----- member variables ---------------------------------------
    1392
    1393     private static final EditorKit defaultKit = new DefaultEditorKit();
    1394     transient JTextComponent editor;
    1395     transient boolean painted;
    1396     transient RootView rootView = new RootView();
    1397     transient UpdateHandler updateHandler = new UpdateHandler();
    1398     private static final TransferHandler defaultTransferHandler = new TextTransferHandler();
    1399     private final DragListener dragListener = getDragListener();
    1400     private static final Position.Bias[] discardBias = new Position.Bias[1];
    1401     private DefaultCaret dropCaret;
    1402     private int caretMargin;
    1403     private boolean rootViewInitialized;
    1404
    1405     /**
    1406      * Root view that acts as a gateway between the component
    1407      * and the View hierarchy.
    1408      */
    1409     class RootView extends View {
    1410
    1411         RootView() {
    1412             super(null);
    1413         }
    1414
    1415         void setView(View v) {
    1416             View oldView = view;
    1417             view = null;
    1418             if (oldView != null) {
    1419                 // get rid of back reference so that the old
    1420                 // hierarchy can be garbage collected.
    1421                 oldView.setParent(null);
    1422             }
    1423             if (v != null) {
    1424                 v.setParent(this);
    1425             }
    1426             view = v;
    1427         }
    1428
    1429         /**
    1430          * Fetches the attributes to use when rendering.  At the root
    1431          * level there are no attributes.  If an attribute is resolved
    1432          * up the view hierarchy this is the end of the line.
    1433          */
    1434         public AttributeSet getAttributes() {
    1435             return null;
    1436         }
    1437
    1438         /**
    1439          * Determines the preferred span for this view along an axis.
    1440          *
    1441          * @param axis may be either X_AXIS or Y_AXIS
    1442          * @return the span the view would like to be rendered into.
    1443          *         Typically the view is told to render into the span
    1444          *         that is returned, although there is no guarantee.
    1445          *         The parent may choose to resize or break the view.
    1446          */
    1447         public float getPreferredSpan(int axis) {
    1448             if (view != null) {
    1449                 return view.getPreferredSpan(axis);
    1450             }
    1451             return 10;
    1452         }
    1453
    1454         /**
    1455          * Determines the minimum span for this view along an axis.
    1456          *
    1457          * @param axis may be either X_AXIS or Y_AXIS
    1458          * @return the span the view would like to be rendered into.
    1459          *         Typically the view is told to render into the span
    1460          *         that is returned, although there is no guarantee.
    1461          *         The parent may choose to resize or break the view.
    1462          */
    1463         public float getMinimumSpan(int axis) {
    1464             if (view != null) {
    1465                 return view.getMinimumSpan(axis);
    1466             }
    1467             return 10;
    1468         }
    1469
    1470         /**
    1471          * Determines the maximum span for this view along an axis.
    1472          *
    1473          * @param axis may be either X_AXIS or Y_AXIS
    1474          * @return the span the view would like to be rendered into.
    1475          *         Typically the view is told to render into the span
    1476          *         that is returned, although there is no guarantee.
    1477          *         The parent may choose to resize or break the view.
    1478          */
    1479         public float getMaximumSpan(int axis) {
    1480             return Integer.MAX_VALUE;
    1481         }
    1482
    1483         /**
    1484          * Specifies that a preference has changed.
    1485          * Child views can call this on the parent to indicate that
    1486          * the preference has changed.  The root view routes this to
    1487          * invalidate on the hosting component.
    1488          * <p>
    1489          * This can be called on a different thread from the
    1490          * event dispatching thread and is basically unsafe to
    1491          * propagate into the component.  To make this safe,
    1492          * the operation is transferred over to the event dispatching
    1493          * thread for completion.  It is a design goal that all view
    1494          * methods be safe to call without concern for concurrency,
    1495          * and this behavior helps make that true.
    1496          *
    1497          * @param child the child view
    1498          * @param width true if the width preference has changed
    1499          * @param height true if the height preference has changed
    1500          */
    1501         public void preferenceChanged(View child, boolean width, boolean height) {
    1502             editor.revalidate();
    1503         }
    1504
    1505         /**
    1506          * Determines the desired alignment for this view along an axis.
    1507          *
    1508          * @param axis may be either X_AXIS or Y_AXIS
    1509          * @return the desired alignment, where 0.0 indicates the origin
    1510          *     and 1.0 the full span away from the origin
    1511          */
    1512         public float getAlignment(int axis) {
    1513             if (view != null) {
    1514                 return view.getAlignment(axis);
    1515             }
    1516             return 0;
    1517         }
    1518
    1519         /**
    1520          * Renders the view.
    1521          *
    1522          * @param g the graphics context
    1523          * @param allocation the region to render into
    1524          */
    1525         public void paint(Graphics g, Shape allocation) {
    1526             if (view != null) {
    1527                 Rectangle alloc = (allocation instanceof Rectangle) ?
    1528                           (Rectangle)allocation : allocation.getBounds();
    1529                 setSize(alloc.width, alloc.height);
    1530                 view.paint(g, allocation);
    1531             }
    1532         }
    1533
    1534         /**
    1535          * Sets the view parent.
    1536          *
    1537          * @param parent the parent view
    1538          */
    1539         public void setParent(View parent) {
    1540             throw new Error("Can't set parent on root view");
    1541         }
    1542
    1543         /**
    1544          * Returns the number of views in this view.  Since
    1545          * this view simply wraps the root of the view hierarchy
    1546          * it has exactly one child.
    1547          *
    1548          * @return the number of views
    1549          * @see #getView
    1550          */
    1551         public int getViewCount() {
    1552             return 1;
    1553         }
    1554
    1555         /**
    1556          * Gets the n-th view in this container.
    1557          *
    1558          * @param n the number of the view to get
    1559          * @return the view
    1560          */
    1561         public View getView(int n) {
    1562             return view;
    1563         }
    1564
    1565         /**
    1566          * Returns the child view index representing the given position in
    1567          * the model.  This is implemented to return the index of the only
    1568          * child.
    1569          *
    1570          * @param pos the position &gt;= 0
    1571          * @return  index of the view representing the given position, or
    1572          *   -1 if no view represents that position
    1573          * @since 1.3
    1574          */
    1575         public int getViewIndex(int pos, Position.Bias b) {
    1576             return 0;
    1577         }
    1578
    1579         /**
    1580          * Fetches the allocation for the given child view.
    1581          * This enables finding out where various views
    1582          * are located, without assuming the views store
    1583          * their location.  This returns the given allocation
    1584          * since this view simply acts as a gateway between
    1585          * the view hierarchy and the associated component.
    1586          *
    1587          * @param index the index of the child
    1588          * @param a  the allocation to this view.
    1589          * @return the allocation to the child
    1590          */
    1591         public Shape getChildAllocation(int index, Shape a) {
    1592             return a;
    1593         }
    1594
    1595         /**
    1596          * Provides a mapping from the document model coordinate space
    1597          * to the coordinate space of the view mapped to it.
    1598          *
    1599          * @param pos the position to convert
    1600          * @param a the allocated region to render into
    1601          * @return the bounding box of the given position
    1602          */
    1603         public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
    1604             if (view != null) {
    1605                 return view.modelToView(pos, a, b);
    1606             }
    1607             return null;
    1608         }
    1609
    1610         /**
    1611          * Provides a mapping from the document model coordinate space
    1612          * to the coordinate space of the view mapped to it.
    1613          *
    1614          * @param p0 the position to convert &gt;= 0
    1615          * @param b0 the bias toward the previous character or the
    1616          *  next character represented by p0, in case the
    1617          *  position is a boundary of two views.
    1618          * @param p1 the position to convert &gt;= 0
    1619          * @param b1 the bias toward the previous character or the
    1620          *  next character represented by p1, in case the
    1621          *  position is a boundary of two views.
    1622          * @param a the allocated region to render into
    1623          * @return the bounding box of the given position is returned
    1624          * @exception BadLocationException  if the given position does
    1625          *   not represent a valid location in the associated document
    1626          * @exception IllegalArgumentException for an invalid bias argument
    1627          * @see View#viewToModel
    1628          */
    1629         public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
    1630             if (view != null) {
    1631                 return view.modelToView(p0, b0, p1, b1, a);
    1632             }
    1633             return null;
    1634         }
    1635
    1636         /**
    1637          * Provides a mapping from the view coordinate space to the logical
    1638          * coordinate space of the model.
    1639          *
    1640          * @param x x coordinate of the view location to convert
    1641          * @param y y coordinate of the view location to convert
    1642          * @param a the allocated region to render into
    1643          * @return the location within the model that best represents the
    1644          *    given point in the view
    1645          */
    1646         public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
    1647             if (view != null) {
    1648                 int retValue = view.viewToModel(x, y, a, bias);
    1649                 return retValue;
    1650             }
    1651             return -1;
    1652         }
    1653
    1654         /**
    1655          * Provides a way to determine the next visually represented model
    1656          * location that one might place a caret.  Some views may not be visible,
    1657          * they might not be in the same order found in the model, or they just
    1658          * might not allow access to some of the locations in the model.
    1659          * This method enables specifying a position to convert
    1660          * within the range of &gt;=0.  If the value is -1, a position
    1661          * will be calculated automatically.  If the value &lt; -1,
    1662          * the {@code BadLocationException} will be thrown.
    1663          *
    1664          * @param pos the position to convert &gt;= 0
    1665          * @param a the allocated region to render into
    1666          * @param direction the direction from the current position that can
    1667          *  be thought of as the arrow keys typically found on a keyboard.
    1668          *  This may be SwingConstants.WEST, SwingConstants.EAST,
    1669          *  SwingConstants.NORTH, or SwingConstants.SOUTH.
    1670          * @return the location within the model that best represents the next
    1671          *  location visual position.
    1672          * @exception BadLocationException the given position is not a valid
    1673          *                                 position within the document
    1674          * @exception IllegalArgumentException for an invalid direction
    1675          */
    1676         public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
    1677                                              int direction,
    1678                                              Position.Bias[] biasRet)
    1679             throws BadLocationException {
    1680             if (pos < -1 || pos > getDocument().getLength()) {
    1681                 throw new BadLocationException("invalid position", pos);
    1682             }
    1683             if( view != null ) {
    1684                 int nextPos = view.getNextVisualPositionFrom(pos, b, a,
    1685                                                      direction, biasRet);
    1686                 if(nextPos != -1) {
    1687                     pos = nextPos;
    1688                 }
    1689                 else {
    1690                     biasRet[0] = b;
    1691                 }
    1692             }
    1693             return pos;
    1694         }
    1695
    1696         /**
    1697          * Gives notification that something was inserted into the document
    1698          * in a location that this view is responsible for.
    1699          *
    1700          * @param e the change information from the associated document
    1701          * @param a the current allocation of the view
    1702          * @param f the factory to use to rebuild if the view has children
    1703          */
    1704         public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
    1705             if (view != null) {
    1706                 view.insertUpdate(e, a, f);
    1707             }
    1708         }
    1709
    1710         /**
    1711          * Gives notification that something was removed from the document
    1712          * in a location that this view is responsible for.
    1713          *
    1714          * @param e the change information from the associated document
    1715          * @param a the current allocation of the view
    1716          * @param f the factory to use to rebuild if the view has children
    1717          */
    1718         public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
    1719             if (view != null) {
    1720                 view.removeUpdate(e, a, f);
    1721             }
    1722         }
    1723
    1724         /**
    1725          * Gives notification from the document that attributes were changed
    1726          * in a location that this view is responsible for.
    1727          *
    1728          * @param e the change information from the associated document
    1729          * @param a the current allocation of the view
    1730          * @param f the factory to use to rebuild if the view has children
    1731          */
    1732         public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
    1733             if (view != null) {
    1734                 view.changedUpdate(e, a, f);
    1735             }
    1736         }
    1737
    1738         /**
    1739          * Returns the document model underlying the view.
    1740          *
    1741          * @return the model
    1742          */
    1743         public Document getDocument() {
    1744             return editor.getDocument();
    1745         }
    1746
    1747         /**
    1748          * Returns the starting offset into the model for this view.
    1749          *
    1750          * @return the starting offset
    1751          */
    1752         public int getStartOffset() {
    1753             if (view != null) {
    1754                 return view.getStartOffset();
    1755             }
    1756             return getElement().getStartOffset();
    1757         }
    1758
    1759         /**
    1760          * Returns the ending offset into the model for this view.
    1761          *
    1762          * @return the ending offset
    1763          */
    1764         public int getEndOffset() {
    1765             if (view != null) {
    1766                 return view.getEndOffset();
    1767             }
    1768             return getElement().getEndOffset();
    1769         }
    1770
    1771         /**
    1772          * Gets the element that this view is mapped to.
    1773          *
    1774          * @return the view
    1775          */
    1776         public Element getElement() {
    1777             if (view != null) {
    1778                 return view.getElement();
    1779             }
    1780             return editor.getDocument().getDefaultRootElement();
    1781         }
    1782
    1783         /**
    1784          * Breaks this view on the given axis at the given length.
    1785          *
    1786          * @param axis may be either X_AXIS or Y_AXIS
    1787          * @param len specifies where a break is desired in the span
    1788          * @param a the current allocation of the view
    1789          * @return the fragment of the view that represents the given span
    1790          *   if the view can be broken, otherwise null
    1791          */
    1792         public View breakView(int axis, float len, Shape a) {
    1793             throw new Error("Can't break root view");
    1794         }
    1795
    1796         /**
    1797          * Determines the resizability of the view along the
    1798          * given axis.  A value of 0 or less is not resizable.
    1799          *
    1800          * @param axis may be either X_AXIS or Y_AXIS
    1801          * @return the weight
    1802          */
    1803         public int getResizeWeight(int axis) {
    1804             if (view != null) {
    1805                 return view.getResizeWeight(axis);
    1806             }
    1807             return 0;
    1808         }
    1809
    1810         /**
    1811          * Sets the view size.
    1812          *
    1813          * @param width the width
    1814          * @param height the height
    1815          */
    1816         public void setSize(float width, float height) {
    1817             if (view != null) {
    1818                 view.setSize(width, height);
    1819             }
    1820         }
    1821
    1822         /**
    1823          * Fetches the container hosting the view.  This is useful for
    1824          * things like scheduling a repaint, finding out the host
    1825          * components font, etc.  The default implementation
    1826          * of this is to forward the query to the parent view.
    1827          *
    1828          * @return the container
    1829          */
    1830         public Container getContainer() {
    1831             return editor;
    1832         }
    1833
    1834         /**
    1835          * Fetches the factory to be used for building the
    1836          * various view fragments that make up the view that
    1837          * represents the model.  This is what determines
    1838          * how the model will be represented.  This is implemented
    1839          * to fetch the factory provided by the associated
    1840          * EditorKit unless that is null, in which case this
    1841          * simply returns the BasicTextUI itself which allows
    1842          * subclasses to implement a simple factory directly without
    1843          * creating extra objects.
    1844          *
    1845          * @return the factory
    1846          */
    1847         public ViewFactory getViewFactory() {
    1848             EditorKit kit = getEditorKit(editor);
    1849             ViewFactory f = kit.getViewFactory();
    1850             if (f != null) {
    1851                 return f;
    1852             }
    1853             return BasicTextUI.this;
    1854         }
    1855
    1856         private View view;
    1857
    1858     }
    1859
    1860     /**
    1861      * Handles updates from various places.  If the model is changed,
    1862      * this class unregisters as a listener to the old model and
    1863      * registers with the new model.  If the document model changes,
    1864      * the change is forwarded to the root view.  If the focus
    1865      * accelerator changes, a new keystroke is registered to request
    1866      * focus.
    1867      */
    1868     class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource {
    1869
    1870         // --- PropertyChangeListener methods -----------------------
    1871
    1872         /**
    1873          * This method gets called when a bound property is changed.
    1874          * We are looking for document changes on the editor.
    1875          */
    1876         public final void propertyChange(PropertyChangeEvent evt) {
    1877             Object oldValue = evt.getOldValue();
    1878             Object newValue = evt.getNewValue();
    1879             String propertyName = evt.getPropertyName();
    1880             if ((oldValue instanceof Document) || (newValue instanceof Document)) {
    1881                 if (oldValue != null) {
    1882                     ((Document)oldValue).removeDocumentListener(this);
    1883                     i18nView = false;
    1884                 }
    1885                 if (newValue != null) {
    1886                     ((Document)newValue).addDocumentListener(this);
    1887                     if ("document" == propertyName) {
    1888                         setView(null);
    1889                         BasicTextUI.this.propertyChange(evt);
    1890                         modelChanged();
    1891                         return;
    1892                     }
    1893                 }
    1894                 modelChanged();
    1895             }
    1896             if ("focusAccelerator" == propertyName) {
    1897                 updateFocusAcceleratorBinding(true);
    1898             } else if ("componentOrientation" == propertyName) {
    1899                 // Changes in ComponentOrientation require the views to be
    1900                 // rebuilt.
    1901                 modelChanged();
    1902             } else if ("font" == propertyName) {
    1903                 modelChanged();
    1904             } else if ("dropLocation" == propertyName) {
    1905                 dropIndexChanged();
    1906             } else if ("editable" == propertyName) {
    1907                 updateCursor();
    1908                 modelChanged();
    1909             }
    1910             BasicTextUI.this.propertyChange(evt);
    1911         }
    1912
    1913         private void dropIndexChanged() {
    1914             if (editor.getDropMode() == DropMode.USE_SELECTION) {
    1915                 return;
    1916             }
    1917
    1918             JTextComponent.DropLocation dropLocation = editor.getDropLocation();
    1919
    1920             if (dropLocation == null) {
    1921                 if (dropCaret != null) {
    1922                     dropCaret.deinstall(editor);
    1923                     editor.repaint(dropCaret);
    1924                     dropCaret = null;
    1925                 }
    1926             } else {
    1927                 if (dropCaret == null) {
    1928                     dropCaret = new BasicCaret();
    1929                     dropCaret.install(editor);
    1930                     dropCaret.setVisible(true);
    1931                 }
    1932
    1933                 dropCaret.setDot(dropLocation.getIndex(),
    1934                                  dropLocation.getBias());
    1935             }
    1936         }
    1937
    1938         // --- DocumentListener methods -----------------------
    1939
    1940         /**
    1941          * The insert notification.  Gets sent to the root of the view structure
    1942          * that represents the portion of the model being represented by the
    1943          * editor.  The factory is added as an argument to the update so that
    1944          * the views can update themselves in a dynamic (not hardcoded) way.
    1945          *
    1946          * @param e  The change notification from the currently associated
    1947          *  document.
    1948          * @see DocumentListener#insertUpdate
    1949          */
    1950         public final void insertUpdate(DocumentEvent e) {
    1951             Document doc = e.getDocument();
    1952             Object o = doc.getProperty("i18n");
    1953             if (o instanceof Boolean) {
    1954                 Boolean i18nFlag = (Boolean) o;
    1955                 if (i18nFlag.booleanValue() != i18nView) {
    1956                     // i18n flag changed, rebuild the view
    1957                     i18nView = i18nFlag.booleanValue();
    1958                     modelChanged();
    1959                     return;
    1960                 }
    1961             }
    1962
    1963             // normal insert update
    1964             Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
    1965             rootView.insertUpdate(e, alloc, rootView.getViewFactory());
    1966         }
    1967
    1968         /**
    1969          * The remove notification.  Gets sent to the root of the view structure
    1970          * that represents the portion of the model being represented by the
    1971          * editor.  The factory is added as an argument to the update so that
    1972          * the views can update themselves in a dynamic (not hardcoded) way.
    1973          *
    1974          * @param e  The change notification from the currently associated
    1975          *  document.
    1976          * @see DocumentListener#removeUpdate
    1977          */
    1978         public final void removeUpdate(DocumentEvent e) {
    1979             Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
    1980             rootView.removeUpdate(e, alloc, rootView.getViewFactory());
    1981         }
    1982
    1983         /**
    1984          * The change notification.  Gets sent to the root of the view structure
    1985          * that represents the portion of the model being represented by the
    1986          * editor.  The factory is added as an argument to the update so that
    1987          * the views can update themselves in a dynamic (not hardcoded) way.
    1988          *
    1989          * @param e  The change notification from the currently associated
    1990          *  document.
    1991          * @see DocumentListener#changedUpdate(DocumentEvent)
    1992          */
    1993         public final void changedUpdate(DocumentEvent e) {
    1994             Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
    1995             rootView.changedUpdate(e, alloc, rootView.getViewFactory());
    1996         }
    1997
    1998         // --- LayoutManager2 methods --------------------------------
    1999
    2000         /**
    2001          * Adds the specified component with the specified name to
    2002          * the layout.
    2003          * @param name the component name
    2004          * @param comp the component to be added
    2005          */
    2006         public void addLayoutComponent(String name, Component comp) {
    2007             // not supported
    2008         }
    2009
    2010         /**
    2011          * Removes the specified component from the layout.
    2012          * @param comp the component to be removed
    2013          */
    2014         public void removeLayoutComponent(Component comp) {
    2015             if (constraints != null) {
    2016                 // remove the constraint record
    2017                 constraints.remove(comp);
    2018             }
    2019         }
    2020
    2021         /**
    2022          * Calculates the preferred size dimensions for the specified
    2023          * panel given the components in the specified parent container.
    2024          * @param parent the component to be laid out
    2025          *
    2026          * @see #minimumLayoutSize
    2027          */
    2028         public Dimension preferredLayoutSize(Container parent) {
    2029             // should not be called (JComponent uses UI instead)
    2030             return null;
    2031         }
    2032
    2033         /**
    2034          * Calculates the minimum size dimensions for the specified
    2035          * panel given the components in the specified parent container.
    2036          * @param parent the component to be laid out
    2037          * @see #preferredLayoutSize
    2038          */
    2039         public Dimension minimumLayoutSize(Container parent) {
    2040             // should not be called (JComponent uses UI instead)
    2041             return null;
    2042         }
    2043
    2044         /**
    2045          * Lays out the container in the specified panel.  This is
    2046          * implemented to position all components that were added
    2047          * with a View object as a constraint.  The current allocation
    2048          * of the associated View is used as the location of the
    2049          * component.
    2050          * <p>
    2051          * A read-lock is acquired on the document to prevent the
    2052          * view tree from being modified while the layout process
    2053          * is active.
    2054          *
    2055          * @param parent the component which needs to be laid out
    2056          */
    2057         public void layoutContainer(Container parent) {
    2058             if ((constraints != null) && (! constraints.isEmpty())) {
    2059                 Rectangle alloc = getVisibleEditorRect();
    2060                 if (alloc != null) {
    2061                     Document doc = editor.getDocument();
    2062                     if (doc instanceof AbstractDocument) {
    2063                         ((AbstractDocument)doc).readLock();
    2064                     }
    2065                     try {
    2066                         rootView.setSize(alloc.width, alloc.height);
    2067                         Enumeration<Component> components = constraints.keys();
    2068                         while (components.hasMoreElements()) {
    2069                             Component comp = components.nextElement();
    2070                             View v = (View) constraints.get(comp);
    2071                             Shape ca = calculateViewPosition(alloc, v);
    2072                             if (ca != null) {
    2073                                 Rectangle compAlloc = (ca instanceof Rectangle) ?
    2074                                     (Rectangle) ca : ca.getBounds();
    2075                                 comp.setBounds(compAlloc);
    2076                             }
    2077                         }
    2078                     } finally {
    2079                         if (doc instanceof AbstractDocument) {
    2080                             ((AbstractDocument)doc).readUnlock();
    2081                         }
    2082                     }
    2083                 }
    2084             }
    2085         }
    2086
    2087         /**
    2088          * Find the Shape representing the given view.
    2089          */
    2090         Shape calculateViewPosition(Shape alloc, View v) {
    2091             int pos = v.getStartOffset();
    2092             View child = null;
    2093             for (View parent = rootView; (parent != null) && (parent != v); parent = child) {
    2094                 int index = parent.getViewIndex(pos, Position.Bias.Forward);
    2095                 alloc = parent.getChildAllocation(index, alloc);
    2096                 child = parent.getView(index);
    2097             }
    2098             return (child != null) ? alloc : null;
    2099         }
    2100
    2101         /**
    2102          * Adds the specified component to the layout, using the specified
    2103          * constraint object.  We only store those components that were added
    2104          * with a constraint that is of type View.
    2105          *
    2106          * @param comp the component to be added
    2107          * @param constraint  where/how the component is added to the layout.
    2108          */
    2109         public void addLayoutComponent(Component comp, Object constraint) {
    2110             if (constraint instanceof View) {
    2111                 if (constraints == null) {
    2112                     constraints = new Hashtable<Component, Object>(7);
    2113                 }
    2114                 constraints.put(comp, constraint);
    2115             }
    2116         }
    2117
    2118         /**
    2119          * Returns the maximum size of this component.
    2120          * @see java.awt.Component#getMinimumSize()
    2121          * @see java.awt.Component#getPreferredSize()
    2122          * @see LayoutManager
    2123          */
    2124         public Dimension maximumLayoutSize(Container target) {
    2125             // should not be called (JComponent uses UI instead)
    2126             return null;
    2127         }
    2128
    2129         /**
    2130          * Returns the alignment along the x axis.  This specifies how
    2131          * the component would like to be aligned relative to other
    2132          * components.  The value should be a number between 0 and 1
    2133          * where 0 represents alignment along the origin, 1 is aligned
    2134          * the furthest away from the origin, 0.5 is centered, etc.
    2135          */
    2136         public float getLayoutAlignmentX(Container target) {
    2137             return 0.5f;
    2138         }
    2139
    2140         /**
    2141          * Returns the alignment along the y axis.  This specifies how
    2142          * the component would like to be aligned relative to other
    2143          * components.  The value should be a number between 0 and 1
    2144          * where 0 represents alignment along the origin, 1 is aligned
    2145          * the furthest away from the origin, 0.5 is centered, etc.
    2146          */
    2147         public float getLayoutAlignmentY(Container target) {
    2148             return 0.5f;
    2149         }
    2150
    2151         /**
    2152          * Invalidates the layout, indicating that if the layout manager
    2153          * has cached information it should be discarded.
    2154          */
    2155         public void invalidateLayout(Container target) {
    2156         }
    2157
    2158         /**
    2159          * The "layout constraints" for the LayoutManager2 implementation.
    2160          * These are View objects for those components that are represented
    2161          * by a View in the View tree.
    2162          */
    2163         private Hashtable<Component, Object> constraints;
    2164
    2165         private boolean i18nView = false;
    2166     }
    2167
    2168     /**
    2169      * Wrapper for text actions to return isEnabled false in case editor is non editable
    2170      */
    2171     class TextActionWrapper extends TextAction {
    2172         public TextActionWrapper(TextAction action) {
    2173             super((String)action.getValue(Action.NAME));
    2174             this.action = action;
    2175         }
    2176         /**
    2177          * The operation to perform when this action is triggered.
    2178          *
    2179          * @param e the action event
    2180          */
    2181         public void actionPerformed(ActionEvent e) {
    2182             action.actionPerformed(e);
    2183         }
    2184         public boolean isEnabled() {
    2185             return (editor == null || editor.isEditable()) ? action.isEnabled() : false;
    2186         }
    2187         TextAction action = null;
    2188     }
    2189
    2190
    2191     /**
    2192      * Registered in the ActionMap.
    2193      */
    2194     class FocusAction extends AbstractAction {
    2195
    2196         public void actionPerformed(ActionEvent e) {
    2197             editor.requestFocus();
    2198         }
    2199
    2200         public boolean isEnabled() {
    2201             return editor.isEditable();
    2202         }
    2203     }
    2204
    2205     private static DragListener getDragListener() {
    2206         synchronized(DragListener.class) {
    2207             DragListener listener =
    2208                 (DragListener)AppContext.getAppContext().
    2209                     get(DragListener.class);
    2210
    2211             if (listener == null) {
    2212                 listener = new DragListener();
    2213                 AppContext.getAppContext().put(DragListener.class, listener);
    2214             }
    2215
    2216             return listener;
    2217         }
    2218     }
    2219
    2220     /**
    2221      * Listens for mouse events for the purposes of detecting drag gestures.
    2222      * BasicTextUI will maintain one of these per AppContext.
    2223      */
    2224     static class DragListener extends MouseInputAdapter
    2225                               implements BeforeDrag {
    2226
    2227         private boolean dragStarted;
    2228
    2229         public void dragStarting(MouseEvent me) {
    2230             dragStarted = true;
    2231         }
    2232
    2233         public void mousePressed(MouseEvent e) {
    2234             JTextComponent c = (JTextComponent)e.getSource();
    2235             if (c.getDragEnabled()) {
    2236                 dragStarted = false;
    2237                 if (isDragPossible(e) && DragRecognitionSupport.mousePressed(e)) {
    2238                     e.consume();
    2239                 }
    2240             }
    2241         }
    2242
    2243         public void mouseReleased(MouseEvent e) {
    2244             JTextComponent c = (JTextComponent)e.getSource();
    2245             if (c.getDragEnabled()) {
    2246                 if (dragStarted) {
    2247                     e.consume();
    2248                 }
    2249
    2250                 DragRecognitionSupport.mouseReleased(e);
    2251             }
    2252         }
    2253
    2254         public void mouseDragged(MouseEvent e) {
    2255             JTextComponent c = (JTextComponent)e.getSource();
    2256             if (c.getDragEnabled()) {
    2257                 if (dragStarted || DragRecognitionSupport.mouseDragged(e, this)) {
    2258                     e.consume();
    2259                 }
    2260             }
    2261         }
    2262
    2263         /**
    2264          * Determines if the following are true:
    2265          * <ul>
    2266          * <li>the component is enabled
    2267          * <li>the press event is located over a selection
    2268          * </ul>
    2269          */
    2270         @SuppressWarnings("deprecation")
    2271         protected boolean isDragPossible(MouseEvent e) {
    2272             JTextComponent c = (JTextComponent)e.getSource();
    2273             if (c.isEnabled()) {
    2274                 Caret caret = c.getCaret();
    2275                 int dot = caret.getDot();
    2276                 int mark = caret.getMark();
    2277                 if (dot != mark) {
    2278                     Point p = new Point(e.getX(), e.getY());
    2279                     int pos = c.viewToModel(p);
    2280
    2281                     int p0 = Math.min(dot, mark);
    2282                     int p1 = Math.max(dot, mark);
    2283                     if ((pos >= p0) && (pos < p1)) {
    2284                         return true;
    2285                     }
    2286                 }
    2287             }
    2288             return false;
    2289         }
    2290     }
    2291
    2292     static class TextTransferHandler extends TransferHandler implements UIResource {
    2293
    2294         private JTextComponent exportComp;
    2295         private boolean shouldRemove;
    2296         private int p0;
    2297         private int p1;
    2298
    2299         /**
    2300          * Whether or not this is a drop using
    2301          * <code>DropMode.INSERT</code>.
    2302          */
    2303         private boolean modeBetween = false;
    2304
    2305         /**
    2306          * Whether or not this is a drop.
    2307          */
    2308         private boolean isDrop = false;
    2309
    2310         /**
    2311          * The drop action.
    2312          */
    2313         private int dropAction = MOVE;
    2314
    2315         /**
    2316          * The drop bias.
    2317          */
    2318         private Position.Bias dropBias;
    2319
    2320         /**
    2321          * Try to find a flavor that can be used to import a Transferable.
    2322          * The set of usable flavors are tried in the following order:
    2323          * <ol>
    2324          *     <li>First, an attempt is made to find a flavor matching the content type
    2325          *         of the EditorKit for the component.
    2326          *     <li>Second, an attempt to find a text/plain flavor is made.
    2327          *     <li>Third, an attempt to find a flavor representing a String reference
    2328          *         in the same VM is made.
    2329          *     <li>Lastly, DataFlavor.stringFlavor is searched for.
    2330          * </ol>
    2331          */
    2332         protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
    2333             DataFlavor plainFlavor = null;
    2334             DataFlavor refFlavor = null;
    2335             DataFlavor stringFlavor = null;
    2336
    2337             if (c instanceof JEditorPane) {
    2338                 for (int i = 0; i < flavors.length; i++) {
    2339                     String mime = flavors[i].getMimeType();
    2340                     if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) {
    2341                         return flavors[i];
    2342                     } else if (plainFlavor == null && mime.startsWith("text/plain")) {
    2343                         plainFlavor = flavors[i];
    2344                     } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
    2345                                                  && flavors[i].getRepresentationClass() == java.lang.String.class) {
    2346                         refFlavor = flavors[i];
    2347                     } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
    2348                         stringFlavor = flavors[i];
    2349                     }
    2350                 }
    2351                 if (plainFlavor != null) {
    2352                     return plainFlavor;
    2353                 } else if (refFlavor != null) {
    2354                     return refFlavor;
    2355                 } else if (stringFlavor != null) {
    2356                     return stringFlavor;
    2357                 }
    2358                 return null;
    2359             }
    2360
    2361
    2362             for (int i = 0; i < flavors.length; i++) {
    2363                 String mime = flavors[i].getMimeType();
    2364                 if (mime.startsWith("text/plain")) {
    2365                     return flavors[i];
    2366                 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
    2367                                              && flavors[i].getRepresentationClass() == java.lang.String.class) {
    2368                     refFlavor = flavors[i];
    2369                 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
    2370                     stringFlavor = flavors[i];
    2371                 }
    2372             }
    2373             if (refFlavor != null) {
    2374                 return refFlavor;
    2375             } else if (stringFlavor != null) {
    2376                 return stringFlavor;
    2377             }
    2378             return null;
    2379         }
    2380
    2381         /**
    2382          * Import the given stream data into the text component.
    2383          */
    2384         protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
    2385                                                throws BadLocationException, IOException {
    2386             if (useRead) {
    2387                 int startPosition = c.getSelectionStart();
    2388                 int endPosition = c.getSelectionEnd();
    2389                 int length = endPosition - startPosition;
    2390                 EditorKit kit = c.getUI().getEditorKit(c);
    2391                 Document doc = c.getDocument();
    2392                 if (length > 0) {
    2393                     doc.remove(startPosition, length);
    2394                 }
    2395                 kit.read(in, doc, startPosition);
    2396             } else {
    2397                 char[] buff = new char[1024];
    2398                 int nch;
    2399                 boolean lastWasCR = false;
    2400                 int last;
    2401                 StringBuffer sbuff = null;
    2402
    2403                 // Read in a block at a time, mapping \r\n to \n, as well as single
    2404                 // \r to \n.
    2405                 while ((nch = in.read(buff, 0, buff.length)) != -1) {
    2406                     if (sbuff == null) {
    2407                         sbuff = new StringBuffer(nch);
    2408                     }
    2409                     last = 0;
    2410                     for(int counter = 0; counter < nch; counter++) {
    2411                         switch(buff[counter]) {
    2412                         case '\r':
    2413                             if (lastWasCR) {
    2414                                 if (counter == 0) {
    2415                                     sbuff.append('\n');
    2416                                 } else {
    2417                                     buff[counter - 1] = '\n';
    2418                                 }
    2419                             } else {
    2420                                 lastWasCR = true;
    2421                             }
    2422                             break;
    2423                         case '\n':
    2424                             if (lastWasCR) {
    2425                                 if (counter > (last + 1)) {
    2426                                     sbuff.append(buff, last, counter - last - 1);
    2427                                 }
    2428                                 // else nothing to do, can skip \r, next write will
    2429                                 // write \n
    2430                                 lastWasCR = false;
    2431                                 last = counter;
    2432                             }
    2433                             break;
    2434                         default:
    2435                             if (lastWasCR) {
    2436                                 if (counter == 0) {
    2437                                     sbuff.append('\n');
    2438                                 } else {
    2439                                     buff[counter - 1] = '\n';
    2440                                 }
    2441                                 lastWasCR = false;
    2442                             }
    2443                             break;
    2444                         }
    2445                     }
    2446                     if (last < nch) {
    2447                         if (lastWasCR) {
    2448                             if (last < (nch - 1)) {
    2449                                 sbuff.append(buff, last, nch - last - 1);
    2450                             }
    2451                         } else {
    2452                             sbuff.append(buff, last, nch - last);
    2453                         }
    2454                     }
    2455                 }
    2456                 if (lastWasCR) {
    2457                     sbuff.append('\n');
    2458                 }
    2459                 c.replaceSelection(sbuff != null ? sbuff.toString() : "");
    2460             }
    2461         }
    2462
    2463         // --- TransferHandler methods ------------------------------------
    2464
    2465         /**
    2466          * This is the type of transfer actions supported by the source.  Some models are
    2467          * not mutable, so a transfer operation of COPY only should
    2468          * be advertised in that case.
    2469          *
    2470          * @param c  The component holding the data to be transfered.  This
    2471          *  argument is provided to enable sharing of TransferHandlers by
    2472          *  multiple components.
    2473          * @return  This is implemented to return NONE if the component is a JPasswordField
    2474          *  since exporting data via user gestures is not allowed.  If the text component is
    2475          *  editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
    2476          */
    2477         public int getSourceActions(JComponent c) {
    2478             if (c instanceof JPasswordField &&
    2479                 c.getClientProperty("JPasswordField.cutCopyAllowed") !=
    2480                 Boolean.TRUE) {
    2481                 return NONE;
    2482             }
    2483
    2484             return ((JTextComponent)c).isEditable() ? COPY_OR_MOVE : COPY;
    2485         }
    2486
    2487         /**
    2488          * Create a Transferable to use as the source for a data transfer.
    2489          *
    2490          * @param comp  The component holding the data to be transfered.  This
    2491          *  argument is provided to enable sharing of TransferHandlers by
    2492          *  multiple components.
    2493          * @return  The representation of the data to be transfered.
    2494          *
    2495          */
    2496         protected Transferable createTransferable(JComponent comp) {
    2497             exportComp = (JTextComponent)comp;
    2498             shouldRemove = true;
    2499             p0 = exportComp.getSelectionStart();
    2500             p1 = exportComp.getSelectionEnd();
    2501             return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null;
    2502         }
    2503
    2504         /**
    2505          * This method is called after data has been exported.  This method should remove
    2506          * the data that was transfered if the action was MOVE.
    2507          *
    2508          * @param source The component that was the source of the data.
    2509          * @param data   The data that was transferred or possibly null
    2510          *               if the action is <code>NONE</code>.
    2511          * @param action The actual action that was performed.
    2512          */
    2513         protected void exportDone(JComponent source, Transferable data, int action) {
    2514             // only remove the text if shouldRemove has not been set to
    2515             // false by importData and only if the action is a move
    2516             if (shouldRemove && action == MOVE) {
    2517                 TextTransferable t = (TextTransferable)data;
    2518                 t.removeText();
    2519             }
    2520
    2521             exportComp = null;
    2522         }
    2523
    2524         public boolean importData(TransferSupport support) {
    2525             isDrop = support.isDrop();
    2526
    2527             if (isDrop) {
    2528                 modeBetween =
    2529                     ((JTextComponent)support.getComponent()).getDropMode() == DropMode.INSERT;
    2530
    2531                 dropBias = ((JTextComponent.DropLocation)support.getDropLocation()).getBias();
    2532
    2533                 dropAction = support.getDropAction();
    2534             }
    2535
    2536             try {
    2537                 return super.importData(support);
    2538             } finally {
    2539                 isDrop = false;
    2540                 modeBetween = false;
    2541                 dropBias = null;
    2542                 dropAction = MOVE;
    2543             }
    2544         }
    2545
    2546         /**
    2547          * This method causes a transfer to a component from a clipboard or a
    2548          * DND drop operation.  The Transferable represents the data to be
    2549          * imported into the component.
    2550          *
    2551          * @param comp  The component to receive the transfer.  This
    2552          *  argument is provided to enable sharing of TransferHandlers by
    2553          *  multiple components.
    2554          * @param t     The data to import
    2555          * @return  true if the data was inserted into the component, false otherwise.
    2556          */
    2557         public boolean importData(JComponent comp, Transferable t) {
    2558             JTextComponent c = (JTextComponent)comp;
    2559
    2560             int pos = modeBetween
    2561                       ? c.getDropLocation().getIndex() : c.getCaretPosition();
    2562
    2563             // if we are importing to the same component that we exported from
    2564             // then don't actually do anything if the drop location is inside
    2565             // the drag location and set shouldRemove to false so that exportDone
    2566             // knows not to remove any data
    2567             if (dropAction == MOVE && c == exportComp && pos >= p0 && pos <= p1) {
    2568                 shouldRemove = false;
    2569                 return true;
    2570             }
    2571
    2572             boolean imported = false;
    2573             DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
    2574             if (importFlavor != null) {
    2575                 try {
    2576                     boolean useRead = false;
    2577                     if (comp instanceof JEditorPane) {
    2578                         JEditorPane ep = (JEditorPane)comp;
    2579                         if (!ep.getContentType().startsWith("text/plain") &&
    2580                                 importFlavor.getMimeType().startsWith(ep.getContentType())) {
    2581                             useRead = true;
    2582                         }
    2583                     }
    2584                     InputContext ic = c.getInputContext();
    2585                     if (ic != null) {
    2586                         ic.endComposition();
    2587                     }
    2588                     Reader r = importFlavor.getReaderForText(t);
    2589
    2590                     if (modeBetween) {
    2591                         Caret caret = c.getCaret();
    2592                         if (caret instanceof DefaultCaret) {
    2593                             ((DefaultCaret)caret).setDot(pos, dropBias);
    2594                         } else {
    2595                             c.setCaretPosition(pos);
    2596                         }
    2597                     }
    2598
    2599                     handleReaderImport(r, c, useRead);
    2600
    2601                     if (isDrop) {
    2602                         c.requestFocus();
    2603                         Caret caret = c.getCaret();
    2604                         if (caret instanceof DefaultCaret) {
    2605                             int newPos = caret.getDot();
    2606                             Position.Bias newBias = ((DefaultCaret)caret).getDotBias();
    2607
    2608                             ((DefaultCaret)caret).setDot(pos, dropBias);
    2609                             ((DefaultCaret)caret).moveDot(newPos, newBias);
    2610                         } else {
    2611                             c.select(pos, c.getCaretPosition());
    2612                         }
    2613                     }
    2614
    2615                     imported = true;
    2616                 } catch (UnsupportedFlavorException ufe) {
    2617                 } catch (BadLocationException ble) {
    2618                 } catch (IOException ioe) {
    2619                 }
    2620             }
    2621             return imported;
    2622         }
    2623
    2624         /**
    2625          * This method indicates if a component would accept an import of the given
    2626          * set of data flavors prior to actually attempting to import it.
    2627          *
    2628          * @param comp  The component to receive the transfer.  This
    2629          *  argument is provided to enable sharing of TransferHandlers by
    2630          *  multiple components.
    2631          * @param flavors  The data formats available
    2632          * @return  true if the data can be inserted into the component, false otherwise.
    2633          */
    2634         public boolean canImport(JComponent comp, DataFlavor[] flavors) {
    2635             JTextComponent c = (JTextComponent)comp;
    2636             if (!(c.isEditable() && c.isEnabled())) {
    2637                 return false;
    2638             }
    2639             return (getImportFlavor(flavors, c) != null);
    2640         }
    2641
    2642         /**
    2643          * A possible implementation of the Transferable interface
    2644          * for text components.  For a JEditorPane with a rich set
    2645          * of EditorKit implementations, conversions could be made
    2646          * giving a wider set of formats.  This is implemented to
    2647          * offer up only the active content type and text/plain
    2648          * (if that is not the active format) since that can be
    2649          * extracted from other formats.
    2650          */
    2651         static class TextTransferable extends BasicTransferable {
    2652
    2653             TextTransferable(JTextComponent c, int start, int end) {
    2654                 super(null, null);
    2655
    2656                 this.c = c;
    2657
    2658                 Document doc = c.getDocument();
    2659
    2660                 try {
    2661                     p0 = doc.createPosition(start);
    2662                     p1 = doc.createPosition(end);
    2663
    2664                     plainData = c.getSelectedText();
    2665
    2666                     if (c instanceof JEditorPane) {
    2667                         JEditorPane ep = (JEditorPane)c;
    2668
    2669                         mimeType = ep.getContentType();
    2670
    2671                         if (mimeType.startsWith("text/plain")) {
    2672                             return;
    2673                         }
    2674
    2675                         StringWriter sw = new StringWriter(p1.getOffset() - p0.getOffset());
    2676                         ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset());
    2677
    2678                         if (mimeType.startsWith("text/html")) {
    2679                             htmlData = sw.toString();
    2680                         } else {
    2681                             richText = sw.toString();
    2682                         }
    2683                     }
    2684                 } catch (BadLocationException ble) {
    2685                 } catch (IOException ioe) {
    2686                 }
    2687             }
    2688
    2689             void removeText() {
    2690                 if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) {
    2691                     try {
    2692                         Document doc = c.getDocument();
    2693                         doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset());
    2694                     } catch (BadLocationException e) {
    2695                     }
    2696                 }
    2697             }
    2698
    2699             // ---- EditorKit other than plain or HTML text -----------------------
    2700
    2701             /**
    2702              * If the EditorKit is not for text/plain or text/html, that format
    2703              * is supported through the "richer flavors" part of BasicTransferable.
    2704              */
    2705             protected DataFlavor[] getRicherFlavors() {
    2706                 if (richText == null) {
    2707                     return null;
    2708                 }
    2709
    2710                 try {
    2711                     DataFlavor[] flavors = new DataFlavor[3];
    2712                     flavors[0] = new DataFlavor(mimeType + ";class=java.lang.String");
    2713                     flavors[1] = new DataFlavor(mimeType + ";class=java.io.Reader");
    2714                     flavors[2] = new DataFlavor(mimeType + ";class=java.io.InputStream;charset=unicode");
    2715                     return flavors;
    2716                 } catch (ClassNotFoundException cle) {
    2717                     // fall through to unsupported (should not happen)
    2718                 }
    2719
    2720                 return null;
    2721             }
    2722
    2723             /**
    2724              * The only richer format supported is the file list flavor
    2725              */
    2726             @SuppressWarnings("deprecation")
    2727             protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException {
    2728                 if (richText == null) {
    2729                     return null;
    2730                 }
    2731
    2732                 if (String.class.equals(flavor.getRepresentationClass())) {
    2733                     return richText;
    2734                 } else if (Reader.class.equals(flavor.getRepresentationClass())) {
    2735                     return new StringReader(richText);
    2736                 } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
    2737                     return new StringBufferInputStream(richText);
    2738                 }
    2739                 throw new UnsupportedFlavorException(flavor);
    2740             }
    2741
    2742             Position p0;
    2743             Position p1;
    2744             String mimeType;
    2745             String richText;
    2746             JTextComponent c;
    2747         }
    2748
    2749     }
    2750
    2751 }
    --- EOF ---