--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/plaf/basic/BasicTextUI.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,2650 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.plaf.basic;
+
+import java.util.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.font.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+import java.awt.im.InputContext;
+import java.beans.*;
+import java.io.*;
+import java.net.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.UIResource;
+import sun.swing.DefaultLookup;
+import sun.awt.AppContext;
+import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
+
+/**
+ * <p>
+ * Basis of a text components look-and-feel. This provides the
+ * basic editor view and controller services that may be useful
+ * when creating a look-and-feel for an extension of
+ * <code>JTextComponent</code>.
+ * <p>
+ * Most state is held in the associated <code>JTextComponent</code>
+ * as bound properties, and the UI installs default values for the
+ * various properties. This default will install something for
+ * all of the properties. Typically, a LAF implementation will
+ * do more however. At a minimum, a LAF would generally install
+ * key bindings.
+ * <p>
+ * This class also provides some concurrency support if the
+ * <code>Document</code> associated with the JTextComponent is a subclass of
+ * <code>AbstractDocument</code>. Access to the View (or View hierarchy) is
+ * serialized between any thread mutating the model and the Swing
+ * event thread (which is expected to render, do model/view coordinate
+ * translation, etc). <em>Any access to the root view should first
+ * acquire a read-lock on the AbstractDocument and release that lock
+ * in a finally block.</em>
+ * <p>
+ * An important method to define is the {@link #getPropertyPrefix} method
+ * which is used as the basis of the keys used to fetch defaults
+ * from the UIManager. The string should reflect the type of
+ * TextUI (eg. TextField, TextArea, etc) without the particular
+ * LAF part of the name (eg Metal, Motif, etc).
+ * <p>
+ * To build a view of the model, one of the following strategies
+ * can be employed.
+ * <ol>
+ * <li>
+ * One strategy is to simply redefine the
+ * ViewFactory interface in the UI. By default, this UI itself acts
+ * as the factory for View implementations. This is useful
+ * for simple factories. To do this reimplement the
+ * {@link #create} method.
+ * <li>
+ * A common strategy for creating more complex types of documents
+ * is to have the EditorKit implementation return a factory. Since
+ * the EditorKit ties all of the pieces necessary to maintain a type
+ * of document, the factory is typically an important part of that
+ * and should be produced by the EditorKit implementation.
+ * </ol>
+ * <p>
+ * <strong>Warning:</strong>
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeans<sup><font size="-2">TM</font></sup>
+ * has been added to the <code>java.beans</code> package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @author Timothy Prinzing
+ * @author Shannon Hickey (drag and drop)
+ */
+public abstract class BasicTextUI extends TextUI implements ViewFactory {
+
+ /**
+ * Creates a new UI.
+ */
+ public BasicTextUI() {
+ painted = false;
+ }
+
+ /**
+ * Creates the object to use for a caret. By default an
+ * instance of BasicCaret is created. This method
+ * can be redefined to provide something else that implements
+ * the InputPosition interface or a subclass of JCaret.
+ *
+ * @return the caret object
+ */
+ protected Caret createCaret() {
+ return new BasicCaret();
+ }
+
+ /**
+ * Creates the object to use for adding highlights. By default
+ * an instance of BasicHighlighter is created. This method
+ * can be redefined to provide something else that implements
+ * the Highlighter interface or a subclass of DefaultHighlighter.
+ *
+ * @return the highlighter
+ */
+ protected Highlighter createHighlighter() {
+ return new BasicHighlighter();
+ }
+
+ /**
+ * Fetches the name of the keymap that will be installed/used
+ * by default for this UI. This is implemented to create a
+ * name based upon the classname. The name is the the name
+ * of the class with the package prefix removed.
+ *
+ * @return the name
+ */
+ protected String getKeymapName() {
+ String nm = getClass().getName();
+ int index = nm.lastIndexOf('.');
+ if (index >= 0) {
+ nm = nm.substring(index+1, nm.length());
+ }
+ return nm;
+ }
+
+ /**
+ * Creates the keymap to use for the text component, and installs
+ * any necessary bindings into it. By default, the keymap is
+ * shared between all instances of this type of TextUI. The
+ * keymap has the name defined by the getKeymapName method. If the
+ * keymap is not found, then DEFAULT_KEYMAP from JTextComponent is used.
+ * <p>
+ * The set of bindings used to create the keymap is fetched
+ * from the UIManager using a key formed by combining the
+ * {@link #getPropertyPrefix} method
+ * and the string <code>.keyBindings</code>. The type is expected
+ * to be <code>JTextComponent.KeyBinding[]</code>.
+ *
+ * @return the keymap
+ * @see #getKeymapName
+ * @see javax.swing.text.JTextComponent
+ */
+ protected Keymap createKeymap() {
+ String nm = getKeymapName();
+ Keymap map = JTextComponent.getKeymap(nm);
+ if (map == null) {
+ Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
+ map = JTextComponent.addKeymap(nm, parent);
+ String prefix = getPropertyPrefix();
+ Object o = DefaultLookup.get(editor, this,
+ prefix + ".keyBindings");
+ if ((o != null) && (o instanceof JTextComponent.KeyBinding[])) {
+ JTextComponent.KeyBinding[] bindings = (JTextComponent.KeyBinding[]) o;
+ JTextComponent.loadKeymap(map, bindings, getComponent().getActions());
+ }
+ }
+ return map;
+ }
+
+ /**
+ * This method gets called when a bound property is changed
+ * on the associated JTextComponent. This is a hook
+ * which UI implementations may change to reflect how the
+ * UI displays bound properties of JTextComponent subclasses.
+ * This is implemented to do nothing (i.e. the response to
+ * properties in JTextComponent itself are handled prior
+ * to calling this method).
+ *
+ * This implementation updates the background of the text
+ * component if the editable and/or enabled state changes.
+ *
+ * @param evt the property change event
+ */
+ protected void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName().equals("editable") ||
+ evt.getPropertyName().equals("enabled")) {
+
+ updateBackground((JTextComponent)evt.getSource());
+ }
+ }
+
+ /**
+ * Updates the background of the text component based on whether the
+ * text component is editable and/or enabled.
+ *
+ * @param c the JTextComponent that needs its background color updated
+ */
+ private void updateBackground(JTextComponent c) {
+ // This is a temporary workaround.
+ // This code does not correctly deal with Synth (Synth doesn't use
+ // properties like this), nor does it deal with the situation where
+ // the developer grabs the color from a JLabel and sets it as
+ // the background for a JTextArea in all look and feels. The problem
+ // scenario results if the Color obtained for the Label and TextArea
+ // is ==, which is the case for the windows look and feel.
+ // Until an appropriate solution is found, the code is being
+ // reverted to what it was before the original fix.
+ if (this instanceof sun.swing.plaf.synth.SynthUI ||
+ (c instanceof JTextArea)) {
+ return;
+ }
+ Color background = c.getBackground();
+ if (background instanceof UIResource) {
+ String prefix = getPropertyPrefix();
+
+ Color disabledBG =
+ DefaultLookup.getColor(c, this, prefix + ".disabledBackground", null);
+ Color inactiveBG =
+ DefaultLookup.getColor(c, this, prefix + ".inactiveBackground", null);
+ Color bg =
+ DefaultLookup.getColor(c, this, prefix + ".background", null);
+
+ /* In an ideal situation, the following check would not be necessary
+ * and we would replace the color any time the previous color was a
+ * UIResouce. However, it turns out that there is existing code that
+ * uses the following inadvisable pattern to turn a text area into
+ * what appears to be a multi-line label:
+ *
+ * JLabel label = new JLabel();
+ * JTextArea area = new JTextArea();
+ * area.setBackground(label.getBackground());
+ * area.setEditable(false);
+ *
+ * JLabel's default background is a UIResource. As such, just
+ * checking for UIResource would have us always changing the
+ * background away from what the developer wanted.
+ *
+ * Therefore, for JTextArea/JEditorPane, we'll additionally check
+ * that the color we're about to replace matches one that was
+ * installed by us from the UIDefaults.
+ */
+ if ((c instanceof JTextArea || c instanceof JEditorPane)
+ && background != disabledBG
+ && background != inactiveBG
+ && background != bg) {
+
+ return;
+ }
+
+ Color newColor = null;
+ if (!c.isEnabled()) {
+ newColor = disabledBG;
+ }
+ if (newColor == null && !c.isEditable()) {
+ newColor = inactiveBG;
+ }
+ if (newColor == null) {
+ newColor = bg;
+ }
+ if (newColor != null && newColor != background) {
+ c.setBackground(newColor);
+ }
+ }
+ }
+
+ /**
+ * Gets the name used as a key to look up properties through the
+ * UIManager. This is used as a prefix to all the standard
+ * text properties.
+ *
+ * @return the name
+ */
+ protected abstract String getPropertyPrefix();
+
+ /**
+ * Initializes component properties, e.g. font, foreground,
+ * background, caret color, selection color, selected text color,
+ * disabled text color, and border color. The font, foreground, and
+ * background properties are only set if their current value is either null
+ * or a UIResource, other properties are set if the current
+ * value is null.
+ *
+ * @see #uninstallDefaults
+ * @see #installUI
+ */
+ protected void installDefaults()
+ {
+ String prefix = getPropertyPrefix();
+ Font f = editor.getFont();
+ if ((f == null) || (f instanceof UIResource)) {
+ editor.setFont(UIManager.getFont(prefix + ".font"));
+ }
+
+ Color bg = editor.getBackground();
+ if ((bg == null) || (bg instanceof UIResource)) {
+ editor.setBackground(UIManager.getColor(prefix + ".background"));
+ }
+
+ Color fg = editor.getForeground();
+ if ((fg == null) || (fg instanceof UIResource)) {
+ editor.setForeground(UIManager.getColor(prefix + ".foreground"));
+ }
+
+ Color color = editor.getCaretColor();
+ if ((color == null) || (color instanceof UIResource)) {
+ editor.setCaretColor(UIManager.getColor(prefix + ".caretForeground"));
+ }
+
+ Color s = editor.getSelectionColor();
+ if ((s == null) || (s instanceof UIResource)) {
+ editor.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground"));
+ }
+
+ Color sfg = editor.getSelectedTextColor();
+ if ((sfg == null) || (sfg instanceof UIResource)) {
+ editor.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground"));
+ }
+
+ Color dfg = editor.getDisabledTextColor();
+ if ((dfg == null) || (dfg instanceof UIResource)) {
+ editor.setDisabledTextColor(UIManager.getColor(prefix + ".inactiveForeground"));
+ }
+
+ Border b = editor.getBorder();
+ if ((b == null) || (b instanceof UIResource)) {
+ editor.setBorder(UIManager.getBorder(prefix + ".border"));
+ }
+
+ Insets margin = editor.getMargin();
+ if (margin == null || margin instanceof UIResource) {
+ editor.setMargin(UIManager.getInsets(prefix + ".margin"));
+ }
+
+ updateCursor();
+ }
+
+ private void installDefaults2() {
+ editor.addMouseListener(dragListener);
+ editor.addMouseMotionListener(dragListener);
+
+ String prefix = getPropertyPrefix();
+
+ Caret caret = editor.getCaret();
+ if (caret == null || caret instanceof UIResource) {
+ caret = createCaret();
+ editor.setCaret(caret);
+
+ int rate = DefaultLookup.getInt(getComponent(), this, prefix + ".caretBlinkRate", 500);
+ caret.setBlinkRate(rate);
+ }
+
+ Highlighter highlighter = editor.getHighlighter();
+ if (highlighter == null || highlighter instanceof UIResource) {
+ editor.setHighlighter(createHighlighter());
+ }
+
+ TransferHandler th = editor.getTransferHandler();
+ if (th == null || th instanceof UIResource) {
+ editor.setTransferHandler(getTransferHandler());
+ }
+ }
+
+ /**
+ * Sets the component properties that haven't been explicitly overridden to
+ * null. A property is considered overridden if its current value
+ * is not a UIResource.
+ *
+ * @see #installDefaults
+ * @see #uninstallUI
+ */
+ protected void uninstallDefaults()
+ {
+ editor.removeMouseListener(dragListener);
+ editor.removeMouseMotionListener(dragListener);
+
+ if (editor.getCaretColor() instanceof UIResource) {
+ editor.setCaretColor(null);
+ }
+
+ if (editor.getSelectionColor() instanceof UIResource) {
+ editor.setSelectionColor(null);
+ }
+
+ if (editor.getDisabledTextColor() instanceof UIResource) {
+ editor.setDisabledTextColor(null);
+ }
+
+ if (editor.getSelectedTextColor() instanceof UIResource) {
+ editor.setSelectedTextColor(null);
+ }
+
+ if (editor.getBorder() instanceof UIResource) {
+ editor.setBorder(null);
+ }
+
+ if (editor.getMargin() instanceof UIResource) {
+ editor.setMargin(null);
+ }
+
+ if (editor.getCaret() instanceof UIResource) {
+ editor.setCaret(null);
+ }
+
+ if (editor.getHighlighter() instanceof UIResource) {
+ editor.setHighlighter(null);
+ }
+
+ if (editor.getTransferHandler() instanceof UIResource) {
+ editor.setTransferHandler(null);
+ }
+
+ if (editor.getCursor() instanceof UIResource) {
+ editor.setCursor(null);
+ }
+ }
+
+ /**
+ * Installs listeners for the UI.
+ */
+ protected void installListeners() {
+ }
+
+ /**
+ * Uninstalls listeners for the UI.
+ */
+ protected void uninstallListeners() {
+ }
+
+ protected void installKeyboardActions() {
+ // backward compatibility support... keymaps for the UI
+ // are now installed in the more friendly input map.
+ editor.setKeymap(createKeymap());
+
+ InputMap km = getInputMap();
+ if (km != null) {
+ SwingUtilities.replaceUIInputMap(editor, JComponent.WHEN_FOCUSED,
+ km);
+ }
+
+ ActionMap map = getActionMap();
+ if (map != null) {
+ SwingUtilities.replaceUIActionMap(editor, map);
+ }
+
+ updateFocusAcceleratorBinding(false);
+ }
+
+ /**
+ * Get the InputMap to use for the UI.
+ */
+ InputMap getInputMap() {
+ InputMap map = new InputMapUIResource();
+
+ InputMap shared =
+ (InputMap)DefaultLookup.get(editor, this,
+ getPropertyPrefix() + ".focusInputMap");
+ if (shared != null) {
+ map.setParent(shared);
+ }
+ return map;
+ }
+
+ /**
+ * Invoked when the focus accelerator changes, this will update the
+ * key bindings as necessary.
+ */
+ void updateFocusAcceleratorBinding(boolean changed) {
+ char accelerator = editor.getFocusAccelerator();
+
+ if (changed || accelerator != '\0') {
+ InputMap km = SwingUtilities.getUIInputMap
+ (editor, JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+ if (km == null && accelerator != '\0') {
+ km = new ComponentInputMapUIResource(editor);
+ SwingUtilities.replaceUIInputMap(editor, JComponent.
+ WHEN_IN_FOCUSED_WINDOW, km);
+ ActionMap am = getActionMap();
+ SwingUtilities.replaceUIActionMap(editor, am);
+ }
+ if (km != null) {
+ km.clear();
+ if (accelerator != '\0') {
+ km.put(KeyStroke.getKeyStroke(accelerator,
+ ActionEvent.ALT_MASK),
+ "requestFocus");
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Invoked when editable property is changed.
+ *
+ * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case
+ * editor is editable
+ * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case
+ * editor is non editable
+ */
+
+ void updateFocusTraversalKeys() {
+ /*
+ * Fix for 4514331 Non-editable JTextArea and similar
+ * should allow Tab to keyboard - accessibility
+ */
+ EditorKit editorKit = getEditorKit(editor);
+ if ( editorKit != null
+ && editorKit instanceof DefaultEditorKit) {
+ Set storedForwardTraversalKeys = editor.
+ getFocusTraversalKeys(KeyboardFocusManager.
+ FORWARD_TRAVERSAL_KEYS);
+ Set storedBackwardTraversalKeys = editor.
+ getFocusTraversalKeys(KeyboardFocusManager.
+ BACKWARD_TRAVERSAL_KEYS);
+ Set forwardTraversalKeys =
+ new HashSet(storedForwardTraversalKeys);
+ Set backwardTraversalKeys =
+ new HashSet(storedBackwardTraversalKeys);
+ if (editor.isEditable()) {
+ forwardTraversalKeys.
+ remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
+ backwardTraversalKeys.
+ remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
+ InputEvent.SHIFT_MASK));
+ } else {
+ forwardTraversalKeys.add(KeyStroke.
+ getKeyStroke(KeyEvent.VK_TAB, 0));
+ backwardTraversalKeys.
+ add(KeyStroke.
+ getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
+ }
+ LookAndFeel.installProperty(editor,
+ "focusTraversalKeysForward",
+ forwardTraversalKeys);
+ LookAndFeel.installProperty(editor,
+ "focusTraversalKeysBackward",
+ backwardTraversalKeys);
+ }
+
+ }
+
+ /**
+ * As needed updates cursor for the target editor.
+ */
+ private void updateCursor() {
+ if ((! editor.isCursorSet())
+ || editor.getCursor() instanceof UIResource) {
+ Cursor cursor = (editor.isEditable()) ? textCursor : null;
+ editor.setCursor(cursor);
+ }
+ }
+
+ /**
+ * Returns the <code>TransferHandler</code> that will be installed if
+ * their isn't one installed on the <code>JTextComponent</code>.
+ */
+ TransferHandler getTransferHandler() {
+ return defaultTransferHandler;
+ }
+
+ /**
+ * Fetch an action map to use.
+ */
+ ActionMap getActionMap() {
+ String mapName = getPropertyPrefix() + ".actionMap";
+ ActionMap map = (ActionMap)UIManager.get(mapName);
+
+ if (map == null) {
+ map = createActionMap();
+ if (map != null) {
+ UIManager.getLookAndFeelDefaults().put(mapName, map);
+ }
+ }
+ ActionMap componentMap = new ActionMapUIResource();
+ componentMap.put("requestFocus", new FocusAction());
+ /*
+ * fix for bug 4515750
+ * JTextField & non-editable JTextArea bind return key - default btn not accessible
+ *
+ * Wrap the return action so that it is only enabled when the
+ * component is editable. This allows the default button to be
+ * processed when the text component has focus and isn't editable.
+ *
+ */
+ if (getEditorKit(editor) instanceof DefaultEditorKit) {
+ if (map != null) {
+ Object obj = map.get(DefaultEditorKit.insertBreakAction);
+ if (obj != null
+ && obj instanceof DefaultEditorKit.InsertBreakAction) {
+ Action action = new TextActionWrapper((TextAction)obj);
+ componentMap.put(action.getValue(Action.NAME),action);
+ }
+ }
+ }
+ if (map != null) {
+ componentMap.setParent(map);
+ }
+ return componentMap;
+ }
+
+ /**
+ * Create a default action map. This is basically the
+ * set of actions found exported by the component.
+ */
+ ActionMap createActionMap() {
+ ActionMap map = new ActionMapUIResource();
+ Action[] actions = editor.getActions();
+ //System.out.println("building map for UI: " + getPropertyPrefix());
+ int n = actions.length;
+ for (int i = 0; i < n; i++) {
+ Action a = actions[i];
+ map.put(a.getValue(Action.NAME), a);
+ //System.out.println(" " + a.getValue(Action.NAME));
+ }
+ map.put(TransferHandler.getCutAction().getValue(Action.NAME),
+ TransferHandler.getCutAction());
+ map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
+ TransferHandler.getCopyAction());
+ map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
+ TransferHandler.getPasteAction());
+ return map;
+ }
+
+ protected void uninstallKeyboardActions() {
+ editor.setKeymap(null);
+ SwingUtilities.replaceUIInputMap(editor, JComponent.
+ WHEN_IN_FOCUSED_WINDOW, null);
+ SwingUtilities.replaceUIActionMap(editor, null);
+ }
+
+ /**
+ * Paints a background for the view. This will only be
+ * called if isOpaque() on the associated component is
+ * true. The default is to paint the background color
+ * of the component.
+ *
+ * @param g the graphics context
+ */
+ protected void paintBackground(Graphics g) {
+ g.setColor(editor.getBackground());
+ g.fillRect(0, 0, editor.getWidth(), editor.getHeight());
+ }
+
+ /**
+ * Fetches the text component associated with this
+ * UI implementation. This will be null until
+ * the ui has been installed.
+ *
+ * @return the editor component
+ */
+ protected final JTextComponent getComponent() {
+ return editor;
+ }
+
+ /**
+ * Flags model changes.
+ * This is called whenever the model has changed.
+ * It is implemented to rebuild the view hierarchy
+ * to represent the default root element of the
+ * associated model.
+ */
+ protected void modelChanged() {
+ // create a view hierarchy
+ ViewFactory f = rootView.getViewFactory();
+ Document doc = editor.getDocument();
+ Element elem = doc.getDefaultRootElement();
+ setView(f.create(elem));
+ }
+
+ /**
+ * Sets the current root of the view hierarchy and calls invalidate().
+ * If there were any child components, they will be removed (i.e.
+ * there are assumed to have come from components embedded in views).
+ *
+ * @param v the root view
+ */
+ protected final void setView(View v) {
+ rootView.setView(v);
+ painted = false;
+ editor.revalidate();
+ editor.repaint();
+ }
+
+ /**
+ * Paints the interface safely with a guarantee that
+ * the model won't change from the view of this thread.
+ * This does the following things, rendering from
+ * back to front.
+ * <ol>
+ * <li>
+ * If the component is marked as opaque, the background
+ * is painted in the current background color of the
+ * component.
+ * <li>
+ * The highlights (if any) are painted.
+ * <li>
+ * The view hierarchy is painted.
+ * <li>
+ * The caret is painted.
+ * </ol>
+ *
+ * @param g the graphics context
+ */
+ protected void paintSafely(Graphics g) {
+ painted = true;
+ Highlighter highlighter = editor.getHighlighter();
+ Caret caret = editor.getCaret();
+
+ // paint the background
+ if (editor.isOpaque()) {
+ paintBackground(g);
+ }
+
+ // paint the highlights
+ if (highlighter != null) {
+ highlighter.paint(g);
+ }
+
+ // paint the view hierarchy
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null) {
+ rootView.paint(g, alloc);
+ }
+
+ // paint the caret
+ if (caret != null) {
+ caret.paint(g);
+ }
+
+ if (dropCaret != null) {
+ dropCaret.paint(g);
+ }
+ }
+
+ // --- ComponentUI methods --------------------------------------------
+
+ /**
+ * Installs the UI for a component. This does the following
+ * things.
+ * <ol>
+ * <li>
+ * Set the associated component to opaque (can be changed
+ * easily by a subclass or on JTextComponent directly),
+ * which is the most common case. This will cause the
+ * component's background color to be painted.
+ * <li>
+ * Install the default caret and highlighter into the
+ * associated component.
+ * <li>
+ * Attach to the editor and model. If there is no
+ * model, a default one is created.
+ * <li>
+ * create the view factory and the view hierarchy used
+ * to represent the model.
+ * </ol>
+ *
+ * @param c the editor component
+ * @see ComponentUI#installUI
+ */
+ public void installUI(JComponent c) {
+ if (c instanceof JTextComponent) {
+ editor = (JTextComponent) c;
+
+ // install defaults
+ installDefaults();
+ installDefaults2();
+
+ // common case is background painted... this can
+ // easily be changed by subclasses or from outside
+ // of the component.
+ LookAndFeel.installProperty(editor, "opaque", Boolean.TRUE);
+ LookAndFeel.installProperty(editor, "autoscrolls", Boolean.TRUE);
+
+ // attach to the model and editor
+ editor.addPropertyChangeListener(updateHandler);
+ Document doc = editor.getDocument();
+ if (doc == null) {
+ // no model, create a default one. This will
+ // fire a notification to the updateHandler
+ // which takes care of the rest.
+ editor.setDocument(getEditorKit(editor).createDefaultDocument());
+ } else {
+ doc.addDocumentListener(updateHandler);
+ modelChanged();
+ }
+
+ // install keymap
+ installListeners();
+ installKeyboardActions();
+
+ LayoutManager oldLayout = editor.getLayout();
+ if ((oldLayout == null) || (oldLayout instanceof UIResource)) {
+ // by default, use default LayoutManger implementation that
+ // will position the components associated with a View object.
+ editor.setLayout(updateHandler);
+ }
+
+ updateBackground(editor);
+ } else {
+ throw new Error("TextUI needs JTextComponent");
+ }
+ }
+
+ /**
+ * Deinstalls the UI for a component. This removes the listeners,
+ * uninstalls the highlighter, removes views, and nulls out the keymap.
+ *
+ * @param c the editor component
+ * @see ComponentUI#uninstallUI
+ */
+ public void uninstallUI(JComponent c) {
+ // detach from the model
+ editor.removePropertyChangeListener(updateHandler);
+ editor.getDocument().removeDocumentListener(updateHandler);
+
+ // view part
+ painted = false;
+ uninstallDefaults();
+ rootView.setView(null);
+ c.removeAll();
+ LayoutManager lm = c.getLayout();
+ if (lm instanceof UIResource) {
+ c.setLayout(null);
+ }
+
+ // controller part
+ uninstallKeyboardActions();
+ uninstallListeners();
+
+ editor = null;
+ }
+
+ /**
+ * Superclass paints background in an uncontrollable way
+ * (i.e. one might want an image tiled into the background).
+ * To prevent this from happening twice, this method is
+ * reimplemented to simply paint.
+ * <p>
+ * <em>NOTE:</em> Superclass is also not thread-safe in
+ * it's rendering of the background, although that's not
+ * an issue with the default rendering.
+ */
+ public void update(Graphics g, JComponent c) {
+ paint(g, c);
+ }
+
+ /**
+ * Paints the interface. This is routed to the
+ * paintSafely method under the guarantee that
+ * the model won't change from the view of this thread
+ * while it's rendering (if the associated model is
+ * derived from AbstractDocument). This enables the
+ * model to potentially be updated asynchronously.
+ *
+ * @param g the graphics context
+ * @param c the editor component
+ */
+ public final void paint(Graphics g, JComponent c) {
+ if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) {
+ Document doc = editor.getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ paintSafely(g);
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the preferred size for the editor component. If the component
+ * has been given a size prior to receiving this request, it will
+ * set the size of the view hierarchy to reflect the size of the component
+ * before requesting the preferred size of the view hierarchy. This
+ * allows formatted views to format to the current component size before
+ * answering the request. Other views don't care about currently formatted
+ * size and give the same answer either way.
+ *
+ * @param c the editor component
+ * @return the size
+ */
+ public Dimension getPreferredSize(JComponent c) {
+ Document doc = editor.getDocument();
+ Insets i = c.getInsets();
+ Dimension d = c.getSize();
+
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) {
+ rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom);
+ }
+ else if (d.width == 0 && d.height == 0) {
+ // Probably haven't been layed out yet, force some sort of
+ // initial sizing.
+ rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
+ }
+ d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) +
+ (long) i.left + (long) i.right, Integer.MAX_VALUE);
+ d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) +
+ (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ return d;
+ }
+
+ /**
+ * Gets the minimum size for the editor component.
+ *
+ * @param c the editor component
+ * @return the size
+ */
+ public Dimension getMinimumSize(JComponent c) {
+ Document doc = editor.getDocument();
+ Insets i = c.getInsets();
+ Dimension d = new Dimension();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right;
+ d.height = (int) rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom;
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ return d;
+ }
+
+ /**
+ * Gets the maximum size for the editor component.
+ *
+ * @param c the editor component
+ * @return the size
+ */
+ public Dimension getMaximumSize(JComponent c) {
+ Document doc = editor.getDocument();
+ Insets i = c.getInsets();
+ Dimension d = new Dimension();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) +
+ (long) i.left + (long) i.right, Integer.MAX_VALUE);
+ d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) +
+ (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ return d;
+ }
+
+ // ---- TextUI methods -------------------------------------------
+
+
+ /**
+ * Gets the allocation to give the root View. Due
+ * to an unfortunate set of historical events this
+ * method is inappropriately named. The Rectangle
+ * returned has nothing to do with visibility.
+ * The component must have a non-zero positive size for
+ * this translation to be computed.
+ *
+ * @return the bounding box for the root view
+ */
+ protected Rectangle getVisibleEditorRect() {
+ Rectangle alloc = editor.getBounds();
+ if ((alloc.width > 0) && (alloc.height > 0)) {
+ alloc.x = alloc.y = 0;
+ Insets insets = editor.getInsets();
+ alloc.x += insets.left;
+ alloc.y += insets.top;
+ alloc.width -= insets.left + insets.right;
+ alloc.height -= insets.top + insets.bottom;
+ return alloc;
+ }
+ return null;
+ }
+
+ /**
+ * Converts the given location in the model to a place in
+ * the view coordinate system.
+ * The component must have a non-zero positive size for
+ * this translation to be computed.
+ *
+ * @param tc the text component for which this UI is installed
+ * @param pos the local location in the model to translate >= 0
+ * @return the coordinates as a rectangle, null if the model is not painted
+ * @exception BadLocationException if the given position does not
+ * represent a valid location in the associated document
+ * @see TextUI#modelToView
+ */
+ public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException {
+ return modelToView(tc, pos, Position.Bias.Forward);
+ }
+
+ /**
+ * Converts the given location in the model to a place in
+ * the view coordinate system.
+ * The component must have a non-zero positive size for
+ * this translation to be computed.
+ *
+ * @param tc the text component for which this UI is installed
+ * @param pos the local location in the model to translate >= 0
+ * @return the coordinates as a rectangle, null if the model is not painted
+ * @exception BadLocationException if the given position does not
+ * represent a valid location in the associated document
+ * @see TextUI#modelToView
+ */
+ public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException {
+ Document doc = editor.getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null) {
+ rootView.setSize(alloc.width, alloc.height);
+ Shape s = rootView.modelToView(pos, alloc, bias);
+ if (s != null) {
+ return s.getBounds();
+ }
+ }
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Converts the given place in the view coordinate system
+ * to the nearest representative location in the model.
+ * The component must have a non-zero positive size for
+ * this translation to be computed.
+ *
+ * @param tc the text component for which this UI is installed
+ * @param pt the location in the view to translate. This
+ * should be in the same coordinate system as the mouse events.
+ * @return the offset from the start of the document >= 0,
+ * -1 if not painted
+ * @see TextUI#viewToModel
+ */
+ public int viewToModel(JTextComponent tc, Point pt) {
+ return viewToModel(tc, pt, discardBias);
+ }
+
+ /**
+ * Converts the given place in the view coordinate system
+ * to the nearest representative location in the model.
+ * The component must have a non-zero positive size for
+ * this translation to be computed.
+ *
+ * @param tc the text component for which this UI is installed
+ * @param pt the location in the view to translate. This
+ * should be in the same coordinate system as the mouse events.
+ * @return the offset from the start of the document >= 0,
+ * -1 if the component doesn't yet have a positive size.
+ * @see TextUI#viewToModel
+ */
+ public int viewToModel(JTextComponent tc, Point pt,
+ Position.Bias[] biasReturn) {
+ int offs = -1;
+ Document doc = editor.getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null) {
+ rootView.setSize(alloc.width, alloc.height);
+ offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
+ }
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ return offs;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getNextVisualPositionFrom(JTextComponent t, int pos,
+ Position.Bias b, int direction, Position.Bias[] biasRet)
+ throws BadLocationException{
+ Document doc = editor.getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ if (painted) {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null) {
+ rootView.setSize(alloc.width, alloc.height);
+ }
+ return rootView.getNextVisualPositionFrom(pos, b, alloc, direction,
+ biasRet);
+ }
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Causes the portion of the view responsible for the
+ * given part of the model to be repainted. Does nothing if
+ * the view is not currently painted.
+ *
+ * @param tc the text component for which this UI is installed
+ * @param p0 the beginning of the range >= 0
+ * @param p1 the end of the range >= p0
+ * @see TextUI#damageRange
+ */
+ public void damageRange(JTextComponent tc, int p0, int p1) {
+ damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
+ }
+
+ /**
+ * Causes the portion of the view responsible for the
+ * given part of the model to be repainted.
+ *
+ * @param p0 the beginning of the range >= 0
+ * @param p1 the end of the range >= p0
+ */
+ public void damageRange(JTextComponent t, int p0, int p1,
+ Position.Bias p0Bias, Position.Bias p1Bias) {
+ if (painted) {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null) {
+ Document doc = t.getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ rootView.setSize(alloc.width, alloc.height);
+ Shape toDamage = rootView.modelToView(p0, p0Bias,
+ p1, p1Bias, alloc);
+ Rectangle rect = (toDamage instanceof Rectangle) ?
+ (Rectangle)toDamage : toDamage.getBounds();
+ editor.repaint(rect.x, rect.y, rect.width, rect.height);
+ } catch (BadLocationException e) {
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Fetches the EditorKit for the UI.
+ *
+ * @param tc the text component for which this UI is installed
+ * @return the editor capabilities
+ * @see TextUI#getEditorKit
+ */
+ public EditorKit getEditorKit(JTextComponent tc) {
+ return defaultKit;
+ }
+
+ /**
+ * Fetches a View with the allocation of the associated
+ * text component (i.e. the root of the hierarchy) that
+ * can be traversed to determine how the model is being
+ * represented spatially.
+ * <p>
+ * <font color=red><b>NOTE:</b>The View hierarchy can
+ * be traversed from the root view, and other things
+ * can be done as well. Things done in this way cannot
+ * be protected like simple method calls through the TextUI.
+ * Therefore, proper operation in the presence of concurrency
+ * must be arranged by any logic that calls this method!
+ * </font>
+ *
+ * @param tc the text component for which this UI is installed
+ * @return the view
+ * @see TextUI#getRootView
+ */
+ public View getRootView(JTextComponent tc) {
+ return rootView;
+ }
+
+
+ /**
+ * Returns the string to be used as the tooltip at the passed in location.
+ * This forwards the method onto the root View.
+ *
+ * @see javax.swing.text.JTextComponent#getToolTipText
+ * @see javax.swing.text.View#getToolTipText
+ * @since 1.4
+ */
+ public String getToolTipText(JTextComponent t, Point pt) {
+ if (!painted) {
+ return null;
+ }
+ Document doc = editor.getDocument();
+ String tt = null;
+ Rectangle alloc = getVisibleEditorRect();
+
+ if (alloc != null) {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ tt = rootView.getToolTipText(pt.x, pt.y, alloc);
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ }
+ return tt;
+ }
+
+ // --- ViewFactory methods ------------------------------
+
+ /**
+ * Creates a view for an element.
+ * If a subclass wishes to directly implement the factory
+ * producing the view(s), it should reimplement this
+ * method. By default it simply returns null indicating
+ * it is unable to represent the element.
+ *
+ * @param elem the element
+ * @return the view
+ */
+ public View create(Element elem) {
+ return null;
+ }
+
+ /**
+ * Creates a view for an element.
+ * If a subclass wishes to directly implement the factory
+ * producing the view(s), it should reimplement this
+ * method. By default it simply returns null indicating
+ * it is unable to represent the part of the element.
+ *
+ * @param elem the element
+ * @param p0 the starting offset >= 0
+ * @param p1 the ending offset >= p0
+ * @return the view
+ */
+ public View create(Element elem, int p0, int p1) {
+ return null;
+ }
+
+ public static class BasicCaret extends DefaultCaret implements UIResource {}
+
+ public static class BasicHighlighter extends DefaultHighlighter implements UIResource {}
+
+ static class BasicCursor extends Cursor implements UIResource {
+ BasicCursor(int type) {
+ super(type);
+ }
+
+ BasicCursor(String name) {
+ super(name);
+ }
+ }
+
+ private static BasicCursor textCursor = new BasicCursor(Cursor.TEXT_CURSOR);
+ // ----- member variables ---------------------------------------
+
+ private static final EditorKit defaultKit = new DefaultEditorKit();
+ transient JTextComponent editor;
+ transient boolean painted;
+ transient RootView rootView = new RootView();
+ transient UpdateHandler updateHandler = new UpdateHandler();
+ private static final TransferHandler defaultTransferHandler = new TextTransferHandler();
+ private final DragListener dragListener = getDragListener();
+ private static final Position.Bias[] discardBias = new Position.Bias[1];
+ private DefaultCaret dropCaret;
+
+ /**
+ * Root view that acts as a gateway between the component
+ * and the View hierarchy.
+ */
+ class RootView extends View {
+
+ RootView() {
+ super(null);
+ }
+
+ void setView(View v) {
+ View oldView = view;
+ view = null;
+ if (oldView != null) {
+ // get rid of back reference so that the old
+ // hierarchy can be garbage collected.
+ oldView.setParent(null);
+ }
+ if (v != null) {
+ v.setParent(this);
+ }
+ view = v;
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. At the root
+ * level there are no attributes. If an attribute is resolved
+ * up the view hierarchy this is the end of the line.
+ */
+ public AttributeSet getAttributes() {
+ return null;
+ }
+
+ /**
+ * Determines the preferred span for this view along an axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the span the view would like to be rendered into.
+ * Typically the view is told to render into the span
+ * that is returned, although there is no guarantee.
+ * The parent may choose to resize or break the view.
+ */
+ public float getPreferredSpan(int axis) {
+ if (view != null) {
+ return view.getPreferredSpan(axis);
+ }
+ return 10;
+ }
+
+ /**
+ * Determines the minimum span for this view along an axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the span the view would like to be rendered into.
+ * Typically the view is told to render into the span
+ * that is returned, although there is no guarantee.
+ * The parent may choose to resize or break the view.
+ */
+ public float getMinimumSpan(int axis) {
+ if (view != null) {
+ return view.getMinimumSpan(axis);
+ }
+ return 10;
+ }
+
+ /**
+ * Determines the maximum span for this view along an axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the span the view would like to be rendered into.
+ * Typically the view is told to render into the span
+ * that is returned, although there is no guarantee.
+ * The parent may choose to resize or break the view.
+ */
+ public float getMaximumSpan(int axis) {
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Specifies that a preference has changed.
+ * Child views can call this on the parent to indicate that
+ * the preference has changed. The root view routes this to
+ * invalidate on the hosting component.
+ * <p>
+ * This can be called on a different thread from the
+ * event dispatching thread and is basically unsafe to
+ * propagate into the component. To make this safe,
+ * the operation is transferred over to the event dispatching
+ * thread for completion. It is a design goal that all view
+ * methods be safe to call without concern for concurrency,
+ * and this behavior helps make that true.
+ *
+ * @param child the child view
+ * @param width true if the width preference has changed
+ * @param height true if the height preference has changed
+ */
+ public void preferenceChanged(View child, boolean width, boolean height) {
+ editor.revalidate();
+ }
+
+ /**
+ * Determines the desired alignment for this view along an axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the desired alignment, where 0.0 indicates the origin
+ * and 1.0 the full span away from the origin
+ */
+ public float getAlignment(int axis) {
+ if (view != null) {
+ return view.getAlignment(axis);
+ }
+ return 0;
+ }
+
+ /**
+ * Renders the view.
+ *
+ * @param g the graphics context
+ * @param allocation the region to render into
+ */
+ public void paint(Graphics g, Shape allocation) {
+ if (view != null) {
+ Rectangle alloc = (allocation instanceof Rectangle) ?
+ (Rectangle)allocation : allocation.getBounds();
+ setSize(alloc.width, alloc.height);
+ view.paint(g, allocation);
+ }
+ }
+
+ /**
+ * Sets the view parent.
+ *
+ * @param parent the parent view
+ */
+ public void setParent(View parent) {
+ throw new Error("Can't set parent on root view");
+ }
+
+ /**
+ * Returns the number of views in this view. Since
+ * this view simply wraps the root of the view hierarchy
+ * it has exactly one child.
+ *
+ * @return the number of views
+ * @see #getView
+ */
+ public int getViewCount() {
+ return 1;
+ }
+
+ /**
+ * Gets the n-th view in this container.
+ *
+ * @param n the number of the view to get
+ * @return the view
+ */
+ public View getView(int n) {
+ return view;
+ }
+
+ /**
+ * Returns the child view index representing the given position in
+ * the model. This is implemented to return the index of the only
+ * child.
+ *
+ * @param pos the position >= 0
+ * @return index of the view representing the given position, or
+ * -1 if no view represents that position
+ * @since 1.3
+ */
+ public int getViewIndex(int pos, Position.Bias b) {
+ return 0;
+ }
+
+ /**
+ * Fetches the allocation for the given child view.
+ * This enables finding out where various views
+ * are located, without assuming the views store
+ * their location. This returns the given allocation
+ * since this view simply acts as a gateway between
+ * the view hierarchy and the associated component.
+ *
+ * @param index the index of the child
+ * @param a the allocation to this view.
+ * @return the allocation to the child
+ */
+ public Shape getChildAllocation(int index, Shape a) {
+ return a;
+ }
+
+ /**
+ * Provides a mapping from the document model coordinate space
+ * to the coordinate space of the view mapped to it.
+ *
+ * @param pos the position to convert
+ * @param a the allocated region to render into
+ * @return the bounding box of the given position
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
+ if (view != null) {
+ return view.modelToView(pos, a, b);
+ }
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the document model coordinate space
+ * to the coordinate space of the view mapped to it.
+ *
+ * @param p0 the position to convert >= 0
+ * @param b0 the bias toward the previous character or the
+ * next character represented by p0, in case the
+ * position is a boundary of two views.
+ * @param p1 the position to convert >= 0
+ * @param b1 the bias toward the previous character or the
+ * next character represented by p1, in case the
+ * position is a boundary of two views.
+ * @param a the allocated region to render into
+ * @return the bounding box of the given position is returned
+ * @exception BadLocationException if the given position does
+ * not represent a valid location in the associated document
+ * @exception IllegalArgumentException for an invalid bias argument
+ * @see View#viewToModel
+ */
+ public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
+ if (view != null) {
+ return view.modelToView(p0, b0, p1, b1, a);
+ }
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the view coordinate space to the logical
+ * coordinate space of the model.
+ *
+ * @param x x coordinate of the view location to convert
+ * @param y y coordinate of the view location to convert
+ * @param a the allocated region to render into
+ * @return the location within the model that best represents the
+ * given point in the view
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
+ if (view != null) {
+ int retValue = view.viewToModel(x, y, a, bias);
+ return retValue;
+ }
+ return -1;
+ }
+
+ /**
+ * Provides a way to determine the next visually represented model
+ * location that one might place a caret. Some views may not be visible,
+ * they might not be in the same order found in the model, or they just
+ * might not allow access to some of the locations in the model.
+ *
+ * @param pos the position to convert >= 0
+ * @param a the allocated region to render into
+ * @param direction the direction from the current position that can
+ * be thought of as the arrow keys typically found on a keyboard.
+ * This may be SwingConstants.WEST, SwingConstants.EAST,
+ * SwingConstants.NORTH, or SwingConstants.SOUTH.
+ * @return the location within the model that best represents the next
+ * location visual position.
+ * @exception BadLocationException
+ * @exception IllegalArgumentException for an invalid direction
+ */
+ public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
+ int direction,
+ Position.Bias[] biasRet)
+ throws BadLocationException {
+ if( view != null ) {
+ int nextPos = view.getNextVisualPositionFrom(pos, b, a,
+ direction, biasRet);
+ if(nextPos != -1) {
+ pos = nextPos;
+ }
+ else {
+ biasRet[0] = b;
+ }
+ }
+ return pos;
+ }
+
+ /**
+ * Gives notification that something was inserted into the document
+ * in a location that this view is responsible for.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ if (view != null) {
+ view.insertUpdate(e, a, f);
+ }
+ }
+
+ /**
+ * Gives notification that something was removed from the document
+ * in a location that this view is responsible for.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ if (view != null) {
+ view.removeUpdate(e, a, f);
+ }
+ }
+
+ /**
+ * Gives notification from the document that attributes were changed
+ * in a location that this view is responsible for.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ if (view != null) {
+ view.changedUpdate(e, a, f);
+ }
+ }
+
+ /**
+ * Returns the document model underlying the view.
+ *
+ * @return the model
+ */
+ public Document getDocument() {
+ return editor.getDocument();
+ }
+
+ /**
+ * Returns the starting offset into the model for this view.
+ *
+ * @return the starting offset
+ */
+ public int getStartOffset() {
+ if (view != null) {
+ return view.getStartOffset();
+ }
+ return getElement().getStartOffset();
+ }
+
+ /**
+ * Returns the ending offset into the model for this view.
+ *
+ * @return the ending offset
+ */
+ public int getEndOffset() {
+ if (view != null) {
+ return view.getEndOffset();
+ }
+ return getElement().getEndOffset();
+ }
+
+ /**
+ * Gets the element that this view is mapped to.
+ *
+ * @return the view
+ */
+ public Element getElement() {
+ if (view != null) {
+ return view.getElement();
+ }
+ return editor.getDocument().getDefaultRootElement();
+ }
+
+ /**
+ * Breaks this view on the given axis at the given length.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @param len specifies where a break is desired in the span
+ * @param the current allocation of the view
+ * @return the fragment of the view that represents the given span
+ * if the view can be broken, otherwise null
+ */
+ public View breakView(int axis, float len, Shape a) {
+ throw new Error("Can't break root view");
+ }
+
+ /**
+ * Determines the resizability of the view along the
+ * given axis. A value of 0 or less is not resizable.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the weight
+ */
+ public int getResizeWeight(int axis) {
+ if (view != null) {
+ return view.getResizeWeight(axis);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets the view size.
+ *
+ * @param width the width
+ * @param height the height
+ */
+ public void setSize(float width, float height) {
+ if (view != null) {
+ view.setSize(width, height);
+ }
+ }
+
+ /**
+ * Fetches the container hosting the view. This is useful for
+ * things like scheduling a repaint, finding out the host
+ * components font, etc. The default implementation
+ * of this is to forward the query to the parent view.
+ *
+ * @return the container
+ */
+ public Container getContainer() {
+ return editor;
+ }
+
+ /**
+ * Fetches the factory to be used for building the
+ * various view fragments that make up the view that
+ * represents the model. This is what determines
+ * how the model will be represented. This is implemented
+ * to fetch the factory provided by the associated
+ * EditorKit unless that is null, in which case this
+ * simply returns the BasicTextUI itself which allows
+ * subclasses to implement a simple factory directly without
+ * creating extra objects.
+ *
+ * @return the factory
+ */
+ public ViewFactory getViewFactory() {
+ EditorKit kit = getEditorKit(editor);
+ ViewFactory f = kit.getViewFactory();
+ if (f != null) {
+ return f;
+ }
+ return BasicTextUI.this;
+ }
+
+ private View view;
+
+ }
+
+ /**
+ * Handles updates from various places. If the model is changed,
+ * this class unregisters as a listener to the old model and
+ * registers with the new model. If the document model changes,
+ * the change is forwarded to the root view. If the focus
+ * accelerator changes, a new keystroke is registered to request
+ * focus.
+ */
+ class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource {
+
+ // --- PropertyChangeListener methods -----------------------
+
+ /**
+ * This method gets called when a bound property is changed.
+ * We are looking for document changes on the editor.
+ */
+ public final void propertyChange(PropertyChangeEvent evt) {
+ Object oldValue = evt.getOldValue();
+ Object newValue = evt.getNewValue();
+ String propertyName = evt.getPropertyName();
+ if ((oldValue instanceof Document) || (newValue instanceof Document)) {
+ if (oldValue != null) {
+ ((Document)oldValue).removeDocumentListener(this);
+ i18nView = false;
+ }
+ if (newValue != null) {
+ ((Document)newValue).addDocumentListener(this);
+ if ("document" == propertyName) {
+ setView(null);
+ BasicTextUI.this.propertyChange(evt);
+ modelChanged();
+ return;
+ }
+ }
+ modelChanged();
+ }
+ if ("focusAccelerator" == propertyName) {
+ updateFocusAcceleratorBinding(true);
+ } else if ("componentOrientation" == propertyName) {
+ // Changes in ComponentOrientation require the views to be
+ // rebuilt.
+ modelChanged();
+ } else if ("font" == propertyName) {
+ modelChanged();
+ } else if ("dropLocation" == propertyName) {
+ dropIndexChanged();
+ } else if ("editable" == propertyName) {
+ updateCursor();
+ modelChanged();
+ }
+ BasicTextUI.this.propertyChange(evt);
+ }
+
+ private void dropIndexChanged() {
+ if (editor.getDropMode() == DropMode.USE_SELECTION) {
+ return;
+ }
+
+ JTextComponent.DropLocation dropLocation = editor.getDropLocation();
+
+ if (dropLocation == null) {
+ if (dropCaret != null) {
+ dropCaret.deinstall(editor);
+ editor.repaint(dropCaret);
+ dropCaret = null;
+ }
+ } else {
+ if (dropCaret == null) {
+ dropCaret = new BasicCaret();
+ dropCaret.install(editor);
+ dropCaret.setVisible(true);
+ }
+
+ dropCaret.setDot(dropLocation.getIndex(),
+ dropLocation.getBias());
+ }
+ }
+
+ // --- DocumentListener methods -----------------------
+
+ /**
+ * The insert notification. Gets sent to the root of the view structure
+ * that represents the portion of the model being represented by the
+ * editor. The factory is added as an argument to the update so that
+ * the views can update themselves in a dynamic (not hardcoded) way.
+ *
+ * @param e The change notification from the currently associated
+ * document.
+ * @see DocumentListener#insertUpdate
+ */
+ public final void insertUpdate(DocumentEvent e) {
+ Document doc = e.getDocument();
+ Object o = doc.getProperty("i18n");
+ if (o instanceof Boolean) {
+ Boolean i18nFlag = (Boolean) o;
+ if (i18nFlag.booleanValue() != i18nView) {
+ // i18n flag changed, rebuild the view
+ i18nView = i18nFlag.booleanValue();
+ modelChanged();
+ return;
+ }
+ }
+
+ // normal insert update
+ Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
+ rootView.insertUpdate(e, alloc, rootView.getViewFactory());
+ }
+
+ /**
+ * The remove notification. Gets sent to the root of the view structure
+ * that represents the portion of the model being represented by the
+ * editor. The factory is added as an argument to the update so that
+ * the views can update themselves in a dynamic (not hardcoded) way.
+ *
+ * @param e The change notification from the currently associated
+ * document.
+ * @see DocumentListener#removeUpdate
+ */
+ public final void removeUpdate(DocumentEvent e) {
+ Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
+ rootView.removeUpdate(e, alloc, rootView.getViewFactory());
+ }
+
+ /**
+ * The change notification. Gets sent to the root of the view structure
+ * that represents the portion of the model being represented by the
+ * editor. The factory is added as an argument to the update so that
+ * the views can update themselves in a dynamic (not hardcoded) way.
+ *
+ * @param e The change notification from the currently associated
+ * document.
+ * @see DocumentListener#changeUpdate
+ */
+ public final void changedUpdate(DocumentEvent e) {
+ Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
+ rootView.changedUpdate(e, alloc, rootView.getViewFactory());
+ }
+
+ // --- LayoutManager2 methods --------------------------------
+
+ /**
+ * Adds the specified component with the specified name to
+ * the layout.
+ * @param name the component name
+ * @param comp the component to be added
+ */
+ public void addLayoutComponent(String name, Component comp) {
+ // not supported
+ }
+
+ /**
+ * Removes the specified component from the layout.
+ * @param comp the component to be removed
+ */
+ public void removeLayoutComponent(Component comp) {
+ if (constraints != null) {
+ // remove the constraint record
+ constraints.remove(comp);
+ }
+ }
+
+ /**
+ * Calculates the preferred size dimensions for the specified
+ * panel given the components in the specified parent container.
+ * @param parent the component to be laid out
+ *
+ * @see #minimumLayoutSize
+ */
+ public Dimension preferredLayoutSize(Container parent) {
+ // should not be called (JComponent uses UI instead)
+ return null;
+ }
+
+ /**
+ * Calculates the minimum size dimensions for the specified
+ * panel given the components in the specified parent container.
+ * @param parent the component to be laid out
+ * @see #preferredLayoutSize
+ */
+ public Dimension minimumLayoutSize(Container parent) {
+ // should not be called (JComponent uses UI instead)
+ return null;
+ }
+
+ /**
+ * Lays out the container in the specified panel. This is
+ * implemented to position all components that were added
+ * with a View object as a constraint. The current allocation
+ * of the associated View is used as the location of the
+ * component.
+ * <p>
+ * A read-lock is acquired on the document to prevent the
+ * view tree from being modified while the layout process
+ * is active.
+ *
+ * @param parent the component which needs to be laid out
+ */
+ public void layoutContainer(Container parent) {
+ if ((constraints != null) && (! constraints.isEmpty())) {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null) {
+ Document doc = editor.getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ try {
+ rootView.setSize(alloc.width, alloc.height);
+ Enumeration components = constraints.keys();
+ while (components.hasMoreElements()) {
+ Component comp = (Component) components.nextElement();
+ View v = (View) constraints.get(comp);
+ Shape ca = calculateViewPosition(alloc, v);
+ if (ca != null) {
+ Rectangle compAlloc = (ca instanceof Rectangle) ?
+ (Rectangle) ca : ca.getBounds();
+ comp.setBounds(compAlloc);
+ }
+ }
+ } finally {
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Find the Shape representing the given view.
+ */
+ Shape calculateViewPosition(Shape alloc, View v) {
+ int pos = v.getStartOffset();
+ View child = null;
+ for (View parent = rootView; (parent != null) && (parent != v); parent = child) {
+ int index = parent.getViewIndex(pos, Position.Bias.Forward);
+ alloc = parent.getChildAllocation(index, alloc);
+ child = parent.getView(index);
+ }
+ return (child != null) ? alloc : null;
+ }
+
+ /**
+ * Adds the specified component to the layout, using the specified
+ * constraint object. We only store those components that were added
+ * with a constraint that is of type View.
+ *
+ * @param comp the component to be added
+ * @param constraint where/how the component is added to the layout.
+ */
+ public void addLayoutComponent(Component comp, Object constraint) {
+ if (constraint instanceof View) {
+ if (constraints == null) {
+ constraints = new Hashtable(7);
+ }
+ constraints.put(comp, constraint);
+ }
+ }
+
+ /**
+ * Returns the maximum size of this component.
+ * @see java.awt.Component#getMinimumSize()
+ * @see java.awt.Component#getPreferredSize()
+ * @see LayoutManager
+ */
+ public Dimension maximumLayoutSize(Container target) {
+ // should not be called (JComponent uses UI instead)
+ return null;
+ }
+
+ /**
+ * Returns the alignment along the x axis. This specifies how
+ * the component would like to be aligned relative to other
+ * components. The value should be a number between 0 and 1
+ * where 0 represents alignment along the origin, 1 is aligned
+ * the furthest away from the origin, 0.5 is centered, etc.
+ */
+ public float getLayoutAlignmentX(Container target) {
+ return 0.5f;
+ }
+
+ /**
+ * Returns the alignment along the y axis. This specifies how
+ * the component would like to be aligned relative to other
+ * components. The value should be a number between 0 and 1
+ * where 0 represents alignment along the origin, 1 is aligned
+ * the furthest away from the origin, 0.5 is centered, etc.
+ */
+ public float getLayoutAlignmentY(Container target) {
+ return 0.5f;
+ }
+
+ /**
+ * Invalidates the layout, indicating that if the layout manager
+ * has cached information it should be discarded.
+ */
+ public void invalidateLayout(Container target) {
+ }
+
+ /**
+ * The "layout constraints" for the LayoutManager2 implementation.
+ * These are View objects for those components that are represented
+ * by a View in the View tree.
+ */
+ private Hashtable constraints;
+
+ private boolean i18nView = false;
+ }
+
+ /**
+ * Wrapper for text actions to return isEnabled false in case editor is non editable
+ */
+ class TextActionWrapper extends TextAction {
+ public TextActionWrapper(TextAction action) {
+ super((String)action.getValue(Action.NAME));
+ this.action = action;
+ }
+ /**
+ * The operation to perform when this action is triggered.
+ *
+ * @param e the action event
+ */
+ public void actionPerformed(ActionEvent e) {
+ action.actionPerformed(e);
+ }
+ public boolean isEnabled() {
+ return (editor == null || editor.isEditable()) ? action.isEnabled() : false;
+ }
+ TextAction action = null;
+ }
+
+
+ /**
+ * Registered in the ActionMap.
+ */
+ class FocusAction extends AbstractAction {
+
+ public void actionPerformed(ActionEvent e) {
+ editor.requestFocus();
+ }
+
+ public boolean isEnabled() {
+ return editor.isEditable();
+ }
+ }
+
+ private static DragListener getDragListener() {
+ synchronized(DragListener.class) {
+ DragListener listener =
+ (DragListener)AppContext.getAppContext().
+ get(DragListener.class);
+
+ if (listener == null) {
+ listener = new DragListener();
+ AppContext.getAppContext().put(DragListener.class, listener);
+ }
+
+ return listener;
+ }
+ }
+
+ /**
+ * Listens for mouse events for the purposes of detecting drag gestures.
+ * BasicTextUI will maintain one of these per AppContext.
+ */
+ static class DragListener extends MouseInputAdapter
+ implements BeforeDrag {
+
+ private boolean dragStarted;
+
+ public void dragStarting(MouseEvent me) {
+ dragStarted = true;
+ }
+
+ public void mousePressed(MouseEvent e) {
+ JTextComponent c = (JTextComponent)e.getSource();
+ if (c.getDragEnabled()) {
+ dragStarted = false;
+ if (isDragPossible(e) && DragRecognitionSupport.mousePressed(e)) {
+ e.consume();
+ }
+ }
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ JTextComponent c = (JTextComponent)e.getSource();
+ if (c.getDragEnabled()) {
+ if (dragStarted) {
+ e.consume();
+ }
+
+ DragRecognitionSupport.mouseReleased(e);
+ }
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ JTextComponent c = (JTextComponent)e.getSource();
+ if (c.getDragEnabled()) {
+ if (dragStarted || DragRecognitionSupport.mouseDragged(e, this)) {
+ e.consume();
+ }
+ }
+ }
+
+ /**
+ * Determines if the following are true:
+ * <ul>
+ * <li>the component is enabled
+ * <li>the press event is located over a selection
+ * </ul>
+ */
+ protected boolean isDragPossible(MouseEvent e) {
+ JTextComponent c = (JTextComponent)e.getSource();
+ if (c.isEnabled()) {
+ Caret caret = c.getCaret();
+ int dot = caret.getDot();
+ int mark = caret.getMark();
+ if (dot != mark) {
+ Point p = new Point(e.getX(), e.getY());
+ int pos = c.viewToModel(p);
+
+ int p0 = Math.min(dot, mark);
+ int p1 = Math.max(dot, mark);
+ if ((pos >= p0) && (pos < p1)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ static class TextTransferHandler extends TransferHandler implements UIResource {
+
+ private JTextComponent exportComp;
+ private boolean shouldRemove;
+ private int p0;
+ private int p1;
+
+ /**
+ * Whether or not this is a drop using
+ * <code>DropMode.INSERT</code>.
+ */
+ private boolean modeBetween = false;
+
+ /**
+ * Whether or not this is a drop.
+ */
+ private boolean isDrop = false;
+
+ /**
+ * The drop action.
+ */
+ private int dropAction = MOVE;
+
+ /**
+ * The drop bias.
+ */
+ private Position.Bias dropBias;
+
+ /**
+ * Try to find a flavor that can be used to import a Transferable.
+ * The set of usable flavors are tried in the following order:
+ * <ol>
+ * <li>First, an attempt is made to find a flavor matching the content type
+ * of the EditorKit for the component.
+ * <li>Second, an attempt to find a text/plain flavor is made.
+ * <li>Third, an attempt to find a flavor representing a String reference
+ * in the same VM is made.
+ * <li>Lastly, DataFlavor.stringFlavor is searched for.
+ * </ol>
+ */
+ protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
+ DataFlavor plainFlavor = null;
+ DataFlavor refFlavor = null;
+ DataFlavor stringFlavor = null;
+
+ if (c instanceof JEditorPane) {
+ for (int i = 0; i < flavors.length; i++) {
+ String mime = flavors[i].getMimeType();
+ if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) {
+ return flavors[i];
+ } else if (plainFlavor == null && mime.startsWith("text/plain")) {
+ plainFlavor = flavors[i];
+ } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
+ && flavors[i].getRepresentationClass() == java.lang.String.class) {
+ refFlavor = flavors[i];
+ } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
+ stringFlavor = flavors[i];
+ }
+ }
+ if (plainFlavor != null) {
+ return plainFlavor;
+ } else if (refFlavor != null) {
+ return refFlavor;
+ } else if (stringFlavor != null) {
+ return stringFlavor;
+ }
+ return null;
+ }
+
+
+ for (int i = 0; i < flavors.length; i++) {
+ String mime = flavors[i].getMimeType();
+ if (mime.startsWith("text/plain")) {
+ return flavors[i];
+ } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
+ && flavors[i].getRepresentationClass() == java.lang.String.class) {
+ refFlavor = flavors[i];
+ } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
+ stringFlavor = flavors[i];
+ }
+ }
+ if (refFlavor != null) {
+ return refFlavor;
+ } else if (stringFlavor != null) {
+ return stringFlavor;
+ }
+ return null;
+ }
+
+ /**
+ * Import the given stream data into the text component.
+ */
+ protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
+ throws BadLocationException, IOException {
+ if (useRead) {
+ int startPosition = c.getSelectionStart();
+ int endPosition = c.getSelectionEnd();
+ int length = endPosition - startPosition;
+ EditorKit kit = c.getUI().getEditorKit(c);
+ Document doc = c.getDocument();
+ if (length > 0) {
+ doc.remove(startPosition, length);
+ }
+ kit.read(in, doc, startPosition);
+ } else {
+ char[] buff = new char[1024];
+ int nch;
+ boolean lastWasCR = false;
+ int last;
+ StringBuffer sbuff = null;
+
+ // Read in a block at a time, mapping \r\n to \n, as well as single
+ // \r to \n.
+ while ((nch = in.read(buff, 0, buff.length)) != -1) {
+ if (sbuff == null) {
+ sbuff = new StringBuffer(nch);
+ }
+ last = 0;
+ for(int counter = 0; counter < nch; counter++) {
+ switch(buff[counter]) {
+ case '\r':
+ if (lastWasCR) {
+ if (counter == 0) {
+ sbuff.append('\n');
+ } else {
+ buff[counter - 1] = '\n';
+ }
+ } else {
+ lastWasCR = true;
+ }
+ break;
+ case '\n':
+ if (lastWasCR) {
+ if (counter > (last + 1)) {
+ sbuff.append(buff, last, counter - last - 1);
+ }
+ // else nothing to do, can skip \r, next write will
+ // write \n
+ lastWasCR = false;
+ last = counter;
+ }
+ break;
+ default:
+ if (lastWasCR) {
+ if (counter == 0) {
+ sbuff.append('\n');
+ } else {
+ buff[counter - 1] = '\n';
+ }
+ lastWasCR = false;
+ }
+ break;
+ }
+ }
+ if (last < nch) {
+ if (lastWasCR) {
+ if (last < (nch - 1)) {
+ sbuff.append(buff, last, nch - last - 1);
+ }
+ } else {
+ sbuff.append(buff, last, nch - last);
+ }
+ }
+ }
+ if (lastWasCR) {
+ sbuff.append('\n');
+ }
+ c.replaceSelection(sbuff != null ? sbuff.toString() : "");
+ }
+ }
+
+ // --- TransferHandler methods ------------------------------------
+
+ /**
+ * This is the type of transfer actions supported by the source. Some models are
+ * not mutable, so a transfer operation of COPY only should
+ * be advertised in that case.
+ *
+ * @param c The component holding the data to be transfered. This
+ * argument is provided to enable sharing of TransferHandlers by
+ * multiple components.
+ * @return This is implemented to return NONE if the component is a JPasswordField
+ * since exporting data via user gestures is not allowed. If the text component is
+ * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
+ */
+ public int getSourceActions(JComponent c) {
+ if (c instanceof JPasswordField &&
+ c.getClientProperty("JPasswordField.cutCopyAllowed") !=
+ Boolean.TRUE) {
+ return NONE;
+ }
+
+ return ((JTextComponent)c).isEditable() ? COPY_OR_MOVE : COPY;
+ }
+
+ /**
+ * Create a Transferable to use as the source for a data transfer.
+ *
+ * @param comp The component holding the data to be transfered. This
+ * argument is provided to enable sharing of TransferHandlers by
+ * multiple components.
+ * @return The representation of the data to be transfered.
+ *
+ */
+ protected Transferable createTransferable(JComponent comp) {
+ exportComp = (JTextComponent)comp;
+ shouldRemove = true;
+ p0 = exportComp.getSelectionStart();
+ p1 = exportComp.getSelectionEnd();
+ return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null;
+ }
+
+ /**
+ * This method is called after data has been exported. This method should remove
+ * the data that was transfered if the action was MOVE.
+ *
+ * @param source The component that was the source of the data.
+ * @param data The data that was transferred or possibly null
+ * if the action is <code>NONE</code>.
+ * @param action The actual action that was performed.
+ */
+ protected void exportDone(JComponent source, Transferable data, int action) {
+ // only remove the text if shouldRemove has not been set to
+ // false by importData and only if the action is a move
+ if (shouldRemove && action == MOVE) {
+ TextTransferable t = (TextTransferable)data;
+ t.removeText();
+ }
+
+ exportComp = null;
+ }
+
+ public boolean importData(TransferSupport support) {
+ isDrop = support.isDrop();
+
+ if (isDrop) {
+ modeBetween =
+ ((JTextComponent)support.getComponent()).getDropMode() == DropMode.INSERT;
+
+ dropBias = ((JTextComponent.DropLocation)support.getDropLocation()).getBias();
+
+ dropAction = support.getDropAction();
+ }
+
+ try {
+ return super.importData(support);
+ } finally {
+ isDrop = false;
+ modeBetween = false;
+ dropBias = null;
+ dropAction = MOVE;
+ }
+ }
+
+ /**
+ * This method causes a transfer to a component from a clipboard or a
+ * DND drop operation. The Transferable represents the data to be
+ * imported into the component.
+ *
+ * @param comp The component to receive the transfer. This
+ * argument is provided to enable sharing of TransferHandlers by
+ * multiple components.
+ * @param t The data to import
+ * @return true if the data was inserted into the component, false otherwise.
+ */
+ public boolean importData(JComponent comp, Transferable t) {
+ JTextComponent c = (JTextComponent)comp;
+
+ int pos = modeBetween
+ ? ((JTextComponent.DropLocation)c.getDropLocation()).getIndex()
+ : c.getCaretPosition();
+
+ // if we are importing to the same component that we exported from
+ // then don't actually do anything if the drop location is inside
+ // the drag location and set shouldRemove to false so that exportDone
+ // knows not to remove any data
+ if (dropAction == MOVE && c == exportComp && pos >= p0 && pos <= p1) {
+ shouldRemove = false;
+ return true;
+ }
+
+ boolean imported = false;
+ DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
+ if (importFlavor != null) {
+ try {
+ boolean useRead = false;
+ if (comp instanceof JEditorPane) {
+ JEditorPane ep = (JEditorPane)comp;
+ if (!ep.getContentType().startsWith("text/plain") &&
+ importFlavor.getMimeType().startsWith(ep.getContentType())) {
+ useRead = true;
+ }
+ }
+ InputContext ic = c.getInputContext();
+ if (ic != null) {
+ ic.endComposition();
+ }
+ Reader r = importFlavor.getReaderForText(t);
+
+ if (modeBetween) {
+ Caret caret = c.getCaret();
+ if (caret instanceof DefaultCaret) {
+ ((DefaultCaret)caret).setDot(pos, dropBias);
+ } else {
+ c.setCaretPosition(pos);
+ }
+ }
+
+ handleReaderImport(r, c, useRead);
+
+ if (isDrop) {
+ c.requestFocus();
+ Caret caret = c.getCaret();
+ if (caret instanceof DefaultCaret) {
+ int newPos = caret.getDot();
+ Position.Bias newBias = ((DefaultCaret)caret).getDotBias();
+
+ ((DefaultCaret)caret).setDot(pos, dropBias);
+ ((DefaultCaret)caret).moveDot(newPos, newBias);
+ } else {
+ c.select(pos, c.getCaretPosition());
+ }
+ }
+
+ imported = true;
+ } catch (UnsupportedFlavorException ufe) {
+ } catch (BadLocationException ble) {
+ } catch (IOException ioe) {
+ }
+ }
+ return imported;
+ }
+
+ /**
+ * This method indicates if a component would accept an import of the given
+ * set of data flavors prior to actually attempting to import it.
+ *
+ * @param comp The component to receive the transfer. This
+ * argument is provided to enable sharing of TransferHandlers by
+ * multiple components.
+ * @param flavors The data formats available
+ * @return true if the data can be inserted into the component, false otherwise.
+ */
+ public boolean canImport(JComponent comp, DataFlavor[] flavors) {
+ JTextComponent c = (JTextComponent)comp;
+ if (!(c.isEditable() && c.isEnabled())) {
+ return false;
+ }
+ return (getImportFlavor(flavors, c) != null);
+ }
+
+ /**
+ * A possible implementation of the Transferable interface
+ * for text components. For a JEditorPane with a rich set
+ * of EditorKit implementations, conversions could be made
+ * giving a wider set of formats. This is implemented to
+ * offer up only the active content type and text/plain
+ * (if that is not the active format) since that can be
+ * extracted from other formats.
+ */
+ static class TextTransferable extends BasicTransferable {
+
+ TextTransferable(JTextComponent c, int start, int end) {
+ super(null, null);
+
+ this.c = c;
+
+ Document doc = c.getDocument();
+
+ try {
+ p0 = doc.createPosition(start);
+ p1 = doc.createPosition(end);
+
+ plainData = c.getSelectedText();
+
+ if (c instanceof JEditorPane) {
+ JEditorPane ep = (JEditorPane)c;
+
+ mimeType = ep.getContentType();
+
+ if (mimeType.startsWith("text/plain")) {
+ return;
+ }
+
+ StringWriter sw = new StringWriter(p1.getOffset() - p0.getOffset());
+ ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset());
+
+ if (mimeType.startsWith("text/html")) {
+ htmlData = sw.toString();
+ } else {
+ richText = sw.toString();
+ }
+ }
+ } catch (BadLocationException ble) {
+ } catch (IOException ioe) {
+ }
+ }
+
+ void removeText() {
+ if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) {
+ try {
+ Document doc = c.getDocument();
+ doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset());
+ } catch (BadLocationException e) {
+ }
+ }
+ }
+
+ // ---- EditorKit other than plain or HTML text -----------------------
+
+ /**
+ * If the EditorKit is not for text/plain or text/html, that format
+ * is supported through the "richer flavors" part of BasicTransferable.
+ */
+ protected DataFlavor[] getRicherFlavors() {
+ if (richText == null) {
+ return null;
+ }
+
+ try {
+ DataFlavor[] flavors = new DataFlavor[3];
+ flavors[0] = new DataFlavor(mimeType + ";class=java.lang.String");
+ flavors[1] = new DataFlavor(mimeType + ";class=java.io.Reader");
+ flavors[2] = new DataFlavor(mimeType + ";class=java.io.InputStream;charset=unicode");
+ return flavors;
+ } catch (ClassNotFoundException cle) {
+ // fall through to unsupported (should not happen)
+ }
+
+ return null;
+ }
+
+ /**
+ * The only richer format supported is the file list flavor
+ */
+ protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException {
+ if (richText == null) {
+ return null;
+ }
+
+ if (String.class.equals(flavor.getRepresentationClass())) {
+ return richText;
+ } else if (Reader.class.equals(flavor.getRepresentationClass())) {
+ return new StringReader(richText);
+ } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
+ return new StringBufferInputStream(richText);
+ }
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ Position p0;
+ Position p1;
+ String mimeType;
+ String richText;
+ JTextComponent c;
+ }
+
+ }
+
+}