View Javadoc

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