001    /*
002     $Id: SwingBuilder.java,v 1.17 2005/10/25 12:32:11 alang Exp $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package groovy.swing;
047    
048    import groovy.lang.Closure;
049    import groovy.lang.MissingMethodException;
050    
051    import groovy.model.DefaultTableModel;
052    import groovy.model.ValueHolder;
053    import groovy.model.ValueModel;
054    
055    import groovy.swing.impl.ComponentFacade;
056    import groovy.swing.impl.ContainerFacade;
057    import groovy.swing.impl.DefaultAction;
058    import groovy.swing.impl.Factory;
059    import groovy.swing.impl.Startable;
060    import groovy.swing.impl.TableLayout;
061    import groovy.swing.impl.TableLayoutCell;
062    import groovy.swing.impl.TableLayoutRow;
063    
064    import groovy.util.BuilderSupport;
065    
066    import java.awt.BorderLayout;
067    import java.awt.CardLayout;
068    import java.awt.Component;
069    import java.awt.Container;
070    import java.awt.Dimension;
071    import java.awt.Dialog;
072    import java.awt.FlowLayout;
073    import java.awt.Frame;
074    import java.awt.GridBagConstraints;
075    import java.awt.GridBagLayout;
076    import java.awt.GridLayout;
077    import java.awt.LayoutManager;
078    import java.awt.Window;
079    
080    import java.text.Format;
081    
082    import java.util.ArrayList;
083    import java.util.Collections;
084    import java.util.HashMap;
085    import java.util.Iterator;
086    import java.util.LinkedList;
087    import java.util.List;
088    import java.util.Map;
089    import java.util.Vector;
090    import java.util.logging.Level;
091    import java.util.logging.Logger;
092    
093    import javax.swing.AbstractButton;
094    import javax.swing.Action;
095    import javax.swing.Box;
096    import javax.swing.BoxLayout;
097    import javax.swing.ButtonGroup;
098    import javax.swing.DefaultBoundedRangeModel;
099    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.17 $
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                        if (constraints != null) {
250                            rpc.getContentPane().add(component, constraints);
251                        } else {
252                            rpc.getContentPane().add(component);
253                        }
254                    }
255                    else if (parent instanceof JScrollPane) {
256                        JScrollPane scrollPane = (JScrollPane) parent;
257                        if (child instanceof JViewport) {
258                            scrollPane.setViewport((JViewport)component);
259                        } 
260                        else {
261                            scrollPane.setViewportView(component);
262                        }
263                    }
264                    else if (parent instanceof JSplitPane) {
265                        JSplitPane splitPane = (JSplitPane) parent;
266                        if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
267                            if (splitPane.getTopComponent() == null) {
268                                splitPane.setTopComponent(component);
269                            }
270                            else {
271                                splitPane.setBottomComponent(component);
272                            }
273                        }
274                        else {
275                            if (splitPane.getLeftComponent() == null) {
276                                splitPane.setLeftComponent(component);
277                            }
278                            else {
279                                splitPane.setRightComponent(component);
280                            }
281                        }
282                    }
283                    else if (parent instanceof JMenuBar && component instanceof JMenu) {
284                        JMenuBar menuBar = (JMenuBar) parent;
285                        menuBar.add((JMenu) component);
286                    }
287                    else if (parent instanceof Container) {
288                        Container container = (Container) parent;
289                        if (constraints != null) {
290                            container.add(component, constraints);
291                        }
292                        else {
293                            container.add(component);
294                        }
295                    }
296                    else if (parent instanceof ContainerFacade) {
297                        ContainerFacade facade = (ContainerFacade) parent;
298                        facade.addComponent(component);
299                    }
300                }
301            }
302        }
303    
304        protected void nodeCompleted(Object parent, Object node) {
305            // set models after the node has been completed
306            if (node instanceof TableModel && parent instanceof JTable) {
307                JTable table = (JTable) parent;
308                TableModel model = (TableModel) node;
309                table.setModel(model);
310            }
311            if (node instanceof Startable) {
312                Startable startable = (Startable) node;
313                startable.start();
314            }
315            if (node instanceof Window) {
316                if (!containingWindows.isEmpty() && containingWindows.getLast() == node) {
317                    containingWindows.removeLast();
318                }
319            }
320        }
321    
322        protected Object createNode(Object name) {
323            return createNode(name, Collections.EMPTY_MAP);
324        }
325    
326        protected Object createNode(Object name, Object value) {
327            if (passThroughNodes.containsKey(name) && (value != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(value.getClass())) {
328                // value may need to go into containing windows list
329                if (value instanceof Window) {
330                    containingWindows.add(value);
331                }
332                return value;
333            }
334            else if (value instanceof String) {
335                Object widget = createNode(name);
336                if (widget != null) {
337                    InvokerHelper.invokeMethod(widget, "setText", value);
338                }
339                return widget;
340            }
341            else {
342                    throw new MissingMethodException((String) name, getClass(), new Object[] {value});
343            }
344        }
345    
346        protected Object createNode(Object name, Map attributes, Object value) {
347            if (passThroughNodes.containsKey(name) && (value != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(value.getClass())) {
348                // value may need to go into containing windows list
349                if (value instanceof Window) {
350                    containingWindows.add(value);
351                }
352                handleWidgetAttributes(value, attributes);
353                return value;
354            }
355            else { 
356                Object widget = createNode(name, attributes);
357                if (widget != null) {
358                    InvokerHelper.invokeMethod(widget, "setText", value.toString());
359                }
360                return widget;
361            }
362        }
363        
364        protected Object createNode(Object name, Map attributes) {
365            String widgetName = (String) attributes.remove("id");
366            constraints = attributes.remove("constraints");
367            Object widget = null;
368            if (passThroughNodes.containsKey(name)) {
369                widget = attributes.get(name);
370                if ((widget != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(widget.getClass())) {
371                    // value may need to go into containing windows list
372                    if (widget instanceof Window) {
373                        containingWindows.add(widget);
374                    }
375                    attributes.remove(name);
376                }
377                else {
378                    widget = null;
379                }
380            }
381            if (widget == null) {
382                Factory factory = (Factory) factories.get(name);
383                if (factory != null) {
384                    try {
385                        widget = factory.newInstance(attributes);
386                        if (widgetName != null) {
387                            widgets.put(widgetName, widget);
388                        }
389                        if (widget == null) {
390                            log.log(Level.WARNING, "Factory for name: " + name + " returned null");
391                        }
392                        else {
393                            if (log.isLoggable(Level.FINE)) {
394                                log.fine("For name: " + name + " created widget: " + widget);
395                            }
396                        }
397                    }
398                    catch (Exception e) {
399                        throw new RuntimeException("Failed to create component for" + name + " reason: " + e, e);
400                    }
401                }
402                else {
403                    log.log(Level.WARNING, "Could not find match for name: " + name);
404                }
405            }
406            handleWidgetAttributes(widget, attributes);
407            return widget;
408        }
409    
410        protected void handleWidgetAttributes(Object widget, Map attributes) {
411            if (widget != null) {
412                if (widget instanceof Action) {
413                    /** @todo we could move this custom logic into the MetaClass for Action */
414                    Action action = (Action) widget;
415    
416                    Closure closure = (Closure) attributes.remove("closure");
417                    if (closure != null && action instanceof DefaultAction) {
418                        DefaultAction defaultAction = (DefaultAction) action;
419                        defaultAction.setClosure(closure);
420                    }
421    
422                    Object accel = attributes.remove("accelerator");
423                    KeyStroke stroke = null;
424                    if (accel instanceof KeyStroke) {
425                        stroke = (KeyStroke) accel;
426                    } else if (accel != null) {
427                        stroke = KeyStroke.getKeyStroke(accel.toString());
428                    }
429                    action.putValue(Action.ACCELERATOR_KEY, stroke);
430    
431                    Object mnemonic = attributes.remove("mnemonic");
432                    if ((mnemonic != null) && !(mnemonic instanceof Number)) {
433                        mnemonic = new Integer(mnemonic.toString().charAt(0));
434                    }
435                    action.putValue(Action.MNEMONIC_KEY, mnemonic);
436    
437                    for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
438                        Map.Entry entry = (Map.Entry) iter.next();
439                        String actionName = (String) entry.getKey();    // todo dk: misleading naming. this can be any property name
440    
441                        // typically standard Action names start with upper case, so lets upper case it            
442                        actionName = capitalize(actionName);            // todo dk: in general, this shouldn't be capitalized
443                        Object value = entry.getValue();
444    
445                        action.putValue(actionName, value);
446                    }
447    
448                }
449                else {
450                    // some special cases...
451                    if (attributes.containsKey("buttonGroup")) {
452                        Object o = attributes.get("buttonGroup");
453                        if ((o instanceof ButtonGroup) && (widget instanceof AbstractButton)) {
454                            ((AbstractButton)widget).getModel().setGroup((ButtonGroup)o);
455                            attributes.remove("buttonGroup");
456                        }
457                    }
458    
459                    // this next statement nd if/else is a workaround until GROOVY-305 is fixed
460                    Object mnemonic = attributes.remove("mnemonic");
461                    if ((mnemonic != null) && (mnemonic instanceof Number)) {
462                        InvokerHelper.setProperty(widget, "mnemonic", new Character((char)((Number)mnemonic).intValue()));
463                    } 
464                    else if (mnemonic != null) {
465                        InvokerHelper.setProperty(widget, "mnemonic", new Character(mnemonic.toString().charAt(0)));
466                    } 
467    
468                    // set the properties
469                    for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
470                        Map.Entry entry = (Map.Entry) iter.next();
471                        String property = entry.getKey().toString();
472                        Object value = entry.getValue();
473                        InvokerHelper.setProperty(widget, property, value);
474                    }
475                }
476            }
477        }
478    
479        protected String capitalize(String text) {
480            char ch = text.charAt(0);
481            if (Character.isUpperCase(ch)) {
482                return text;
483            }
484            StringBuffer buffer = new StringBuffer(text.length());
485            buffer.append(Character.toUpperCase(ch));
486            buffer.append(text.substring(1));
487            return buffer.toString();
488        }
489    
490        protected void registerWidgets() {
491            //
492            // non-widget support classes
493            //
494            registerBeanFactory("action", DefaultAction.class);
495            passThroughNodes.put("action", javax.swing.Action.class);
496            registerBeanFactory("buttonGroup", ButtonGroup.class);
497            registerFactory("map", new Factory() {      // todo dk: is that still needed?
498                public Object newInstance(Map properties)
499                    throws InstantiationException, InstantiationException, IllegalAccessException {
500                    return properties;
501                }
502            });
503            // ulimate pass through type
504            passThroughNodes.put("widget", java.awt.Component.class);
505    
506            //
507            // standalone window classes
508            //
509            registerFactory("dialog", new Factory() {
510                public Object newInstance(Map properties)
511                    throws InstantiationException, InstantiationException, IllegalAccessException {
512                    return createDialog(properties);
513                }
514            });
515            registerFactory("frame", new Factory() {
516                public Object newInstance(Map properties)
517                    throws InstantiationException, InstantiationException, IllegalAccessException {
518                    return createFrame(properties);
519                }
520            });
521            registerBeanFactory("fileChooser", JFileChooser.class);
522            registerFactory("frame", new Factory() {        // todo dk: frame registered twice ???
523                public Object newInstance(Map properties)
524                    throws InstantiationException, InstantiationException, IllegalAccessException {
525                    return createFrame(properties);
526                }
527            });
528            registerBeanFactory("optionPane", JOptionPane.class);
529            registerFactory("window", new Factory() {
530                public Object newInstance(Map properties)
531                    throws InstantiationException, InstantiationException, IllegalAccessException {
532                    return createWindow(properties);
533                }
534            });
535            
536            //
537            // widgets
538            //
539            registerBeanFactory("button", JButton.class);
540            registerBeanFactory("checkBox", JCheckBox.class);
541            registerBeanFactory("checkBoxMenuItem", JCheckBoxMenuItem.class);
542            registerBeanFactory("colorChooser", JColorChooser.class);
543            registerFactory("comboBox", new Factory() {
544                public Object newInstance(Map properties)
545                    throws InstantiationException, InstantiationException, IllegalAccessException {
546                    return createComboBox(properties);
547                }
548            });
549            registerBeanFactory("desktopPane", JDesktopPane.class);
550            registerBeanFactory("editorPane", JEditorPane.class);
551            registerFactory("formattedTextField", new Factory() {
552                public Object newInstance(Map properties)
553                    throws InstantiationException, InstantiationException, IllegalAccessException {
554                    return createFormattedTextField(properties);
555                }
556            });
557            registerBeanFactory("internalFrame", JInternalFrame.class);
558            registerBeanFactory("label", JLabel.class);
559            registerBeanFactory("layeredPane", JLayeredPane.class);
560            registerBeanFactory("list", JList.class);
561            registerBeanFactory("menu", JMenu.class);
562            registerBeanFactory("menuBar", JMenuBar.class);
563            registerBeanFactory("menuItem", JMenuItem.class);
564            registerBeanFactory("panel", JPanel.class);
565            registerBeanFactory("passwordField", JPasswordField.class);
566            registerBeanFactory("popupMenu", JPopupMenu.class);
567            registerBeanFactory("progressBar", JProgressBar.class);
568            registerBeanFactory("radioButton", JRadioButton.class);
569            registerBeanFactory("radioButtonMenuItem", JRadioButtonMenuItem.class);
570            registerBeanFactory("scrollBar", JScrollBar.class);
571            registerBeanFactory("scrollPane", JScrollPane.class);
572            registerBeanFactory("separator", JSeparator.class);
573            registerBeanFactory("slider", JSlider.class);
574            registerBeanFactory("spinner", JSpinner.class);
575            registerFactory("splitPane", new Factory() {
576                public Object newInstance(Map properties) {
577                    JSplitPane answer = new JSplitPane();
578                    answer.setLeftComponent(null);
579                    answer.setRightComponent(null);
580                    answer.setTopComponent(null);
581                    answer.setBottomComponent(null);
582                    return answer;
583                }
584            });
585            registerBeanFactory("tabbedPane", JTabbedPane.class);
586            registerBeanFactory("table", JTable.class);
587            registerBeanFactory("textArea", JTextArea.class);
588            registerBeanFactory("textPane", JTextPane.class);
589            registerBeanFactory("textField", JTextField.class);
590            registerBeanFactory("toggleButton", JToggleButton.class);
591            registerBeanFactory("toolBar", JToolBar.class);
592            //registerBeanFactory("tooltip", JToolTip.class); // doens't work, user toolTipText property
593            registerBeanFactory("tree", JTree.class);
594            registerBeanFactory("viewport", JViewport.class); // sub class?
595    
596            //
597            // MVC models   
598            //
599            registerBeanFactory("boundedRangeModel", DefaultBoundedRangeModel.class);
600    
601            // spinner models
602            registerBeanFactory("spinnerDateModel", SpinnerDateModel.class);
603            registerBeanFactory("spinnerListModel", SpinnerListModel.class);
604            registerBeanFactory("spinnerNumberModel", SpinnerNumberModel.class);
605    
606            // table models
607            registerFactory("tableModel", new Factory() {
608                public Object newInstance(Map properties) {
609                    ValueModel model = (ValueModel) properties.remove("model");
610                    if (model == null) {
611                        Object list = properties.remove("list");
612                        if (list == null) {
613                            list = new ArrayList();
614                        }
615                        model = new ValueHolder(list);
616                    }
617                    return new DefaultTableModel(model);
618                }
619            });
620            passThroughNodes.put("tableModel", javax.swing.table.TableModel.class);
621    
622            registerFactory("propertyColumn", new Factory() {
623                public Object newInstance(Map properties) {
624                    Object current = getCurrent();
625                    if (current instanceof DefaultTableModel) {
626                        DefaultTableModel model = (DefaultTableModel) current;
627                        Object header = properties.remove("header");
628                        if (header == null) {
629                            header = "";
630                        }
631                        String property = (String) properties.remove("propertyName");
632                        if (property == null) {
633                            throw new IllegalArgumentException("Must specify a property for a propertyColumn");
634                        }
635                        Class type = (Class) properties.remove("type");
636                        if (type == null) {
637                            type = Object.class;
638                        }
639                        return model.addPropertyColumn(header, property, type);
640                    }
641                    else {
642                        throw new RuntimeException("propertyColumn must be a child of a tableModel");
643                    }
644                }
645            });
646    
647            registerFactory("closureColumn", new Factory() {
648                public Object newInstance(Map properties) {
649                    Object current = getCurrent();
650                    if (current instanceof DefaultTableModel) {
651                        DefaultTableModel model = (DefaultTableModel) current;
652                        Object header = properties.remove("header");
653                        if (header == null) {
654                            header = "";
655                        }
656                        Closure readClosure = (Closure) properties.remove("read");
657                        if (readClosure == null) {
658                            throw new IllegalArgumentException("Must specify 'read' Closure property for a closureColumn");
659                        }
660                        Closure writeClosure = (Closure) properties.remove("write");
661                        Class type = (Class) properties.remove("type");
662                        if (type == null) {
663                            type = Object.class;
664                        }
665                        return model.addClosureColumn(header, readClosure, writeClosure, type);
666                    }
667                    else {
668                        throw new RuntimeException("propertyColumn must be a child of a tableModel");
669                    }
670                }
671            });
672    
673    
674            //Standard Layouts
675            registerBeanFactory("borderLayout", BorderLayout.class);
676            registerBeanFactory("cardLayout", CardLayout.class);
677            registerBeanFactory("flowLayout", FlowLayout.class);
678            registerBeanFactory("gridBagLayout", GridBagLayout.class);
679            registerBeanFactory("gridLayout", GridLayout.class);
680            registerBeanFactory("overlayLayout", OverlayLayout.class);
681            registerBeanFactory("springLayout", SpringLayout.class);
682            registerBeanFactory("gridBagConstraints", GridBagConstraints.class);
683            registerBeanFactory("gbc", GridBagConstraints.class); // shortcut name
684    
685            // box layout
686            registerFactory("boxLayout", new Factory() {
687                public Object newInstance(Map properties)
688                    throws InstantiationException, InstantiationException, IllegalAccessException {
689                    return createBoxLayout(properties);
690                }
691            });
692    
693            // Box related layout components
694            registerFactory("hbox", new Factory() {
695                public Object newInstance(Map properties) {
696                    return Box.createHorizontalBox();
697                }
698            });
699            registerFactory("hglue", new Factory() {
700                public Object newInstance(Map properties) {
701                    return Box.createHorizontalGlue();
702                }
703            });
704            registerFactory("hstrut", new Factory() {
705                public Object newInstance(Map properties) {
706                    try {
707                    Object num = properties.remove("width");
708                    if (num instanceof Number) {
709                        return Box.createHorizontalStrut(((Number)num).intValue());
710                    } else {
711                        return Box.createHorizontalStrut(6);
712                    }
713                    } catch (RuntimeException re) {
714                        re.printStackTrace(System.out);
715                        throw re;
716                    }
717                }
718            });
719            registerFactory("vbox", new Factory() {
720                public Object newInstance(Map properties) {
721                    return Box.createVerticalBox();
722                }
723            });
724            registerFactory("vglue", new Factory() {
725                public Object newInstance(Map properties) {
726                    return Box.createVerticalGlue();
727                }
728            });
729            registerFactory("vstrut", new Factory() {
730                public Object newInstance(Map properties) {
731                    Object num = properties.remove("height");
732                    if (num instanceof Number) {
733                        return Box.createVerticalStrut(((Number)num).intValue());
734                    } else {
735                        return Box.createVerticalStrut(6);
736                    }
737                }
738            });
739            registerFactory("glue", new Factory() {
740                public Object newInstance(Map properties) {
741                    return Box.createGlue();
742                }
743            });
744            registerFactory("rigidArea", new Factory() {
745                public Object newInstance(Map properties) {
746                    Dimension dim;
747                    Object o = properties.remove("size");
748                    if (o instanceof Dimension) {
749                        dim = (Dimension) o;
750                    } else {
751                        int w, h;
752                        o = properties.remove("width");
753                        w = ((o instanceof Number)) ? ((Number)o).intValue() : 6;
754                        o = properties.remove("height");
755                        h = ((o instanceof Number)) ? ((Number)o).intValue() : 6;
756                        dim = new Dimension(w, h);
757                    }
758                    return Box.createRigidArea(dim);
759                }
760            });
761            
762            // table layout
763            registerBeanFactory("tableLayout", TableLayout.class);
764            registerFactory("tr", new Factory() {
765                public Object newInstance(Map properties) {
766                    Object parent = getCurrent();
767                    if (parent instanceof TableLayout) {
768                        return new TableLayoutRow((TableLayout) parent);
769                    }
770                    else {
771                        throw new RuntimeException("'tr' must be within a 'tableLayout'");
772                    }
773                }
774            });
775            registerFactory("td", new Factory() {
776                public Object newInstance(Map properties) {
777                    Object parent = getCurrent();
778                    if (parent instanceof TableLayoutRow) {
779                        return new TableLayoutCell((TableLayoutRow) parent);
780                    }
781                    else {
782                        throw new RuntimeException("'td' must be within a 'tr'");
783                    }
784                }
785            });
786        }
787    
788        protected Object createBoxLayout(Map properties) {
789            Object parent = getCurrent();
790            if (parent instanceof Container) {
791                Object axisObject = properties.remove("axis");
792                int axis = BoxLayout.X_AXIS;
793                if (axisObject != null) {
794                    Integer i = (Integer) axisObject;
795                    axis = i.intValue();
796                }
797                
798                Container target = (Container) parent;
799                if (target instanceof RootPaneContainer) {
800                    target = ((RootPaneContainer) target).getContentPane();
801                }
802                BoxLayout answer = new BoxLayout(target, axis);
803                
804                // now lets try set the layout property
805                InvokerHelper.setProperty(parent, "layout", answer);
806                return answer;
807            }
808            else {
809                throw new RuntimeException("Must be nested inside a Container");
810            }
811        }
812    
813        protected Object createDialog(Map properties) {
814            JDialog dialog;
815            Object owner = properties.remove("owner");
816            // if owner not explicit, use the last window type in the list
817            if ((owner == null) && !containingWindows.isEmpty()) {
818                owner = containingWindows.getLast();
819            }
820            if (owner instanceof Frame) {
821                dialog = new JDialog((Frame) owner);
822            }
823            else if (owner instanceof Dialog) {
824                dialog = new JDialog((Dialog) owner);
825            }
826            else {
827                dialog = new JDialog();
828            }
829            containingWindows.add(dialog);
830            return dialog;
831        }
832        
833        /**
834         * Uses 'format," or "value,"  (in order)
835         *
836         */
837        protected Object createFormattedTextField(Map properties) {
838            JFormattedTextField ftf;
839            if (properties.containsKey("format")) {
840                ftf = new JFormattedTextField((Format) properties.remove("format"));
841            }
842            else if (properties.containsKey("value")) {
843                ftf = new JFormattedTextField(properties.remove("value"));
844            }
845            else {
846                ftf = new JFormattedTextField();
847            }
848            return ftf;
849        }
850    
851        protected Object createFrame(Map properties) {
852            JFrame frame = new JFrame();
853            containingWindows.add(frame);
854            return frame;
855        }
856        
857        protected Object createWindow(Map properties) {
858            JWindow window;
859            Object owner = properties.remove("owner");
860            // if owner not explicit, use the last window type in the list
861            if ((owner == null) && !containingWindows.isEmpty()) {
862                owner = containingWindows.getLast();
863            }
864            if (owner instanceof Frame) {
865                window = new JWindow((Frame) owner);
866            }
867            else if (owner instanceof Window) {
868                window = new JWindow((Window) owner);
869            }
870            else {
871                window = new JWindow();
872            }
873            containingWindows.add(window);
874            return window;
875        }
876    
877        protected Object createComboBox(Map properties) {
878            Object items = properties.remove("items");
879            if (items instanceof Vector) {
880                return new JComboBox((Vector) items);
881            }
882            else if (items instanceof List) {
883                List list = (List) items;
884                return new JComboBox(list.toArray());
885            }
886            else if (items instanceof Object[]) {
887                return new JComboBox((Object[]) items);
888            }
889            else {
890                return new JComboBox();
891            }
892        }
893    
894        protected void registerBeanFactory(String name, final Class beanClass) {
895            registerFactory(name, new Factory() {
896                public Object newInstance(Map properties) throws InstantiationException, IllegalAccessException {
897                    return beanClass.newInstance();
898                }
899            });
900    
901        }
902    
903        protected void registerFactory(String name, Factory factory) {
904            factories.put(name, factory);
905        }
906    }