View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  package com.eviware.soapui.support.dnd;
13  
14  import java.awt.AlphaComposite;
15  import java.awt.Color;
16  import java.awt.Component;
17  import java.awt.GradientPaint;
18  import java.awt.Graphics2D;
19  import java.awt.Point;
20  import java.awt.Rectangle;
21  import java.awt.SystemColor;
22  import java.awt.datatransfer.DataFlavor;
23  import java.awt.datatransfer.Transferable;
24  import java.awt.dnd.DnDConstants;
25  import java.awt.dnd.DragGestureEvent;
26  import java.awt.dnd.DragGestureListener;
27  import java.awt.dnd.DragSource;
28  import java.awt.dnd.DragSourceDragEvent;
29  import java.awt.dnd.DragSourceDropEvent;
30  import java.awt.dnd.DragSourceEvent;
31  import java.awt.dnd.DragSourceListener;
32  import java.awt.dnd.DropTarget;
33  import java.awt.dnd.DropTargetDragEvent;
34  import java.awt.dnd.DropTargetDropEvent;
35  import java.awt.dnd.DropTargetEvent;
36  import java.awt.dnd.DropTargetListener;
37  import java.awt.event.ActionEvent;
38  import java.awt.event.ActionListener;
39  import java.awt.event.MouseEvent;
40  import java.awt.geom.AffineTransform;
41  import java.awt.geom.Rectangle2D;
42  import java.awt.image.BufferedImage;
43  import java.util.ArrayList;
44  import java.util.List;
45  
46  import javax.swing.Timer;
47  import javax.swing.ToolTipManager;
48  
49  import com.eviware.soapui.SoapUI;
50  import com.eviware.soapui.model.ModelItem;
51  import com.eviware.soapui.support.dnd.handlers.InterfaceToProjectDropHandler;
52  import com.eviware.soapui.support.dnd.handlers.MockResponseToTestCaseDropHandler;
53  import com.eviware.soapui.support.dnd.handlers.MockResponseToTestStepDropHandler;
54  import com.eviware.soapui.support.dnd.handlers.MockResponseToTestStepsDropHandler;
55  import com.eviware.soapui.support.dnd.handlers.MockServiceToProjectDropHandler;
56  import com.eviware.soapui.support.dnd.handlers.OperationToMockServiceDropHandler;
57  import com.eviware.soapui.support.dnd.handlers.RequestToMockOperationDropHandler;
58  import com.eviware.soapui.support.dnd.handlers.RequestToTestCaseDropHandler;
59  import com.eviware.soapui.support.dnd.handlers.RequestToTestStepDropHandler;
60  import com.eviware.soapui.support.dnd.handlers.RequestToTestStepsDropHandler;
61  import com.eviware.soapui.support.dnd.handlers.TestCaseToProjectDropHandler;
62  import com.eviware.soapui.support.dnd.handlers.TestCaseToTestCaseDropHandler;
63  import com.eviware.soapui.support.dnd.handlers.TestCaseToTestSuiteDropHandler;
64  import com.eviware.soapui.support.dnd.handlers.TestStepToTestCaseDropHandler;
65  import com.eviware.soapui.support.dnd.handlers.TestStepToTestStepDropHandler;
66  import com.eviware.soapui.support.dnd.handlers.TestStepToTestStepsDropHandler;
67  import com.eviware.soapui.support.dnd.handlers.TestSuiteToProjectDropHandler;
68  import com.eviware.soapui.support.dnd.handlers.TestSuiteToTestSuiteDropHandler;
69  
70  public class SoapUIDragAndDropHandler implements DragGestureListener, DragSourceListener
71  {
72  	public static final int ON_RANGE = 3;
73  	private final SoapUIDragAndDropable<ModelItem> dragAndDropable;
74  	private BufferedImage _imgGhost; // The 'drag image'
75  	private Point _ptOffset = new Point(); // Where, in the drag image, the mouse
76  	private static List<ModelItemDropHandler<ModelItem>> handlers;
77  	private Rectangle2D _raGhost = new Rectangle2D.Float();
78  	private final int dropType;
79  	private Point _ptLast = new Point();
80  
81  	static
82  	{
83  		handlers = new ArrayList<ModelItemDropHandler<ModelItem>>();
84  		SoapUIDragAndDropHandler.addDropHandler( new TestStepToTestCaseDropHandler() );
85  		SoapUIDragAndDropHandler.addDropHandler( new TestStepToTestStepsDropHandler() );
86  		SoapUIDragAndDropHandler.addDropHandler( new TestStepToTestStepDropHandler() );
87  		SoapUIDragAndDropHandler.addDropHandler( new TestSuiteToProjectDropHandler() );
88  		SoapUIDragAndDropHandler.addDropHandler( new InterfaceToProjectDropHandler() );
89  		SoapUIDragAndDropHandler.addDropHandler( new TestCaseToProjectDropHandler() );
90  		SoapUIDragAndDropHandler.addDropHandler( new TestCaseToTestSuiteDropHandler() );
91  		SoapUIDragAndDropHandler.addDropHandler( new TestCaseToTestCaseDropHandler() );
92  		SoapUIDragAndDropHandler.addDropHandler( new RequestToTestCaseDropHandler() );
93  		SoapUIDragAndDropHandler.addDropHandler( new RequestToTestStepsDropHandler() );
94  		SoapUIDragAndDropHandler.addDropHandler( new RequestToTestStepDropHandler() );
95  		SoapUIDragAndDropHandler.addDropHandler( new RequestToMockOperationDropHandler() );
96  		SoapUIDragAndDropHandler.addDropHandler( new MockServiceToProjectDropHandler() );
97  		SoapUIDragAndDropHandler.addDropHandler( new OperationToMockServiceDropHandler() );
98  		SoapUIDragAndDropHandler.addDropHandler( new MockResponseToTestCaseDropHandler() );
99  		SoapUIDragAndDropHandler.addDropHandler( new MockResponseToTestStepDropHandler() );
100 		SoapUIDragAndDropHandler.addDropHandler( new MockResponseToTestStepsDropHandler() );
101 		SoapUIDragAndDropHandler.addDropHandler( new TestSuiteToTestSuiteDropHandler() );
102 	}
103 
104 	@SuppressWarnings( "unchecked" )
105 	public SoapUIDragAndDropHandler( SoapUIDragAndDropable target, int dropType )
106 	{
107 		this.dragAndDropable = target;
108 		this.dropType = dropType;
109 
110 		// Also, make this JTree a drag target
111 		DropTarget dropTarget = new DropTarget( target.getComponent(), new SoapUIDropTargetListener() );
112 		dropTarget.setDefaultActions( DnDConstants.ACTION_COPY_OR_MOVE );
113 	}
114 
115 	@SuppressWarnings( "unchecked" )
116 	public static void addDropHandler( ModelItemDropHandler dropHandler )
117 	{
118 		handlers.add( dropHandler );
119 	}
120 
121 	public void dragGestureRecognized( DragGestureEvent e )
122 	{
123 		Point ptDragOrigin = e.getDragOrigin();
124 		ModelItem modelItem = dragAndDropable.getModelItemForLocation( ptDragOrigin.x, ptDragOrigin.y );
125 		if( modelItem == null )
126 			return;
127 
128 		Rectangle raPath = dragAndDropable.getModelItemBounds( modelItem );
129 		if( raPath == null )
130 			return;
131 
132 		_ptOffset = new Point( ptDragOrigin.x - raPath.x, ptDragOrigin.y - raPath.y );
133 
134 		Component renderer = dragAndDropable.getRenderer( modelItem );
135 		if( renderer != null )
136 		{
137 			renderer.setSize( ( int )raPath.getWidth(), ( int )raPath.getHeight() ); // <--
138 
139 			// Get a buffered image of the selection for dragging a ghost image
140 			_imgGhost = new BufferedImage( ( int )raPath.getWidth(), ( int )raPath.getHeight(),
141 					BufferedImage.TYPE_INT_ARGB_PRE );
142 			Graphics2D g2 = _imgGhost.createGraphics();
143 
144 			// Ask the cell renderer to paint itself into the BufferedImage
145 			g2.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC, 0.5f ) );
146 			renderer.paint( g2 );
147 
148 			g2.setComposite( AlphaComposite.getInstance( AlphaComposite.DST_OVER, 0.5f ) );
149 
150 			int width = dragAndDropable.getComponent().getWidth();
151 			g2.setPaint( new GradientPaint( 0, 0, SystemColor.controlShadow, width, 0, new Color( 255, 255, 255, 0 ) ) );
152 			g2.fillRect( 0, 0, width, _imgGhost.getHeight() );
153 			g2.dispose();
154 		}
155 		else
156 		{
157 			_imgGhost = null;
158 		}
159 
160 		dragAndDropable.selectModelItem( modelItem ); // Select this path in the
161 																		// tree
162 
163 		// Wrap the path being transferred into a Transferable object
164 		Transferable transferable = new ModelItemTransferable( modelItem );
165 
166 		// We pass our drag image just in case it IS supported by the platform
167 		e.startDrag( null, _imgGhost, new Point( 5, 5 ), transferable, this );
168 	}
169 
170 	public void dragDropEnd( DragSourceDropEvent dsde )
171 	{
172 		if( _raGhost != null )
173 			dragAndDropable.getComponent().repaint( _raGhost.getBounds() );
174 
175 		_ptOffset = null;
176 		SoapUI.getNavigator().getMainTree().setToolTipText( null );
177 	}
178 
179 	public void dragEnter( DragSourceDragEvent dsde )
180 	{
181 
182 	}
183 
184 	public void dragExit( DragSourceEvent dse )
185 	{
186 
187 	}
188 
189 	public void dragOver( DragSourceDragEvent dsde )
190 	{
191 	}
192 
193 	public void dropActionChanged( DragSourceDragEvent dsde )
194 	{
195 	}
196 
197 	// DropTargetListener interface object...
198 	class SoapUIDropTargetListener implements DropTargetListener
199 	{
200 		// Fields...
201 		private ModelItem _pathLast = null;
202 		private Rectangle2D _raCueLine = new Rectangle2D.Float();
203 		private Color _colorCueLine;
204 		private Timer _timerHover;
205 		// private int _nLeftRight = 0; // Cumulative left/right mouse movement
206 		private String dropInfo;
207 
208 		// Constructor...
209 		public SoapUIDropTargetListener()
210 		{
211 			_colorCueLine = new Color( SystemColor.controlShadow.getRed(), SystemColor.controlShadow.getGreen(),
212 					SystemColor.controlShadow.getBlue(), 128 );
213 
214 			// Set up a hover timer, so that a node will be automatically expanded
215 			_timerHover = new Timer( 1000, new ActionListener()
216 			{
217 				public void actionPerformed( ActionEvent e )
218 				{
219 					if( _ptOffset != null )
220 						dragAndDropable.toggleExpansion( _pathLast );
221 				}
222 			} );
223 			_timerHover.setRepeats( false ); // Set timer to one-shot mode
224 		}
225 
226 		// DropTargetListener interface
227 		public void dragEnter( DropTargetDragEvent e )
228 		{
229 			int dt = getDropTypeAtPoint( e.getLocation() );
230 
231 			if( dt == DropType.NONE || !isDragAcceptable( e, dt ) )
232 				e.rejectDrag();
233 			else
234 				e.acceptDrag( e.getDropAction() );
235 		}
236 
237 		private int getDropTypeAtPoint( Point pt )
238 		{
239 			ModelItem modelItem = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
240 			if( modelItem == null )
241 				return DropType.NONE;
242 
243 			Rectangle raPath = dragAndDropable.getModelItemBounds( modelItem );
244 
245 			if( pt.y > ( raPath.y + ( raPath.getHeight() / 2 ) + ON_RANGE ) )
246 			{
247 				return DropType.AFTER;
248 			}
249 			else if( pt.y < ( raPath.y + ( raPath.getHeight() / 2 ) - ON_RANGE ) )
250 			{
251 				return DropType.BEFORE;
252 			}
253 			else
254 			{
255 				return DropType.ON;
256 			}
257 		}
258 
259 		public void dragExit( DropTargetEvent e )
260 		{
261 			if( !DragSource.isDragImageSupported() )
262 			{
263 				dragAndDropable.getComponent().repaint( _raGhost.getBounds() );
264 			}
265 		}
266 
267 		/***
268 		 * This is where the ghost image is drawn
269 		 */
270 		public void dragOver( DropTargetDragEvent e )
271 		{
272 			// Even if the mouse is not moving, this method is still invoked 10
273 			// times per second
274 			Point pt = e.getLocation();
275 			if( pt.equals( _ptLast ) )
276 				return;
277 
278 			_ptLast = pt;
279 
280 			Graphics2D g2 = ( Graphics2D )dragAndDropable.getComponent().getGraphics();
281 
282 			// If a drag image is not supported by the platform, then draw my own
283 			// drag image
284 			if( !DragSource.isDragImageSupported() && _imgGhost != null && _ptOffset != null )
285 			{
286 				dragAndDropable.getComponent().paintImmediately( _raGhost.getBounds() ); // Rub
287 																													// out
288 																													// the
289 				// last ghost
290 				// image and cue line
291 				// And remember where we are about to draw the new ghost image
292 				_raGhost.setRect( pt.x - _ptOffset.x, pt.y - _ptOffset.y, _imgGhost.getWidth(), _imgGhost.getHeight() );
293 				g2.drawImage( _imgGhost, AffineTransform.getTranslateInstance( _raGhost.getX(), _raGhost.getY() ), null );
294 			}
295 			else
296 				// Just rub out the last cue line
297 				dragAndDropable.getComponent().paintImmediately( _raCueLine.getBounds() );
298 
299 			ModelItem modelItem = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
300 			if( modelItem == null )
301 			{
302 				e.rejectDrag();
303 				return;
304 			}
305 
306 			if( !( modelItem == _pathLast ) )
307 			{
308 				// movement trend
309 				_pathLast = modelItem;
310 				_timerHover.restart();
311 			}
312 
313 			// In any case draw (over the ghost image if necessary) a cue line
314 			// indicating where a drop will occur
315 			Rectangle raPath = dragAndDropable.getModelItemBounds( modelItem );
316 
317 			int dt = dropType;
318 
319 			if( dropType == DropType.AFTER )
320 			{
321 				_raCueLine.setRect( 0, raPath.y + ( int )raPath.getHeight() - 2, dragAndDropable.getComponent().getWidth(),
322 						2 );
323 			}
324 			else if( dropType == DropType.BEFORE )
325 			{
326 				_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), 2 );
327 			}
328 			else if( dropType == DropType.ON )
329 			{
330 				_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), raPath.getHeight() );
331 			}
332 			else
333 			{
334 				if( pt.y > ( raPath.y + ( raPath.getHeight() / 2 ) + ON_RANGE ) )
335 				{
336 					_raCueLine.setRect( 0, raPath.y + ( int )raPath.getHeight() - 2, dragAndDropable.getComponent()
337 							.getWidth(), 2 );
338 					dt = DropType.AFTER;
339 				}
340 				else if( pt.y < ( raPath.y + ( raPath.getHeight() / 2 ) - ON_RANGE ) )
341 				{
342 					_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), 2 );
343 					dt = DropType.BEFORE;
344 				}
345 				else
346 				{
347 					_raCueLine.setRect( 0, raPath.y, dragAndDropable.getComponent().getWidth(), raPath.getHeight() );
348 					dt = DropType.ON;
349 				}
350 			}
351 
352 			boolean dragAcceptable = isDragAcceptable( e, dt );
353 			g2.setColor( _colorCueLine );
354 			g2.fill( _raCueLine );
355 
356 			if( dragAcceptable )
357 			{
358 				dragAndDropable.setDragInfo( dropInfo );
359 			}
360 			else
361 			{
362 				dragAndDropable.setDragInfo( "" );
363 			}
364 
365 			ToolTipManager.sharedInstance().mouseMoved(
366 					new MouseEvent( dragAndDropable.getComponent(), 0, 0, 0, pt.x, pt.y + 10, // X-Y
367 																														// of
368 																														// the
369 																														// mouse
370 																														// for
371 																														// the
372 																														// tool
373 																														// tip
374 							0, false ) );
375 
376 			// And include the cue line in the area to be rubbed out next time
377 			_raGhost = _raGhost.createUnion( _raCueLine );
378 
379 			if( !dragAcceptable )
380 				e.rejectDrag();
381 			else
382 				e.acceptDrag( e.getDropAction() );
383 		}
384 
385 		public void dropActionChanged( DropTargetDragEvent e )
386 		{
387 			int dt = getDropTypeAtPoint( e.getLocation() );
388 
389 			if( dt == DropType.NONE || !isDragAcceptable( e, dt ) )
390 				e.rejectDrag();
391 			else
392 				e.acceptDrag( e.getDropAction() );
393 		}
394 
395 		public void drop( DropTargetDropEvent e )
396 		{
397 			int dt = getDropTypeAtPoint( e.getLocation() );
398 			_timerHover.stop();
399 
400 			if( dt == DropType.NONE || !isDropAcceptable( e, dt ) )
401 			{
402 				e.rejectDrop();
403 				return;
404 			}
405 
406 			e.acceptDrop( e.getDropAction() );
407 
408 			Transferable transferable = e.getTransferable();
409 
410 			DataFlavor[] flavors = transferable.getTransferDataFlavors();
411 			for( int i = 0; i < flavors.length; i++ )
412 			{
413 				DataFlavor flavor = flavors[i];
414 				if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
415 				{
416 					try
417 					{
418 						Point pt = e.getLocation();
419 						ModelItem pathTarget = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
420 						ModelItem pathSource = ( ModelItem )transferable.getTransferData( flavor );
421 
422 						for( ModelItemDropHandler<ModelItem> handler : handlers )
423 						{
424 							if( handler.canDrop( pathSource, pathTarget, e.getDropAction(), dt ) )
425 							{
426 								// System.out.println( "Got drop handler for " +
427 								// pathSource.getName() + " to " + pathTarget.getName()
428 								// + "; " + handler.getClass().getSimpleName() );
429 
430 								handler.drop( pathSource, pathTarget, e.getDropAction(), dt );
431 								break;
432 							}
433 						}
434 
435 						break; // No need to check remaining flavors
436 					}
437 					catch( Exception ioe )
438 					{
439 						System.out.println( ioe );
440 						e.dropComplete( false );
441 						return;
442 					}
443 				}
444 			}
445 
446 			e.dropComplete( true );
447 		}
448 
449 		// Helpers...
450 		public boolean isDragAcceptable( DropTargetDragEvent e, int dt )
451 		{
452 			// Only accept COPY or MOVE gestures (ie LINK is not supported)
453 			if( ( e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE ) == 0 )
454 			{
455 				return false;
456 			}
457 
458 			// Only accept this particular flavor
459 			if( !e.isDataFlavorSupported( ModelItemTransferable.MODELITEM_DATAFLAVOR ) )
460 			{
461 				return false;
462 			}
463 
464 			Transferable transferable = e.getTransferable();
465 
466 			DataFlavor[] flavors = transferable.getTransferDataFlavors();
467 			for( int i = 0; i < flavors.length; i++ )
468 			{
469 				DataFlavor flavor = flavors[i];
470 				if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
471 				{
472 					try
473 					{
474 						Point pt = e.getLocation();
475 						ModelItem pathTarget = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
476 						ModelItem pathSource = ( ModelItem )transferable.getTransferData( flavor );
477 
478 						for( ModelItemDropHandler<ModelItem> handler : handlers )
479 						{
480 							if( handler.canDrop( pathSource, pathTarget, e.getDropAction(), dt ) )
481 							{
482 								dropInfo = handler.getDropInfo( pathSource, pathTarget, e.getDropAction(), dt );
483 								// System.out.println( "Got drag handler for " +
484 								// pathSource.getName() + " to " + pathTarget.getName()
485 								// + "; " + handler.getClass().getSimpleName() );
486 								return true;
487 							}
488 						}
489 
490 						// System.out.println( "Missing drop handler for " +
491 						// pathSource.getName() + " to " + pathTarget.getName() );
492 
493 						dropInfo = null;
494 					}
495 					catch( Exception ex )
496 					{
497 						SoapUI.logError( ex );
498 					}
499 				}
500 			}
501 
502 			return false;
503 		}
504 
505 		public boolean isDropAcceptable( DropTargetDropEvent e, int dt )
506 		{
507 			// Only accept COPY or MOVE gestures (ie LINK is not supported)
508 			if( ( e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE ) == 0 )
509 				return false;
510 
511 			// Only accept this particular flavor
512 			if( !e.isDataFlavorSupported( ModelItemTransferable.MODELITEM_DATAFLAVOR ) )
513 			{
514 				return false;
515 			}
516 
517 			Transferable transferable = e.getTransferable();
518 
519 			DataFlavor[] flavors = transferable.getTransferDataFlavors();
520 			for( int i = 0; i < flavors.length; i++ )
521 			{
522 				DataFlavor flavor = flavors[i];
523 				if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
524 				{
525 					try
526 					{
527 						Point pt = e.getLocation();
528 						ModelItem pathSource = ( ModelItem )transferable.getTransferData( flavor );
529 						ModelItem pathTarget = dragAndDropable.getModelItemForLocation( pt.x, pt.y );
530 
531 						for( ModelItemDropHandler<ModelItem> handler : handlers )
532 						{
533 							if( handler.canDrop( pathSource, pathTarget, e.getDropAction(), dt ) )
534 								return true;
535 						}
536 					}
537 					catch( Exception ex )
538 					{
539 						SoapUI.logError( ex );
540 					}
541 				}
542 			}
543 
544 			return false;
545 		}
546 	}
547 }