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  
13  package com.eviware.soapui.ui;
14  
15  import java.awt.BorderLayout;
16  import java.awt.Component;
17  import java.awt.Insets;
18  import java.awt.Point;
19  import java.awt.Rectangle;
20  import java.awt.dnd.Autoscroll;
21  import java.awt.event.ActionEvent;
22  import java.awt.event.KeyAdapter;
23  import java.awt.event.KeyEvent;
24  import java.awt.event.MouseAdapter;
25  import java.awt.event.MouseEvent;
26  import java.util.ArrayList;
27  import java.util.Enumeration;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  
32  import javax.swing.AbstractAction;
33  import javax.swing.BorderFactory;
34  import javax.swing.JPanel;
35  import javax.swing.JPopupMenu;
36  import javax.swing.JScrollPane;
37  import javax.swing.JToggleButton;
38  import javax.swing.JTree;
39  import javax.swing.KeyStroke;
40  import javax.swing.SwingUtilities;
41  import javax.swing.ToolTipManager;
42  import javax.swing.event.TreeSelectionEvent;
43  import javax.swing.event.TreeSelectionListener;
44  import javax.swing.tree.TreeModel;
45  import javax.swing.tree.TreePath;
46  import javax.swing.tree.TreeSelectionModel;
47  
48  import com.eviware.soapui.model.ModelItem;
49  import com.eviware.soapui.model.project.Project;
50  import com.eviware.soapui.model.tree.SoapUITreeModel;
51  import com.eviware.soapui.model.tree.SoapUITreeNode;
52  import com.eviware.soapui.model.tree.SoapUITreeNodeRenderer;
53  import com.eviware.soapui.model.tree.nodes.ProjectTreeNode;
54  import com.eviware.soapui.model.workspace.Workspace;
55  import com.eviware.soapui.support.UISupport;
56  import com.eviware.soapui.support.action.SoapUIMultiAction;
57  import com.eviware.soapui.support.action.swing.ActionList;
58  import com.eviware.soapui.support.action.swing.ActionListBuilder;
59  import com.eviware.soapui.support.action.swing.ActionSupport;
60  import com.eviware.soapui.support.components.JXToolBar;
61  
62  /***
63   * The soapUI navigator tree
64   * 
65   * @author Ole.Matzura
66   */
67  
68  public class Navigator extends JPanel
69  {
70  	private Workspace workspace;
71  	private JTree mainTree;
72  	private SoapUITreeModel treeModel;
73  	private Set<NavigatorListener> listeners = new HashSet<NavigatorListener>();
74  
75  	public Navigator( Workspace workspace )
76  	{
77  		super( new BorderLayout() );
78  		this.workspace = workspace;
79  
80  		buildUI();
81  	}
82  
83  	private void buildUI()
84  	{
85  		treeModel = new SoapUITreeModel( workspace );
86  
87  		mainTree = new NavigatorTree( treeModel );
88  		mainTree.setRootVisible( true );
89  		mainTree.setExpandsSelectedPaths( true );
90  		mainTree.setScrollsOnExpand( true );
91  		mainTree.setToggleClickCount( 0 );
92  		mainTree.addMouseListener( new TreeMouseListener() );
93  		mainTree.addTreeSelectionListener( new InternalTreeSelectionListener() );
94  		mainTree.setCellRenderer( new SoapUITreeNodeRenderer() );
95  		mainTree.setBorder( null );
96  		mainTree.getSelectionModel().setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
97  		mainTree.addKeyListener( new TreeKeyListener() );
98  
99  		ToolTipManager.sharedInstance().registerComponent( mainTree );
100 
101 		add( new JScrollPane( mainTree ), BorderLayout.CENTER );
102 		add( buildToolbar(), BorderLayout.NORTH );
103 		setBorder( BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) );
104 	}
105 
106 	private Component buildToolbar()
107 	{
108 		JXToolBar toolbar = UISupport.createSmallToolbar();
109 
110 		JToggleButton toggleButton = new JToggleButton( new TogglePropertiesAction() );
111 		toggleButton.setToolTipText( "Toggles displaying of Test Properties in tree" );
112 		toggleButton.setSize( 10, 12 );
113 		toolbar.addFixed( toggleButton );
114 		toolbar.addGlue();
115 
116 		return toolbar;
117 	}
118 
119 	private static class NavigatorTree extends JTree implements Autoscroll
120 	{
121 		public NavigatorTree( SoapUITreeModel treeModel )
122 		{
123 			super( treeModel );
124 		}
125 
126 		private static final int AUTOSCROLL_MARGIN = 12;
127 
128 		public void autoscroll( Point pt )
129 		{
130 			// Figure out which row we�re on.
131 			int nRow = getRowForLocation( pt.x, pt.y );
132 
133 			// If we are not on a row then ignore this autoscroll request
134 			if( nRow < 0 )
135 				return;
136 
137 			Rectangle raOuter = getBounds();
138 			// Now decide if the row is at the top of the screen or at the
139 			// bottom. We do this to make the previous row (or the next
140 			// row) visible as appropriate. If we�re at the absolute top or
141 			// bottom, just return the first or last row respectively.
142 
143 			nRow = ( pt.y + raOuter.y <= AUTOSCROLL_MARGIN ) // Is row at top of
144 			// screen?
145 			? ( nRow <= 0 ? 0 : nRow - 1 ) // Yes, scroll up one row
146 					: ( nRow < getRowCount() - 1 ? nRow + 1 : nRow ); // No,
147 			// scroll
148 			// down one
149 			// row
150 
151 			scrollRowToVisible( nRow );
152 		}
153 
154 		// Calculate the insets for the *JTREE*, not the viewport
155 		// the tree is in. This makes it a bit messy.
156 		public Insets getAutoscrollInsets()
157 		{
158 			Rectangle raOuter = getBounds();
159 			Rectangle raInner = getParent().getBounds();
160 			return new Insets( raInner.y - raOuter.y + AUTOSCROLL_MARGIN, raInner.x - raOuter.x + AUTOSCROLL_MARGIN,
161 					raOuter.height - raInner.height - raInner.y + raOuter.y + AUTOSCROLL_MARGIN, raOuter.width
162 							- raInner.width - raInner.x + raOuter.x + AUTOSCROLL_MARGIN );
163 		}
164 	}
165 
166 	public Project getCurrentProject()
167 	{
168 		TreePath path = mainTree.getSelectionPath();
169 		if( path == null )
170 			return null;
171 
172 		Object node = ( Object )path.getLastPathComponent();
173 		while( node != null && !( node instanceof ProjectTreeNode ) )
174 		{
175 			path = path.getParentPath();
176 			node = ( path == null ? null : path.getLastPathComponent() );
177 		}
178 
179 		if( node == null )
180 			return null;
181 
182 		return ( ( ProjectTreeNode )node ).getProject();
183 	}
184 
185 	public void addNavigatorListener( NavigatorListener listener )
186 	{
187 		listeners.add( listener );
188 	}
189 
190 	public void removeNavigatorListener( NavigatorListener listener )
191 	{
192 		listeners.remove( listener );
193 	}
194 
195 	public void selectModelItem( ModelItem modelItem )
196 	{
197 		TreePath path = treeModel.getPath( modelItem );
198 		mainTree.setSelectionPath( path );
199 		mainTree.expandPath( path );
200 		mainTree.scrollPathToVisible( path );
201 	}
202 
203 	public TreePath getTreePath( ModelItem modelItem )
204 	{
205 		return treeModel.getPath( modelItem );
206 	}
207 
208 	public JTree getMainTree()
209 	{
210 		return mainTree;
211 	}
212 
213 	public ModelItem getSelectedItem()
214 	{
215 		TreePath path = mainTree.getSelectionPath();
216 		if( path == null )
217 			return null;
218 
219 		return ( ( SoapUITreeNode )path.getLastPathComponent() ).getModelItem();
220 	}
221 
222 	private final class TreeKeyListener extends KeyAdapter
223 	{
224 		public void keyPressed( KeyEvent e )
225 		{
226 			TreePath selectionPath = mainTree.getSelectionPath();
227 			if( selectionPath == null || mainTree.getSelectionCount() == 0 )
228 				return;
229 
230 			if( mainTree.getSelectionCount() == 1 )
231 			{
232 				SoapUITreeNode lastPathComponent = ( SoapUITreeNode )selectionPath.getLastPathComponent();
233 				ActionList actions = lastPathComponent.getActions();
234 				if( actions != null )
235 				{
236 					actions.dispatchKeyEvent( e );
237 				}
238 
239 				if( !e.isConsumed() )
240 				{
241 					KeyStroke ks = KeyStroke.getKeyStrokeForEvent( e );
242 					if( ks.equals( UISupport.getKeyStroke( "alt C" ) ) )
243 					{
244 						mainTree.collapsePath( selectionPath );
245 						e.consume();
246 					}
247 					else if( ks.equals( UISupport.getKeyStroke( "alt E" ) ) )
248 					{
249 						mainTree.collapsePath( selectionPath );
250 						int row = mainTree.getSelectionRows()[0];
251 						TreePath nextPath = mainTree.getPathForRow( row + 1 );
252 
253 						TreePath path = mainTree.getPathForRow( row );
254 						while( path != null && !path.equals( nextPath ) )
255 						{
256 							mainTree.expandRow( row );
257 							path = mainTree.getPathForRow( ++row );
258 						}
259 
260 						e.consume();
261 					}
262 				}
263 			}
264 			else
265 			{
266 				TreePath[] selectionPaths = mainTree.getSelectionPaths();
267 				List<ModelItem> targets = new ArrayList<ModelItem>();
268 				for( TreePath treePath : selectionPaths )
269 				{
270 					SoapUITreeNode node = ( SoapUITreeNode )treePath.getLastPathComponent();
271 					targets.add( node.getModelItem() );
272 				}
273 
274 				if( targets.size() > 0 )
275 				{
276 					ActionList actions = ActionListBuilder.buildMultiActions( targets
277 							.toArray( new ModelItem[targets.size()] ) );
278 					if( actions.getActionCount() > 0 )
279 					{
280 						actions.dispatchKeyEvent( e );
281 					}
282 				}
283 			}
284 		}
285 	}
286 
287 	public class InternalTreeSelectionListener implements TreeSelectionListener
288 	{
289 		public void valueChanged( TreeSelectionEvent e )
290 		{
291 			Object obj = e.getPath().getLastPathComponent();
292 			if( obj instanceof SoapUITreeNode )
293 			{
294 				SoapUITreeNode treeNode = ( SoapUITreeNode )obj;
295 
296 				if( !listeners.isEmpty() )
297 				{
298 					TreePath newPath = e.getNewLeadSelectionPath();
299 					NavigatorListener[] array = listeners.toArray( new NavigatorListener[listeners.size()] );
300 					for( NavigatorListener listener : array )
301 					{
302 						listener.nodeSelected( newPath == null ? null : treeNode );
303 					}
304 				}
305 			}
306 		}
307 	}
308 
309 	public class TreeMouseListener extends MouseAdapter
310 	{
311 		private final class CollapseRowAction extends AbstractAction
312 		{
313 			private final int row;
314 
315 			public CollapseRowAction( int row )
316 			{
317 				this.row = row;
318 			}
319 
320 			public void actionPerformed( ActionEvent e )
321 			{
322 				collapseAll( mainTree.getPathForRow( row ) );
323 				mainTree.collapseRow( row );
324 			}
325 
326 			private void collapseAll( TreePath tp )
327 			{
328 				if( tp == null )
329 					return;
330 
331 				Object node = tp.getLastPathComponent();
332 				TreeModel model = mainTree.getModel();
333 				if( !model.isLeaf( node ) )
334 				{
335 					mainTree.collapsePath( tp );
336 					for( int i = 0; i < model.getChildCount( node ); i++ )
337 					{
338 						// for (int i = node.childCount()-4;i>=0;i--){
339 						collapseAll( tp.pathByAddingChild( model.getChild( node, i ) ) );
340 					}
341 					mainTree.collapsePath( tp );
342 				}
343 			}
344 		}
345 
346 		private final class ExpandRowAction extends AbstractAction
347 		{
348 			private final int row;
349 
350 			public ExpandRowAction( int row )
351 			{
352 				this.row = row;
353 			}
354 
355 			public void actionPerformed( ActionEvent e )
356 			{
357 				mainTree.expandRow( row );
358 				expandAll( mainTree.getPathForRow( row ) );
359 			}
360 
361 			private void expandAll( TreePath tp )
362 			{
363 				if( tp == null )
364 					return;
365 
366 				Object node = tp.getLastPathComponent();
367 				TreeModel model = mainTree.getModel();
368 				if( !model.isLeaf( node ) )
369 				{
370 					mainTree.expandPath( tp );
371 					for( int i = 0; i < model.getChildCount( node ); i++ )
372 					{
373 						expandAll( tp.pathByAddingChild( model.getChild( node, i ) ) );
374 					}
375 				}
376 			}
377 
378 		}
379 
380 		private ActionList actions;
381 
382 		public void mouseClicked( MouseEvent e )
383 		{
384 			if( e.isPopupTrigger() )
385 				showPopup( e );
386 			else if( e.getClickCount() < 2 )
387 				return;
388 			if( mainTree.getSelectionCount() == 1 )
389 			{
390 				int row = mainTree.getRowForLocation( e.getX(), e.getY() );
391 				TreePath path = mainTree.getSelectionPath();
392 				if( path == null && row == -1 )
393 					return;
394 
395 				if( path == null || mainTree.getRowForPath( path ) != row )
396 					mainTree.setSelectionRow( row );
397 
398 				SoapUITreeNode node = ( SoapUITreeNode )path.getLastPathComponent();
399 				actions = node.getActions();
400 				if( actions != null )
401 				{
402 					SwingUtilities.invokeLater( new Runnable()
403 					{
404 						public void run()
405 						{
406 							if( actions != null )
407 							{
408 								actions.performDefaultAction( new ActionEvent( mainTree, 0, null ) );
409 								actions = null;
410 							}
411 						}
412 					} );
413 				}
414 			}
415 		}
416 
417 		public void mousePressed( MouseEvent e )
418 		{
419 			if( e.isPopupTrigger() )
420 				showPopup( e );
421 		}
422 
423 		public void mouseReleased( MouseEvent e )
424 		{
425 			if( e.isPopupTrigger() )
426 				showPopup( e );
427 		}
428 
429 		private void showPopup( MouseEvent e )
430 		{
431 			if( mainTree.getSelectionCount() < 2 )
432 			{
433 				TreePath path = mainTree.getPathForLocation( ( int )e.getPoint().getX(), ( int )e.getPoint().getY() );
434 				if( path == null )
435 				{
436 					int row = ( int )e.getPoint().getY() / mainTree.getRowHeight();
437 					if( row != -1 )
438 					{
439 						JPopupMenu collapsePopup = new JPopupMenu();
440 						collapsePopup.add( "Collapse" ).addActionListener( new CollapseRowAction( row ) );
441 						collapsePopup.add( "Expand" ).addActionListener( new ExpandRowAction( row ) );
442 						collapsePopup.show( mainTree, e.getX(), e.getY() );
443 					}
444 
445 					return;
446 				}
447 				SoapUITreeNode node = ( SoapUITreeNode )path.getLastPathComponent();
448 
449 				JPopupMenu popupMenu = node.getPopup();
450 				if( popupMenu == null )
451 					return;
452 
453 				mainTree.setSelectionPath( path );
454 
455 				popupMenu.show( mainTree, e.getX(), e.getY() );
456 			}
457 			else
458 			{
459 				TreePath[] selectionPaths = mainTree.getSelectionPaths();
460 				List<ModelItem> targets = new ArrayList<ModelItem>();
461 				for( TreePath treePath : selectionPaths )
462 				{
463 					SoapUITreeNode node = ( SoapUITreeNode )treePath.getLastPathComponent();
464 					targets.add( node.getModelItem() );
465 				}
466 
467 				if( targets.size() > 0 )
468 				{
469 					ActionList actions = ActionListBuilder.buildMultiActions( targets
470 							.toArray( new ModelItem[targets.size()] ) );
471 					if( actions.getActionCount() > 0 )
472 					{
473 						JPopupMenu popup = new JPopupMenu();
474 						ActionSupport.addActions( actions, popup );
475 						popup.show( mainTree, e.getX(), e.getY() );
476 					}
477 				}
478 			}
479 		}
480 	}
481 
482 	public boolean isVisible( TreePath path )
483 	{
484 		return mainTree.isVisible( path );
485 	}
486 
487 	public boolean isExpanded( TreePath path )
488 	{
489 		return mainTree.isExpanded( path );
490 	}
491 
492 	private class TogglePropertiesAction extends AbstractAction
493 	{
494 		public TogglePropertiesAction()
495 		{
496 			putValue( SMALL_ICON, UISupport.createImageIcon( "/toggle_properties.gif" ) );
497 		}
498 
499 		public void actionPerformed( ActionEvent e )
500 		{
501 			Enumeration<TreePath> expandedDescendants = mainTree.getExpandedDescendants( getTreePath( workspace ) );
502 			TreePath selectionPath = mainTree.getSelectionPath();
503 
504 			treeModel.setShowProperties( !treeModel.isShowProperties() );
505 
506 			while( expandedDescendants != null && expandedDescendants.hasMoreElements() )
507 			{
508 				mainTree.expandPath( expandedDescendants.nextElement() );
509 			}
510 
511 			if( selectionPath != null )
512 				mainTree.setSelectionPath( selectionPath );
513 		}
514 	}
515 
516 	public class MultiActionInvoker extends AbstractAction
517 	{
518 		private final SoapUIMultiAction action;
519 		private final List<ModelItem> targets;
520 
521 		public MultiActionInvoker( SoapUIMultiAction action, List<ModelItem> targets )
522 		{
523 			super( action.getName() );
524 
525 			this.action = action;
526 			this.targets = targets;
527 		}
528 
529 		public void actionPerformed( ActionEvent e )
530 		{
531 			action.perform( targets.toArray( new ModelItem[targets.size()] ), null );
532 		}
533 	}
534 }