/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.support.dnd;
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;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
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;
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;
}
}
}