View Javadoc

1   /*
2    $Id: SwingBuilder.java,v 1.13 2005/06/10 09:55:30 cstein Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.swing;
47  
48  import groovy.lang.Closure;
49  import groovy.lang.MissingMethodException;
50  
51  import groovy.model.DefaultTableModel;
52  import groovy.model.ValueHolder;
53  import groovy.model.ValueModel;
54  
55  import groovy.swing.impl.ComponentFacade;
56  import groovy.swing.impl.ContainerFacade;
57  import groovy.swing.impl.DefaultAction;
58  import groovy.swing.impl.Factory;
59  import groovy.swing.impl.Startable;
60  import groovy.swing.impl.TableLayout;
61  import groovy.swing.impl.TableLayoutCell;
62  import groovy.swing.impl.TableLayoutRow;
63  
64  import groovy.util.BuilderSupport;
65  
66  import java.awt.BorderLayout;
67  import java.awt.CardLayout;
68  import java.awt.Component;
69  import java.awt.Container;
70  import java.awt.Dimension;
71  import java.awt.Dialog;
72  import java.awt.FlowLayout;
73  import java.awt.Frame;
74  import java.awt.GridBagConstraints;
75  import java.awt.GridBagLayout;
76  import java.awt.GridLayout;
77  import java.awt.LayoutManager;
78  import java.awt.Window;
79  
80  import java.text.Format;
81  
82  import java.util.ArrayList;
83  import java.util.Collections;
84  import java.util.HashMap;
85  import java.util.Iterator;
86  import java.util.LinkedList;
87  import java.util.List;
88  import java.util.Map;
89  import java.util.Vector;
90  import java.util.logging.Level;
91  import java.util.logging.Logger;
92  
93  import javax.swing.AbstractButton;
94  import javax.swing.Action;
95  import javax.swing.Box;
96  import javax.swing.BoxLayout;
97  import javax.swing.ButtonGroup;
98  import javax.swing.DefaultBoundedRangeModel;
99  import javax.swing.JButton;
100 import javax.swing.JCheckBox;
101 import javax.swing.JCheckBoxMenuItem;
102 import javax.swing.JColorChooser;
103 import javax.swing.JComboBox;
104 import javax.swing.JComponent;
105 import javax.swing.JDesktopPane;
106 import javax.swing.JDialog;
107 import javax.swing.JEditorPane;
108 import javax.swing.JFileChooser;
109 import javax.swing.JFormattedTextField;
110 import javax.swing.JFrame;
111 import javax.swing.JInternalFrame;
112 import javax.swing.JLabel;
113 import javax.swing.JLayeredPane;
114 import javax.swing.JList;
115 import javax.swing.JMenu;
116 import javax.swing.JMenuBar;
117 import javax.swing.JMenuItem;
118 import javax.swing.JOptionPane;
119 import javax.swing.JPanel;
120 import javax.swing.JPasswordField;
121 import javax.swing.JPopupMenu;
122 import javax.swing.JProgressBar;
123 import javax.swing.JRadioButton;
124 import javax.swing.JRadioButtonMenuItem;
125 import javax.swing.JScrollBar;
126 import javax.swing.JScrollPane;
127 import javax.swing.JSeparator;
128 import javax.swing.JSlider;
129 import javax.swing.JSpinner;
130 import javax.swing.JSplitPane;
131 import javax.swing.JTabbedPane;
132 import javax.swing.JTable;
133 import javax.swing.JTextArea;
134 import javax.swing.JTextField;
135 import javax.swing.JTextPane;
136 import javax.swing.JToggleButton;
137 import javax.swing.JToolBar;
138 import javax.swing.JToolTip;
139 import javax.swing.JTree;
140 import javax.swing.JViewport;
141 import javax.swing.JWindow;
142 import javax.swing.KeyStroke;
143 import javax.swing.OverlayLayout;
144 import javax.swing.RootPaneContainer;
145 import javax.swing.SpinnerDateModel;
146 import javax.swing.SpinnerListModel;
147 import javax.swing.SpinnerNumberModel;
148 import javax.swing.SpringLayout;
149 import javax.swing.table.TableColumn;
150 import javax.swing.table.TableModel;
151 
152 import org.codehaus.groovy.runtime.InvokerHelper;
153 
154 /***
155  * A helper class for creating Swing widgets using GroovyMarkup
156  * 
157  * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
158  * @version $Revision: 1.13 $
159  */
160 public class SwingBuilder extends BuilderSupport {
161 
162     private Logger log = Logger.getLogger(getClass().getName());
163     private Map factories = new HashMap();
164     private Object constraints;
165     private Map passThroughNodes = new HashMap();
166     private Map widgets = new HashMap();
167     // tracks all containing windows, for auto-owned dialogs
168     private LinkedList containingWindows = new LinkedList();
169 
170     public SwingBuilder() {
171         registerWidgets();
172     }
173 
174     public Object getProperty(String name) {
175         Object widget = widgets.get(name);
176         if (widget == null) {
177             return super.getProperty(name);
178         }
179         return widget;
180     }
181 
182     protected void setParent(Object parent, Object child) {
183         if (child instanceof Action) {
184             Action action = (Action) child;
185             try {
186                 InvokerHelper.setProperty(parent, "action", action);
187             } catch (RuntimeException re) {
188                 // must not have an action property...
189                 // so we ignore it and go on
190             }
191             Object keyStroke = action.getValue("KeyStroke");
192             //System.out.println("keystroke: " + keyStroke + " for: " + action);
193             if (parent instanceof JComponent) {
194                 JComponent component = (JComponent) parent;
195                 KeyStroke stroke = null;
196                 if (keyStroke instanceof String) {
197                     stroke = KeyStroke.getKeyStroke((String) keyStroke);
198                 }
199                 else if (keyStroke instanceof KeyStroke) {
200                     stroke = (KeyStroke) keyStroke;
201                 }
202                 if (stroke != null) {
203                     String key = action.toString();
204                     component.getInputMap().put(stroke, key);
205                     component.getActionMap().put(key, action);
206                 }
207             }
208         }
209         else if (child instanceof LayoutManager) {
210             if (parent instanceof RootPaneContainer) {
211                 RootPaneContainer rpc = (RootPaneContainer) parent;
212                 parent = rpc.getContentPane();
213             }
214             InvokerHelper.setProperty(parent, "layout", child);
215         }
216         else if (child instanceof JToolTip && parent instanceof JComponent) {
217             ((JToolTip)child).setComponent((JComponent)parent);
218         }
219         else if (parent instanceof JTable && child instanceof TableColumn) {
220             JTable table = (JTable) parent;
221             TableColumn column = (TableColumn) child;
222             table.addColumn(column);
223         }
224         else if (parent instanceof JTabbedPane && child instanceof Component) {
225             JTabbedPane tabbedPane = (JTabbedPane) parent;
226             tabbedPane.add((Component)child);
227         } 
228         else if (child instanceof Window) {
229             // do nothing.  owner of window is set elsewhere, and this 
230             // shouldn't get added to any parent as a child 
231             // if it is a top level component anyway
232         }
233         else { 
234             Component component = null;
235             if (child instanceof Component) {
236                 component = (Component) child;
237             }
238             else if (child instanceof ComponentFacade) {
239                 ComponentFacade facade = (ComponentFacade) child;
240                 component = facade.getComponent();
241             }
242             if (component != null) {
243                 if (parent instanceof JFrame && component instanceof JMenuBar) {
244                     JFrame frame = (JFrame) parent;
245                     frame.setJMenuBar((JMenuBar) component);
246                 }
247                 else if (parent instanceof RootPaneContainer) {
248                     RootPaneContainer rpc = (RootPaneContainer) parent;
249                     rpc.getContentPane().add(component);
250                 }
251                 else if (parent instanceof JScrollPane) {
252                     JScrollPane scrollPane = (JScrollPane) parent;
253                     if (child instanceof JViewport) {
254                         scrollPane.setViewport((JViewport)component);
255                     } 
256                     else {
257                         scrollPane.setViewportView(component);
258                     }
259                 }
260                 else if (parent instanceof JSplitPane) {
261                     JSplitPane splitPane = (JSplitPane) parent;
262                     if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
263                         if (splitPane.getTopComponent() == null) {
264                             splitPane.setTopComponent(component);
265                         }
266                         else {
267                             splitPane.setBottomComponent(component);
268                         }
269                     }
270                     else {
271                         if (splitPane.getLeftComponent() == null) {
272                             splitPane.setLeftComponent(component);
273                         }
274                         else {
275                             splitPane.setRightComponent(component);
276                         }
277                     }
278                 }
279                 else if (parent instanceof JMenuBar && component instanceof JMenu) {
280                     JMenuBar menuBar = (JMenuBar) parent;
281                     menuBar.add((JMenu) component);
282                 }
283                 else if (parent instanceof Container) {
284                     Container container = (Container) parent;
285                     if (constraints != null) {
286                         container.add(component, constraints);
287                     }
288                     else {
289                         container.add(component);
290                     }
291                 }
292                 else if (parent instanceof ContainerFacade) {
293                     ContainerFacade facade = (ContainerFacade) parent;
294                     facade.addComponent(component);
295                 }
296             }
297         }
298     }
299 
300     protected void nodeCompleted(Object parent, Object node) {
301         // set models after the node has been completed
302         if (node instanceof TableModel && parent instanceof JTable) {
303             JTable table = (JTable) parent;
304             TableModel model = (TableModel) node;
305             table.setModel(model);
306         }
307         if (node instanceof Startable) {
308             Startable startable = (Startable) node;
309             startable.start();
310         }
311         if (node instanceof Window) {
312             if (!containingWindows.isEmpty() && containingWindows.getLast() == node) {
313                 containingWindows.removeLast();
314             }
315         }
316     }
317 
318     protected Object createNode(Object name) {
319         return createNode(name, Collections.EMPTY_MAP);
320     }
321 
322     protected Object createNode(Object name, Object value) {
323         if (passThroughNodes.containsKey(name) && (value != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(value.getClass())) {
324             // value may need to go into containing windows list
325             if (value instanceof Window) {
326                 containingWindows.add(value);
327             }
328             return value;
329         }
330         else if (value instanceof String) {
331             Object widget = createNode(name);
332             if (widget != null) {
333                 InvokerHelper.invokeMethod(widget, "setText", value);
334             }
335             return widget;
336         }
337         else {
338         	throw new MissingMethodException((String) name, getClass(), new Object[] {value});
339         }
340     }
341 
342     protected Object createNode(Object name, Map attributes, Object value) {
343         if (passThroughNodes.containsKey(name) && (value != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(value.getClass())) {
344             // value may need to go into containing windows list
345             if (value instanceof Window) {
346                 containingWindows.add(value);
347             }
348             handleWidgetAttributes(value, attributes);
349             return value;
350         }
351         else { 
352             Object widget = createNode(name, attributes);
353             if (widget != null) {
354                 InvokerHelper.invokeMethod(widget, "setText", value.toString());
355             }
356             return widget;
357         }
358     }
359     
360     protected Object createNode(Object name, Map attributes) {
361         String widgetName = (String) attributes.remove("id");
362         constraints = attributes.remove("constraints");
363         Object widget = null;
364         if (passThroughNodes.containsKey(name)) {
365             widget = attributes.get(name);
366             if ((widget != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(widget.getClass())) {
367                 // value may need to go into containing windows list
368                 if (widget instanceof Window) {
369                     containingWindows.add(widget);
370                 }
371                 attributes.remove(name);
372             }
373             else {
374                 widget = null;
375             }
376         }
377         if (widget == null) {
378             Factory factory = (Factory) factories.get(name);
379             if (factory != null) {
380                 try {
381                     widget = factory.newInstance(attributes);
382                     if (widgetName != null) {
383                         widgets.put(widgetName, widget);
384                     }
385                     if (widget == null) {
386                         log.log(Level.WARNING, "Factory for name: " + name + " returned null");
387                     }
388                     else {
389                         if (log.isLoggable(Level.FINE)) {
390                             log.fine("For name: " + name + " created widget: " + widget);
391                         }
392                     }
393                 }
394                 catch (Exception e) {
395                     throw new RuntimeException("Failed to create component for" + name + " reason: " + e, e);
396                 }
397             }
398             else {
399                 log.log(Level.WARNING, "Could not find match for name: " + name);
400             }
401         }
402         handleWidgetAttributes(widget, attributes);
403         return widget;
404     }
405 
406     protected void handleWidgetAttributes(Object widget, Map attributes) {
407         if (widget != null) {
408             if (widget instanceof Action) {
409                 /*** @todo we could move this custom logic into the MetaClass for Action */
410                 Action action = (Action) widget;
411 
412                 Closure closure = (Closure) attributes.remove("closure");
413                 if (closure != null && action instanceof DefaultAction) {
414                     DefaultAction defaultAction = (DefaultAction) action;
415                     defaultAction.setClosure(closure);
416                 }
417 
418                 Object accel = attributes.remove("accelerator");
419                 KeyStroke stroke = null;
420                 if (accel instanceof KeyStroke) {
421                     stroke = (KeyStroke) accel;
422                 } else if (accel != null) {
423                     stroke = KeyStroke.getKeyStroke(accel.toString());
424                 }
425                 action.putValue(Action.ACCELERATOR_KEY, stroke);
426 
427                 Object mnemonic = attributes.remove("mnemonic");
428                 if ((mnemonic != null) && !(mnemonic instanceof Number)) {
429                     mnemonic = new Integer(mnemonic.toString().charAt(0));
430                 }
431                 action.putValue(Action.MNEMONIC_KEY, mnemonic);
432 
433                 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
434                     Map.Entry entry = (Map.Entry) iter.next();
435                     String actionName = (String) entry.getKey();
436 
437                     // typically standard Action names start with upper case, so lets upper case it            
438                     actionName = capitalize(actionName);
439                     Object value = entry.getValue();
440 
441                     action.putValue(actionName, value);
442                 }
443 
444             }
445             else {
446                 // some special cases...
447                 if (attributes.containsKey("buttonGroup")) {
448                     Object o = attributes.get("buttonGroup");
449                     if ((o instanceof ButtonGroup) && (widget instanceof AbstractButton)) {
450                         ((AbstractButton)widget).getModel().setGroup((ButtonGroup)o);
451                         attributes.remove("buttonGroup");
452                     }
453                 }
454 
455                 // this next statement nd if/else is a workaround until GROOVY-305 is fixed
456                 Object mnemonic = attributes.remove("mnemonic");
457                 if ((mnemonic != null) && (mnemonic instanceof Number)) {
458                     InvokerHelper.setProperty(widget, "mnemonic", new Character((char)((Number)mnemonic).intValue()));
459                 } 
460                 else if (mnemonic != null) {
461                     InvokerHelper.setProperty(widget, "mnemonic", new Character(mnemonic.toString().charAt(0)));
462                 } 
463 
464                 // set the properties
465                 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
466                     Map.Entry entry = (Map.Entry) iter.next();
467                     String property = entry.getKey().toString();
468                     Object value = entry.getValue();
469                     InvokerHelper.setProperty(widget, property, value);
470                 }
471             }
472         }
473     }
474 
475     protected String capitalize(String text) {
476         char ch = text.charAt(0);
477         if (Character.isUpperCase(ch)) {
478             return text;
479         }
480         StringBuffer buffer = new StringBuffer(text.length());
481         buffer.append(Character.toUpperCase(ch));
482         buffer.append(text.substring(1));
483         return buffer.toString();
484     }
485 
486     protected void registerWidgets() {
487         //
488         // non-widget support classes
489         //
490         registerBeanFactory("action", DefaultAction.class);
491         passThroughNodes.put("action", javax.swing.Action.class);
492         registerBeanFactory("buttonGroup", ButtonGroup.class);
493         registerFactory("map", new Factory() {
494             public Object newInstance(Map properties)
495                 throws InstantiationException, InstantiationException, IllegalAccessException {
496                 return properties;
497             }
498         });
499         // ulimate pass through type
500         passThroughNodes.put("widget", java.awt.Component.class);
501 
502         //
503         // standalone window classes
504         //
505         registerFactory("dialog", new Factory() {
506             public Object newInstance(Map properties)
507                 throws InstantiationException, InstantiationException, IllegalAccessException {
508                 return createDialog(properties);
509             }
510         });
511         registerFactory("frame", new Factory() {
512             public Object newInstance(Map properties)
513                 throws InstantiationException, InstantiationException, IllegalAccessException {
514                 return createFrame(properties);
515             }
516         });
517         registerBeanFactory("fileChooser", JFileChooser.class);
518         registerFactory("frame", new Factory() {
519             public Object newInstance(Map properties)
520                 throws InstantiationException, InstantiationException, IllegalAccessException {
521                 return createFrame(properties);
522             }
523         });
524         registerBeanFactory("optionPane", JOptionPane.class);
525         registerFactory("window", new Factory() {
526             public Object newInstance(Map properties)
527                 throws InstantiationException, InstantiationException, IllegalAccessException {
528                 return createWindow(properties);
529             }
530         });
531         
532         //
533         // widgets
534         //
535         registerBeanFactory("button", JButton.class);
536         registerBeanFactory("checkBox", JCheckBox.class);
537         registerBeanFactory("checkBoxMenuItem", JCheckBoxMenuItem.class);
538         registerBeanFactory("colorChooser", JColorChooser.class);
539         registerFactory("comboBox", new Factory() {
540             public Object newInstance(Map properties)
541                 throws InstantiationException, InstantiationException, IllegalAccessException {
542                 return createComboBox(properties);
543             }
544         });
545         registerBeanFactory("desktopPane", JDesktopPane.class);
546         registerBeanFactory("editorPane", JEditorPane.class);
547         registerFactory("formattedTextField", new Factory() {
548             public Object newInstance(Map properties)
549                 throws InstantiationException, InstantiationException, IllegalAccessException {
550                 return createFormattedTextField(properties);
551             }
552         });
553         registerBeanFactory("internalFrame", JInternalFrame.class);
554         registerBeanFactory("label", JLabel.class);
555         registerBeanFactory("layeredPane", JLayeredPane.class);
556         registerBeanFactory("list", JList.class);
557         registerBeanFactory("menu", JMenu.class);
558         registerBeanFactory("menuBar", JMenuBar.class);
559         registerBeanFactory("menuItem", JMenuItem.class);
560         registerBeanFactory("panel", JPanel.class);
561         registerBeanFactory("passwordField", JPasswordField.class);
562         registerBeanFactory("popupMenu", JPopupMenu.class);
563         registerBeanFactory("progressBar", JProgressBar.class);
564         registerBeanFactory("radioButton", JRadioButton.class);
565         registerBeanFactory("radioButtonMenuItem", JRadioButtonMenuItem.class);
566         registerBeanFactory("scrollBar", JScrollBar.class);
567         registerBeanFactory("scrollPane", JScrollPane.class);
568         registerBeanFactory("separator", JSeparator.class);
569         registerBeanFactory("slider", JSlider.class);
570         registerBeanFactory("spinner", JSpinner.class);
571         registerFactory("splitPane", new Factory() {
572             public Object newInstance(Map properties) {
573                 JSplitPane answer = new JSplitPane();
574                 answer.setLeftComponent(null);
575                 answer.setRightComponent(null);
576                 answer.setTopComponent(null);
577                 answer.setBottomComponent(null);
578                 return answer;
579             }
580         });
581         registerBeanFactory("tabbedPane", JTabbedPane.class);
582         registerBeanFactory("table", JTable.class);
583         registerBeanFactory("textArea", JTextArea.class);
584         registerBeanFactory("textPane", JTextPane.class);
585         registerBeanFactory("textField", JTextField.class);
586         registerBeanFactory("toggleButton", JToggleButton.class);
587         registerBeanFactory("toolBar", JToolBar.class);
588         //registerBeanFactory("tooltip", JToolTip.class); // doens't work, user toolTipText property
589         registerBeanFactory("tree", JTree.class);
590         registerBeanFactory("viewport", JViewport.class); // sub class?
591 
592         //
593         // MVC models   
594         //
595         registerBeanFactory("boundedRangeModel", DefaultBoundedRangeModel.class);
596 
597         // spinner models
598 	registerBeanFactory("spinnerDateModel", SpinnerDateModel.class);
599         registerBeanFactory("spinnerListModel", SpinnerListModel.class);
600         registerBeanFactory("spinnerNumberModel", SpinnerNumberModel.class);
601 
602 	// table models
603         registerFactory("tableModel", new Factory() {
604             public Object newInstance(Map properties) {
605                 ValueModel model = (ValueModel) properties.remove("model");
606                 if (model == null) {
607                     Object list = properties.remove("list");
608                     if (list == null) {
609                         list = new ArrayList();
610                     }
611                     model = new ValueHolder(list);
612                 }
613                 return new DefaultTableModel(model);
614             }
615         });
616         passThroughNodes.put("tableModel", javax.swing.table.TableModel.class);
617 
618         registerFactory("propertyColumn", new Factory() {
619             public Object newInstance(Map properties) {
620                 Object current = getCurrent();
621                 if (current instanceof DefaultTableModel) {
622                     DefaultTableModel model = (DefaultTableModel) current;
623                     Object header = properties.remove("header");
624                     if (header == null) {
625                         header = "";
626                     }
627                     String property = (String) properties.remove("propertyName");
628                     if (property == null) {
629                         throw new IllegalArgumentException("Must specify a property for a propertyColumn");
630                     }
631                     Class type = (Class) properties.remove("type");
632                     if (type == null) {
633                         type = Object.class;
634                     }
635                     return model.addPropertyColumn(header, property, type);
636                 }
637                 else {
638                     throw new RuntimeException("propertyColumn must be a child of a tableModel");
639                 }
640             }
641         });
642 
643         registerFactory("closureColumn", new Factory() {
644             public Object newInstance(Map properties) {
645                 Object current = getCurrent();
646                 if (current instanceof DefaultTableModel) {
647                     DefaultTableModel model = (DefaultTableModel) current;
648                     Object header = properties.remove("header");
649                     if (header == null) {
650                         header = "";
651                     }
652                     Closure readClosure = (Closure) properties.remove("read");
653                     if (readClosure == null) {
654                         throw new IllegalArgumentException("Must specify 'read' Closure property for a closureColumn");
655                     }
656                     Closure writeClosure = (Closure) properties.remove("write");
657                     Class type = (Class) properties.remove("type");
658                     if (type == null) {
659                         type = Object.class;
660                     }
661                     return model.addClosureColumn(header, readClosure, writeClosure, type);
662                 }
663                 else {
664                     throw new RuntimeException("propertyColumn must be a child of a tableModel");
665                 }
666             }
667         });
668 
669 
670         //Standard Layouts
671         registerBeanFactory("borderLayout", BorderLayout.class);
672         registerBeanFactory("cardLayout", CardLayout.class);
673         registerBeanFactory("flowLayout", FlowLayout.class);
674         registerBeanFactory("gridBagLayout", GridBagLayout.class);
675         registerBeanFactory("gridLayout", GridLayout.class);
676         registerBeanFactory("overlayLayout", OverlayLayout.class);
677         registerBeanFactory("springLayout", SpringLayout.class);
678         registerBeanFactory("gridBagConstarints", GridBagConstraints.class);
679         registerBeanFactory("gbc", GridBagConstraints.class); // shortcut name
680 
681         // box layout
682         registerFactory("boxLayout", new Factory() {
683             public Object newInstance(Map properties)
684                 throws InstantiationException, InstantiationException, IllegalAccessException {
685                 return createBoxLayout(properties);
686             }
687         });
688 
689         // Box related layout components
690         registerFactory("hbox", new Factory() {
691             public Object newInstance(Map properties) {
692                 return Box.createHorizontalBox();
693             }
694         });
695         registerFactory("hglue", new Factory() {
696             public Object newInstance(Map properties) {
697                 return Box.createHorizontalGlue();
698             }
699         });
700         registerFactory("hstrut", new Factory() {
701             public Object newInstance(Map properties) {
702                 try {
703                 Object num = properties.remove("width");
704                 if (num instanceof Number) {
705                     return Box.createHorizontalStrut(((Number)num).intValue());
706                 } else {
707                     return Box.createHorizontalStrut(6);
708                 }
709                 } catch (RuntimeException re) {
710                     re.printStackTrace(System.out);
711                     throw re;
712                 }
713             }
714         });
715         registerFactory("vbox", new Factory() {
716             public Object newInstance(Map properties) {
717                 return Box.createVerticalBox();
718             }
719         });
720         registerFactory("vglue", new Factory() {
721             public Object newInstance(Map properties) {
722                 return Box.createVerticalGlue();
723             }
724         });
725         registerFactory("vstrut", new Factory() {
726             public Object newInstance(Map properties) {
727                 Object num = properties.remove("height");
728                 if (num instanceof Number) {
729                     return Box.createVerticalStrut(((Number)num).intValue());
730                 } else {
731                     return Box.createVerticalStrut(6);
732                 }
733             }
734         });
735         registerFactory("glue", new Factory() {
736             public Object newInstance(Map properties) {
737                 return Box.createGlue();
738             }
739         });
740         registerFactory("rigidArea", new Factory() {
741             public Object newInstance(Map properties) {
742                 Dimension dim;
743                 Object o = properties.remove("size");
744                 if (o instanceof Dimension) {
745                     dim = (Dimension) o;
746                 } else {
747                     int w, h;
748                     o = properties.remove("width");
749                     w = ((o instanceof Number)) ? ((Number)o).intValue() : 6;
750                     o = properties.remove("height");
751                     h = ((o instanceof Number)) ? ((Number)o).intValue() : 6;
752                     dim = new Dimension(w, h);
753                 }
754                 return Box.createRigidArea(dim);
755             }
756         });
757         
758         // table layout
759         registerBeanFactory("tableLayout", TableLayout.class);
760         registerFactory("tr", new Factory() {
761             public Object newInstance(Map properties) {
762                 Object parent = getCurrent();
763                 if (parent instanceof TableLayout) {
764                     return new TableLayoutRow((TableLayout) parent);
765                 }
766                 else {
767                     throw new RuntimeException("'tr' must be within a 'tableLayout'");
768                 }
769             }
770         });
771         registerFactory("td", new Factory() {
772             public Object newInstance(Map properties) {
773                 Object parent = getCurrent();
774                 if (parent instanceof TableLayoutRow) {
775                     return new TableLayoutCell((TableLayoutRow) parent);
776                 }
777                 else {
778                     throw new RuntimeException("'td' must be within a 'tr'");
779                 }
780             }
781         });
782     }
783 
784     protected Object createBoxLayout(Map properties) {
785         Object parent = getCurrent();
786         if (parent instanceof Container) {
787             Object axisObject = properties.remove("axis");
788             int axis = 0;
789             if (axisObject != null) {
790                 Integer i = (Integer) axisObject;
791                 axis = i.intValue();
792             }
793             BoxLayout answer = new BoxLayout((Container) parent, axis);
794             
795             // now lets try set the layout property
796             InvokerHelper.setProperty(parent, "layout", answer);
797             return answer;
798         }
799         else {
800             throw new RuntimeException("Must be nested inside a Container");
801         }
802     }
803 
804     protected Object createDialog(Map properties) {
805         JDialog dialog;
806         Object owner = properties.remove("owner");
807         // if owner not explicit, use the last window type in the list
808         if ((owner == null) && !containingWindows.isEmpty()) {
809             owner = containingWindows.getLast();
810         }
811         if (owner instanceof Frame) {
812             dialog = new JDialog((Frame) owner);
813         }
814         else if (owner instanceof Dialog) {
815             dialog = new JDialog((Dialog) owner);
816         }
817         else {
818             dialog = new JDialog();
819         }
820         containingWindows.add(dialog);
821         return dialog;
822     }
823     
824     /***
825      * Uses 'format," or "value,"  (in order)
826      *
827      */
828     protected Object createFormattedTextField(Map properties) {
829         JFormattedTextField ftf;
830         if (properties.containsKey("format")) {
831             ftf = new JFormattedTextField((Format) properties.remove("format"));
832         }
833         else if (properties.containsKey("value")) {
834             ftf = new JFormattedTextField(properties.remove("value"));
835         }
836         else {
837             ftf = new JFormattedTextField();
838         }
839         return ftf;
840     }
841 
842     protected Object createFrame(Map properties) {
843         JFrame frame = new JFrame();
844         containingWindows.add(frame);
845         return frame;
846     }
847     
848     protected Object createWindow(Map properties) {
849         JWindow window;
850         Object owner = properties.remove("owner");
851         // if owner not explicit, use the last window type in the list
852         if ((owner == null) && !containingWindows.isEmpty()) {
853             owner = containingWindows.getLast();
854         }
855         if (owner instanceof Frame) {
856             window = new JWindow((Frame) owner);
857         }
858         else if (owner instanceof Window) {
859             window = new JWindow((Window) owner);
860         }
861         else {
862             window = new JWindow();
863         }
864         containingWindows.add(window);
865         return window;
866     }
867 
868     protected Object createComboBox(Map properties) {
869         Object items = properties.remove("items");
870         if (items instanceof Vector) {
871             return new JComboBox((Vector) items);
872         }
873         else if (items instanceof List) {
874             List list = (List) items;
875             return new JComboBox(list.toArray());
876         }
877         else if (items instanceof Object[]) {
878             return new JComboBox((Object[]) items);
879         }
880         else {
881             return new JComboBox();
882         }
883     }
884 
885     protected void registerBeanFactory(String name, final Class beanClass) {
886         registerFactory(name, new Factory() {
887             public Object newInstance(Map properties) throws InstantiationException, IllegalAccessException {
888                 return beanClass.newInstance();
889             }
890         });
891 
892     }
893 
894     protected void registerFactory(String name, Factory factory) {
895         factories.put(name, factory);
896     }
897 }