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 )
227 			{
228 				SoapUITreeNode lastPathComponent = ( SoapUITreeNode )selectionPath.getLastPathComponent();
229 				ActionList actions = lastPathComponent.getActions();
230 				if( actions != null )
231 				{
232 					actions.dispatchKeyEvent( e );
233 				}
234 
235 				if( !e.isConsumed() )
236 				{
237 					KeyStroke ks = KeyStroke.getKeyStrokeForEvent( e );
238 					if( ks.equals( UISupport.getKeyStroke( "alt C" ) ) )
239 					{
240 						mainTree.collapsePath( selectionPath );
241 						e.consume();
242 					}
243 					else if( ks.equals( UISupport.getKeyStroke( "alt E" ) ) )
244 					{
245 						mainTree.collapsePath( selectionPath );
246 						int row = mainTree.getSelectionRows()[0];
247 						TreePath nextPath = mainTree.getPathForRow( row + 1 );
248 
249 						TreePath path = mainTree.getPathForRow( row );
250 						while( path != null && !path.equals( nextPath ) )
251 						{
252 							mainTree.expandRow( row );
253 							path = mainTree.getPathForRow( ++row );
254 						}
255 
256 						e.consume();
257 					}
258 				}
259 			}
260 		}
261 	}
262 
263 	public class InternalTreeSelectionListener implements TreeSelectionListener
264 	{
265 		public void valueChanged( TreeSelectionEvent e )
266 		{
267 			Object obj = e.getPath().getLastPathComponent();
268 			if( obj instanceof SoapUITreeNode )
269 			{
270 				SoapUITreeNode treeNode = ( SoapUITreeNode )obj;
271 
272 				if( !listeners.isEmpty() )
273 				{
274 					TreePath newPath = e.getNewLeadSelectionPath();
275 					NavigatorListener[] array = listeners.toArray( new NavigatorListener[listeners.size()] );
276 					for( NavigatorListener listener : array )
277 					{
278 						listener.nodeSelected( newPath == null ? null : treeNode );
279 					}
280 				}
281 			}
282 		}
283 	}
284 
285 	public class TreeMouseListener extends MouseAdapter
286 	{
287 		private final class CollapseRowAction extends AbstractAction
288 		{
289 			private final int row;
290 
291 			public CollapseRowAction( int row )
292 			{
293 				this.row = row;
294 			}
295 
296 			public void actionPerformed( ActionEvent e )
297 			{
298 				collapseAll( mainTree.getPathForRow( row ) );
299 				mainTree.collapseRow( row );
300 			}
301 			
302 			private void collapseAll( TreePath tp )
303 			{
304 				if( tp == null )
305 					return;
306 				
307 				Object node = tp.getLastPathComponent();
308 				TreeModel model = mainTree.getModel();
309 				if( !model.isLeaf( node ) )
310 				{
311 					mainTree.collapsePath( tp );
312 					for( int i = 0; i < model.getChildCount( node ); i++ )
313 					{
314 						// for (int i = node.childCount()-4;i>=0;i--){
315 						collapseAll( tp.pathByAddingChild( model.getChild( node, i ) ) );
316 					}
317 					mainTree.collapsePath( tp );
318 				}
319 			}
320 		}
321 
322 		private final class ExpandRowAction extends AbstractAction
323 		{
324 			private final int row;
325 
326 			public ExpandRowAction( int row )
327 			{
328 				this.row = row;
329 			}
330 
331 			public void actionPerformed( ActionEvent e )
332 			{
333 				mainTree.expandRow( row );
334 				expandAll( mainTree.getPathForRow( row ) );
335 			}
336 
337 			private void expandAll( TreePath tp )
338 			{
339 				if( tp == null )
340 					return;
341 
342 				Object node = tp.getLastPathComponent();
343 				TreeModel model = mainTree.getModel();
344 				if( !model.isLeaf( node ) )
345 				{
346 					mainTree.expandPath( tp );
347 					for( int i = 0; i < model.getChildCount( node ); i++ )
348 					{
349 						expandAll( tp.pathByAddingChild( model.getChild( node, i ) ) );
350 					}
351 				}
352 			}
353 
354 			
355 		}
356 
357 		public void mouseClicked( MouseEvent e )
358 		{
359 			if( e.isPopupTrigger() )
360 				showPopup( e );
361 			else if( e.getClickCount() < 2 )
362 				return;
363 			if( mainTree.getSelectionCount() == 1 )
364 			{
365 				int row = mainTree.getRowForLocation( e.getX(), e.getY() );
366 				TreePath path = mainTree.getSelectionPath();
367 				if( path == null && row == -1 )
368 					return;
369 
370 				if( path == null || mainTree.getRowForPath( path ) != row )
371 					mainTree.setSelectionRow( row );
372 
373 				SoapUITreeNode node = ( SoapUITreeNode )path.getLastPathComponent();
374 				ActionList actions = node.getActions();
375 				if( actions != null )
376 					actions.performDefaultAction( new ActionEvent( mainTree, 0, null ) );
377 			}
378 		}
379 
380 		public void mousePressed( MouseEvent e )
381 		{
382 			if( e.isPopupTrigger() )
383 				showPopup( e );
384 		}
385 
386 		public void mouseReleased( MouseEvent e )
387 		{
388 			if( e.isPopupTrigger() )
389 				showPopup( e );
390 		}
391 
392 		private void showPopup( MouseEvent e )
393 		{
394 			if( mainTree.getSelectionCount() < 2 )
395 			{
396 				TreePath path = mainTree.getPathForLocation( ( int )e.getPoint().getX(), ( int )e.getPoint().getY() );
397 				if( path == null )
398 				{
399 					int row = ( int )e.getPoint().getY() / mainTree.getRowHeight();
400 					if( row != -1 )
401 					{
402 						JPopupMenu collapsePopup = new JPopupMenu();
403 						collapsePopup.add( "Collapse" ).addActionListener( new CollapseRowAction( row ) );
404 						collapsePopup.add( "Expand" ).addActionListener( new ExpandRowAction( row ) );
405 						collapsePopup.show( mainTree, e.getX(), e.getY() );
406 					}
407 
408 					return;
409 				}
410 				SoapUITreeNode node = ( SoapUITreeNode )path.getLastPathComponent();
411 
412 				JPopupMenu popupMenu = node.getPopup();
413 				if( popupMenu == null )
414 					return;
415 
416 				mainTree.setSelectionPath( path );
417 
418 				popupMenu.show( mainTree, e.getX(), e.getY() );
419 			}
420 			else
421 			{
422 				TreePath[] selectionPaths = mainTree.getSelectionPaths();
423 				List<ModelItem> targets = new ArrayList<ModelItem>();
424 				for( TreePath treePath : selectionPaths )
425 				{
426 					SoapUITreeNode node = ( SoapUITreeNode )treePath.getLastPathComponent();
427 					targets.add( node.getModelItem() );
428 				}
429 
430 				if( targets.size() > 0 )
431 				{
432 					ActionList actions = ActionListBuilder.buildMultiActions( targets
433 							.toArray( new ModelItem[targets.size()] ) );
434 					if( actions.getActionCount() > 0 )
435 					{
436 						JPopupMenu popup = new JPopupMenu();
437 						ActionSupport.addActions( actions, popup );
438 						popup.show( mainTree, e.getX(), e.getY() );
439 					}
440 				}
441 			}
442 		}
443 	}
444 
445 	public boolean isVisible( TreePath path )
446 	{
447 		return mainTree.isVisible( path );
448 	}
449 
450 	public boolean isExpanded( TreePath path )
451 	{
452 		return mainTree.isExpanded( path );
453 	}
454 
455 	private class TogglePropertiesAction extends AbstractAction
456 	{
457 		public TogglePropertiesAction()
458 		{
459 			putValue( SMALL_ICON, UISupport.createImageIcon( "/toggle_properties.gif" ) );
460 		}
461 
462 		public void actionPerformed( ActionEvent e )
463 		{
464 			Enumeration<TreePath> expandedDescendants = mainTree.getExpandedDescendants( getTreePath( workspace ) );
465 			TreePath selectionPath = mainTree.getSelectionPath();
466 
467 			treeModel.setShowProperties( !treeModel.isShowProperties() );
468 
469 			while( expandedDescendants != null && expandedDescendants.hasMoreElements() )
470 			{
471 				mainTree.expandPath( expandedDescendants.nextElement() );
472 			}
473 
474 			if( selectionPath != null )
475 				mainTree.setSelectionPath( selectionPath );
476 		}
477 	}
478 
479 	public class MultiActionInvoker extends AbstractAction
480 	{
481 		private final SoapUIMultiAction action;
482 		private final List<ModelItem> targets;
483 
484 		public MultiActionInvoker( SoapUIMultiAction action, List<ModelItem> targets )
485 		{
486 			super( action.getName() );
487 
488 			this.action = action;
489 			this.targets = targets;
490 		}
491 
492 		public void actionPerformed( ActionEvent e )
493 		{
494 			action.perform( targets.toArray( new ModelItem[targets.size()] ), null );
495 		}
496 	}
497 }