/*
* 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.explorer.view;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.awt.Point;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.IOException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
import java.util.TreeSet;
import javax.swing.JPopupMenu;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import org.openide.DialogDisplayer;
import org.openide.awt.JPopupMenuPlus;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.nodes.Node;
import org.openide.util.datatransfer.*;
import org.openide.util.UserCancelException;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
/** Class that provides methods for common tasks needed during
* drag and drop when working with explorer views.
*
* @author Dafe Simonek
*/
final class DragDropUtilities extends Object {
static final boolean dragAndDropEnabled = isDragAndDropEnabled();
static final int NODE_UP = -1;
static final int NODE_CENTRAL = 0;
static final int NODE_DOWN = 1;
static final Point CURSOR_CENTRAL_POINT = new Point (10, 10);
static Runnable postDropRun = null;
/** No need to instantiate this class */
private DragDropUtilities() {
}
// helper constants
static final int NoDrag = 0;
static final int NoDrop = 1;
//static final int Modifiers4Move =
/**
* Checks system property netbeans.dnd.enabled. If it is not
* present return false for JDK1.3 and true for JDK1.4 and newer.
*/
private static boolean isDragAndDropEnabled() {
// This should say: JDk1.4 and newer
if (! System.getProperty("java.version").startsWith("1.3")) {
// if jdk1.4(or newer) then dnd enabled as default
if (System.getProperty("netbeans.dnd.enabled") != null) { // NOI18N
return Boolean.getBoolean("netbeans.dnd.enabled"); // NOI18N
} else {
return true;
}
}
return false;
}
/** Utility method - chooses and returns right cursor
* for given user drag action.
*/
static Cursor chooseCursor (int dragAction, boolean canDrop) {
//System.out.print("------> chooseCursor(action: "+dragAction+", can? "+canDrop+")");
// if the node does not provide icon use system default
Image image;
String name;
try {
switch (dragAction) {
case DnDConstants.ACTION_COPY:
case DnDConstants.ACTION_COPY_OR_MOVE:
if (canDrop) {
image = Utilities.loadImage(
"org/openide/resources/cursorscopysingle.gif"); // NOI18N
name = "ACTION_COPY"; // NOI18N
} else {
image = Utilities.loadImage(
"org/openide/resources/cursorsnone.gif"); // NOI18N
name = "NO_ACTION_COPY"; // NOI18N
}
break;
case DnDConstants.ACTION_MOVE:
if (canDrop) {
image = Utilities.loadImage(
"org/openide/resources/cursorsmovesingle.gif"); // NOI18N
name = "ACTION_MOVE"; // NOI18N
} else {
image = Utilities.loadImage(
"org/openide/resources/cursorsnone.gif"); // NOI18N
name = "NO_ACTION_MOVE"; // NOI18N
}
break;
case DnDConstants.ACTION_LINK:
if (canDrop) {
image = Utilities.loadImage(
"org/openide/resources/cursorsunknownsingle.gif"); // NOI18N
name = "ACTION_LINK"; // NOI18N
} else {
image = Utilities.loadImage(
"org/openide/resources/cursorsnone.gif"); // NOI18N
name = "NO_ACTION_LINK"; // NOI18N
}
break;
default:
image = Utilities.loadImage(
"org/openide/resources/cursorsnone.gif"); // NOI18N
name = "ACTION_NONE"; // NOI18N
break;
}
//System.out.println("--> "+image.getSource());
return createCustomCursor(image, name);
} catch (Exception ex) {
ErrorManager.getDefault ().notify (ex);
}
return DragSource.DefaultMoveNoDrop;
}
/**
* Returns cursor created from given icon.
*/
private static Cursor createCustomCursor(Image icon, String name) {
Toolkit t = Toolkit.getDefaultToolkit();
Dimension d = t.getBestCursorSize(16, 16);
Image i = icon;
if (d.width != icon.getWidth(null)) {
// need to resize the icon
Image empty = createBufferedImage(d.width, d.height);
i = Utilities.mergeImages(icon, empty, 0, 0);
}
return t.createCustomCursor(i, new Point(1,1), name);
}
/**
* Creates BufferedImage and Transparency.BITMASK
* Note: this method is copied from org.openide.util.IconManager. Should
* it be exposed in Utilities? I don't know (dstrupl).
*/
private static final java.awt.image.BufferedImage createBufferedImage(int width, int height) {
java.awt.image.ColorModel model = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration().getColorModel(java.awt.Transparency.BITMASK);
java.awt.image.BufferedImage buffImage = new java.awt.image.BufferedImage(model,
model.createCompatibleWritableRaster(width, height), model.isAlphaPremultiplied(), null);
return buffImage;
}
/** Utility method.
* @return true if given node supports given action,
* false otherwise.
*/
static boolean checkNodeForAction (Node node, int dragAction) {
if (node.canCut() &&
((dragAction == DnDConstants.ACTION_MOVE) ||
(dragAction == DnDConstants.ACTION_COPY_OR_MOVE)))
return true;
if (node.canCopy() &&
((dragAction == DnDConstants.ACTION_COPY) ||
(dragAction == DnDConstants.ACTION_COPY_OR_MOVE) ||
(dragAction == DnDConstants.ACTION_LINK) ||
(dragAction == DnDConstants.ACTION_REFERENCE)))
return true;
// hmmm, conditions not satisfied..
return false;
}
/** Gets right transferable of given nodes (according to given
* drag action) and also converts the transferable.<br>
* Can be called only with correct action constant.
* @return The transferable.
*/
static Transferable getNodeTransferable (Node[] nodes, int dragAction)
throws IOException {
Transferable[] tArray = new Transferable[nodes.length];
//System.out.println("Sel count: " + nodes.length); // NOI18N
for (int i = 0; i < nodes.length; i++) {
Clipboard c = getClipboard();
if (c instanceof ExClipboard) {
ExClipboard cb = (ExClipboard)c;
if (dragAction == DnDConstants.ACTION_MOVE) {
tArray[i] = cb.convert (nodes[i].clipboardCut());
//System.out.println("Clipboard CUT for node: "+nodes[0]);
} else {
tArray[i] = cb.convert (nodes[i].clipboardCopy());
//System.out.println("Clipboard COPY for node: "+nodes[0]);
}
} else {
// In case of standalone library we cannot do
// conversion here. Is this ok?
if (dragAction == DnDConstants.ACTION_MOVE) {
tArray[i] = nodes[i].clipboardCut();
//System.out.println("Clipboard CUT for node: "+nodes[0]);
} else {
tArray[i] = nodes[i].clipboardCopy();
//System.out.println("Clipboard COPY for node: "+nodes[0]);
}
}
}
if (tArray.length == 1)
// only one node, so return regular single transferable
return tArray[0];
// enclose the transferables into multi transferable
return new ExTransferable.Multi(tArray);
}
/** Returns transferable of given node
* @return The transferable.
*/
static Transferable getNodeTransferable (Node node, int dragAction)
throws IOException {
return getNodeTransferable(new Node[] { node }, dragAction);
}
/** Sets a runnable it will be executed after drop action is performed.
* @param run a runnable for execution */
static void setPostDropRun (Runnable run) {
postDropRun = run;
}
/* Invokes the stored runnable if it is there and than set to null.
*/
static private void invokePostDropRun () {
if (postDropRun!=null) {
SwingUtilities.invokeLater (postDropRun);
postDropRun = null;
}
}
/** Performs the drop. Performs paste on given paste type.
*/
static void performDrop (PasteType type) {
//System.out.println("performing drop...."+type); // NOI18N
try {
Transferable trans = type.paste();
/*Clipboard clipboard = T opManager.getDefault().getClipboard();
if (trans != null) {
ClipboardOwner owner = trans instanceof ClipboardOwner ?
(ClipboardOwner)trans
:
new StringSelection ("");
clipboard.setContents(trans, owner);
}*/
} catch (UserCancelException exc) {
// ignore - user just pressed cancel in some dialog....
} catch (java.io.IOException e) {
ErrorManager.getDefault ().notify(e);
}
}
/** Returns array of paste types for given transferable.
* If given transferable contains multiple transferables,
* multi paste type which encloses pate types of all contained
* transferables is returned.
* Returns empty array if given node did not accepted the transferable
* (or some sub-transferables in multi transferable)
*
* @param node given node to ask fro paste types
* @param trans transferable to discover
*/
static PasteType[] getPasteTypes (Node node, Transferable trans) {
// find out if given transferable is multi
boolean isMulti = false;
try {
isMulti = trans.isDataFlavorSupported(ExTransferable.multiFlavor);
} catch (Exception e) {
// patch to get the Netbeans start under Solaris
// [PENDINGworkaround]
}
if (!isMulti) {
// only single, so return paste types
PasteType [] pt = null;
try {
pt = node.getPasteTypes(trans);
} catch (NullPointerException npe) {
ErrorManager.getDefault ().notify (npe);
// there are not paste types
}
return pt;
} else {
// multi transferable, we must do extra work
try {
MultiTransferObject obj = (MultiTransferObject)
trans.getTransferData(ExTransferable.multiFlavor);
int count = obj.getCount();
Transferable[] t = new Transferable[count];
PasteType[] p = new PasteType[count];
PasteType[] curTypes = null;
// extract default paste types of transferables
for (int i = 0; i < count; i++) {
t[i] = obj.getTransferableAt(i);
curTypes = node.getPasteTypes(t[i]);
// return if not accepted
if (curTypes.length == 0)
return curTypes;
p[i] = curTypes[0];
}
// return new multi paste type
return new PasteType[] { new MultiPasteType(t, p) };
} catch (UnsupportedFlavorException e) {
// ignore and return empty array
}
catch (IOException e) {
// ignore and return empty array
}
}
return new PasteType[0];
}
/** Notifies user that the drop was not succesfull. */
static void dropNotSuccesfull () {
DialogDisplayer.getDefault().notify(
new NotifyDescriptor.Message(
NbBundle.getBundle(TreeViewDropSupport.class).
getString("MSG_NoPasteTypes"),
NotifyDescriptor.WARNING_MESSAGE)
);
}
/** If our clipboard is not found return the default system clipboard. */
private static Clipboard getClipboard() {
Clipboard c = (java.awt.datatransfer.Clipboard)
org.openide.util.Lookup.getDefault().lookup(java.awt.datatransfer.Clipboard.class);
if (c == null) {
c = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
}
return c;
}
/** Paste type used when in clipbopard is MultiTransferable */
static final class MultiPasteType extends PasteType {
// Attributes
/** Array of transferables */
Transferable[] t;
/** Array of paste types */
PasteType[] p;
// Operations
/** Constructs new MultiPasteType for the given
* transferables and paste types.*/
MultiPasteType(Transferable[] t, PasteType[] p) {
this.t = t;
this.p = p;
}
/** Performs the paste action.
* @return Transferable which should be inserted into the
* clipboard after paste action. It can be null, which means
* that clipboard content should be cleared.
*/
public Transferable paste() throws IOException {
int size = p.length;
Transferable[] arr = new Transferable[size];
// perform paste for all source transferables
for (int i = 0; i < size; i++) {
//System.out.println("Pasting #" + i); // NOI18N
arr[i] = p[i].paste();
}
return new ExTransferable.Multi(arr);
}
} // end of MultiPasteType
/** Utility method created by Enno Sandner. Is it needed?
* I don't know (dstrupl).
*/
static Node secureFindNode(Object o) {
try {
return Visualizer.findNode(o);
}
catch (ClassCastException e) {
e.printStackTrace();
return null;
}
}
/**
* Creates and populates popup as a result of
* dropping an item.
* @author Enno Sandner
*/
static JPopupMenu createDropFinishPopup(final TreeSet pasteTypes) {
JPopupMenu menu = new JPopupMenuPlus();
//System.arraycopy(pasteTypes, 0, pasteTypes_, 0, pasteTypes.length);
final JMenuItem[] items_ = new JMenuItem[pasteTypes.size ()];
ActionListener aListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem)e.getSource();
final Iterator it = pasteTypes.iterator ();
for (int i=0; it.hasNext(); i++) {
PasteType action = (PasteType)it.next();
if (items_[i].equals(source)) {
DragDropUtilities.performDrop(action);
invokePostDropRun ();
break;
}
}
}
};
Iterator it = pasteTypes.iterator ();
for (int i=0; it.hasNext(); i++) {
items_[i]=new JMenuItem(((PasteType)it.next()).getName ());
items_[i].addActionListener(aListener);
menu.add(items_[i]);
}
menu.addSeparator();
JMenuItem abortItem=new JMenuItem(NbBundle.getBundle(DragDropUtilities.class).getString("MSG_ABORT"));
menu.add(abortItem);
return menu;
}
}