| /* |
| * Copyright 2003-2007 the original author or authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package groovy.swing |
| |
| import groovy.model.DefaultTableModel |
| import groovy.swing.factory.* |
| import groovy.swing.impl.ComponentFacade |
| import groovy.swing.impl.ContainerFacade |
| import groovy.swing.impl.Startable |
| import java.awt.* |
| import java.lang.reflect.InvocationTargetException |
| import java.util.logging.Level |
| import java.util.logging.Logger |
| import javax.swing.* |
| import javax.swing.table.TableColumn |
| import javax.swing.table.TableModel |
| import org.codehaus.groovy.binding.FullBinding |
| import org.codehaus.groovy.binding.PropertyBinding |
| import org.codehaus.groovy.runtime.InvokerHelper |
| |
| /** |
| * A helper class for creating Swing widgets using GroovyMarkup |
| * |
| * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> |
| * @version $Revision$ |
| */ |
| public class SwingBuilder extends BuilderSupport { |
| |
| private static final Logger LOG = Logger.getLogger(SwingBuilder.class.getName()); |
| private factories = [:] |
| private constraints |
| private widgets = [:] |
| // tracks all containing windows, for auto-owned dialogs |
| private LinkedList containingWindows = new LinkedList() |
| private boolean headless = false |
| private disposalClosures = [] |
| |
| |
| public SwingBuilder() { |
| registerWidgets(); |
| headless = GraphicsEnvironment.isHeadless(); |
| } |
| |
| public Object getProperty(String name) { |
| Object widget = widgets.get(name); |
| if (widget == null) { |
| return super.getProperty(name); |
| } |
| return widget; |
| } |
| |
| protected void setParent(Object parent, Object child) { |
| if (parent instanceof Collection) { |
| ((Collection) parent).add(child); |
| } else if (child instanceof Action) { |
| setParentForAction(parent, (Action) child); |
| } else if ((child instanceof LayoutManager) && (parent instanceof Container)) { |
| Container target = getLayoutTarget((Container) parent); |
| InvokerHelper.setProperty(target, "layout", child); |
| // doesn't work, use toolTipText property |
| // } else if (child instanceof JToolTip && parent instanceof JComponent) { |
| // ((JToolTip) child).setComponent((JComponent) parent); |
| } else if (parent instanceof JTable && child instanceof TableColumn) { |
| JTable table = (JTable) parent; |
| TableColumn column = (TableColumn) child; |
| table.addColumn(column); |
| } else if (parent instanceof JTabbedPane && child instanceof Component) { |
| JTabbedPane tabbedPane = (JTabbedPane) parent; |
| tabbedPane.add((Component) child); |
| } else if (child instanceof Window) { |
| // do nothing. owner of window is set elsewhere, and this |
| // shouldn't get added to any parent as a child |
| // if it is a top level component anyway |
| } else { |
| Component component = null; |
| if (child instanceof Component) { |
| component = (Component) child; |
| } else if (child instanceof ComponentFacade) { |
| ComponentFacade facade = (ComponentFacade) child; |
| component = facade.getComponent(); |
| } |
| if (component != null) { |
| setParentForComponent(parent, component); |
| } |
| } |
| } |
| |
| private void setParentForComponent(Object parent, Component component) { |
| if (parent instanceof JFrame && component instanceof JMenuBar) { |
| JFrame frame = (JFrame) parent; |
| frame.setJMenuBar((JMenuBar) component); |
| } else if (parent instanceof RootPaneContainer) { |
| RootPaneContainer rpc = (RootPaneContainer) parent; |
| if (constraints != null) { |
| rpc.getContentPane().add(component, constraints); |
| } else { |
| rpc.getContentPane().add(component); |
| } |
| } else if (parent instanceof JScrollPane) { |
| JScrollPane scrollPane = (JScrollPane) parent; |
| if (component instanceof JViewport) { |
| scrollPane.setViewport((JViewport) component); |
| } else { |
| scrollPane.setViewportView(component); |
| } |
| } else if (parent instanceof JSplitPane) { |
| JSplitPane splitPane = (JSplitPane) parent; |
| if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { |
| if (splitPane.getTopComponent() == null) { |
| splitPane.setTopComponent(component); |
| } else { |
| splitPane.setBottomComponent(component); |
| } |
| } else { |
| if (splitPane.getLeftComponent() == null) { |
| splitPane.setLeftComponent(component); |
| } else { |
| splitPane.setRightComponent(component); |
| } |
| } |
| } else if (parent instanceof JMenuBar && component instanceof JMenu) { |
| JMenuBar menuBar = (JMenuBar) parent; |
| menuBar.add((JMenu) component); |
| } else if (parent instanceof Container) { |
| Container container = (Container) parent; |
| if (constraints != null) { |
| container.add(component, constraints); |
| } else { |
| container.add(component); |
| } |
| } else if (parent instanceof ContainerFacade) { |
| ContainerFacade facade = (ContainerFacade) parent; |
| facade.addComponent(component); |
| } |
| } |
| |
| private void setParentForAction(Object parent, Action action) { |
| try { |
| InvokerHelper.setProperty(parent, "action", action); |
| } catch (RuntimeException re) { |
| // must not have an action property... |
| // so we ignore it and go on |
| } |
| Object keyStroke = action.getValue("KeyStroke"); |
| if (parent instanceof JComponent) { |
| JComponent component = (JComponent) parent; |
| KeyStroke stroke = null; |
| if (keyStroke instanceof String) { |
| stroke = KeyStroke.getKeyStroke((String) keyStroke); |
| } else if (keyStroke instanceof KeyStroke) { |
| stroke = (KeyStroke) keyStroke; |
| } |
| if (stroke != null) { |
| String key = action.toString(); |
| component.getInputMap().put(stroke, key); |
| component.getActionMap().put(key, action); |
| } |
| } |
| } |
| |
| public static Container getLayoutTarget(Container parent) { |
| if (parent instanceof RootPaneContainer) { |
| RootPaneContainer rpc = (RootPaneContainer) parent; |
| parent = rpc.getContentPane(); |
| } |
| return parent; |
| } |
| |
| protected void nodeCompleted(Object parent, Object node) { |
| // set models after the node has been completed |
| if (node instanceof TableModel && parent instanceof JTable) { |
| JTable table = (JTable) parent; |
| TableModel model = (TableModel) node; |
| table.setModel(model); |
| if ((model instanceof DefaultTableModel) |
| && (model.getColumnCount() > 0 )) |
| { |
| table.setColumnModel(((DefaultTableModel) model).getColumnModel()); |
| table.setAutoCreateColumnsFromModel(false); |
| } |
| } |
| if (node instanceof Startable) { |
| Startable startable = (Startable) node; |
| startable.start(); |
| } |
| if (node instanceof Window) { |
| if (!containingWindows.isEmpty() && containingWindows.getLast() == node) { |
| containingWindows.removeLast(); |
| } |
| } |
| } |
| |
| protected Object createNode(Object name) { |
| return createNode(name, Collections.EMPTY_MAP, null); |
| } |
| |
| protected Object createNode(Object name, Object value) { |
| return createNode(name, Collections.EMPTY_MAP, value); |
| } |
| |
| protected Object createNode(Object name, Map attributes) { |
| return createNode(name, attributes, null); |
| } |
| |
| protected Object createNode(Object name, Map attributes, Object value) { |
| String widgetName = (String) attributes.remove("id"); |
| constraints = attributes.remove("constraints"); |
| Object widget; |
| Factory factory = (Factory) factories.get(name); |
| if (factory == null) { |
| LOG.log(Level.WARNING, "Could not find match for name: $name"); |
| return null; |
| } |
| try { |
| widget = factory.newInstance(this, name, value, attributes); |
| if (widget == null) { |
| LOG.log(Level.WARNING, "Factory for name: $name returned null"); |
| return null; |
| } |
| if (widgetName != null) { |
| widgets.put(widgetName, widget); |
| } |
| if (LOG.isLoggable(Level.FINE)) { |
| LOG.fine("For name: $name created widget: $widget"); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException("Failed to create component for '$name' reason: $e", e); |
| } |
| handleWidgetAttributes(widget, attributes); |
| return widget; |
| } |
| |
| protected void handleWidgetAttributes(Object widget, Map attributes) { |
| // first, short circuit |
| if (attributes.isEmpty() || (widget == null)) { |
| return; |
| } |
| |
| // some special cases... |
| if (attributes.containsKey("buttonGroup")) { |
| Object o = attributes.get("buttonGroup"); |
| if ((o instanceof ButtonGroup) && (widget instanceof AbstractButton)) { |
| ((AbstractButton) widget).getModel().setGroup((ButtonGroup) o); |
| attributes.remove("buttonGroup"); |
| } |
| } |
| |
| // this next statement nd if/else is a workaround until GROOVY-305 is fixed |
| Object mnemonic = attributes.remove("mnemonic"); |
| if (mnemonic != null) { |
| if (mnemonic instanceof Number) { |
| InvokerHelper.setProperty(widget, "mnemonic", new Character((char) ((Number) mnemonic).intValue())); |
| } else { |
| InvokerHelper.setProperty(widget, "mnemonic", new Character(mnemonic.toString().charAt(0))); |
| } |
| } |
| |
| // set the properties |
| |
| for (entry in attributes.entrySet()) { |
| String property = entry.key.toString(); |
| Object value = entry.value; |
| if (value instanceof FullBinding) { |
| FullBinding fb = (FullBinding) value; |
| PropertyBinding ptb = new PropertyBinding(widget, property); |
| fb.setTargetBinding(ptb); |
| try { |
| fb.update(); |
| } catch (Exception e) { |
| // just eat it? |
| } |
| try { |
| fb.rebind(); |
| } catch (Exception e) { |
| // just eat it? |
| } |
| } else { |
| InvokerHelper.setProperty(widget, property, value); |
| } |
| } |
| } |
| |
| public static String capitalize(String text) { |
| char ch = text.charAt(0); |
| if (Character.isUpperCase(ch)) { |
| return text; |
| } |
| StringBuffer buffer = new StringBuffer(text.length()); |
| buffer.append(Character.toUpperCase(ch)); |
| buffer.append(text.substring(1)); |
| return buffer.toString(); |
| } |
| |
| protected void registerWidgets() { |
| // |
| // non-widget support classes |
| // |
| registerFactory("action", new ActionFactory()); |
| registerFactory("actions", new CollectionFactory()); |
| registerBeanFactory("buttonGroup", ButtonGroup); |
| registerFactory("map", new MapFactory()); |
| |
| // binding related classes |
| registerFactory("animate", new AnimateFactory()); |
| registerFactory("bind", new BindFactory()); |
| registerFactory("model", new ModelFactory()); |
| |
| // ulimate pass through types |
| registerFactory("widget", new WidgetFactory(Component)); //TODO prohibit child content somehow |
| registerFactory("container", new WidgetFactory(Component)); |
| registerFactory("bean", new WidgetFactory(Object)); |
| |
| |
| // |
| // standalone window classes |
| // |
| registerFactory("dialog", new DialogFactory()); |
| registerBeanFactory("fileChooser", JFileChooser); |
| registerFactory("frame", new FrameFactory()); |
| registerBeanFactory("optionPane", JOptionPane); |
| registerFactory("window", new WindowFactory()); |
| |
| |
| // |
| // widgets |
| // |
| registerFactory("button", new RichActionWidgetFactory(JButton)); |
| registerFactory("checkBox", new RichActionWidgetFactory(JCheckBox)); |
| registerFactory("checkBoxMenuItem", new RichActionWidgetFactory(JCheckBoxMenuItem)); |
| registerFactory("menuItem", new RichActionWidgetFactory(JMenuItem)); |
| registerFactory("radioButton", new RichActionWidgetFactory(JRadioButton)); |
| registerFactory("radioButtonMenuItem", new RichActionWidgetFactory(JRadioButtonMenuItem)); |
| registerFactory("toggleButton", new RichActionWidgetFactory(JToggleButton)); |
| |
| registerFactory("editorPane", new TextArgWidgetFactory(JEditorPane)); |
| registerFactory("label", new TextArgWidgetFactory(JLabel)); |
| registerFactory("passwordField", new TextArgWidgetFactory(JPasswordField)); |
| registerFactory("textArea", new TextArgWidgetFactory(JTextArea)); |
| registerFactory("textField", new TextArgWidgetFactory(JTextField)); |
| registerFactory("textPane", new TextArgWidgetFactory(JTextPane)); |
| |
| registerBeanFactory("colorChooser", JColorChooser); |
| registerFactory("comboBox", new ComboBoxFactory()); |
| registerBeanFactory("desktopPane", JDesktopPane); |
| registerFactory("formattedTextField", new FormattedTextFactory()); |
| registerBeanFactory("internalFrame", JInternalFrame); |
| registerBeanFactory("layeredPane", JLayeredPane); |
| registerBeanFactory("list", JList); |
| registerBeanFactory("menu", JMenu); |
| registerBeanFactory("menuBar", JMenuBar); |
| registerBeanFactory("panel", JPanel); |
| registerBeanFactory("popupMenu", JPopupMenu); |
| registerBeanFactory("progressBar", JProgressBar); |
| registerBeanFactory("scrollBar", JScrollBar); |
| registerBeanFactory("scrollPane", JScrollPane); |
| registerFactory("separator", new SeparatorFactory()); |
| registerBeanFactory("slider", JSlider); |
| registerBeanFactory("spinner", JSpinner); |
| registerFactory("splitPane", new SplitPaneFactory()); |
| registerBeanFactory("tabbedPane", JTabbedPane); |
| registerBeanFactory("table", JTable); |
| registerBeanFactory("tableColumn", TableColumn); |
| registerBeanFactory("toolBar", JToolBar); |
| //registerBeanFactory("tooltip", JToolTip); // doesn't work, use toolTipText property |
| registerBeanFactory("tree", JTree); |
| registerBeanFactory("viewport", JViewport); // sub class? |
| |
| |
| // |
| // MVC models |
| // |
| registerBeanFactory("boundedRangeModel", DefaultBoundedRangeModel); |
| |
| // spinner models |
| registerBeanFactory("spinnerDateModel", SpinnerDateModel); |
| registerBeanFactory("spinnerListModel", SpinnerListModel); |
| registerBeanFactory("spinnerNumberModel", SpinnerNumberModel); |
| |
| // table models |
| registerFactory("tableModel", new TableModelFactory()); |
| registerFactory("propertyColumn", new PropertyColumnFactory()); |
| registerFactory("closureColumn", new ClosureColumnFactory()); |
| |
| |
| // |
| // Layouts |
| // |
| registerBeanFactory("borderLayout", BorderLayout); |
| registerBeanFactory("cardLayout", CardLayout); |
| registerBeanFactory("flowLayout", FlowLayout); |
| registerBeanFactory("gridBagLayout", GridBagLayout); |
| registerBeanFactory("gridLayout", GridLayout); |
| registerBeanFactory("overlayLayout", OverlayLayout); |
| registerBeanFactory("springLayout", SpringLayout); |
| registerBeanFactory("gridBagConstraints", GridBagConstraints); |
| registerBeanFactory("gbc", GridBagConstraints); // shortcut name |
| |
| // Box layout and friends |
| registerFactory("boxLayout", new BoxLayoutFactory()); |
| registerFactory("box", new BoxFactory()); |
| registerFactory("hbox", new HBoxFactory()); |
| registerFactory("hglue", new HGlueFactory()); |
| registerFactory("hstrut", new HStrutFactory()); |
| registerFactory("vbox", new VBoxFactory()); |
| registerFactory("vglue", new VGlueFactory()); |
| registerFactory("vstrut", new VStrutFactory()); |
| registerFactory("glue", new GlueFactory()); |
| registerFactory("rigidArea", new RigidAreaFactory()); |
| |
| // table layout |
| registerFactory("tableLayout", new TableLayoutFactory()); |
| registerFactory("tr", new TRFactory()); |
| registerFactory("td", new TDFactory()); |
| |
| } |
| |
| public void registerBeanFactory(String theName, final Class beanClass) { |
| registerFactory(theName, new BeanFactory(beanClass)); |
| } |
| |
| public void registerFactory(String name, Factory factory) { |
| factories.put(name, factory); |
| } |
| |
| public Object getConstraints() { |
| return constraints; |
| } |
| |
| public LinkedList getContainingWindows() { |
| return containingWindows; |
| } |
| |
| public Object getCurrent() { //NOPMD not pointless, makes it public from private |
| return super.getCurrent(); |
| } |
| |
| public static void checkValueIsNull(Object value, Object name) { |
| if (value != null) { |
| throw new RuntimeException("$name elements do not accept a value argument."); |
| } |
| } |
| |
| public static boolean checkValueIsType(Object value, Object name, Class type) { |
| if (value != null) { |
| if (type.isAssignableFrom(value.getClass())) { |
| return true; |
| } else { |
| throw new RuntimeException("The value argument of $name must be of type $type.name"); |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| public static boolean checkValueIsTypeNotString(Object value, Object name, Class type) { |
| if (value != null) { |
| if (type.isAssignableFrom(value.getClass())) { |
| return true; |
| } else if (value instanceof String) { |
| return false; |
| } else { |
| throw new RuntimeException("The value argument of $name must be of type $type.name or a String."); |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| public SwingBuilder edt(Closure c) { |
| c.setDelegate(this); |
| if (headless || SwingUtilities.isEventDispatchThread()) { |
| c.call(this); |
| } else { |
| try { |
| SwingUtilities.invokeAndWait(c.curry([this])); |
| } catch (InterruptedException e) { |
| throw new GroovyRuntimeException("interrupted swing interaction", e); |
| } catch (InvocationTargetException e) { |
| throw new GroovyRuntimeException("exception in event dispatch thread", e.getTargetException()); |
| } |
| } |
| return this; |
| } |
| |
| public static SwingBuilder build(Closure c) { |
| SwingBuilder builder = new SwingBuilder(); |
| return builder.edt(c); |
| } |
| |
| public KeyStroke shortcut(key, modifier = 0) { |
| return KeyStroke.getKeyStroke(key, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | modifier); |
| } |
| |
| public KeyStroke shortcut(String key, modifier = 0) { |
| KeyStroke ks = KeyStroke.getKeyStroke(key); |
| if (ks == null) { |
| return null; |
| } else { |
| return KeyStroke.getKeyStroke(ks.getKeyCode(), ks.getModifiers() | modifier | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); |
| } |
| } |
| |
| public void addDisposalClosure(closure) { |
| disposalClosures += closure |
| } |
| |
| public void dispose() { |
| disposalClosures.reverseEach {it()} |
| } |
| } |