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