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