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