View Javadoc

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