View Javadoc

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