View Javadoc

1   /*
2    *  soapui, copyright (C) 2005 Ole Matzura / eviware.com 
3    *
4    *  SoapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of the GNU Lesser General Public License as published by the Free Software Foundation; 
6    *  either version 2.1 of the License, or (at your option) any later version.
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;
14  
15  import java.awt.BorderLayout;
16  import java.awt.Color;
17  import java.awt.Component;
18  import java.awt.Toolkit;
19  import java.awt.event.ActionEvent;
20  import java.awt.event.KeyEvent;
21  import java.awt.event.MouseAdapter;
22  import java.awt.event.MouseEvent;
23  import java.awt.event.WindowAdapter;
24  import java.awt.event.WindowEvent;
25  import java.beans.PropertyChangeEvent;
26  import java.beans.PropertyChangeListener;
27  import java.beans.PropertyVetoException;
28  import java.io.File;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  
35  import javax.swing.AbstractAction;
36  import javax.swing.Action;
37  import javax.swing.BorderFactory;
38  import javax.swing.DefaultListModel;
39  import javax.swing.ImageIcon;
40  import javax.swing.JComponent;
41  import javax.swing.JDesktopPane;
42  import javax.swing.JFrame;
43  import javax.swing.JInternalFrame;
44  import javax.swing.JLabel;
45  import javax.swing.JList;
46  import javax.swing.JMenu;
47  import javax.swing.JMenuBar;
48  import javax.swing.JOptionPane;
49  import javax.swing.JPanel;
50  import javax.swing.JPopupMenu;
51  import javax.swing.JScrollPane;
52  import javax.swing.JSplitPane;
53  import javax.swing.JTabbedPane;
54  import javax.swing.JTextArea;
55  import javax.swing.JTree;
56  import javax.swing.KeyStroke;
57  import javax.swing.ListCellRenderer;
58  import javax.swing.SwingUtilities;
59  import javax.swing.ToolTipManager;
60  import javax.swing.UIManager;
61  import javax.swing.event.InternalFrameAdapter;
62  import javax.swing.event.InternalFrameEvent;
63  import javax.swing.event.MenuEvent;
64  import javax.swing.event.MenuListener;
65  import javax.swing.event.TreeSelectionEvent;
66  import javax.swing.event.TreeSelectionListener;
67  import javax.swing.plaf.ColorUIResource;
68  import javax.swing.tree.TreePath;
69  import javax.swing.tree.TreeSelectionModel;
70  
71  import com.eviware.soapui.model.DesktopPanel;
72  import com.eviware.soapui.model.ModelItem;
73  import com.eviware.soapui.model.PanelBuilder;
74  import com.eviware.soapui.model.project.Project;
75  import com.eviware.soapui.model.tree.SoapUITreeModel;
76  import com.eviware.soapui.model.tree.SoapUITreeNode;
77  import com.eviware.soapui.model.tree.SoapUITreeNodeRenderer;
78  import com.eviware.soapui.model.tree.nodes.ProjectTreeNode;
79  import com.eviware.soapui.model.workspace.Workspace;
80  import com.eviware.soapui.model.workspace.WorkspaceFactory;
81  import com.eviware.soapui.support.UISupport;
82  import com.jgoodies.looks.plastic.PlasticXPLookAndFeel;
83  import com.jgoodies.looks.plastic.theme.SkyBluerTahoma;
84  
85  /***
86   * Main SoapUI entry point.
87   * 
88   * @author Ole.Matzura
89   * 
90   * For version 0.6
91   * @todo improve error assertion reporting +
92   * @todo find/search/replace +
93   * @todo undo +
94   * @todo recreate request and keep values +
95   * @todo dont create optional elements +
96   * @todo option to create requests when importing +
97   * @todo show soapaction +
98   * @todo save response +
99   * @todo color-coding +
100  * @todo rapportera fel vid null-svar +
101  * @todo fixa bugg vid update interface + 
102  * @todo xmlbeans2 +
103  * @todo httpclient rc3 +
104  * @todo refactor request panels + 
105  * @todo refactor project/interface/testsuite listeners +
106  * @todo create properties table + 
107  * 
108  * For version 0.7
109  * @todo workspace in home folder +
110  * @todo allow renaming of operation names in tree +
111  * @todo add functionality for copying values from test response to following test request +
112  * @todo fix relative xsd imports when loading from file system +
113  * @todo fix cancelling of request when closing request window -> introduce DesktopPanel interface +
114  * @todo refactor panel creation / listener handling/release +
115  * @todo fix update of testcase window when adding test steps +
116  * @todo add delete-line action in xml-editor (Ctrl-D) +
117  * @todo add submit action to xml request editor (Ctrl Enter) +
118  * @todo add mousewheel support to xml editor +
119  * @todo add mousewheel + C support to xml editor (move cursor) +
120  * @todo fix revalidation problems +
121  * @todo add validation to xmleditors +
122  * @todo check localisation of schema errors + 
123  * @todo add possibility to reload schema (neccessary?) +
124  * @todo fix cloning of assertions when cloning test steps +
125  * @todo ask for creation of optional elements when (re)creating new request +
126  * @todo add action to add existing namespaces to xpath in xpathassertion +
127  * 
128  * For version 0.8
129  * @todo fix default values when creating requests +
130  * @todo bug-fixes
131  * - creation of namespace declarations creates multiple with same name +
132  * - check defintion update +
133  * - deletion of endpoint always deletes first +
134  * - deleted assertions result in error during save/exit? +
135  * - disable submit when missing endpoint + 
136  * - clone request null-pointer + 
137  * - fix value transfer element <> attribute + 
138  * - fix cancelling of test requests +
139  * @todo create assertion events / listeners +
140  * @todo ask to add testcase if missing +
141  * @todo add alt-enter to response area +
142  * @todo allow url config of SchemaCompliance assertion + 
143  * @todo add accelerators for common actions + 
144  * @todo fix positions of dialogs +
145  * @todo add possibility to specify testsuite/testcase in maven-plugin +
146  * @todo refactoring: replaced SoapUIAction with standard swing Action +
147  * @todo refactoring: checked all JOptionPanes for correct title, etc. + 
148  * @todo add filename-filters to filechoosers +
149  * @todo add shortcut for cancel request to editor + 
150  * 
151  * For version 0.9
152  * @todo initial class/code comments +
153  * @todo bug-fixes
154  * - fix cancelling of test runs +
155  * - fix removal of request test-steps when associated operations / interfaces are removed + 
156  * - update of request-step status in testcase panel + 
157  * - focus on find-field in find/replace dialog +
158  * - fix filters to not remove directories.. +
159  * - change wsdl file filter to *.wsdl +
160  * - disable declare in transfer panel when no transfer is selected +
161  * - declare in transfer panel declares target from target response (should be request) +
162  * - ask to cancel when closing running testcase +
163  * - fix pretty-printing of large xml-documents +
164  * - fix find and replace +
165  * - fix node insert error on multiple clone teststep +
166  * - fix log-messages from canceled requests +
167  * - disable replace-related options in find/replace if editor is not editable +
168  * @todo add possibility to move teststeps within testcase + 
169  * @todo improve/disable disabling of request-editor +
170  * @todo add addAssertion button to testrequest panel +
171  * @todo add copy/pase actions to editor popup +
172  * @todo model-items tests
173  * @todo improve wsdl/xsd viewer +
174  * @todo improve line-number reporting in validations +
175  * @todo added shift-tab to move focus between request/response editors +
176  * @todo refactorings
177  * - SoapUIException +
178  * - UIFacade for JOptionPane + (partially)
179  * 
180  * For version 1.0b
181  * @todo initial user documentation / samples +
182  * @todo bug-fixes +
183  * @todo more tests / testing! +
184  * @todo beta release +
185 
186  * For version 1.0
187  * @todo official release +
188  * 
189  * 1.1 Improvements
190  * @todo add possibility to transfer values from source request (instead of response)
191  * @todo add possibility to move teststeps to another testcase
192  * @todo workspace management
193  * @todo add reload-project!?
194  * @todo option to reload schemas on run testcase
195  */
196 
197 public class SoapUI
198 {
199    // ------------------------------ FIELDS ------------------------------
200 
201    private static final String DEFAULT_WORKSPACE_FILE = "default-soapui-workspace.xml";
202 
203    private static SoapUI instance;
204 
205    private JFrame frame;
206 
207    private Map<ModelItem, JInternalFrame> modelItemToDesktopFrameMap = new HashMap<ModelItem, JInternalFrame>();
208    private Map<ModelItem, DesktopPanel> modelItemToDesktopPanelMap = new HashMap<ModelItem, DesktopPanel>();
209 
210    private Map<JInternalFrame, ModelItem> contentPanelToModelItemMap = new HashMap<JInternalFrame, ModelItem>();
211 
212    private JTextArea logArea;
213    
214    private JDesktopPane desktopPane = new JDesktopPane();
215 
216    private JTree mainTree = new JTree();
217 
218    private SoapUITreeModel treeModel;
219 
220    private Workspace workspace;
221 
222    private static List<String> logCache = new ArrayList<String>();
223    private static int openFrameCount = 0;
224    private static final int xOffset = 30, yOffset = 30;
225 
226    private JMenu windowMenu;
227 
228    private JMenu fileMenu;
229 
230    private JMenuBar menuBar;
231    
232    private InternalDesktopFrameListener internalFrameListener = new InternalDesktopFrameListener();
233 
234    private JList desktopPanelsList;
235 
236    private DefaultListModel desktopPanels;
237 
238    private ModelItemPropertyChangeListener modelItemPropertyListener = new ModelItemPropertyChangeListener();
239 
240    private JSplitPane contentSplit;
241 
242 	private JPanel overviewPanel;
243 	
244 	public final static String SOAPUI_VERSION = "1.0";
245 
246 	private JTabbedPane detailTabs;
247    
248 
249    // -------------------------- STATIC METHODS --------------------------
250 
251    static
252    {
253       try
254       {
255          PlasticXPLookAndFeel.setMyCurrentTheme(new SoapUITheme());
256          PlasticXPLookAndFeel.setTabStyle("Metal");
257 
258          UIManager.setLookAndFeel(new PlasticXPLookAndFeel());
259       }
260 
261       catch (Throwable e)
262       {
263          System.err.println( "Error initializing PlasticXPLookAndFeel; " + e.getMessage() );
264       }
265    }
266 
267    // --------------------------- CONSTRUCTORS ---------------------------
268 
269    public SoapUI()
270    {
271       ToolTipManager.sharedInstance().setInitialDelay( 200 );
272       
273       frame = new JFrame("SoapUI " + SOAPUI_VERSION);
274       frame.addWindowListener(new WindowAdapter()
275       {
276          public void windowClosing(WindowEvent e)
277          {
278             if( JOptionPane.showConfirmDialog( frame, "Exit SoapUI?", "Question", JOptionPane.OK_CANCEL_OPTION )
279                   != JOptionPane.OK_OPTION )
280                return;
281             
282             workspace.onClose();
283             frame.dispose();
284          }
285 
286 			public void windowClosed(WindowEvent e)
287 			{
288 				System.exit( 0 );
289 			}
290          
291       });
292 
293       UISupport.setMainFrame( frame );
294       instance = this;
295    }
296 
297    private void buildUI()
298    {
299       treeModel = new SoapUITreeModel(workspace);
300       mainTree.setModel(treeModel);
301       mainTree.setRootVisible(true);
302       mainTree.setExpandsSelectedPaths(true);
303       mainTree.setToggleClickCount( 0 );
304       mainTree.addMouseListener(new TreeMouseListener());
305       mainTree.addTreeSelectionListener(new InternalTreeSelectionListener());
306       mainTree.setCellRenderer( new SoapUITreeNodeRenderer() );
307       
308       ToolTipManager.sharedInstance().registerComponent(mainTree);
309 
310       JSplitPane splitPane = UISupport.createHorizontalSplit( buildMainPanel(), buildContentPanel() ); 
311       
312       frame.setJMenuBar(buildMainMenu());
313       frame.getContentPane().add(splitPane, BorderLayout.CENTER);
314       frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
315       frame.setSize(900, 700);
316 
317       splitPane.setDividerLocation(250);
318    }
319 
320    public static SoapUI getInstance()
321    {
322       return instance;
323    }
324 
325    private JMenuBar buildMainMenu()
326    {
327       menuBar = new JMenuBar();
328       fileMenu = new JMenu("File");
329       fileMenu.setMnemonic( KeyEvent.VK_F );
330       
331       Action[] actions = workspace.getActions();
332       for (int i = 0; i < actions.length; i++)
333       {
334          fileMenu.add( actions[i] );
335       }
336 
337       fileMenu.addSeparator();
338       fileMenu.add( new SaveAction() );
339 
340       fileMenu.addSeparator();
341       fileMenu.add( new ExitAction() );
342        
343       menuBar.add(fileMenu); 
344       
345       windowMenu = new JMenu( "Window");
346       windowMenu.setMnemonic( KeyEvent.VK_W );
347       windowMenu.add(new CloseCurrentAction());
348       windowMenu.add(new CloseAllAction());
349       windowMenu.add(new CloseOtherAction());
350       windowMenu.addMenuListener( new MenuListener()
351             {
352                public void menuSelected(MenuEvent e)
353                {
354                   while( windowMenu.getMenuComponentCount() > 3 )
355                      windowMenu.remove( 3 );
356                   
357                   if( contentPanelToModelItemMap.size() == 0 ) return;
358                   windowMenu.addSeparator();
359                   Iterator<JInternalFrame> i = contentPanelToModelItemMap.keySet().iterator();
360                   while( i.hasNext() )
361                   {
362                      windowMenu.add( new PaneSelectionAction( i.next() ));
363                   }
364                }
365 
366                public void menuDeselected(MenuEvent e)
367                {
368                }
369 
370                public void menuCanceled(MenuEvent e)
371                {
372                }});
373 
374       menuBar.add(windowMenu); 
375 //      menuBar.add(new JMenu("About"));
376        
377       return menuBar;
378    }
379 
380    public JFrame getFrame()
381    {
382       return frame;
383    }
384 
385    private JComponent buildMainPanel()
386    {
387       JSplitPane splitPane = UISupport.createVerticalSplit();
388       JPanel mainPanel = new JPanel(new BorderLayout());
389 
390       mainTree.setBorder(null);
391       mainTree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
392       splitPane.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
393 
394       splitPane.setTopComponent(new JScrollPane(mainTree));
395       splitPane.setBottomComponent(buildOverviewPanel());
396       splitPane.setDividerLocation( 500 );
397       splitPane.setResizeWeight( 0.6 );
398       return splitPane;
399    }
400 
401    private Component buildOverviewPanel()
402    {
403       detailTabs = new JTabbedPane( JTabbedPane.BOTTOM );
404 		desktopPanels = new DefaultListModel();
405       desktopPanelsList = new JList( desktopPanels );
406       desktopPanelsList.setCellRenderer( new DesktopItemsCellRenderer() );
407       desktopPanelsList.setToolTipText( "Open windows" );
408       desktopPanelsList.addMouseListener( new MouseAdapter() {
409 
410          public void mouseClicked(MouseEvent e)
411          {
412             if( e.getClickCount() < 2 ) return;
413             ModelItem modelItem = (ModelItem) desktopPanelsList.getSelectedValue();
414             if( modelItem == null ) return;
415             
416             showTab( modelItem );
417          }} );
418       
419 		JPanel panel = new JPanel( new BorderLayout() );
420       panel.setBackground( Color.LIGHT_GRAY );
421       panel.add( detailTabs, BorderLayout.CENTER );
422       detailTabs.addTab( "Windows", new JScrollPane( desktopPanelsList ));
423 
424       overviewPanel = new JPanel( new BorderLayout() );
425 		detailTabs.addTab( "Details", overviewPanel );
426       
427       return panel;
428    }
429 
430    private void setOverviewPanel(Component panel)
431    {
432    	if( overviewPanel.getComponentCount() == 0 && panel == null )
433    		return;
434    	
435    	overviewPanel.removeAll();
436    	if( panel != null )
437    		overviewPanel.add( panel, BorderLayout.CENTER );
438    	overviewPanel.revalidate();
439    	overviewPanel.repaint();
440    }
441 
442    private Component buildContentPanel()
443    {
444       contentSplit = UISupport.createVerticalSplit();
445       JPanel contentPanel = new JPanel(new BorderLayout());
446 
447       contentSplit.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
448 
449       contentSplit.setTopComponent(buildTabbedPanel());
450       contentSplit.setBottomComponent(buildLogPanel());
451       contentSplit.setDividerLocation(550);
452       contentSplit.setResizeWeight( 0.9 );
453       return contentSplit;
454    }
455 
456    private class PaneSelectionAction extends AbstractAction
457    {
458       private final JInternalFrame frame2;
459 
460       public PaneSelectionAction(JInternalFrame frame)
461       {
462          super( frame.getTitle() );
463          frame2 = frame;
464       }
465 
466       public void actionPerformed(ActionEvent e)
467       {
468          showTab( contentPanelToModelItemMap.get( frame2 ));
469       }}
470    
471    private Component buildTabbedPanel()
472    {
473       JScrollPane scrollPane = new JScrollPane( desktopPane );
474       return scrollPane;
475    }
476 
477    private Component buildLogPanel()
478    {
479       logArea = new JTextArea();
480       logArea.setEditable(false);
481       logArea.setToolTipText("SoapUI Log");
482       
483       for (String message : logCache)
484       {
485          logArea.setText( logArea.getText() + message + "\n" );
486       }
487       
488       JPanel panel = new JPanel(new BorderLayout());
489       panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
490       panel.add(new JScrollPane(logArea), BorderLayout.CENTER);
491       return panel;
492    }
493 
494    // -------------------------- OTHER METHODS --------------------------
495 
496    public synchronized void log(final String msg)
497    {
498       if( instance == null || instance.logArea == null ) 
499       {
500          logCache.add( msg );
501          return;
502       }
503       
504       if (SwingUtilities.isEventDispatchThread())
505       {
506          instance.logArea.setText(instance.logArea.getText() + msg + "\r\n");
507       }
508       else
509       {
510          SwingUtilities.invokeLater(new Runnable()
511          {
512             public void run()
513             {
514                instance.logArea.setText(instance.logArea.getText() + msg + "\r\n");
515             }
516          });
517       }
518    }
519 
520    // -------------------------- INNER CLASSES --------------------------
521 
522    private static class SoapUITheme extends SkyBluerTahoma
523    {
524       public ColorUIResource getControl()
525       {
526          return new ColorUIResource(new Color(238, 237, 237));
527       }
528    }
529 
530    // --------------------------- main() method ---------------------------
531 
532    public static void main(String[] args) throws Exception
533    {
534       SoapUI soapUI = new SoapUI();
535       Workspace workspace = null;
536 
537       if (args.length > 0)
538          workspace = WorkspaceFactory.getInstance().openWorkspace(args);
539       else
540          workspace = WorkspaceFactory.getInstance().openWorkspace(new String[] { System.getProperty("user.home") + 
541          		File.separatorChar + DEFAULT_WORKSPACE_FILE });
542 
543       soapUI.show(workspace);
544    }
545 
546    private void show(Workspace workspace)
547    {
548       this.workspace = workspace;
549       buildUI();
550 
551       frame.setVisible(true);
552    }
553 
554    public class TreeMouseListener extends MouseAdapter
555    {
556       public void mouseClicked(MouseEvent e)
557       {
558          if (e.isPopupTrigger())
559             showPopup(e);
560          else if (e.getClickCount() < 2)
561             return;
562          if (mainTree.getSelectionCount() != 1)
563             return;
564 
565          TreePath path = mainTree.getSelectionPath();
566          SoapUITreeNode node = (SoapUITreeNode) path.getLastPathComponent();
567          
568          showTab( node.getModelItem() );
569       }
570 
571       public void mousePressed(MouseEvent e)
572       {
573          if (e.isPopupTrigger())
574             showPopup(e);
575       }
576 
577       public void mouseReleased(MouseEvent e)
578       {
579          if (e.isPopupTrigger())
580             showPopup(e);
581       }
582 
583       private void showPopup(MouseEvent e)
584       {
585          TreePath path = mainTree.getPathForLocation((int) e.getPoint().getX(), (int) e.getPoint().getY());
586          if (path == null)
587             return;
588          SoapUITreeNode node = (SoapUITreeNode) path.getLastPathComponent();
589 
590          JPopupMenu popupMenu = node.getPopup();
591          if (popupMenu == null)
592             return;
593 
594          mainTree.setSelectionPath(path);
595 
596          popupMenu.show(mainTree, e.getX(), e.getY());
597       }
598    }
599 
600    public class CloseCurrentAction extends AbstractAction
601    {
602       public CloseCurrentAction()
603       {
604          super("Close Current");
605          putValue(Action.SHORT_DESCRIPTION, "Closes the current window");
606          putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_R, KeyEvent.CTRL_MASK ));
607 
608       }
609 
610       public void actionPerformed(ActionEvent e)
611       {
612          JInternalFrame frame = desktopPane.getSelectedFrame();
613          if( frame != null )
614             closeTab( contentPanelToModelItemMap.get(frame));
615       }
616    }
617 
618    public class CloseOtherAction extends AbstractAction
619    {
620       public CloseOtherAction()
621       {
622          super("Close others");
623          putValue(Action.SHORT_DESCRIPTION, "Closes all windows except the current one");
624          putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_O, KeyEvent.CTRL_MASK ));
625          
626       }
627 
628       public void actionPerformed(ActionEvent e)
629       {
630          JInternalFrame frame = desktopPane.getSelectedFrame();
631          if( frame == null )
632             return;
633 
634          Iterator<JInternalFrame> i = contentPanelToModelItemMap.keySet().iterator();
635          while( i.hasNext() )
636          {
637             JInternalFrame f = i.next();
638             if( f != frame ) 
639             {
640                closeTab( contentPanelToModelItemMap.get( f ) );
641                i = contentPanelToModelItemMap.keySet().iterator();
642             }
643          }
644       }
645    }
646 
647    public class CloseAllAction extends AbstractAction
648    {
649       public CloseAllAction()
650       {
651          super("Close all");
652          putValue( Action.SHORT_DESCRIPTION, "Closes all windows");
653          putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_L, KeyEvent.CTRL_MASK ));
654       }
655 
656       public void actionPerformed(ActionEvent e)
657       {
658          while( contentPanelToModelItemMap.size() > 0 )
659          {
660             Iterator<JInternalFrame> i = contentPanelToModelItemMap.keySet().iterator();
661             try
662             {
663                i.next().setClosed( true );
664             }
665             catch (PropertyVetoException e1)
666             {
667                e1.printStackTrace();
668             }
669          }
670          
671          contentPanelToModelItemMap.clear();
672          modelItemToDesktopFrameMap.clear();
673          modelItemToDesktopPanelMap.clear();
674       }
675    }
676 
677    public class InternalTreeSelectionListener implements TreeSelectionListener
678    {
679       public void valueChanged(TreeSelectionEvent e)
680       {
681          Object obj = e.getPath().getLastPathComponent();
682          if (obj instanceof SoapUITreeNode)
683          {
684             SoapUITreeNode treeNode = (SoapUITreeNode) obj;
685             setOverviewPanel(treeNode.getOverviewPanel());
686          }
687       }
688    }
689 
690    public Workspace getWorkspace()
691    {
692       return workspace;
693    }
694    
695    public boolean hasTab( ModelItem modelItem )
696    {
697       return modelItemToDesktopFrameMap.containsKey( modelItem );
698    }
699    
700    public JInternalFrame showTab( ModelItem modelItem )
701    {
702       PanelBuilder panelBuilder = modelItem.getPanelBuilder();
703       if (modelItemToDesktopFrameMap.containsKey(modelItem))
704       {
705          JInternalFrame frame = modelItemToDesktopFrameMap.get(modelItem);
706          try
707          {
708             desktopPane.getDesktopManager().deiconifyFrame( frame );
709             frame.setSelected( true );
710             frame.moveToFront();
711             return frame;
712          }
713          catch (PropertyVetoException e)
714          {
715             e.printStackTrace();
716          }
717       }
718       else if (panelBuilder != null && panelBuilder.canBuildDesktopPanel())
719       {
720          String title = panelBuilder.getDesktopPanelTitle();
721          DesktopPanel desktopPanel = panelBuilder.buildDesktopPanel();
722 			JComponent panel = desktopPanel.getComponent();
723          panel.setOpaque( true );
724          
725          JInternalFrame frame = createContentFrame(title, panel);
726 
727          desktopPane.add( frame );
728          try
729          {
730             frame.setSelected( true );
731          }
732          catch (PropertyVetoException e)
733          {
734             e.printStackTrace();
735          }
736          
737          modelItemToDesktopFrameMap.put(modelItem, frame);
738          modelItemToDesktopPanelMap.put( modelItem, desktopPanel );
739          contentPanelToModelItemMap.put(frame, modelItem);
740          
741          desktopPanels.addElement( modelItem );
742          desktopPanelsList.setSelectedValue( modelItem, false );
743          modelItem.addPropertyChangeListener( modelItemPropertyListener );
744          
745          return frame;
746       }
747       else Toolkit.getDefaultToolkit().beep();
748       
749       return null;
750    }
751 
752    private JInternalFrame createContentFrame(String title, JComponent panel)
753    {
754       JInternalFrame frame = new JInternalFrame(title, true, true, true, true );
755       frame.addInternalFrameListener( internalFrameListener );
756       frame.setContentPane( panel );
757       frame.setSize( panel.getPreferredSize() );
758       frame.setVisible( true );
759       frame.setDefaultCloseOperation( JInternalFrame.DO_NOTHING_ON_CLOSE );
760       frame.setLocation(xOffset*(openFrameCount%10), yOffset*(openFrameCount%10));
761       frame.setBorder( BorderFactory.createCompoundBorder(
762             BorderFactory.createRaisedBevelBorder(), BorderFactory.createEmptyBorder( 2, 2, 2, 2 )));
763       openFrameCount++;
764       return frame;
765    }
766 
767    public void closeTab(ModelItem modelItem)
768    {
769       if (modelItemToDesktopFrameMap.containsKey(modelItem))
770       {
771       	JInternalFrame frame = modelItemToDesktopFrameMap.get(modelItem);
772       	frame.doDefaultCloseAction();
773       }
774    }
775    
776    public Project getCurrentProject()
777    {
778       TreePath path = mainTree.getSelectionPath();
779       Object node = (Object) path.getLastPathComponent();
780       while (node != null && !(node instanceof ProjectTreeNode))
781       {
782          path = path.getParentPath();
783          node = (path == null ? null : path.getLastPathComponent());
784       }
785 
786       if (node == null)
787          return null;
788 
789       return ((ProjectTreeNode) node).getProject();
790    }
791    
792    /*** Returns an ImageIcon, or null if the path was invalid. */
793    public static ImageIcon createImageIcon(String path) {
794        java.net.URL imgURL = SoapUI.class.getResource(path);
795        if (imgURL != null) {
796            return new ImageIcon(imgURL);
797        } else {
798            System.err.println("Couldn't find icon file: " + path);
799            return null;
800        }
801    }
802    
803    private class ExitAction extends AbstractAction
804    {
805       public ExitAction()
806       {
807          super( "Exit" );
808          putValue( Action.SHORT_DESCRIPTION, "Saves all projects and exits SoapUI" );
809          putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_Q, KeyEvent.CTRL_MASK ));
810       }
811 
812       public void actionPerformed(ActionEvent e)
813       {
814          frame.dispatchEvent( new WindowEvent( frame, WindowEvent.WINDOW_CLOSING ));
815       }
816    }
817 
818    private class SaveAction extends AbstractAction
819    {
820       public SaveAction()
821       {
822          super( "Save all" );
823          putValue( Action.SHORT_DESCRIPTION, "Saves all projects" );
824          putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_S, KeyEvent.CTRL_MASK ));
825       }
826 
827       public void actionPerformed(ActionEvent e)
828       {
829          workspace.save();
830       }
831    }
832    
833    
834    private class InternalDesktopFrameListener extends InternalFrameAdapter
835    {
836       public void internalFrameClosing(InternalFrameEvent e)
837       {
838       	ModelItem modelItem = contentPanelToModelItemMap.get( e.getInternalFrame() );
839       	DesktopPanel desktopPanel = modelItemToDesktopPanelMap.get( modelItem );
840       	if( !desktopPanel.onClose() )
841       	{
842       		return;
843       	}
844       	
845          modelItemToDesktopFrameMap.remove( modelItem );
846          modelItemToDesktopPanelMap.remove( modelItem );
847          contentPanelToModelItemMap.remove( e.getInternalFrame() );
848          desktopPanels.removeElement( modelItem );
849          
850          e.getInternalFrame().dispose();
851       }
852 
853       public void internalFrameActivated(InternalFrameEvent e)
854       {
855          ModelItem modelItem = contentPanelToModelItemMap.get( e.getInternalFrame() );
856          if( modelItem != null )
857          {
858             desktopPanelsList.setSelectedValue( modelItem, false );
859             selectModelItem( modelItem );
860          }
861       }
862    }
863 
864    private class DesktopItemsCellRenderer extends JLabel implements ListCellRenderer 
865    {
866       public Component getListCellRendererComponent(
867         JList list,
868         Object value,           
869         int index,               
870         boolean isSelected,      
871         boolean cellHasFocus)    
872       {
873           ModelItem modelItem = (ModelItem) value;
874           
875           String title = modelItemToDesktopFrameMap.get( modelItem ).getTitle();
876           setText( title );
877           
878           setToolTipText( title );
879           setIcon( modelItem.getIcon() );
880           
881           if (isSelected) 
882           {
883              setBackground(list.getSelectionBackground());
884              setForeground(list.getSelectionForeground());
885           }
886           else 
887           {
888              setBackground(list.getBackground());
889              setForeground(list.getForeground());
890           }
891           
892           setEnabled(list.isEnabled());
893           setFont(list.getFont());
894           setOpaque(true);
895           
896           return this;
897       }
898    }
899    
900    private class ModelItemPropertyChangeListener implements PropertyChangeListener
901    {
902       public void propertyChange(PropertyChangeEvent evt)
903       {
904          ModelItem modelItem = (ModelItem) evt.getSource();
905          int ix = desktopPanels.indexOf( modelItem );
906          if( ix >= 0 )
907             desktopPanels.set( ix, modelItem );
908       }}
909 
910    public void selectModelItem(ModelItem modelItem)
911    {
912       TreePath path = treeModel.getPath( modelItem );
913       mainTree.setSelectionPath( path );
914       mainTree.scrollPathToVisible( path );
915    }
916 
917    public SoapUITreeModel getTreeModel()
918    {
919       return treeModel;
920    }
921 
922 }