/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI 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 Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.support.dnd;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.support.dnd.handlers.InterfaceToProjectDropHandler;
import com.eviware.soapui.support.dnd.handlers.MockResponseToTestCaseDropHandler;
import com.eviware.soapui.support.dnd.handlers.MockResponseToTestStepDropHandler;
import com.eviware.soapui.support.dnd.handlers.MockResponseToTestStepsDropHandler;
import com.eviware.soapui.support.dnd.handlers.MockServiceToProjectDropHandler;
import com.eviware.soapui.support.dnd.handlers.OperationToMockServiceDropHandler;
import com.eviware.soapui.support.dnd.handlers.RequestToMockOperationDropHandler;
import com.eviware.soapui.support.dnd.handlers.RequestToTestCaseDropHandler;
import com.eviware.soapui.support.dnd.handlers.RequestToTestStepDropHandler;
import com.eviware.soapui.support.dnd.handlers.RequestToTestStepsDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestCaseToProjectDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestCaseToTestCaseDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestCaseToTestSuiteDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestStepToTestCaseDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestStepToTestStepDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestStepToTestStepsDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestSuiteToProjectDropHandler;
import com.eviware.soapui.support.dnd.handlers.TestSuiteToTestSuiteDropHandler;
public class SoapUIDragAndDropHandler implements DragGestureListener, DragSourceListener
{
public static final int ON_RANGE = 3;
private final SoapUIDragAndDropable<ModelItem> dragAndDropable;
private BufferedImage _imgGhost; // The 'drag image'
private Point _ptOffset = new Point(); // Where, in the drag image, the mouse
private static List<ModelItemDropHandler<ModelItem>> handlers;
private Rectangle2D _raGhost = new Rectangle2D.Float();
private final int dropType;
private Point _ptLast = new Point();
static
{
handlers = new ArrayList<ModelItemDropHandler<ModelItem>>();
SoapUIDragAndDropHandler.addDropHandler( new TestStepToTestCaseDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestStepToTestStepsDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestStepToTestStepDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestSuiteToProjectDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new InterfaceToProjectDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestCaseToProjectDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestCaseToTestSuiteDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestCaseToTestCaseDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new RequestToTestCaseDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new RequestToTestStepsDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new RequestToTestStepDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new RequestToMockOperationDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new MockServiceToProjectDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new OperationToMockServiceDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new MockResponseToTestCaseDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new MockResponseToTestStepDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new MockResponseToTestStepsDropHandler() );
SoapUIDragAndDropHandler.addDropHandler( new TestSuiteToTestSuiteDropHandler() );
}
@SuppressWarnings( "unchecked" )
public SoapUIDragAndDropHandler( SoapUIDragAndDropable target, int dropType )
{
this.dragAndDropable = target;
this.dropType = dropType;
// Also, make this JTree a drag target
DropTarget dropTarget = new DropTarget( target.getComponent(), new SoapUIDropTargetListener() );
dropTarget.setDefaultActions( DnDConstants.ACTION_COPY_OR_MOVE );
}
@SuppressWarnings( "unchecked" )
public static void addDropHandler( ModelItemDropHandler dropHandler )
{
handlers.add( dropHandler );
}
public void dragGestureRecognized( DragGestureEvent e )
{
Point ptDragOrigin = e.getDragOrigin();
ModelItem modelItem = dragAndDropable.getModelItemForLocation( ptDragOrigin.x, ptDragOrigin.y );
if( modelItem == null )
return;
Rectangle raPath = dragAndDropable.getModelItemBounds( modelItem );
if( raPath == null )
return;
_ptOffset = new Point( ptDragOrigin.x - raPath.x, ptDragOrigin.y - raPath.y );
Component renderer = dragAndDropable.getRenderer( modelItem );
if( renderer != null )
{
renderer.setSize( ( int )raPath.getWidth(), ( int )raPath.getHeight() ); // <--
// Get a buffered image of the selection for dragging a ghost image
_imgGhost = new BufferedImage( ( int )raPath.getWidth(), ( int )raPath.getHeight(),
BufferedImage.TYPE_INT_ARGB_PRE );
Graphics2D g2 = _imgGhost.createGraphics();
// Ask the cell renderer to paint itself into the BufferedImage
g2.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC, 0.5f ) );
renderer.paint( g2 );
g2.setComposite( AlphaComposite.getInstance( AlphaComposite.DST_OVER, 0.5f ) );
int width = dragAndDropable.getComponent().getWidth();
g2.setPaint( new GradientPaint( 0, 0, SystemColor.controlShadow, width, 0, new Color( 255, 255, 255, 0 ) ) );
g2.fillRect( 0, 0, width, _imgGhost.getHeight() );
g2.dispose();
}
else
{
_imgGhost = null;
}
dragAndDropable.selectModelItem( modelItem ); // Select this path in the
// tree
// Wrap the path being transferred into a Transferable object
Transferable transferable = new ModelItemTransferable( modelItem );
// We pass our drag image just in case it IS supported by the platform
e.startDrag( null, _imgGhost, new Point( 5, 5 ), transferable, this );
}
public void dragDropEnd( DragSourceDropEvent dsde )
{
if( _raGhost != null )
dragAndDropable.getComponent().repaint( _raGhost.getBounds() );
_ptOffset = null;
SoapUI.getNavigator().getMainTree().setToolTipText( null );
}
public void dragEnter( DragSourceDragEvent dsde )
{
}
public void dragExit( DragSourceEvent dse )
{
}
public void dragOver( DragSourceDragEvent dsde )
{
}
public void dropActionChanged( DragSourceDragEvent dsde )
{
}
// DropTargetListener interface object...
class SoapUIDropTargetListener implements DropTargetListener
{
// Fields...
private ModelItem _pathLast = null;
private Rectangle2D _raCueLine = new Rectangle2D.Float();
private Color _colorCueLine;
private Timer _timerHover;
// private int _nLeftRight = 0; // Cumulative left/right mouse movement
private String dropInfo;
// Constructor...
public SoapUIDropTargetListener()
{
_colorCueLine = new Color( SystemColor.controlShadow.getRed(), SystemColor.controlShadow.getGreen(),
SystemColor.controlShadow.getBlue(), 128 );
// Set up a hover timer, so that a node will be automatically expanded
_timerHover = new Timer( 1000, new ActionListener()
{
public void actionPerformed( ActionEvent e )
{
if( _ptOffset != null )
dragAndDropable.toggleExpansion( _pathLast );
}
} );
_timerHover.setRepeats( false ); // Set timer to one-shot mode
}
// DropTargetListener interface
public void dragEnter( DropTargetDragEvent e )
{
int dt = getDropTypeAtPoint( e.getLocation() );
if( dt == DropType.NONE || !isDragAcceptable( e, dt ) )
e.rejectDrag();
else
e.acceptDrag( e.getDropAction() );
}
private int getDropTypeAtPoint( Point pt )
{
ModelItem modelItem = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
if( modelItem == null )
return DropType.NONE;
Rectangle raPath = dragAndDropable.getModelItemBounds( modelItem );
if( pt.y > ( raPath.y + ( raPath.getHeight() / 2 ) + ON_RANGE ) )
{
return DropType.AFTER;
}
else if( pt.y < ( raPath.y + ( raPath.getHeight() / 2 ) - ON_RANGE ) )
{
return DropType.BEFORE;
}
else
{
return DropType.ON;
}
}
public void dragExit( DropTargetEvent e )
{
if( !DragSource.isDragImageSupported() )
{
dragAndDropable.getComponent().repaint( _raGhost.getBounds() );
}
}
/**
* This is where the ghost image is drawn
*/
public void dragOver( DropTargetDragEvent e )
{
// Even if the mouse is not moving, this method is still invoked 10
// times per second
Point pt = e.getLocation();
if( pt.equals( _ptLast ) )
return;
_ptLast = pt;
Graphics2D g2 = ( Graphics2D )dragAndDropable.getComponent().getGraphics();
// If a drag image is not supported by the platform, then draw my own
// drag image
if( !DragSource.isDragImageSupported() && _imgGhost != null && _ptOffset != null )
{
dragAndDropable.getComponent().paintImmediately( _raGhost.getBounds() ); // Rub
// out
// the
// last ghost
// image and cue line
// And remember where we are about to draw the new ghost image
_raGhost.setRect( pt.x - _ptOffset.x, pt.y - _ptOffset.y, _imgGhost.getWidth(), _imgGhost.getHeight() );
g2.drawImage( _imgGhost, AffineTransform.getTranslateInstance( _raGhost.getX(), _raGhost.getY() ), null );
}
else
// Just rub out the last cue line
dragAndDropable.getComponent().paintImmediately( _raCueLine.getBounds() );
ModelItem modelItem = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
if( modelItem == null )
{
e.rejectDrag();
return;
}
if( !( modelItem == _pathLast ) )
{
// movement trend
_pathLast = modelItem;
_timerHover.restart();
}
// In any case draw (over the ghost image if necessary) a cue line
// indicating where a drop will occur
Rectangle raPath = dragAndDropable.getModelItemBounds( modelItem );
int dt = dropType;
if( dropType == DropType.AFTER )
{
_raCueLine.setRect( 0, raPath.y + ( int )raPath.getHeight() - 2, dragAndDropable.getComponent().getWidth(),
2 );
}
else if( dropType == DropType.BEFORE )
{
_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), 2 );
}
else if( dropType == DropType.ON )
{
_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), raPath.getHeight() );
}
else
{
if( pt.y > ( raPath.y + ( raPath.getHeight() / 2 ) + ON_RANGE ) )
{
_raCueLine.setRect( 0, raPath.y + ( int )raPath.getHeight() - 2, dragAndDropable.getComponent()
.getWidth(), 2 );
dt = DropType.AFTER;
}
else if( pt.y < ( raPath.y + ( raPath.getHeight() / 2 ) - ON_RANGE ) )
{
_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), 2 );
dt = DropType.BEFORE;
}
else
{
_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), raPath.getHeight() );
dt = DropType.ON;
}
}
boolean dragAcceptable = isDragAcceptable( e, dt );
g2.setColor( _colorCueLine );
g2.fill( _raCueLine );
if( dragAcceptable )
{
dragAndDropable.setDragInfo( dropInfo );
}
else
{
dragAndDropable.setDragInfo( "" );
}
ToolTipManager.sharedInstance().mouseMoved(
new MouseEvent( dragAndDropable.getComponent(), 0, 0, 0, pt.x, pt.y + 10, // X-Y
// of
// the
// mouse
// for
// the
// tool
// tip
0, false ) );
// And include the cue line in the area to be rubbed out next time
_raGhost = _raGhost.createUnion( _raCueLine );
if( !dragAcceptable )
e.rejectDrag();
else
e.acceptDrag( e.getDropAction() );
}
public void dropActionChanged( DropTargetDragEvent e )
{
int dt = getDropTypeAtPoint( e.getLocation() );
if( dt == DropType.NONE || !isDragAcceptable( e, dt ) )
e.rejectDrag();
else
e.acceptDrag( e.getDropAction() );
}
public void drop( DropTargetDropEvent e )
{
int dt = getDropTypeAtPoint( e.getLocation() );
_timerHover.stop();
if( dt == DropType.NONE || !isDropAcceptable( e, dt ) )
{
e.rejectDrop();
return;
}
e.acceptDrop( e.getDropAction() );
Transferable transferable = e.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for( int i = 0; i < flavors.length; i++ )
{
DataFlavor flavor = flavors[i];
if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
{
try
{
Point pt = e.getLocation();
ModelItem pathTarget = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
ModelItem pathSource = ( ModelItem )transferable.getTransferData( flavor );
for( ModelItemDropHandler<ModelItem> handler : handlers )
{
if( handler.canDrop( pathSource, pathTarget, e.getDropAction(), dt ) )
{
// System.out.println( "Got drop handler for " +
// pathSource.getName() + " to " + pathTarget.getName()
// + "; " + handler.getClass().getSimpleName() );
handler.drop( pathSource, pathTarget, e.getDropAction(), dt );
break;
}
}
break; // No need to check remaining flavors
}
catch( Exception ioe )
{
System.out.println( ioe );
e.dropComplete( false );
return;
}
}
}
e.dropComplete( true );
}
// Helpers...
public boolean isDragAcceptable( DropTargetDragEvent e, int dt )
{
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if( ( e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE ) == 0 )
{
return false;
}
// Only accept this particular flavor
if( !e.isDataFlavorSupported( ModelItemTransferable.MODELITEM_DATAFLAVOR ) )
{
return false;
}
Transferable transferable = e.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for( int i = 0; i < flavors.length; i++ )
{
DataFlavor flavor = flavors[i];
if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
{
try
{
Point pt = e.getLocation();
ModelItem pathTarget = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
ModelItem pathSource = ( ModelItem )transferable.getTransferData( flavor );
for( ModelItemDropHandler<ModelItem> handler : handlers )
{
if( handler.canDrop( pathSource, pathTarget, e.getDropAction(), dt ) )
{
dropInfo = handler.getDropInfo( pathSource, pathTarget, e.getDropAction(), dt );
// System.out.println( "Got drag handler for " +
// pathSource.getName() + " to " + pathTarget.getName()
// + "; " + handler.getClass().getSimpleName() );
return true;
}
}
// System.out.println( "Missing drop handler for " +
// pathSource.getName() + " to " + pathTarget.getName() );
dropInfo = null;
}
catch( Exception ex )
{
SoapUI.logError( ex );
}
}
}
return false;
}
public boolean isDropAcceptable( DropTargetDropEvent e, int dt )
{
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if( ( e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE ) == 0 )
return false;
// Only accept this particular flavor
if( !e.isDataFlavorSupported( ModelItemTransferable.MODELITEM_DATAFLAVOR ) )
{
return false;
}
Transferable transferable = e.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for( int i = 0; i < flavors.length; i++ )
{
DataFlavor flavor = flavors[i];
if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
{
try
{
Point pt = e.getLocation();
ModelItem pathSource = ( ModelItem )transferable.getTransferData( flavor );
ModelItem pathTarget = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
for( ModelItemDropHandler<ModelItem> handler : handlers )
{
if( handler.canDrop( pathSource, pathTarget, e.getDropAction(), dt ) )
return true;
}
}
catch( Exception ex )
{
SoapUI.logError( ex );
}
}
}
return false;
}
}
}