Index: openide/src/org/openide/explorer/view/ExplorerDragSupport.java
@@ -213,4 +213,18 @@
*/
abstract Node[] obtainNodes (DragGestureEvent dge);
+
+ // <>
+ // We need to let some top components listen to the drags so
+ // they can for example clear explorer selection.
+ // See bug 5048028.
+ public void addDragSourceListener(DragSourceListener dsl) {
+ getDefaultGestureRecognizer().getDragSource().addDragSourceListener(dsl);
+ }
+ public void removeDragSourceListener(DragSourceListener dsl) {
+ getDefaultGestureRecognizer().getDragSource().removeDragSourceListener(dsl);
+ }
+ // >
+
+
} /* end class ExplorerDragSupport */
Index: openide/src/org/openide/explorer/view/TreeView.java
@@ -16,6 +16,9 @@
import java.awt.*;
import java.awt.dnd.DnDConstants;
+// <>
+import java.awt.dnd.DragSourceListener;
+// >
import java.awt.dnd.Autoscroll;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
@@ -350,6 +353,34 @@
}
}
+ // <>
+ // We need to let some top components listen to the drags so
+ // they can for example clear explorer selection.
+ // See bug 5048028.
+ /**
+ * Add a drag source listener.
+ * @param dsl The drag source listener to be added
+ */
+ public void addDragSourceListener(DragSourceListener dsl) {
+ if (!dragActive) {
+ setDragSource(true);
+ }
+ dragSupport.addDragSourceListener(dsl);
+ }
+ /**
+ * Remove a drag source listener.
+ * @param dsl The drag source listener to be removed
+ */
+ public void removeDragSourceListener(DragSourceListener dsl) {
+ if (dragSupport != null) {
+ dragSupport.removeDragSourceListener(dsl);
+ }
+ }
+ // >
+
+
+
+
/** Drop support is enabled by default.
* @return true if dropping to the view is enabled, false
* otherwise
Index: openide/src/org/openide/text/CloneableEditor.java
@@ -198,6 +198,21 @@
pane.setEditorKit (support.kit ());
pane.setDocument (doc);
+// <> Temporary put back (to fix a bug),
+// when is figured out another method to hack this (at least from editor module).
+// Modified for by Tor: install drag & drop handlers on the editor
+ // Enable drag & drop
+// pane.setDragEnabled(true); // seems not necessary
+ pane.setTransferHandler(new TextTransferHandler());
+ if (pane.getDropTarget() != null) {
+ TextDropTargetListener dropTargetListener = new TextDropTargetListener();
+ try {
+ pane.getDropTarget().addDropTargetListener(dropTargetListener);
+ } catch(java.util.TooManyListenersException tmle) {
+ ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, tmle);
+ }
+ }
+// >
if (doc instanceof NbDocument.CustomEditor) {
NbDocument.CustomEditor ce = (NbDocument.CustomEditor)doc;
customComponent = ce.createEditor(pane);
Index: openide/src/org/openide/text/TextDropTargetListener.java
@@ -0,0 +1,326 @@
+/*
+ * Sun Public License Notice
+ *
+ * The contents of this file are subject to the Sun Public License
+ * Version 1.0 (the "License"). You may not use this file except in
+ * compliance with the License. A copy of the License is available at
+ * http://www.sun.com/
+ *
+ * The Original Code is NetBeans. The Initial Developer of the Original
+ * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ */
+
+package org.openide.text;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.font.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+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 javax.swing.Timer;
+
+/* A drop listener for text components. This seems necessary because
+ * the NetBeans editor doesn't inherit from the Swing plaf.basic package,
+ * so it's missing a bunch of drag & drop behavior.
+ * In particular, this class is responsible for moving the caret
+ * to the nearest drag location in the document, as well as autoscrolling
+ * the view when necesary.
+ *
+ * This code is basically a merged version of text-related code in + * javax.swing.plaf.basic: BasicTextUI's TextDropTargetListener and + * its parent class, BasickDropTargetListener. + *
+ * I had to copy it since it has package protected access in + * javax.swing.plaf.basic. + * + *
+ * @author Tor Norbye + */ +class TextDropTargetListener implements DropTargetListener, + UIResource, ActionListener { + + // The first code is the general BasicDropTargetListener code; at the + // end you'll find the TextDropTargetListener code + + + /** + * construct a DropTargetAutoScroller + *
+ * @param c the Component
+ * @param p the Point
+ */
+ protected TextDropTargetListener() {
+ }
+
+
+ /**
+ * Update the geometry of the autoscroll region. The geometry is
+ * maintained as a pair of rectangles. The region can cause
+ * a scroll if the pointer sits inside it for the duration of the
+ * timer. The region that causes the timer countdown is the area
+ * between the two rectangles.
+ *
+ * This is implemented to use the visible area of the component + * as the outer rectangle and the insets are based upon the + * Scrollable information (if any). If the Scrollable is + * scrollable along an axis, the step increment is used as + * the autoscroll inset. If the component is not scrollable, + * the insets will be zero (i.e. autoscroll will not happen). + */ + void updateAutoscrollRegion(JComponent c) { + // compute the outer + Rectangle visible = c.getVisibleRect(); + outer.reshape(visible.x, visible.y, visible.width, visible.height); + + // compute the insets + // TBD - the thing with the scrollable + Insets i = new Insets(0, 0, 0, 0); + if (c instanceof Scrollable) { + Scrollable s = (Scrollable) c; + i.left = s.getScrollableUnitIncrement(visible, SwingConstants.HORIZONTAL, 1); + i.top = s.getScrollableUnitIncrement(visible, SwingConstants.VERTICAL, 1); + i.right = s.getScrollableUnitIncrement(visible, SwingConstants.HORIZONTAL, -1); + i.bottom = s.getScrollableUnitIncrement(visible, SwingConstants.VERTICAL, -1); + } + + // set the inner from the insets + inner.reshape(visible.x + i.left, + visible.y + i.top, + visible.width - (i.left + i.right), + visible.height - (i.top + i.bottom)); + } + + /** + * Perform an autoscroll operation. This is implemented to scroll by the + * unit increment of the Scrollable using scrollRectToVisible. If the + * cursor is in a corner of the autoscroll region, more than one axis will + * scroll. + */ + void autoscroll(JComponent c, Point pos) { + if (c instanceof Scrollable) { + Scrollable s = (Scrollable) c; + if (pos.y < inner.y) { + // scroll top downward + int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1); + Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy); + c.scrollRectToVisible(r); + } else if (pos.y > (inner.y + inner.height)) { + // scroll bottom upward + int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1); + Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy); + c.scrollRectToVisible(r); + } + + if (pos.x < inner.x) { + // scroll left side to the right + int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1); + Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height); + c.scrollRectToVisible(r); + } else if (pos.x > (inner.x + inner.width)) { + // scroll right side to the left + int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1); + Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height); + c.scrollRectToVisible(r); + } + } + } + + /** + * Initializes the internal properties if they haven't been already + * inited. This is done lazily to avoid loading of desktop properties. + */ + private void initPropertiesIfNecessary() { + if (timer == null) { + Toolkit t = Toolkit.getDefaultToolkit(); + Integer initial = new Integer(100); + Integer interval = new Integer(100); + + try { + initial = (Integer)t.getDesktopProperty( + "DnD.Autoscroll.initialDelay"); + } catch (Exception e) { + // ignore + } + try { + interval = (Integer)t.getDesktopProperty( + "DnD.Autoscroll.interval"); + } catch (Exception e) { + // ignore + } + timer = new Timer(interval.intValue(), this); + + timer.setCoalesce(true); + timer.setInitialDelay(initial.intValue()); + + try { + hysteresis = ((Integer)t.getDesktopProperty( + "DnD.Autoscroll.cursorHysteresis")).intValue(); + } catch (Exception e) { + // ignore + } + } + } + + static JComponent getComponent(DropTargetEvent e) { + DropTargetContext context = e.getDropTargetContext(); + return (JComponent) context.getComponent(); + } + + // --- ActionListener methods -------------------------------------- + + /** + * The timer fired, perform autoscroll if the pointer is within the + * autoscroll region. + *
+ * @param e the ActionEvent
+ */
+ public synchronized void actionPerformed(ActionEvent e) {
+ updateAutoscrollRegion(component);
+ if (outer.contains(lastPosition) && !inner.contains(lastPosition)) {
+ autoscroll(component, lastPosition);
+ }
+ }
+
+ // --- DropTargetListener methods -----------------------------------
+
+ public void dragEnter(DropTargetDragEvent e) {
+ component = getComponent(e);
+ TransferHandler th = component.getTransferHandler();
+ canImport = th.canImport(component, e.getCurrentDataFlavors());
+ if (canImport) {
+ saveComponentState(component);
+ lastPosition = e.getLocation();
+ updateAutoscrollRegion(component);
+ initPropertiesIfNecessary();
+ }
+ }
+
+ public void dragOver(DropTargetDragEvent e) {
+ if (canImport) {
+ Point p = e.getLocation();
+ updateInsertionLocation(component, p);
+
+
+ // check autoscroll
+ synchronized(this) {
+ if (Math.abs(p.x - lastPosition.x) > hysteresis ||
+ Math.abs(p.y - lastPosition.y) > hysteresis) {
+ // no autoscroll
+ if (timer.isRunning()) timer.stop();
+ } else {
+ if (!timer.isRunning()) timer.start();
+ }
+ lastPosition = p;
+ }
+ }
+ }
+
+ public void dragExit(DropTargetEvent e) {
+ if (canImport) {
+ restoreComponentState(component);
+ }
+ cleanup();
+ }
+
+ public void drop(DropTargetDropEvent e) {
+ if (canImport) {
+ restoreComponentStateForDrop(component);
+ }
+ cleanup();
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {
+ }
+
+ /**
+ * Cleans up internal state after the drop has finished (either succeeded
+ * or failed).
+ */
+ private void cleanup() {
+ if (timer != null) {
+ timer.stop();
+ }
+ component = null;
+ lastPosition = null;
+ }
+
+ // --- fields --------------------------------------------------
+
+ private Timer timer;
+ private Point lastPosition;
+ private Rectangle outer = new Rectangle();
+ private Rectangle inner = new Rectangle();
+ private int hysteresis = 10;
+ private boolean canImport;
+
+ /**
+ * The current component. The value is cached from the drop events and used
+ * by the timer. When a drag exits or a drop occurs, this value is cleared.
+ */
+ private JComponent component;
+
+
+
+
+ // TEXT DROP LISTENER SPECIFIC STUFF - from BasicTextUI's
+ // TextDropListener
+
+
+ /**
+ * called to save the state of a component in case it needs to
+ * be restored because a drop is not performed.
+ */
+ protected void saveComponentState(JComponent comp) {
+ JTextComponent c = (JTextComponent) comp;
+ Caret caret = c.getCaret();
+ dot = caret.getDot();
+ mark = caret.getMark();
+ visible = caret.isVisible();
+ caret.setVisible(true);
+ }
+
+ /**
+ * called to restore the state of a component
+ * because a drop was not performed.
+ */
+ protected void restoreComponentState(JComponent comp) {
+ JTextComponent c = (JTextComponent) comp;
+ Caret caret = c.getCaret();
+ caret.setDot(mark);
+ caret.moveDot(dot);
+ caret.setVisible(visible);
+ }
+
+ /**
+ * called to restore the state of a component
+ * because a drop was performed.
+ */
+ protected void restoreComponentStateForDrop(JComponent comp) {
+ JTextComponent c = (JTextComponent) comp;
+ Caret caret = c.getCaret();
+ caret.setVisible(visible);
+ }
+
+ /**
+ * called to set the insertion location to match the current
+ * mouse pointer coordinates.
+ */
+ protected void updateInsertionLocation(JComponent comp, Point p) {
+ JTextComponent c = (JTextComponent) comp;
+ c.setCaretPosition(c.viewToModel(p));
+ }
+
+ int dot;
+ int mark;
+ boolean visible;
+}
Index: openide/src/org/openide/text/TextTransferHandler.java
@@ -0,0 +1,748 @@
+/*
+ * Sun Public License Notice
+ *
+ * The contents of this file are subject to the Sun Public License
+ * Version 1.0 (the "License"). You may not use this file except in
+ * compliance with the License. A copy of the License is available at
+ * http://www.sun.com/
+ *
+ * The Original Code is NetBeans. The Initial Developer of the Original
+ * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ */
+
+package org.openide.text;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.font.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+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 javax.swing.Timer;
+
+import org.openide.windows.TopComponent;
+
+
+/* A transfer handler for the editor component. This seems necessary because
+ * the NetBeans editor doesn't inherit from the Swing plaf.basic package,
+ * so it's missing a bunch of drag & drop behavior.
+ *
+ * This code is basically a merged version of text-related code in + * javax.swing.plaf.basic: BasicTextUI, BasicTransferable, ... + * I had to copy it since it has package protected access in + * javax.swing.plaf.basic. + *
+ * There is one important difference. In order to allow the DESTINATION + * to decide if the transferable should be moved or copied (e.g. if the + * destination is the same document, move, if it's the clipboard palette, + * copy), there's a global flag that can be set which basically turns off + * moving when a drag is in progress. Yup, this is a bit of a hack, but + * I couldn't find a better way. With Swing, the copy-vs-move decision is + * made when the drag is -started-, and at that point we don't know yet + * where you're going to drop. The docs for TransferHandler exportAsDrag says: + *
action - the transfer action initially requested; this should + * be a value of either. + * However, it does not say HOW you can change the action, and from looking + * at the code, I suspect it cannot be done, since the action is passed + * to a gesture listener that has private access. + *COPY
orMOVE
; + * the value may be changed during the course of the drag operation + *
+ * @author Tor Norbye + */ + +public class TextTransferHandler extends TransferHandler implements UIResource { + + /** Flag which is only defined during a drag & drop operation. + * Clients (typically drop zones) can set it to true to indicate + * that the data being dragged should be copied, not moved. + * For example, the clipboard viewer sets this when it handles + * the import. That way, the exportDone method knows not to remove + * the text being placed on the clipboard from the document, since + * text dragging (without modified keys) defaults to moving, not + * copying. And we don't want to disallow copying in getSourceActions, + * since dragging text from one place in the document to another + * SHOULD be moved, not copied. */ + public static boolean dontRemove = false; + + + private JTextComponent exportComp; + private boolean shouldRemove; + private int p0; + private int p1; + + /** + * Try to find a flavor that can be used to import a Transferable. + * The set of usable flavors are tried in the following order: + *
NONE
.
+ * @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;
+ if (!dontRemove) {
+ t.removeText();
+ }
+ }
+
+ exportComp = null;
+ }
+
+ /**
+ * 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;
+
+ // 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 (c == exportComp && c.getCaretPosition() >= p0 && c.getCaretPosition() <= 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;
+
+ }
+
+ try {
+ // XXX Hacking the code clips transfer.
+ Runnable r = (Runnable)t.getTransferData(CodeClipTransferData.CODE_CLIP_DATA_FLAVOR);
+ if(r != null) {
+ r.run();
+ }
+ } catch(Exception e) {
+// e.printStackTrace();
+ }
+ }
+ Reader r = importFlavor.getReaderForText(t);
+ handleReaderImport(r, c, useRead);
+ imported = true;
+
+ // #4946925 Trying to put the activation to the drop target.
+ TopComponent tc = (TopComponent)SwingUtilities.getAncestorOfClass(TopComponent.class, c);
+ if(tc != null) {
+ tc.requestActive();
+ }
+ } 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 implements Transferable, UIResource {
+
+ // begin copied from BasicTransferable
+ protected String plainData = null;
+ protected String htmlData = null;
+
+ private static DataFlavor[] htmlFlavors;
+ private static DataFlavor[] stringFlavors;
+ private static DataFlavor[] plainFlavors;
+
+ static {
+ try {
+ htmlFlavors = new DataFlavor[3];
+ htmlFlavors[0] = new DataFlavor("text/html;class=java.lang.String");
+ htmlFlavors[1] = new DataFlavor("text/html;class=java.io.Reader");
+ htmlFlavors[2] = new DataFlavor("text/html;charset=unicode;class=java.io.InputStream");
+
+ plainFlavors = new DataFlavor[3];
+ plainFlavors[0] = new DataFlavor("text/plain;class=java.lang.String");
+ plainFlavors[1] = new DataFlavor("text/plain;class=java.io.Reader");
+ plainFlavors[2] = new DataFlavor("text/plain;charset=unicode;class=java.io.InputStream");
+
+ stringFlavors = new DataFlavor[2];
+ stringFlavors[0] = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType+";class=java.lang.String");
+ stringFlavors[1] = DataFlavor.stringFlavor;
+
+ } catch (ClassNotFoundException cle) {
+ System.err.println("error initializing javax.swing.plaf.basic.BasicTranserable");
+ }
+ }
+
+ /**
+ * Returns an array of DataFlavor objects indicating the flavors the data
+ * can be provided in. The array should be ordered according to preference
+ * for providing the data (from most richly descriptive to least descriptive).
+ * @return an array of data flavors in which this data can be transferred
+ */
+ public DataFlavor[] getTransferDataFlavors() {
+ DataFlavor[] richerFlavors = getRicherFlavors();
+ int nRicher = (richerFlavors != null) ? richerFlavors.length : 0;
+ int nHTML = (isHTMLSupported()) ? htmlFlavors.length : 0;
+ int nPlain = (isPlainSupported()) ? plainFlavors.length: 0;
+ int nString = (isPlainSupported()) ? stringFlavors.length : 0;
+ int nFlavors = nRicher + nHTML + nPlain + nString;
+ DataFlavor[] flavors = new DataFlavor[nFlavors];
+
+ // fill in the array
+ int nDone = 0;
+ if (nRicher > 0) {
+ System.arraycopy(richerFlavors, 0, flavors, nDone, nRicher);
+ nDone += nRicher;
+ }
+ if (nHTML > 0) {
+ System.arraycopy(htmlFlavors, 0, flavors, nDone, nHTML);
+ nDone += nHTML;
+ }
+ if (nPlain > 0) {
+ System.arraycopy(plainFlavors, 0, flavors, nDone, nPlain);
+ nDone += nPlain;
+ }
+ if (nString > 0) {
+ System.arraycopy(stringFlavors, 0, flavors, nDone, nString);
+ nDone += nString;
+ }
+ return flavors;
+ }
+
+ /**
+ * Returns whether or not the specified data flavor is supported for
+ * this object.
+ * @param flavor the requested flavor for the data
+ * @return boolean indicating whether or not the data flavor is supported
+ */
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ DataFlavor[] flavors = getTransferDataFlavors();
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavors[i].equals(flavor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns an object which represents the data to be transferred. The class
+ * of the object returned is defined by the representation class of the flavor.
+ *
+ * @param flavor the requested flavor for the data
+ * @see DataFlavor#getRepresentationClass
+ * @exception IOException if the data is no longer available
+ * in the requested flavor.
+ * @exception UnsupportedFlavorException if the requested data flavor is
+ * not supported.
+ */
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ DataFlavor[] richerFlavors = getRicherFlavors();
+ if (isRicherFlavor(flavor)) {
+ return getRicherData(flavor);
+ } else if (isHTMLFlavor(flavor)) {
+ String data = getHTMLData();
+ data = (data == null) ? "" : data;
+ if (String.class.equals(flavor.getRepresentationClass())) {
+ return data;
+ } else if (Reader.class.equals(flavor.getRepresentationClass())) {
+ return new StringReader(data);
+ } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
+ return new StringBufferInputStream(data);
+ }
+ // fall through to unsupported
+ } else if (isPlainFlavor(flavor)) {
+ String data = getPlainData();
+ data = (data == null) ? "" : data;
+ if (String.class.equals(flavor.getRepresentationClass())) {
+ return data;
+ } else if (Reader.class.equals(flavor.getRepresentationClass())) {
+ return new StringReader(data);
+ } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
+ return new StringBufferInputStream(data);
+ }
+ // fall through to unsupported
+
+ } else if (isStringFlavor(flavor)) {
+ String data = getPlainData();
+ data = (data == null) ? "" : data;
+ return data;
+ }
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ // --- richer subclass flavors ----------------------------------------------
+
+ protected boolean isRicherFlavor(DataFlavor flavor) {
+ DataFlavor[] richerFlavors = getRicherFlavors();
+ int nFlavors = (richerFlavors != null) ? richerFlavors.length : 0;
+ for (int i = 0; i < nFlavors; i++) {
+ if (richerFlavors[i].equals(flavor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // --- html flavors ----------------------------------------------------------
+
+ /**
+ * Returns whether or not the specified data flavor is an HTML flavor that
+ * is supported.
+ * @param flavor the requested flavor for the data
+ * @return boolean indicating whether or not the data flavor is supported
+ */
+ protected boolean isHTMLFlavor(DataFlavor flavor) {
+ DataFlavor[] flavors = htmlFlavors;
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavors[i].equals(flavor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Should the HTML flavors be offered? If so, the method
+ * getHTMLData should be implemented to provide something reasonable.
+ */
+ protected boolean isHTMLSupported() {
+ return htmlData != null;
+ }
+
+ /**
+ * Fetch the data in a text/html format
+ */
+ protected String getHTMLData() {
+ return htmlData;
+ }
+
+ // --- plain text flavors ----------------------------------------------------
+
+ /**
+ * Returns whether or not the specified data flavor is an plain flavor that
+ * is supported.
+ * @param flavor the requested flavor for the data
+ * @return boolean indicating whether or not the data flavor is supported
+ */
+ protected boolean isPlainFlavor(DataFlavor flavor) {
+ DataFlavor[] flavors = plainFlavors;
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavors[i].equals(flavor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Should the plain text flavors be offered? If so, the method
+ * getPlainData should be implemented to provide something reasonable.
+ */
+ protected boolean isPlainSupported() {
+ return plainData != null;
+ }
+
+ /**
+ * Fetch the data in a text/plain format.
+ */
+ protected String getPlainData() {
+ return plainData;
+ }
+
+ // --- string flavorss --------------------------------------------------------
+
+ /**
+ * Returns whether or not the specified data flavor is a String flavor that
+ * is supported.
+ * @param flavor the requested flavor for the data
+ * @return boolean indicating whether or not the data flavor is supported
+ */
+ protected boolean isStringFlavor(DataFlavor flavor) {
+ DataFlavor[] flavors = stringFlavors;
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavors[i].equals(flavor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // end copied from BasicTransferable
+
+
+ TextTransferable(JTextComponent c, int start, int end) {
+ 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;
+ }
+
+ // XXX Hack to enable poping up a dialog when DnD of code clip.
+ public static class CodeClipTransferData extends StringSelection {
+
+ // XXX Fake DataFlavor.. for cheating to retrieve the callback.
+ public static final DataFlavor CODE_CLIP_DATA_FLAVOR
+ = new DataFlavor(CodeClipTransferData.class, CodeClipTransferData.class.getName()); // TEMP
+
+ // XXX Callback to provide the popup.
+ private Runnable callback;
+ // XXX We need to manipulate the data in this class (the superclass is private).
+ private String data;
+
+
+ public CodeClipTransferData(String data) {
+ super(data); // Just fake.
+ this.data = data;
+ }
+
+
+ public void setCallback(Runnable callback) {
+ this.callback = callback;
+ }
+
+ public void resetData(String data) {
+ this.data = data;
+ }
+
+ // XXX Overriden to manipulate with our data field and provide the hacking callback.
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ if(flavor == CODE_CLIP_DATA_FLAVOR) {
+ return callback;
+ }
+
+ // JCK Test StringSelection0007: if 'flavor' is null, throw NPE
+ if (flavor.equals(DataFlavor.stringFlavor)) {
+ return (Object)data;
+ } else if (flavor.equals(DataFlavor.plainTextFlavor)) { // deprecated
+ return new StringReader(data);
+ } else {
+ throw new UnsupportedFlavorException(flavor);
+ }
+ }
+
+ }
+}
Index: editor/libsrc/org/netbeans/editor/BaseCaret.java
@@ -35,9 +35,9 @@
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.IOException;
-import javax.swing.Action;
-import javax.swing.Timer;
-import javax.swing.SwingUtilities;
+// <>
+import javax.swing.*;
+// >
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
@@ -49,6 +49,9 @@
import javax.swing.event.EventListenerList;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Position;
+// <>
+import sun.awt.dnd.SunDragSourceContextPeer;
+// >
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
@@ -58,8 +61,25 @@
/**
* Caret implementation
*
-* @author Miloslav Metelka
-* @version 1.00
+* +* Modified for by Tor: added drag & drop handling in the mouse +* listeners: specifically, recognize drag gestures and initiate drags +* when recognized. Did this by merging in code from +* BasicDragGestureRecognizer.java and BaseTextUI.java's +* TextDragGestureRecognizer and modifiying the existing +* various mouse-event methods to check for drags before proceeding +* with their previous (selection-swiping etc.) stuff. +*
+* There was one problem with that - if you click inside the +* selection (to unselect the text), the mouse click is ignored - and +* the selection not unselected. This was especially troublesome if +* you've Selected All so it's hard to go outside the selection. +* So I modified it such that if you release the mouse, when you've +* ignored a mouse press (because a drag was possible), the selection +* is cleared and the caret moved. +*
+* +* @author Miloslav Metelka, Tor Norbye */ public class BaseCaret extends Rectangle implements Caret, FocusListener, @@ -1112,7 +1132,27 @@ } } + // <> + // Add support for dragging within the source editor. You can select + // text, then drag this text around to a different location in the + // editor buffer, or even to other windows. In , this lets you + // drag source text to the palette to create a code clip. + // The code works by setting up the mouse listeners to recognize a + // drag gesture then uses the standard Swing mechanism to export + // a transferable. public void mousePressed(MouseEvent evt) { + dndArmedEvent = null; + + MouseEvent e = evt; + if (isDragPossible(e) && mapDragOperationFromModifiers(e) != TransferHandler.NONE) { + dndArmedEvent = e; + e.consume(); + return; + } + setPosition(evt); + } + + private void setPosition(MouseEvent evt) { JTextComponent c = component; if (c != null) { Utilities.getEditorUI(c).getWordMatch().clear(); // [PENDING] should be done cleanly @@ -1139,16 +1179,49 @@ } public void mouseReleased(MouseEvent evt) { + // If you click on a selection, but don't drag it, we should + // treat it as a regular mouse click in the buffer -> move the + // dot to this point + if (dndArmedEvent != null) { + setPosition(dndArmedEvent); + dndArmedEvent = null; + } } public void mouseEntered(MouseEvent evt) { + //dndArmedEvent = null; } public void mouseExited(MouseEvent evt) { + //if (dndArmedEvent != null && mapDragOperationFromModifiers(e) == TransferHandler.NONE) { + // dndArmedEvent = null; + //} } // MouseMotionListener methods public void mouseDragged(MouseEvent evt) { + if (dndArmedEvent != null) { + MouseEvent e = evt; + e.consume(); + + int action = mapDragOperationFromModifiers(e); + if (action == TransferHandler.NONE) { + return; + } + + int dx = Math.abs(e.getX() - dndArmedEvent.getX()); + int dy = Math.abs(e.getY() - dndArmedEvent.getY()); + if ((dx > getMotionThreshold()) || (dy > getMotionThreshold())) { + // start transfer... shouldn't be a click at this point + JComponent c = getComponent(e); + TransferHandler th = c.getTransferHandler(); + //th.exportAsDrag(c, dndArmedEvent, action); + th.exportAsDrag(c, dndArmedEvent, TransferHandler.COPY); + dndArmedEvent = null; + } + return; + } + JTextComponent c = component; if (SwingUtilities.isLeftMouseButton(evt)) { if (c != null) { @@ -1167,6 +1240,116 @@ public void mouseMoved(MouseEvent evt) { } + + + // Drag Gesture related stuff; borrowed from BasicDragGestureRecognizer.java + // in javax.swing.plaf.basic. The mouse method stuff is merged into the + // above mouse handlers, so consult the source. + + private MouseEvent dndArmedEvent = null; + + private static int motionThreshold; + + private static boolean checkedMotionThreshold = false; + + private static int getMotionThreshold() { + if (checkedMotionThreshold) { + return motionThreshold; + } else { + checkedMotionThreshold = true; + try { + motionThreshold = ((Integer)Toolkit.getDefaultToolkit().getDesktopProperty("DnD.gestureMotionThreshold")).intValue(); + } catch (Exception e) { + motionThreshold = 5; + } + } + return motionThreshold; + } + + protected int mapDragOperationFromModifiers(MouseEvent e) { + int mods = e.getModifiersEx(); + + if ((mods & InputEvent.BUTTON1_DOWN_MASK) != InputEvent.BUTTON1_DOWN_MASK) { + return TransferHandler.NONE; + } + + JComponent c = getComponent(e); + TransferHandler th = c.getTransferHandler(); + return SunDragSourceContextPeer.convertModifiersToDropAction(mods, th.getSourceActions(c)); + } + + private TransferHandler getTransferHandler(MouseEvent e) { + JComponent c = getComponent(e); + return c == null ? null : c.getTransferHandler(); + } + + + protected JComponent getComponent(MouseEvent e) { + Object src = e.getSource(); + if (src instanceof JComponent) { + JComponent c = (JComponent) src; + return c; + } + return null; + } + + // Copied from BaseTextUI's TextDragGestureRecognizer + /** + * Determines if the following are true: + *
+ * This is implemented to check for a TransferHandler. + * Subclasses should perform the remaining conditions. + */ + protected boolean isDragPossible(MouseEvent e) { + JTextComponent c = (JTextComponent) this.getComponent(e); + + // Check if the associated text component has drag enabled: + //boolean dragEnabled = c.getDragEnabled(); + // However, we'd like the default to be "true" in the whole + // IDE, since that's how most IDEs work. However, that means + // that all places in the IDE which create an editor + // (new JEditorPane()) we'd have to go and call + // pane.setDraggable(true). Instead, there's probably no good + // reason to have inconsistent behavior for different editors + // in the IDE, so we globally enforce editor draggability. + // (An alternative would be for the install() method in + // the UIDelegate to go and set draggable true, giving + // clients a chance to turn it off after install, but this + // seems like overkill for something clients ought not to + // do.) + boolean dragEnabled = true; + + if (dragEnabled) { + 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; + } + + // End stuff copied from BasicDragGestureRecognizer + + // > + + + + + // PropertyChangeListener methods public void propertyChange(PropertyChangeEvent evt) { String propName = evt.getPropertyName();