001    /*
002     $Id: SwingBuilder.java,v 1.13 2005/06/10 09:55:30 cstein 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.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    }