DragNDropTool.java
/*
* @(#)DragNDropTool.java
*
* Project: JHotdraw - a GUI framework for technical drawings
* http://www.jhotdraw.org
* http://jhotdraw.sourceforge.net
* Copyright: © by the original author(s) and all contributors
* License: Lesser GNU Public License (LGPL)
* http://www.opensource.org/licenses/lgpl-license.html
*/
package CH.ifa.draw.contrib;
import CH.ifa.draw.standard.AbstractTool;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Vector;
import CH.ifa.draw.framework.*;
import java.awt.dnd.*;
import java.util.*;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.awt.datatransfer.*;
import java.io.Serializable;
import CH.ifa.draw.contrib.*;
import CH.ifa.draw.standard.*;
import java.awt.image.BufferedImage;
import java.io.*;
/**
* This is a tool which handles drag and drop between Components in
* JHotDraw and drags from JHotDraw. It also indirectly
* handles management of Drops from extra-JVM sources.
*
* There can be only 1 such tool in an application. A view can be registered
* with only a single DropSource as far as I know.
*
* @todo For intra JVM transfers we need to pass Point origin as well, and not
* assume it will be valid which currently will cause a null pointer exception.
* or worse, will be valid with some local value.
* The dropSource will prevent simultaneous drops.
*
* For a Container to be initialized to support Drag and Drop, it must first
* have a connection to a heavyweight component. Or more precisely it must have
* a peer. That means new Component() is not capable of being initiated until
* it has attachment to a top level component i.e. JFrame.add(comp); If you add
* a Component to a Container, that Container must be the child of some
* Container which is added in its heirachy to a topmost Component. I will
* refine this description with more appropriate terms as I think of new ways to
* express this.
*
* @author SourceForge(dnoyeb) aka C.L.Gilbert
* @version <$CURRENT_VERSION$>
*/
public class DragNDropTool extends AbstractTool implements DropTargetListener,
DragGestureListener,
DragSourceListener {
protected Tool fChild = null;
protected DragSource dragSource = null;
private ArrayList fDropTargets = null;
private ArrayList fDragGestureRecognizers=null;
/**
* origin must be made to go in the Transferable somehow.
*/
private Point origin = null;
public static DataFlavor VECTORFlavor = new DataFlavor( Vector.class , "Vector");
public static DataFlavor ASCIIFlavor = new DataFlavor("text/plain; charset=ascii", "ASCII text");
public DragNDropTool(DrawingEditor editor) {
super(editor);
// dragSource = new DragSource();
dragSource = DragSource.getDefaultDragSource();
// System.out.println("Visible Image support = " + dragSource.isDragImageSupported());
fDropTargets = new ArrayList();
fDragGestureRecognizers = new ArrayList();
}
/**
* Sent when a new view is created
*/
public void viewCreated(DrawingView view) {
super.viewCreated(view);
if(Component.class.isInstance( view )/* && DNDInterface.class.isInstance( views[x] )*/) {
Component c = (Component) view;
try {
DropTarget dt = new DropTarget(c ,DnDConstants.ACTION_COPY_OR_MOVE ,this);
fDropTargets.add( dt );
//System.out.println("View " + view.getID() + " Initialized to DND.");
}
catch (java.lang.NullPointerException npe) {
System.err.println("View Failed to initialize to DND.");
System.err.println("Container likely did not have peer before the DropTarget was added");
System.err.println(npe);
}
}
if(isActive()) {
createDragGestureRecognizer(view, this);
}
}
/**
* Send when an existing view is about to be destroyed.
*/
public void viewDestroying(DrawingView view) {
if(Component.class.isInstance( view )/* && DNDInterface.class.isInstance( views[x] )*/) {
Component c = (Component) view;
DropTarget dt = c.getDropTarget();
if(dt != null ) {
dt.setComponent( null );
dt.removeDropTargetListener( this );
}
destroyDragGestreRecognizer(view,this);
}
super.viewDestroying(view);
}
/**
* Turn on drag by adding a DragGestureRegognizer to all Views which are
* based on Components.
*/
public void activate() {
if(isActive()) {
return;
}
super.activate();
DrawingView[] dv = editor().views();
for(int x=0;x < dv.length;x++) {
createDragGestureRecognizer(dv[x],this);
}
}
/**
* Called when the tool is deactivated. This will deinitialize all the
* windows which were set up to use drag and drop. This must be done or
* the windows will continue to communicate their status to this tool which
* could be taxing on the functionality of other tools.
*/
public void deactivate() {
if(!isActive()) {
return;
}
DrawingView [] dv = editor().views();
for(int x=0;x < dv.length;x++) {
destroyDragGestreRecognizer(dv[x],this);
}
super.deactivate();
}
/**
* Sets the type of cursor based on what is under the coordinates in the
* active view.
*/
public static void setCursor(int x, int y, DrawingView view) {
Handle handle = view.findHandle(x, y);
Figure figure = view.drawing().findFigure(x, y);
if (handle != null) {
if( LocatorHandle.class.isInstance( handle ) ) {
LocatorHandle lh = (LocatorHandle) handle;
Locator loc = lh.getLocator();
if(RelativeLocator.class.isInstance( loc )) {
RelativeLocator rl = (RelativeLocator) loc;
if( rl.equals( RelativeLocator.north() ) ) {
view.setCursor( new Cursor( Cursor.N_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.northEast() ) ){
view.setCursor( new Cursor( Cursor.NE_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.east() ) ){
view.setCursor( new Cursor( Cursor.E_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.southEast() ) ){
view.setCursor( new Cursor( Cursor.SE_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.south() ) ){
view.setCursor( new Cursor( Cursor.S_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.southWest() ) ){
view.setCursor( new Cursor( Cursor.SW_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.west() ) ){
view.setCursor( new Cursor( Cursor.W_RESIZE_CURSOR) );
}
else if(rl.equals( RelativeLocator.northWest() ) ){
view.setCursor( new Cursor( Cursor.NW_RESIZE_CURSOR) );
}
}
}
}
else if (figure != null) {
view.setCursor(new Cursor( Cursor.MOVE_CURSOR ) );
}
else {
view.setCursor(Cursor.getDefaultCursor());
}
}
/**
* Handles mouse moves (if the mouse button is up).
* Switches the cursors depending on whats under them.
*/
public void mouseMove(MouseEvent evt, int x, int y) {
setCursor(evt.getX(),evt.getY(),view());
}
/**
* Handles mouse up events. The events are forwarded to the
* current tracker.
*/
public void mouseUp(MouseEvent e, int x, int y) {
view().unfreezeView(); //this should probably go after child mouse up
if (fChild != null) // JDK1.1 doesn't guarantee mouseDown, mouseDrag, mouseUp
fChild.mouseUp(e, x, y);
fChild = null;
}
/**
* Handles mouse down events and starts the corresponding tracker.
*/
public void mouseDown(MouseEvent e, int x, int y)
{
// on MS-Windows NT: AWT generates additional mouse down events
// when the left button is down && right button is clicked.
// To avoid dead locks we ignore such events
if (fChild != null) {
return;
}
view().freezeView();
Handle handle = view().findHandle(e.getX(), e.getY());
if (handle != null) {
fChild = createHandleTracker(handle);
}
else {
Figure figure = drawing().findFigure(e.getX(), e.getY());
if (figure != null) {
//fChild = createDragTracker(view(), figure);
if (e.isShiftDown()) {
view().toggleSelection(figure);
} else if( !view().selection().contains(figure)) {
view().clearSelection();
view().addToSelection(figure);
}
}
else {
if (!e.isShiftDown()) {
view().clearSelection();
}
fChild = createAreaTracker();
}
}
if(fChild != null)
fChild.mouseDown(e, x, y);
}
/**
* Handles mouse drag events. The events are forwarded to the
* current tracker.
*/
public void mouseDrag(MouseEvent e, int x, int y) {
if (fChild != null) // JDK1.1 doesn't guarantee mouseDown, mouseDrag, mouseUp
fChild.mouseDrag(e, x, y);
}
/**
* Factory method to create an area tracker. It is used to select an
* area.
*/
protected Tool createAreaTracker() {
return new SelectAreaTracker(editor());
}
/**
* Factory method to create a Drag tracker. It is used to drag a figure.
*/
/* protected Tool createDragTracker(DrawingView view, Figure f) {
return new DragTracker(view, f);
}*/
/**
* Factory method to create a Handle tracker. It is used to track a handle.
*/
protected Tool createHandleTracker(Handle handle) {
return new HandleTracker(editor(), handle);
}
/**
* Used to create the gesture recognizer which in effect turns on draggability.
*/
private void createDragGestureRecognizer(DrawingView dv, DragGestureListener dgl) {
if(Component.class.isInstance( dv ) ) {
Component c = (Component) dv;
DragGestureRecognizer dgr =
dragSource.createDefaultDragGestureRecognizer(
c,
DnDConstants.ACTION_COPY_OR_MOVE,
this);
fDragGestureRecognizers.add( dgr );
}
}
/**
* Used to destroy the gesture listener which ineffect turns off dragability.
*/
private void destroyDragGestreRecognizer(DrawingView dv, DragGestureListener dgl) {
Iterator i = fDragGestureRecognizers.iterator();
while(i.hasNext()) {
DragGestureRecognizer dgr = (DragGestureRecognizer)i.next();
if ( dgr.getComponent() == dv ) {
dgr.removeDragGestureListener( this );
dgr.setComponent( null );
i.remove();
break;
}
}
}
//DragGestureListener Interface
/**
* This function is called when the drag action is detected. If it agrees
* with the attempt to drag it calls startDrag(), if not it does nothing.
*/
public void dragGestureRecognized(DragGestureEvent dge) {
Component c = dge.getComponent();
Vector selectedElements;
java.awt.image.BufferedImage bi;
// int sx=50;
// int sy=50;
if (fChild != null)
return;
if(DrawingView.class.isInstance( c ) ) {
boolean found = false;
DrawingView dv = (DrawingView) c;
selectedElements = dv.selectionZOrdered();
Iterator itr = selectedElements.iterator();
if( itr.hasNext() == false )
return;
Point p = dge.getDragOrigin();
while ( itr.hasNext() ) {
/* Figure figgy = (Figure) itr.next();
if( figgy.containsPoint( p.x, p.y ) ) {*/
if( ((Figure) itr.next()).containsPoint( p.x, p.y ) ) {
/* Rectangle r = figgy.displayBox();
sx = r.width;
sy = r.height;*/
found = true;
break;
}
}
if( found == true ) {
fChild = null;
origin = p;
MyTransferable trans = new MyTransferable( selectedElements );
/* SAVE FOR FUTURE DRAG IMAGE SUPPORT */
/* drag image support that I need to test on some supporting platform.
windows is not supporting this on NT so far. Ill test 98 and 2K next
boolean support = dragSource.isDragImageSupported();
bi = new BufferedImage(sx,sy,BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
Iterator itr2 = selectedElements.iterator();
while ( itr2.hasNext() ) {
Figure fig = (Figure) itr2.next();
fig = (Figure)fig.clone();
Rectangle rold = fig.displayBox();
fig.moveBy(-rold.x,-rold.y);
fig.draw(g);
}
g.setBackground(Color.red);
dge.getDragSource().startDrag(
dge,
DragSource.DefaultMoveDrop,
bi,
new Point(0,0),
trans,
this);
*/
dge.getDragSource().startDrag(
dge,
DragSource.DefaultMoveDrop,
trans,
this);
}
}
}
//End DragGestureListener Interface
//Begin DropTargetListener Interface
/**
* Called when a drag operation has encountered the DropTarget.
*/
public void dragEnter(DropTargetDragEvent dtde) {
// System.out.println("DropTargetDragEvent-dragEnter");
supportDropTargetDragEvent(dtde);
}
/**
* The drag operation has departed the DropTarget without dropping.
*/
public void dragExit(DropTargetEvent dte) {
}
/**
* Called when a drag operation is ongoing on the DropTarget.
*/
public void dragOver(DropTargetDragEvent dtde) {
supportDropTargetDragEvent(dtde);
}
/**
* The drag operation has terminated with a drop on this DropTarget.
*/
public void drop(DropTargetDropEvent dtde) {
Transferable trans;
Vector figures;
DrawingView lView;
// System.out.println("DropTargetDropEvent-drop");
if( dtde.isDataFlavorSupported( VECTORFlavor ) == true ) {
if( (dtde.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {
if(dtde.isLocalTransfer() == false ) {
System.err.println("Intra-JVM Transfers not implemented for figures yet.");
return;
}
dtde.acceptDrop( dtde.getDropAction() );
figures = (Vector) ProcessReceivedData(VECTORFlavor, dtde.getTransferable());
if( figures != null ) {
lView = (DrawingView) dtde.getDropTargetContext().getComponent();
lView.clearSelection();
Iterator itr = figures.iterator();
Point newP = dtde.getLocation();
int dx = newP.x - origin.x; /* distance the mouse has moved */
int dy = newP.y - origin.y; /* distance the mouse has moved */
while ( itr.hasNext() ) {
Figure f = (Figure) itr.next();
f.moveBy( dx, dy );
lView.add( f );
if(dtde.getDropAction() == DnDConstants.ACTION_MOVE )
lView.addToSelection( f );
}
lView.checkDamage();
dtde.getDropTargetContext().dropComplete(true);
}
else
dtde.getDropTargetContext().dropComplete(false);
}
else
dtde.rejectDrop();
}
else if(dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
System.out.println("String flavor dropped.");
dtde.acceptDrop(dtde.getDropAction());
Object o = ProcessReceivedData(DataFlavor.stringFlavor, dtde.getTransferable());
if( o != null ) {
System.out.println("Received string flavored data.");
dtde.getDropTargetContext().dropComplete(true);
}
else
dtde.getDropTargetContext().dropComplete(false);
}
else if (dtde.isDataFlavorSupported(ASCIIFlavor) == true) {
System.out.println("ASCII Flavor dropped.");
dtde.acceptDrop(DnDConstants.ACTION_COPY);
Object o = ProcessReceivedData(ASCIIFlavor, dtde.getTransferable());
if( o!= null ) {
System.out.println("Received ASCII Flavored data.");
dtde.getDropTargetContext().dropComplete(true);
System.out.println(o);
}
else
dtde.getDropTargetContext().dropComplete(false);
}
else if(dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)){
System.out.println("Java File List Flavor dropped.");
int acts = dtde.getDropAction();
dtde.acceptDrop(DnDConstants.ACTION_COPY);
java.io.File [] fList = (java.io.File[]) ProcessReceivedData(DataFlavor.javaFileListFlavor, dtde.getTransferable());
if(fList != null) {
System.out.println("Got list of files.");
for(int x=0; x< fList.length; x++ ) {
System.out.println(fList[x].getAbsolutePath());
}
dtde.getDropTargetContext().dropComplete(true);
}
else
dtde.getDropTargetContext().dropComplete(false);
}
}
/**
* Called if the user has modified the current drop gesture.
*/
public void dropActionChanged(DropTargetDragEvent dtde) {
// System.out.println("DropTargetDragEvent-dropActionChanged");
supportDropTargetDragEvent(dtde);
}
//End DropTargetListener Interface
//Begin DragSourceListener Interface
/**
* This method is invoked to signify that the Drag and Drop operation is complete.
*/
public void dragDropEnd(DragSourceDropEvent dsde) {
DrawingView view = (DrawingView) dsde.getDragSourceContext().getComponent();
Vector figures;
// System.out.println("DragSourceDropEvent-dragDropEnd");
if( dsde.getDropSuccess() == true ) {
if( dsde.getDropAction() == DnDConstants.ACTION_MOVE ) {
// System.out.println("DragSourceDropEvent-ACTION_MOVE");
figures = (Vector) ProcessReceivedData(VECTORFlavor, dsde.getDragSourceContext().getTransferable());
if( figures != null ) {
Iterator itr = figures.iterator();
while ( itr.hasNext() )
view.remove( (Figure) itr.next() );
view.clearSelection();
view.checkDamage();
}
}
else if( dsde.getDropAction() == DnDConstants.ACTION_COPY ) {
// System.out.println("DragSourceDropEvent-ACTION_COPY");
}
}
}
/**
* Called as the hotspot enters a platform dependent drop site.
*/
public void dragEnter(DragSourceDragEvent dsde) {
}
/**
* Called as the hotspot exits a platform dependent drop site.
*/
public void dragExit(DragSourceEvent dse) {
}
/**
* Called as the hotspot moves over a platform dependent drop site.
*/
public void dragOver(DragSourceDragEvent dsde) {
}
/**
* Called when the user has modified the drop gesture.
*/
public void dropActionChanged(DragSourceDragEvent dsde) {
}
//End DragSourceListener Interface
/**
* Tests wether the Drag event is of a type that we support handling
*/
protected static void supportDropTargetDragEvent(DropTargetDragEvent dtde) {
if( dtde.isDataFlavorSupported( VECTORFlavor ) == true ) {
if( dtde.getDropAction() == DnDConstants.ACTION_COPY ) {
dtde.acceptDrag( DnDConstants.ACTION_COPY );
}
else if( dtde.getDropAction() == DnDConstants.ACTION_MOVE ) {
dtde.acceptDrag( DnDConstants.ACTION_MOVE );
}
}
else if( dtde.isDataFlavorSupported( ASCIIFlavor ) == true ) {
dtde.acceptDrag(dtde.getDropAction());
}
else if( dtde.isDataFlavorSupported(DataFlavor.stringFlavor) == true){
dtde.acceptDrag(dtde.getDropAction());
}
else if( dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) == true) {
dtde.acceptDrag(dtde.getDropAction());
}
else
dtde.rejectDrag();
}
/**
* Will return null if the data can not be processed
*/
protected static Object ProcessReceivedData(DataFlavor flavor, Transferable transferable) {
if( transferable == null ) {
return null;
}
try {
/* if(flavor.equals(DataFlavor.plainTextFlavor)) {
//As of 1.3 use stringFlavor.getReaderForText
java.io.Reader reader = DataFlavor.stringFlavor.getReaderForText( transferable );
return reader;
} else*/ if(flavor.equals(DataFlavor.stringFlavor)) {
String str = (String) transferable.getTransferData(DataFlavor.stringFlavor);
return str;
}
else if(flavor.equals(DataFlavor.javaFileListFlavor)) {
java.util.List aList = (java.util.List)transferable.getTransferData(DataFlavor.javaFileListFlavor);
java.io.File fList [] = new java.io.File[aList.size()];
aList.toArray(fList);
return aList;
} else if(flavor.equals(ASCIIFlavor)) {
String txt = null;
/* this may be too much work for locally received data */
InputStream is = (InputStream)transferable.getTransferData(ASCIIFlavor);
int length = is.available();
byte[] bytes = new byte[length];
int n = is.read(bytes);
if(n >0 ) {
/* seems to be a 0 tacked on the end of Windows strings. I
* havent checked other platforms. This does not happen
* with windows socket io. strange?
*/
//for (int i = 0; i < length; i++) {
// if (bytes[i] == 0) {
// length = i;
// break;
// }
//}
txt = new String(bytes, 0, n);
}
return txt;
} else if(flavor.equals(VECTORFlavor)) {
/**
* I don't think this will work for remote transfer?
* Maybe it will pass the vector, but the contents?
*/
Vector v = (Vector) transferable.getTransferData(VECTORFlavor);
return v;
}
else
return null;
}
catch(java.io.IOException ioe) {
System.err.println(ioe);
return null;
}
catch(UnsupportedFlavorException ufe) {
System.err.println(ufe);
return null;
}
catch(ClassCastException cce) {
System.err.println(cce);
return null;
}
}
protected Object ProcessRemotelyReceivedData(DataFlavor flavor, Transferable transferable) {
return null;
}
/**
* These transferable objects are used to package your data when you want
* to initiate a transfer. They are not used when you only want to receive
* data. Formating the data is the responsibility of the sender primarily.
* Untested. Used for dragging ASCII text out of JHotDraw
*/
/* public class ASCIIText implements Transferable
{
String s = new String("This is ASCII text");
byte[] bytes;
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { ASCIIFlavor };
}
public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
return dataFlavor.equals(ASCIIFlavor);
}
public Object getTransferData(DataFlavor dataFlavor)
throws UnsupportedFlavorException, IOException {
if (!isDataFlavorSupported(dataFlavor))
throw new UnsupportedFlavorException(dataFlavor);
bytes = new byte[s.length() + 1];
for (int i = 0; i < s.length(); i++)
bytes = s.getBytes();
bytes[s.length()] = 0;
return new ByteArrayInputStream(bytes);
}
}*/
/**
* Class used to transfer the
*/
class MyTransferable implements Transferable , Serializable {
Object o;
public MyTransferable(Object o) {
this.o = o;
}
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor [] { VECTORFlavor };
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
if ( flavor.equals( VECTORFlavor ) == true )
return true;
return false;
}
public Object getTransferData(DataFlavor flavor) throws
UnsupportedFlavorException, IOException {
if( isDataFlavorSupported(flavor) == false)
throw new UnsupportedFlavorException( flavor );
return o;
}
}
}