1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package groovy.swing;
47
48 import groovy.lang.Closure;
49 import groovy.model.DefaultTableModel;
50 import groovy.model.ValueHolder;
51 import groovy.model.ValueModel;
52 import groovy.swing.impl.ComponentFacade;
53 import groovy.swing.impl.ContainerFacade;
54 import groovy.swing.impl.DefaultAction;
55 import groovy.swing.impl.Factory;
56 import groovy.swing.impl.Startable;
57 import groovy.swing.impl.TableLayout;
58 import groovy.swing.impl.TableLayoutCell;
59 import groovy.swing.impl.TableLayoutRow;
60 import groovy.util.BuilderSupport;
61
62 import java.awt.Component;
63 import java.awt.Container;
64 import java.awt.Dimension;
65 import java.awt.Dialog;
66 import java.awt.Frame;
67 import java.awt.LayoutManager;
68 import java.util.ArrayList;
69 import java.util.Collections;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.Iterator;
73 import java.util.List;
74 import java.util.Map;
75 import java.util.Set;
76 import java.util.Vector;
77 import java.util.logging.Level;
78 import java.util.logging.Logger;
79
80 import javax.swing.AbstractButton;
81 import javax.swing.Action;
82 import javax.swing.Box;
83 import javax.swing.BoxLayout;
84 import javax.swing.ButtonGroup;
85 import javax.swing.JButton;
86 import javax.swing.JCheckBox;
87 import javax.swing.JCheckBoxMenuItem;
88 import javax.swing.JComboBox;
89 import javax.swing.JComponent;
90 import javax.swing.JDesktopPane;
91 import javax.swing.JDialog;
92 import javax.swing.JEditorPane;
93 import javax.swing.JFileChooser;
94 import javax.swing.JFrame;
95 import javax.swing.JInternalFrame;
96 import javax.swing.JLabel;
97 import javax.swing.JList;
98 import javax.swing.JMenu;
99 import javax.swing.JMenuBar;
100 import javax.swing.JMenuItem;
101 import javax.swing.JOptionPane;
102 import javax.swing.JPanel;
103 import javax.swing.JPasswordField;
104 import javax.swing.JPopupMenu;
105 import javax.swing.JProgressBar;
106 import javax.swing.JRadioButton;
107 import javax.swing.JRadioButtonMenuItem;
108 import javax.swing.JScrollPane;
109 import javax.swing.JSeparator;
110 import javax.swing.JSplitPane;
111 import javax.swing.JTabbedPane;
112 import javax.swing.JTable;
113 import javax.swing.JTextArea;
114 import javax.swing.JTextField;
115 import javax.swing.JTextPane;
116 import javax.swing.JToggleButton;
117 import javax.swing.JToolBar;
118 import javax.swing.JTree;
119 import javax.swing.KeyStroke;
120 import javax.swing.RootPaneContainer;
121 import javax.swing.table.TableColumn;
122 import javax.swing.table.TableModel;
123
124 import org.codehaus.groovy.runtime.InvokerHelper;
125
126 /***
127 * A helper class for creating Swing widgets using GroovyMarkup
128 *
129 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
130 * @version $Revision: 1.8 $
131 */
132 public class SwingBuilder extends BuilderSupport {
133
134 private Logger log = Logger.getLogger(getClass().getName());
135 private Map factories = new HashMap();
136 private Object constraints;
137 private Map passThroughNodes = new HashMap();
138
139 public SwingBuilder() {
140 registerWidgets();
141 }
142
143 protected void setParent(Object parent, Object child) {
144 if (child instanceof Action) {
145 Action action = (Action) child;
146 try {
147 InvokerHelper.setProperty(parent, "action", action);
148 } catch (RuntimeException re) {
149
150
151 }
152 Object keyStroke = action.getValue("KeyStroke");
153
154 if (parent instanceof JComponent) {
155 JComponent component = (JComponent) parent;
156 KeyStroke stroke = null;
157 if (keyStroke instanceof String) {
158 stroke = KeyStroke.getKeyStroke((String) keyStroke);
159 }
160 else if (keyStroke instanceof KeyStroke) {
161 stroke = (KeyStroke) keyStroke;
162 }
163 if (stroke != null) {
164 String key = action.toString();
165 component.getInputMap().put(stroke, key);
166 component.getActionMap().put(key, action);
167 }
168 }
169 }
170 else if (child instanceof LayoutManager) {
171 if (parent instanceof RootPaneContainer) {
172 RootPaneContainer rpc = (RootPaneContainer) parent;
173 parent = rpc.getContentPane();
174 }
175 InvokerHelper.setProperty(parent, "layout", child);
176 }
177 else if (parent instanceof JTable && child instanceof TableColumn) {
178 JTable table = (JTable) parent;
179 TableColumn column = (TableColumn) child;
180 table.addColumn(column);
181 }
182 else if (parent instanceof JTabbedPane && child instanceof Component) {
183 JTabbedPane tabbedPane = (JTabbedPane) parent;
184 tabbedPane.add((Component)child);
185 }
186 else {
187 Component component = null;
188 if (child instanceof Component) {
189 component = (Component) child;
190 }
191 else if (child instanceof ComponentFacade) {
192 ComponentFacade facade = (ComponentFacade) child;
193 component = facade.getComponent();
194 }
195 if (component != null) {
196 if (parent instanceof JFrame && component instanceof JMenuBar) {
197 JFrame frame = (JFrame) parent;
198 frame.setJMenuBar((JMenuBar) component);
199 }
200 else if (parent instanceof RootPaneContainer) {
201 RootPaneContainer rpc = (RootPaneContainer) parent;
202 rpc.getContentPane().add(component);
203 }
204 else if (parent instanceof JScrollPane) {
205 JScrollPane scrollPane = (JScrollPane) parent;
206 scrollPane.setViewportView(component);
207 }
208 else if (parent instanceof JSplitPane) {
209 JSplitPane splitPane = (JSplitPane) parent;
210 if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
211 if (splitPane.getTopComponent() == null) {
212 splitPane.setTopComponent(component);
213 }
214 else {
215 splitPane.setBottomComponent(component);
216 }
217 }
218 else {
219 if (splitPane.getLeftComponent() == null) {
220 splitPane.setLeftComponent(component);
221 }
222 else {
223 splitPane.setRightComponent(component);
224 }
225 }
226 }
227 else if (parent instanceof JMenuBar && component instanceof JMenu) {
228 JMenuBar menuBar = (JMenuBar) parent;
229 menuBar.add((JMenu) component);
230 }
231 else if (parent instanceof Container) {
232 Container container = (Container) parent;
233 if (constraints != null) {
234 container.add(component, constraints);
235 }
236 else {
237 container.add(component);
238 }
239 }
240 else if (parent instanceof ContainerFacade) {
241 ContainerFacade facade = (ContainerFacade) parent;
242 facade.addComponent(component);
243 }
244 }
245 }
246 }
247
248 protected void nodeCompleted(Object parent, Object node) {
249
250 if (node instanceof TableModel && parent instanceof JTable) {
251 JTable table = (JTable) parent;
252 TableModel model = (TableModel) node;
253 table.setModel(model);
254 }
255 if (node instanceof Startable) {
256 Startable startable = (Startable) node;
257 startable.start();
258 }
259 }
260
261 protected Object createNode(Object name) {
262 return createNode(name, Collections.EMPTY_MAP);
263 }
264
265 protected Object createNode(Object name, Object value) {
266 if (passThroughNodes.containsKey(name) && (value != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(value.getClass())) {
267 return value;
268 }
269 else {
270 Object widget = createNode(name);
271 if (widget != null && value instanceof String) {
272 InvokerHelper.invokeMethod(widget, "setText", value);
273 }
274 return widget;
275 }
276 }
277
278 protected Object createNode(Object name, Map attributes, Object value) {
279 if (passThroughNodes.containsKey(name) && (value != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(value.getClass())) {
280 handleWidgetAttributes(value, attributes);
281 return value;
282 }
283 else {
284 Object widget = createNode(name, attributes);
285 if (widget != null) {
286 InvokerHelper.invokeMethod(widget, "setText", value.toString());
287 }
288 return widget;
289 }
290 }
291
292 protected Object createNode(Object name, Map attributes) {
293 constraints = attributes.remove("constraints");
294 Object widget = null;
295 if (passThroughNodes.containsKey(name)) {
296 widget = attributes.get(name);
297 if ((widget != null) && ((Class)passThroughNodes.get(name)).isAssignableFrom(widget.getClass())) {
298 attributes.remove(name);
299 }
300 else {
301 widget = null;
302 }
303 }
304 if (widget == null) {
305 Factory factory = (Factory) factories.get(name);
306 if (factory != null) {
307 try {
308 widget = factory.newInstance(attributes);
309 if (widget == null) {
310 log.log(Level.WARNING, "Factory for name: " + name + " returned null");
311 }
312 else {
313 if (log.isLoggable(Level.FINE)) {
314 log.fine("For name: " + name + " created widget: " + widget);
315 }
316 }
317 }
318 catch (Exception e) {
319 throw new RuntimeException("Failed to create component for" + name + " reason: " + e, e);
320 }
321 }
322 else {
323 log.log(Level.WARNING, "Could not find match for name: " + name);
324 }
325 }
326 handleWidgetAttributes(widget, attributes);
327 return widget;
328 }
329
330 protected void handleWidgetAttributes(Object widget, Map attributes) {
331 if (widget != null) {
332 if (widget instanceof Action) {
333 /*** @todo we could move this custom logic into the MetaClass for Action */
334 Action action = (Action) widget;
335
336 Closure closure = (Closure) attributes.remove("closure");
337 if (closure != null && action instanceof DefaultAction) {
338 DefaultAction defaultAction = (DefaultAction) action;
339 defaultAction.setClosure(closure);
340 }
341
342 Object accel = attributes.remove("accelerator");
343 KeyStroke stroke = null;
344 if (accel instanceof KeyStroke) {
345 stroke = (KeyStroke) accel;
346 } else if (accel != null) {
347 stroke = KeyStroke.getKeyStroke(accel.toString());
348 }
349 action.putValue(Action.ACCELERATOR_KEY, stroke);
350
351 Object mnemonic = attributes.remove("mnemonic");
352 if ((mnemonic != null) && !(mnemonic instanceof Number)) {
353 mnemonic = new Integer(mnemonic.toString().charAt(0));
354 }
355 action.putValue(Action.MNEMONIC_KEY, mnemonic);
356
357 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
358 Map.Entry entry = (Map.Entry) iter.next();
359 String actionName = (String) entry.getKey();
360
361
362 actionName = capitalize(actionName);
363 Object value = entry.getValue();
364
365 action.putValue(actionName, value);
366 }
367
368 }
369 else {
370
371 if (attributes.containsKey("buttonGroup")) {
372 Object o = attributes.get("buttonGroup");
373 if ((o instanceof ButtonGroup) && (widget instanceof AbstractButton)) {
374 ((AbstractButton)widget).getModel().setGroup((ButtonGroup)o);
375 attributes.remove("buttonGroup");
376 }
377 }
378
379
380 Object mnemonic = attributes.remove("mnemonic");
381 if ((mnemonic != null) && (mnemonic instanceof Number)) {
382 InvokerHelper.setProperty(widget, "mnemonic", new Character((char)((Number)mnemonic).intValue()));
383 }
384 else if (mnemonic != null) {
385 InvokerHelper.setProperty(widget, "mnemonic", new Character(mnemonic.toString().charAt(0)));
386 }
387
388
389 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
390 Map.Entry entry = (Map.Entry) iter.next();
391 String property = entry.getKey().toString();
392 Object value = entry.getValue();
393 InvokerHelper.setProperty(widget, property, value);
394 }
395 }
396 }
397 }
398
399 protected String capitalize(String text) {
400 char ch = text.charAt(0);
401 if (Character.isUpperCase(ch)) {
402 return text;
403 }
404 StringBuffer buffer = new StringBuffer(text.length());
405 buffer.append(Character.toUpperCase(ch));
406 buffer.append(text.substring(1));
407 return buffer.toString();
408 }
409
410 protected void registerWidgets() {
411 registerBeanFactory("action", DefaultAction.class);
412 passThroughNodes.put("action", javax.swing.Action.class);
413 registerFactory("boxLayout", new Factory() {
414 public Object newInstance(Map properties)
415 throws InstantiationException, InstantiationException, IllegalAccessException {
416 return createBoxLayout(properties);
417 }
418 });
419 registerBeanFactory("button", JButton.class);
420 registerBeanFactory("buttonGroup", ButtonGroup.class);
421 registerBeanFactory("checkBox", JCheckBox.class);
422 registerBeanFactory("checkBoxMenuItem", JCheckBoxMenuItem.class);
423
424 registerFactory("comboBox", new Factory() {
425 public Object newInstance(Map properties)
426 throws InstantiationException, InstantiationException, IllegalAccessException {
427 return createComboBox(properties);
428 }
429 });
430
431 registerBeanFactory("desktopPane", JDesktopPane.class);
432
433 registerFactory("dialog", new Factory() {
434 public Object newInstance(Map properties)
435 throws InstantiationException, InstantiationException, IllegalAccessException {
436 return createDialog(properties);
437 }
438 });
439
440 registerBeanFactory("editorPane", JEditorPane.class);
441 registerBeanFactory("fileChooser", JFileChooser.class);
442 registerBeanFactory("frame", JFrame.class);
443 registerBeanFactory("internalFrame", JInternalFrame.class);
444 registerBeanFactory("label", JLabel.class);
445 registerBeanFactory("list", JList.class);
446
447 registerFactory("map", new Factory() {
448 public Object newInstance(Map properties)
449 throws InstantiationException, InstantiationException, IllegalAccessException {
450 return properties;
451 }
452 });
453
454 registerBeanFactory("menu", JMenu.class);
455 registerBeanFactory("menuBar", JMenuBar.class);
456 registerBeanFactory("menuItem", JMenuItem.class);
457 registerBeanFactory("panel", JPanel.class);
458 registerBeanFactory("passwordField", JPasswordField.class);
459 registerBeanFactory("popupMenu", JPopupMenu.class);
460 registerBeanFactory("progressBar", JProgressBar.class);
461 registerBeanFactory("radioButton", JRadioButton.class);
462 registerBeanFactory("radioButtonMenuItem", JRadioButtonMenuItem.class);
463 registerBeanFactory("optionPane", JOptionPane.class);
464 registerBeanFactory("scrollPane", JScrollPane.class);
465 registerBeanFactory("separator", JSeparator.class);
466 registerBeanFactory("tabbedPane", JTabbedPane.class);
467
468 registerFactory("splitPane", new Factory() {
469 public Object newInstance(Map properties) {
470 JSplitPane answer = new JSplitPane();
471 answer.setLeftComponent(null);
472 answer.setRightComponent(null);
473 answer.setTopComponent(null);
474 answer.setBottomComponent(null);
475 return answer;
476 }
477 });
478
479
480 registerFactory("hbox", new Factory() {
481 public Object newInstance(Map properties) {
482 return Box.createHorizontalBox();
483 }
484 });
485 registerFactory("hglue", new Factory() {
486 public Object newInstance(Map properties) {
487 return Box.createHorizontalGlue();
488 }
489 });
490 registerFactory("hstrut", new Factory() {
491 public Object newInstance(Map properties) {
492 try {
493 Object num = properties.remove("width");
494 if (num instanceof Number) {
495 return Box.createHorizontalStrut(((Number)num).intValue());
496 } else {
497 return Box.createHorizontalStrut(6);
498 }
499 } catch (RuntimeException re) {
500 re.printStackTrace(System.out);
501 throw re;
502 }
503 }
504 });
505 registerFactory("vbox", new Factory() {
506 public Object newInstance(Map properties) {
507 return Box.createVerticalBox();
508 }
509 });
510 registerFactory("vglue", new Factory() {
511 public Object newInstance(Map properties) {
512 return Box.createVerticalGlue();
513 }
514 });
515 registerFactory("vstrut", new Factory() {
516 public Object newInstance(Map properties) {
517 Object num = properties.remove("height");
518 if (num instanceof Number) {
519 return Box.createVerticalStrut(((Number)num).intValue());
520 } else {
521 return Box.createVerticalStrut(6);
522 }
523 }
524 });
525 registerFactory("glue", new Factory() {
526 public Object newInstance(Map properties) {
527 return Box.createGlue();
528 }
529 });
530 registerFactory("rigidArea", new Factory() {
531 public Object newInstance(Map properties) {
532 Dimension dim;
533 Object o = properties.remove("size");
534 if (o instanceof Dimension) {
535 dim = (Dimension) o;
536 } else {
537 int w, h;
538 o = properties.remove("width");
539 w = ((o instanceof Number)) ? ((Number)o).intValue() : 6;
540 o = properties.remove("height");
541 h = ((o instanceof Number)) ? ((Number)o).intValue() : 6;
542 dim = new Dimension(w, h);
543 }
544 return Box.createRigidArea(dim);
545 }
546 });
547
548 registerBeanFactory("tabbedPane", JTabbedPane.class);
549 registerBeanFactory("table", JTable.class);
550
551 registerBeanFactory("textArea", JTextArea.class);
552 registerBeanFactory("textPane", JTextPane.class);
553 registerBeanFactory("textField", JTextField.class);
554 registerBeanFactory("toggleButton", JToggleButton.class);
555 registerBeanFactory("tree", JTree.class);
556 registerBeanFactory("toolBar", JToolBar.class);
557
558
559 registerFactory("tableModel", new Factory() {
560 public Object newInstance(Map properties) {
561 ValueModel model = (ValueModel) properties.remove("model");
562 if (model == null) {
563 Object list = properties.remove("list");
564 if (list == null) {
565 list = new ArrayList();
566 }
567 model = new ValueHolder(list);
568 }
569 return new DefaultTableModel(model);
570 }
571 });
572 passThroughNodes.put("tableModel", javax.swing.table.TableModel.class);
573
574 registerFactory("propertyColumn", new Factory() {
575 public Object newInstance(Map properties) {
576 Object current = getCurrent();
577 if (current instanceof DefaultTableModel) {
578 DefaultTableModel model = (DefaultTableModel) current;
579 Object header = properties.remove("header");
580 if (header == null) {
581 header = "";
582 }
583 String property = (String) properties.remove("propertyName");
584 if (property == null) {
585 throw new IllegalArgumentException("Must specify a property for a propertyColumn");
586 }
587 Class type = (Class) properties.remove("type");
588 if (type == null) {
589 type = Object.class;
590 }
591 return model.addPropertyColumn(header, property, type);
592 }
593 else {
594 throw new RuntimeException("propertyColumn must be a child of a tableModel");
595 }
596 }
597 });
598
599 registerFactory("closureColumn", new Factory() {
600 public Object newInstance(Map properties) {
601 Object current = getCurrent();
602 if (current instanceof DefaultTableModel) {
603 DefaultTableModel model = (DefaultTableModel) current;
604 Object header = properties.remove("header");
605 if (header == null) {
606 header = "";
607 }
608 Closure readClosure = (Closure) properties.remove("read");
609 if (readClosure == null) {
610 throw new IllegalArgumentException("Must specify 'read' Closure property for a closureColumn");
611 }
612 Closure writeClosure = (Closure) properties.remove("write");
613 Class type = (Class) properties.remove("type");
614 if (type == null) {
615 type = Object.class;
616 }
617 return model.addClosureColumn(header, readClosure, writeClosure, type);
618 }
619 else {
620 throw new RuntimeException("propertyColumn must be a child of a tableModel");
621 }
622 }
623 });
624
625
626 registerBeanFactory("tableLayout", TableLayout.class);
627 registerFactory("tr", new Factory() {
628 public Object newInstance(Map properties) {
629 Object parent = getCurrent();
630 if (parent instanceof TableLayout) {
631 return new TableLayoutRow((TableLayout) parent);
632 }
633 else {
634 throw new RuntimeException("'tr' must be within a 'tableLayout'");
635 }
636 }
637 });
638 registerFactory("td", new Factory() {
639 public Object newInstance(Map properties) {
640 Object parent = getCurrent();
641 if (parent instanceof TableLayoutRow) {
642 return new TableLayoutCell((TableLayoutRow) parent);
643 }
644 else {
645 throw new RuntimeException("'td' must be within a 'tr'");
646 }
647 }
648 });
649
650
651 passThroughNodes.put("widget", java.awt.Component.class);
652 }
653
654 protected Object createBoxLayout(Map properties) {
655 Object parent = getCurrent();
656 if (parent instanceof Container) {
657 Object axisObject = properties.remove("axis");
658 int axis = 0;
659 if (axisObject != null) {
660 Integer i = (Integer) axisObject;
661 axis = i.intValue();
662 }
663 BoxLayout answer = new BoxLayout((Container) parent, axis);
664
665
666 InvokerHelper.setProperty(parent, "layout", answer);
667 return answer;
668 }
669 else {
670 throw new RuntimeException("Must be nested inside a Container");
671 }
672 }
673
674 protected Object createDialog(Map properties) {
675 Object owner = properties.remove("owner");
676 if (owner instanceof Frame) {
677 return new JDialog((Frame) owner);
678 }
679 else if (owner instanceof Dialog) {
680 return new JDialog((Dialog) owner);
681 }
682 else {
683 return new JDialog();
684 }
685 }
686
687 protected Object createComboBox(Map properties) {
688 Object items = properties.remove("items");
689 if (items instanceof Vector) {
690 return new JComboBox((Vector) items);
691 }
692 else if (items instanceof List) {
693 List list = (List) items;
694 return new JComboBox(list.toArray());
695 }
696 else if (items instanceof Object[]) {
697 return new JComboBox((Object[]) items);
698 }
699 else {
700 return new JComboBox();
701 }
702 }
703
704 protected void registerBeanFactory(String name, final Class beanClass) {
705 registerFactory(name, new Factory() {
706 public Object newInstance(Map properties) throws InstantiationException, IllegalAccessException {
707 return beanClass.newInstance();
708 }
709 });
710
711 }
712
713 protected void registerFactory(String name, Factory factory) {
714 factories.put(name, factory);
715 }
716 }