1
2
3
4
5
6
7
8
9
10
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
131 int nRow = getRowForLocation( pt.x, pt.y );
132
133
134 if( nRow < 0 )
135 return;
136
137 Rectangle raOuter = getBounds();
138
139
140
141
142
143 nRow = ( pt.y + raOuter.y <= AUTOSCROLL_MARGIN )
144
145 ? ( nRow <= 0 ? 0 : nRow - 1 )
146 : ( nRow < getRowCount() - 1 ? nRow + 1 : nRow );
147
148
149
150
151 scrollRowToVisible( nRow );
152 }
153
154
155
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
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 }