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  
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.Enumeration;
27  import java.util.HashSet;
28  import java.util.Set;
29  
30  import javax.swing.AbstractAction;
31  import javax.swing.BorderFactory;
32  import javax.swing.JPanel;
33  import javax.swing.JPopupMenu;
34  import javax.swing.JScrollPane;
35  import javax.swing.JToggleButton;
36  import javax.swing.JTree;
37  import javax.swing.KeyStroke;
38  import javax.swing.ToolTipManager;
39  import javax.swing.event.TreeSelectionEvent;
40  import javax.swing.event.TreeSelectionListener;
41  import javax.swing.tree.TreePath;
42  import javax.swing.tree.TreeSelectionModel;
43  
44  import com.eviware.soapui.model.ModelItem;
45  import com.eviware.soapui.model.project.Project;
46  import com.eviware.soapui.model.tree.SoapUITreeModel;
47  import com.eviware.soapui.model.tree.SoapUITreeNode;
48  import com.eviware.soapui.model.tree.SoapUITreeNodeRenderer;
49  import com.eviware.soapui.model.tree.nodes.ProjectTreeNode;
50  import com.eviware.soapui.model.workspace.Workspace;
51  import com.eviware.soapui.support.UISupport;
52  import com.eviware.soapui.support.action.swing.ActionList;
53  import com.eviware.soapui.support.components.JXToolBar;
54  
55  /***
56   * The soapUI navigator tree
57   * 
58   * @author Ole.Matzura
59   */
60  
61  public class Navigator extends JPanel
62  {
63  	private Workspace workspace;
64  	private JTree mainTree;
65  	private SoapUITreeModel treeModel;
66  	private Set<NavigatorListener> listeners = new HashSet<NavigatorListener>();
67  
68  	public Navigator( Workspace workspace )
69  	{
70  		super( new BorderLayout() );
71  		this.workspace = workspace;
72  		
73  		buildUI();
74  	}
75  	
76  	private void buildUI()
77  	{
78  		treeModel = new SoapUITreeModel(workspace);
79  		
80        mainTree = new NavigatorTree( treeModel );
81        mainTree.setRootVisible(true);
82        mainTree.setExpandsSelectedPaths(true);
83        mainTree.setToggleClickCount( 0 );
84        mainTree.addMouseListener(new TreeMouseListener());
85        mainTree.addTreeSelectionListener(new InternalTreeSelectionListener());
86        mainTree.setCellRenderer( new SoapUITreeNodeRenderer() );
87        mainTree.setBorder(null);
88        mainTree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
89        mainTree.addKeyListener( new TreeKeyListener() );
90        
91        ToolTipManager.sharedInstance().registerComponent(mainTree);
92        
93        add( new JScrollPane( mainTree ), BorderLayout.CENTER );
94        add( buildToolbar(), BorderLayout.NORTH );
95        setBorder( BorderFactory.createEmptyBorder( 0, 0, 0, 0 ));
96  	}
97  	
98  	private Component buildToolbar()
99  	{
100 		JXToolBar toolbar = UISupport.createSmallToolbar();
101 		
102 		JToggleButton toggleButton = new JToggleButton( new TogglePropertiesAction( ));
103 		toggleButton.setToolTipText( "Toggles displaying of Test Properties in tree" );
104 		toggleButton.setSize( 10, 12 );
105 		toolbar.addFixed( toggleButton );
106 		toolbar.addGlue();
107 		
108 		return toolbar;
109 	}
110 
111 	private static class NavigatorTree extends JTree implements Autoscroll
112 	{
113 		public NavigatorTree( SoapUITreeModel treeModel )
114 		{
115 			super( treeModel );
116 		}
117 		
118 		private static final int AUTOSCROLL_MARGIN = 12;
119 
120 		public void autoscroll( Point pt )
121 		{
122 			// Figure out which row we?re on.
123 			int nRow = getRowForLocation( pt.x, pt.y );
124 
125 			// If we are not on a row then ignore this autoscroll request
126 			if( nRow < 0 )
127 				return;
128 
129 			Rectangle raOuter = getBounds();
130 			// Now decide if the row is at the top of the screen or at the
131 			// bottom. We do this to make the previous row (or the next
132 			// row) visible as appropriate. If we?re at the absolute top or
133 			// bottom, just return the first or last row respectively.
134 
135 			nRow = ( pt.y + raOuter.y <= AUTOSCROLL_MARGIN ) // Is row at top of
136 			// screen?
137 			? ( nRow <= 0 ? 0 : nRow - 1 ) // Yes, scroll up one row
138 						: ( nRow < getRowCount() - 1 ? nRow + 1 : nRow ); // No,
139 			// scroll
140 			// down one
141 			// row
142 
143 			scrollRowToVisible( nRow );
144 		}
145 
146 		// Calculate the insets for the *JTREE*, not the viewport
147 		// the tree is in. This makes it a bit messy.
148 		public Insets getAutoscrollInsets()
149 		{
150 			Rectangle raOuter = getBounds();
151 			Rectangle raInner = getParent().getBounds();
152 			return new Insets( raInner.y - raOuter.y + AUTOSCROLL_MARGIN, raInner.x - raOuter.x
153 						+ AUTOSCROLL_MARGIN, raOuter.height - raInner.height - raInner.y + raOuter.y
154 						+ AUTOSCROLL_MARGIN, raOuter.width - raInner.width - raInner.x + raOuter.x
155 						+ AUTOSCROLL_MARGIN );
156 		}
157 	}
158 	
159 	public Project getCurrentProject()
160    {
161       TreePath path = mainTree.getSelectionPath();
162       if( path == null )
163       	return null;
164       
165       Object node = (Object) path.getLastPathComponent();
166       while (node != null && !(node instanceof ProjectTreeNode))
167       {
168          path = path.getParentPath();
169          node = (path == null ? null : path.getLastPathComponent());
170       }
171 
172       if (node == null)
173          return null;
174 
175       return ((ProjectTreeNode) node).getProject();
176    }
177 	
178 	public void addNavigatorListener( NavigatorListener listener )
179 	{
180 		listeners.add( listener );
181 	}
182 	
183 	public void removeNavigatorListener( NavigatorListener listener )
184 	{
185 		listeners.remove( listener );
186 	}
187 	
188 	public void selectModelItem(ModelItem modelItem)
189    {
190       TreePath path = treeModel.getPath( modelItem );
191       mainTree.setSelectionPath( path );
192      	mainTree.expandPath( path );
193       mainTree.scrollPathToVisible( path );
194    }
195 	
196 	public TreePath getTreePath( ModelItem modelItem )
197 	{
198 		return treeModel.getPath( modelItem );
199 	}
200 	
201 	public JTree getMainTree()
202 	{
203 		return mainTree;
204 	}
205 	
206 	public ModelItem getSelectedItem()
207 	{
208 		TreePath path = mainTree.getSelectionPath();
209       if( path == null )
210       	return null;
211       
212      return ((SoapUITreeNode) path.getLastPathComponent()).getModelItem();
213 	}
214 	
215 	private final class TreeKeyListener extends KeyAdapter
216 	{
217 		public void keyPressed(KeyEvent e)
218 		{
219 			TreePath selectionPath = mainTree.getSelectionPath();
220 			if( selectionPath != null )
221 			{
222 				SoapUITreeNode lastPathComponent = (SoapUITreeNode) selectionPath.getLastPathComponent();
223 				ActionList actions = lastPathComponent.getActions();
224 				if( actions != null )
225 				{
226 					actions.dispatchKeyEvent( e );
227 				}
228 				
229 				if( !e.isConsumed() )
230 				{
231 					KeyStroke ks = KeyStroke.getKeyStrokeForEvent( e );
232 					if( ks.equals( UISupport.getKeyStroke( "alt C" )))
233 					{
234 						mainTree.collapsePath( selectionPath );
235 						e.consume();
236 					}
237 					else if( ks.equals( UISupport.getKeyStroke( "alt E" )))
238 					{
239 						mainTree.collapsePath( selectionPath );
240 						int row = mainTree.getSelectionRows()[0];
241 						TreePath nextPath = mainTree.getPathForRow( row+1 );
242 						
243 						TreePath path = mainTree.getPathForRow( row );
244 						while( path != null && !path.equals( nextPath ))
245 						{
246 							mainTree.expandRow( row );
247 							path = mainTree.getPathForRow( ++row );
248 						}
249 					
250 						e.consume();
251 					}
252 				}
253 			}
254 		}
255 	}
256 	
257 	public class InternalTreeSelectionListener implements TreeSelectionListener
258    {
259       public void valueChanged(TreeSelectionEvent e)
260       {
261          Object obj = e.getPath().getLastPathComponent();
262          if (obj instanceof SoapUITreeNode)
263          {
264             SoapUITreeNode treeNode = (SoapUITreeNode) obj;
265 
266             if( !listeners.isEmpty() )
267             {
268             	TreePath newPath = e.getNewLeadSelectionPath();
269 	            NavigatorListener[] array = listeners.toArray( new NavigatorListener[listeners.size()]);
270 	            for( NavigatorListener listener : array )
271 	            {
272 	            	listener.nodeSelected( newPath == null ? null : treeNode );
273 	            }
274             }
275          }
276       }
277    }
278 
279 	public class TreeMouseListener extends MouseAdapter
280    {
281       public void mouseClicked(MouseEvent e)
282       {
283          if (e.isPopupTrigger())
284             showPopup(e);
285          else if (e.getClickCount() < 2)
286             return;
287          if (mainTree.getSelectionCount() != 1)
288             return;
289 
290          int row = mainTree.getRowForLocation( e.getX(), e.getY() );
291          TreePath path = mainTree.getSelectionPath();
292          if( path == null && row == -1 )
293          	return;
294          
295          if( path == null || mainTree.getRowForPath( path ) != row )
296          	mainTree.setSelectionRow( row );
297          
298          SoapUITreeNode node = (SoapUITreeNode) path.getLastPathComponent();
299          ActionList actions = node.getActions();
300          if( actions != null )
301          	actions.performDefaultAction( new ActionEvent( mainTree, 0, null ));
302       }
303 
304       public void mousePressed(MouseEvent e)
305       {
306          if (e.isPopupTrigger())
307             showPopup(e);
308       }
309 
310       public void mouseReleased(MouseEvent e)
311       {
312          if (e.isPopupTrigger())
313             showPopup(e);
314       }
315 
316       private void showPopup(MouseEvent e)
317       {
318          TreePath path = mainTree.getPathForLocation((int) e.getPoint().getX(), (int) e.getPoint().getY());
319          if (path == null)
320             return;
321          SoapUITreeNode node = (SoapUITreeNode) path.getLastPathComponent();
322 
323          JPopupMenu popupMenu = node.getPopup();
324          if (popupMenu == null)
325             return;
326 
327          mainTree.setSelectionPath(path);
328 
329          popupMenu.show(mainTree, e.getX(), e.getY());
330       }
331    }
332 
333 	public boolean isVisible( TreePath path )
334 	{
335 		return mainTree.isVisible( path );
336 	}
337 
338 	public boolean isExpanded( TreePath path )
339 	{
340 		return mainTree.isExpanded( path );
341 	}
342 	
343 	private class TogglePropertiesAction extends AbstractAction
344 	{
345 		public TogglePropertiesAction()
346 		{
347 			putValue( SMALL_ICON, UISupport.createImageIcon( "/toggle_properties.gif" ));
348 		}
349 		
350 		public void actionPerformed( ActionEvent e )
351 		{
352 			Enumeration<TreePath> expandedDescendants = mainTree.getExpandedDescendants( getTreePath( workspace ) );
353 			TreePath selectionPath = mainTree.getSelectionPath();
354 			
355 			treeModel.setShowProperties( !treeModel.isShowProperties() );
356 			
357 			while( expandedDescendants != null && expandedDescendants.hasMoreElements() )
358 			{
359 				mainTree.expandPath( expandedDescendants.nextElement() );
360 			}
361 			
362 			if( selectionPath != null )
363 				mainTree.setSelectionPath( selectionPath );
364 		}
365 	}
366 }