You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/03/06 23:34:44 UTC
[48/50] [abbrv] incubator-taverna-workbench git commit: taverna-*
taverna-*
Project: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/commit/fb641cfc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/tree/fb641cfc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/diff/fb641cfc
Branch: refs/heads/master
Commit: fb641cfc7feff5907b90301193a9b2aca45e8333
Parents: f676ef3
Author: Stian Soiland-Reyes <st...@apache.org>
Authored: Fri Mar 6 22:28:51 2015 +0000
Committer: Stian Soiland-Reyes <st...@apache.org>
Committed: Fri Mar 6 22:28:51 2015 +0000
----------------------------------------------------------------------
.travis.yml | 1 -
taverna-uibuilder/pom.xml | 26 +
.../lang/uibuilder/AbstractListComponent.java | 521 +++++++++++++++++++
.../t2/lang/uibuilder/AlignableComponent.java | 29 ++
.../sf/taverna/t2/lang/uibuilder/Alignment.java | 66 +++
.../taverna/t2/lang/uibuilder/BeanCheckBox.java | 54 ++
.../t2/lang/uibuilder/BeanComponent.java | 420 +++++++++++++++
.../t2/lang/uibuilder/BeanEnumComboBox.java | 89 ++++
.../taverna/t2/lang/uibuilder/BeanTextArea.java | 89 ++++
.../t2/lang/uibuilder/BeanTextComponent.java | 174 +++++++
.../t2/lang/uibuilder/BeanTextField.java | 60 +++
.../net/sf/taverna/t2/lang/uibuilder/Icons.java | 41 ++
.../taverna/t2/lang/uibuilder/ListHandler.java | 67 +++
.../lang/uibuilder/RecursiveListComponent.java | 98 ++++
.../sf/taverna/t2/lang/uibuilder/UIBuilder.java | 236 +++++++++
.../lang/uibuilder/UIConstructionException.java | 29 ++
.../t2/lang/uibuilder/WrappedListComponent.java | 105 ++++
.../sf/taverna/t2/lang/uibuilder/package.html | 4 +
.../net/sf/taverna/t2/lang/uibuilder/delete.png | Bin 0 -> 1120 bytes
.../net/sf/taverna/t2/lang/uibuilder/down.png | Bin 0 -> 1077 bytes
.../net/sf/taverna/t2/lang/uibuilder/new.png | Bin 0 -> 1146 bytes
.../net/sf/taverna/t2/lang/uibuilder/up.png | Bin 0 -> 1074 bytes
.../taverna/t2/lang/uibuilder/Application.java | 48 ++
.../taverna/t2/lang/uibuilder/Application2.java | 44 ++
.../t2/lang/uibuilder/BeanWithBoundProps.java | 74 +++
.../lang/uibuilder/BeanWithListProperties.java | 35 ++
.../t2/lang/uibuilder/BeanWithNestedList.java | 28 +
.../t2/lang/uibuilder/PrimitiveTypeBean.java | 101 ++++
.../taverna/t2/lang/uibuilder/SampleEnum.java | 13 +
.../taverna/t2/lang/uibuilder/TopLevelBean.java | 43 ++
uibuilder/pom.xml | 26 -
.../lang/uibuilder/AbstractListComponent.java | 521 -------------------
.../t2/lang/uibuilder/AlignableComponent.java | 29 --
.../sf/taverna/t2/lang/uibuilder/Alignment.java | 66 ---
.../taverna/t2/lang/uibuilder/BeanCheckBox.java | 54 --
.../t2/lang/uibuilder/BeanComponent.java | 420 ---------------
.../t2/lang/uibuilder/BeanEnumComboBox.java | 89 ----
.../taverna/t2/lang/uibuilder/BeanTextArea.java | 89 ----
.../t2/lang/uibuilder/BeanTextComponent.java | 174 -------
.../t2/lang/uibuilder/BeanTextField.java | 60 ---
.../net/sf/taverna/t2/lang/uibuilder/Icons.java | 41 --
.../taverna/t2/lang/uibuilder/ListHandler.java | 67 ---
.../lang/uibuilder/RecursiveListComponent.java | 98 ----
.../sf/taverna/t2/lang/uibuilder/UIBuilder.java | 236 ---------
.../lang/uibuilder/UIConstructionException.java | 29 --
.../t2/lang/uibuilder/WrappedListComponent.java | 105 ----
.../sf/taverna/t2/lang/uibuilder/package.html | 4 -
.../net/sf/taverna/t2/lang/uibuilder/delete.png | Bin 1120 -> 0 bytes
.../net/sf/taverna/t2/lang/uibuilder/down.png | Bin 1077 -> 0 bytes
.../net/sf/taverna/t2/lang/uibuilder/new.png | Bin 1146 -> 0 bytes
.../net/sf/taverna/t2/lang/uibuilder/up.png | Bin 1074 -> 0 bytes
.../taverna/t2/lang/uibuilder/Application.java | 48 --
.../taverna/t2/lang/uibuilder/Application2.java | 44 --
.../t2/lang/uibuilder/BeanWithBoundProps.java | 74 ---
.../lang/uibuilder/BeanWithListProperties.java | 35 --
.../t2/lang/uibuilder/BeanWithNestedList.java | 28 -
.../t2/lang/uibuilder/PrimitiveTypeBean.java | 101 ----
.../taverna/t2/lang/uibuilder/SampleEnum.java | 13 -
.../taverna/t2/lang/uibuilder/TopLevelBean.java | 43 --
59 files changed, 2494 insertions(+), 2495 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index dff5f3a..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1 +0,0 @@
-language: java
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/pom.xml b/taverna-uibuilder/pom.xml
new file mode 100644
index 0000000..bc84391
--- /dev/null
+++ b/taverna-uibuilder/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>lang</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>uibuilder</artifactId>
+ <packaging>bundle</packaging>
+ <name>UI builder based on beans</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.log4j</groupId>
+ <artifactId>com.springsource.org.apache.log4j</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+ </dependencies>
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java
new file mode 100644
index 0000000..9a7761c
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java
@@ -0,0 +1,521 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import static net.sf.taverna.t2.lang.uibuilder.Icons.getIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Superclass for list and wrapped list components generated by the UI builder.
+ * Accepts properties to determine whether the 'new', 'move' and 'delete'
+ * controls should be displayed and what, if any, concrete class should be used
+ * when constructing new items. Properties are as follows:
+ * <p>
+ * <ul>
+ * <li><code>nodelete</code> If set do not show the 'delete item' button</li>
+ * <li><code>nomove</code> If set do not show the 'move up' and 'move down'
+ * buttons</li>
+ * <li><code>new=some.class.Name</code> If set then use the specified class,
+ * loaded with the target object's classloader, when adding new items. Also adds
+ * the 'New Item' button to the top of the list panel. The specified class must
+ * be valid to insert into the list and must have a no-argument constructor</li>
+ * </ul>
+ *
+ * @author Tom Oinn
+ *
+ */
+public abstract class AbstractListComponent extends JPanel {
+
+ private static final long serialVersionUID = 2067559836729348490L;
+
+ private static Logger logger = Logger
+ .getLogger(AbstractListComponent.class);
+
+ private String fieldName;
+ @SuppressWarnings("unchecked")
+ private List theList;
+ private Properties props;
+ private Map<String, Properties> fieldProps;
+ private List<String> subFields;
+ private String parent;
+ private JPanel listItemContainer;
+ private boolean showDelete = true;
+ private boolean showMove = true;
+ @SuppressWarnings("unchecked")
+ private Class newItemClass = null;
+ protected static Color deferredButtonColour = Color.orange;
+ private static Object[] prototypes;
+
+ static {
+ try {
+ prototypes = new Object[] { new URL("http://some.host.com/path"),
+ new Boolean(true), new Integer(1), new Float(1),
+ new Short((short) 1), new Long((long) 1),
+ new Character('a'), new Double((double) 1),
+ new Byte((byte) 1) };
+ } catch (MalformedURLException e) {
+ logger.error("Unable to generate URL", e);
+ }
+ }
+
+ /**
+ * Build a generic list component
+ *
+ * @param fieldName
+ * the name of the field this component represents in its parent
+ * bean
+ * @param theList
+ * the list value of the field
+ * @param props
+ * properties for this field
+ * @param fieldProps
+ * aggregated properties for all fields in the UI builder scope
+ * @param newItemClass
+ * the class to use for construction of new item instances, or
+ * null if this is not supported
+ * @param subFields
+ * all field names within this field, this will be empty for a
+ * wrapped list
+ * @param parent
+ * the parent field ID used to access subfield properties
+ * @throws NoSuchMethodException
+ * @throws ClassNotFoundException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ @SuppressWarnings("unchecked")
+ protected AbstractListComponent(String fieldName, List theList,
+ Properties props, Map<String, Properties> fieldProps,
+ final Class<?> newItemClass, List<String> subFields, String parent)
+ throws NoSuchMethodException, IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ super();
+ this.fieldName = fieldName;
+ this.theList = theList;
+ this.props = props;
+ this.fieldProps = fieldProps;
+ this.subFields = subFields;
+ this.parent = parent;
+ if (props.containsKey("nodelete")) {
+ showDelete = false;
+ }
+ if (props.containsKey("nomove")) {
+ showMove = false;
+ }
+ this.newItemClass = newItemClass;
+ setOpaque(false);
+ setLayout(new BorderLayout());
+ String displayName = fieldName;
+ if (props.containsKey("name")) {
+ displayName = props.getProperty("name");
+ }
+ setBorder(BorderFactory.createTitledBorder(displayName));
+ if (newItemClass != null) {
+ // Generate 'add new' UI here
+ JPanel newItemPanel = new JPanel();
+ newItemPanel.setOpaque(false);
+ newItemPanel.setLayout(new BoxLayout(newItemPanel,
+ BoxLayout.LINE_AXIS));
+ newItemPanel.add(Box.createHorizontalGlue());
+
+ final JButton newItemButton = new JButton("New Item",
+ getIcon("new"));
+ newItemPanel.add(newItemButton);
+ newItemButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ Object instance = null;
+ try {
+ instance = newItemClass.newInstance();
+ } catch (InstantiationException ie) {
+ // Probably because the class has no default
+ // constructor, use the prototype list
+ for (Object prototype : prototypes) {
+ if (newItemClass.isAssignableFrom(prototype
+ .getClass())) {
+ instance = prototype;
+ break;
+ }
+ }
+ if (instance == null) {
+ throw ie;
+ }
+ }
+ addNewItemToList(instance);
+ newItemButton.setForeground(BeanComponent.validColour);
+ } catch (Exception ex) {
+ newItemButton
+ .setForeground(BeanComponent.invalidColour);
+ logger.error("", ex);
+ }
+ }
+ });
+ add(newItemPanel, BorderLayout.NORTH);
+ }
+ listItemContainer = new JPanel();
+ listItemContainer.setOpaque(false);
+ listItemContainer.setLayout(new BoxLayout(listItemContainer,
+ BoxLayout.PAGE_AXIS));
+ updateListContents();
+ add(listItemContainer, BorderLayout.CENTER);
+ }
+
+ protected void updateListContents() throws NoSuchMethodException,
+ IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException, ClassNotFoundException {
+ listItemContainer.removeAll();
+ boolean first = true;
+ int index = 0;
+ List<JComponent> listComponents = getListComponents();
+ for (JComponent component : listComponents) {
+ if (first && newItemClass == null) {
+ first = false;
+ } else {
+ List<Component> c = getSeparatorComponents();
+ if (c != null) {
+ for (Component jc : c) {
+ listItemContainer.add(jc);
+ }
+ }
+ }
+ JComponent wrappedComponent = wrapWithControls(component, index++,
+ listComponents.size());
+ if (wrappedComponent != null) {
+ listItemContainer.add(wrappedComponent);
+ } else {
+ listItemContainer.add(component);
+ }
+ }
+ revalidate();
+ }
+
+ /**
+ * Wrap the given component in a panel including whatever list manipulation
+ * controls are needed. The index of the item being wrapped is supplied to
+ * inform the various actions the controls can perform. By default this
+ * returns a JPanel with delete and move controls
+ *
+ * @param component
+ * @param index
+ * @return
+ */
+ protected JPanel wrapWithControls(JComponent component, final int index,
+ int listSize) {
+ int numberOfButtons = 3;
+ if (!showDelete) {
+ numberOfButtons--;
+ }
+ if (!showMove) {
+ numberOfButtons -= 2;
+ }
+ if (numberOfButtons == 0) {
+ return null;
+ }
+ JPanel result = new JPanel();
+ result.setOpaque(false);
+ result.setLayout(new BorderLayout());
+ result.add(component, BorderLayout.CENTER);
+ // Construct the controls
+ JPanel controls = new JPanel();
+ controls.setOpaque(false);
+ controls.setLayout(new BorderLayout());
+ controls
+ .add(new JSeparator(SwingConstants.VERTICAL), BorderLayout.WEST);
+ result.add(controls, BorderLayout.EAST);
+ JPanel buttons = new JPanel();
+ buttons.setOpaque(false);
+ buttons.setLayout(new GridLayout(0, numberOfButtons));
+
+ if (showMove) {
+ // Move up button, or spacer if already at index 0
+ if (index > 0) {
+ JButton moveUpButton = createButton("up", false);
+ moveUpButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ itemMoved(index, index - 1);
+ } catch (Exception ex) {
+ logger.error("Unable to move item", ex);
+ }
+ }
+ });
+ buttons.add(moveUpButton);
+ } else {
+ buttons.add(Box.createGlue());
+ }
+
+ // Move down button, or spacer if index == listSize-1
+ if (index < (listSize - 1)) {
+ JButton moveDownButton = createButton("down", false);
+ moveDownButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ itemMoved(index, index + 1);
+ } catch (Exception ex) {
+ logger.error("Unable to move item", ex);
+ }
+ }
+ });
+ buttons.add(moveDownButton);
+ } else {
+ buttons.add(Box.createGlue());
+ }
+ }
+
+ if (showDelete) {
+ // Delete button
+ JButton deleteButton = createButton("delete", true);
+ deleteButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ deleteItemAtIndex(index);
+ } catch (Exception ex) {
+ logger.error("Unable to delete item", ex);
+ }
+ }
+ });
+ buttons.add(deleteButton);
+ }
+ JPanel buttonWrapper = new JPanel();
+ buttonWrapper.setLayout(new BorderLayout());
+ buttonWrapper.setOpaque(false);
+ buttonWrapper.add(buttons, BorderLayout.NORTH);
+ controls.add(buttonWrapper, BorderLayout.CENTER);
+ return result;
+ }
+
+ private static Timer timer = new Timer();
+
+ @SuppressWarnings("serial")
+ private JButton createButton(String iconName, final boolean deferActions) {
+ JButton result = new JButton(getIcon(iconName)) {
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(getIcon().getIconWidth() + 8, (int) super
+ .getPreferredSize().getHeight());
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return new Dimension(getIcon().getIconWidth() + 8, (int) super
+ .getMinimumSize().getHeight());
+ }
+
+ private boolean active = false;
+ private Color defaultBackground = null;
+
+ @Override
+ public void addActionListener(final ActionListener theListener) {
+ if (defaultBackground == null) {
+ defaultBackground = getBackground();
+ }
+ if (!deferActions) {
+ super.addActionListener(theListener);
+ return;
+ } else {
+ super.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ if (active) {
+ theListener.actionPerformed(ae);
+ } else {
+ setActive(true);
+ timer.schedule(new TimerTask() {
+
+ @Override
+ public void run() {
+ SwingUtilities
+ .invokeLater(new Runnable() {
+ public void run() {
+ setActive(false);
+ }
+ });
+ }
+ }, 1000);
+ }
+ }
+ });
+ }
+ }
+
+ private synchronized void setActive(boolean isActive) {
+ if (isActive == active) {
+ return;
+ } else {
+ active = isActive;
+ setBackground(active ? deferredButtonColour
+ : defaultBackground);
+
+ }
+ }
+ };
+ result.setFocusable(false);
+ return result;
+ }
+
+ /**
+ * Called when building the UI, must return a list of editor components
+ * corresponding to items in the list
+ *
+ * @throws NoSuchMethodException
+ * @throws ClassNotFoundException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ protected abstract List<JComponent> getListComponents()
+ throws NoSuchMethodException, IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException;
+
+ /**
+ * Override to specify a separator component to be used inbetween internal
+ * list components, by default no component is used (this returns null).
+ *
+ * @return
+ */
+ protected List<Component> getSeparatorComponents() {
+ return null;
+ }
+
+ /**
+ * Called when the user has clicked on the 'new item' button, this method is
+ * passed the new instance of the specified type and should handle the
+ * addition of this item to the list and the update of the UI to reflect
+ * this change
+ *
+ * @param o
+ * the object to add to the list
+ * @throws ClassNotFoundException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws NoSuchMethodException
+ * @throws IllegalArgumentException
+ */
+ protected abstract void addNewItemToList(Object o)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException;
+
+ /**
+ * Called when the user has clicked on the 'delete item' button next to a
+ * particular item in the list, this method is passed the index within the
+ * list of the item to be deleted and must update the UI appropriately
+ *
+ * @param index
+ * @throws ClassNotFoundException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws NoSuchMethodException
+ * @throws IllegalArgumentException
+ */
+ protected abstract void deleteItemAtIndex(int index)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException;
+
+ /**
+ * Called when the user has moved an item from one index to another, passed
+ * the old index of the item and the desired new index. This method must
+ * effect the actual move within the list and the update of the UI.
+ *
+ * @throws ClassNotFoundException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws NoSuchMethodException
+ * @throws IllegalArgumentException
+ */
+ protected abstract void itemMoved(int fromIndex, int toIndex)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException;
+
+ /**
+ * Get the field name of this component
+ *
+ * @return
+ */
+ protected final String getFieldName() {
+ return this.fieldName;
+ }
+
+ /**
+ * Return the underlying list presented by this component
+ *
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ protected final List getUnderlyingList() {
+ return this.theList;
+ }
+
+ /**
+ * Get a list of (renamed) sub-fields of this field, so if this field was
+ * foo.bar.someList and had a foo.bar.someList.urgle this would contain
+ * 'urgle'
+ *
+ * @return
+ */
+ protected final List<String> getSubFields() {
+ return this.subFields;
+ }
+
+ /**
+ * Get the properties applied to this list component
+ *
+ * @return
+ */
+ protected final Properties getProperties() {
+ return this.props;
+ }
+
+ /**
+ * The parent field name is the name of this field plus its parent string
+ * and is used when recursively building sub-panels within the list. Pass
+ * this into the 'parent' argument of the UIBuilder's construction methods.
+ *
+ * @return
+ */
+ protected final String getParentFieldName() {
+ return this.parent;
+ }
+
+ /**
+ * Get the map of all field->property object block defined by the UI builder
+ * constructing this component. This is used along with the parent field
+ * name to determine properties of sub-components when recursing into a
+ * nested collection.
+ *
+ * @return
+ */
+ protected final Map<String, Properties> getFieldProperties() {
+ return this.fieldProps;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java
new file mode 100644
index 0000000..483771b
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java
@@ -0,0 +1,29 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+/**
+ * Superinterface for components which have a label and which may be mutually
+ * aligned within a panel. This assumes the component is laid out with a label
+ * to the left of the main editing area, and that we want to ensure that all
+ * editing areas line up and can do this by setting the preferred size of the
+ * label.
+ *
+ * @author Tom Oinn
+ *
+ */
+public interface AlignableComponent {
+
+ /**
+ * Set the preferred width of the label for this alignable component
+ *
+ * @param newWidth
+ */
+ public void setLabelWidth(int newWidth);
+
+ /**
+ * Get the current preferred width of the label for this alignable component
+ *
+ * @return
+ */
+ public int getLabelWidth();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java
new file mode 100644
index 0000000..6143887
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java
@@ -0,0 +1,66 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.SwingUtilities;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Static utility method to align alignable components within a container
+ *
+ * @author Tom Oinn
+ *
+ */
+public abstract class Alignment {
+
+ private static Logger logger = Logger
+ .getLogger(Alignment.class);
+
+ /**
+ * Find all instances of BeanFieldTextArea in the specified container and
+ * set all label widths to the same as the widest of them, aligning the
+ * content areas as a result
+ *
+ * @param container
+ */
+ public static void alignInContainer(Container container) {
+ int widestLabel = 0;
+ final List<AlignableComponent> fields = new ArrayList<AlignableComponent>();
+ for (Component comp : container.getComponents()) {
+ if (comp instanceof AlignableComponent) {
+ AlignableComponent field = (AlignableComponent) comp;
+ int fieldWidth = field.getLabelWidth();
+ if (fieldWidth > widestLabel) {
+ widestLabel = fieldWidth;
+ }
+ fields.add(field);
+ }
+ }
+ final int widestLabelVal = widestLabel;
+ if (!SwingUtilities.isEventDispatchThread()) {
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ for (AlignableComponent field : fields) {
+ field.setLabelWidth(widestLabelVal);
+ }
+ }
+ });
+ } catch (InterruptedException e) {
+ logger.error("", e);
+ } catch (InvocationTargetException e) {
+ logger.error("", e);
+ }
+ } else {
+ for (AlignableComponent field : fields) {
+ field.setLabelWidth(widestLabelVal);
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java
new file mode 100644
index 0000000..1d86f4a
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java
@@ -0,0 +1,54 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Properties;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+
+/**
+ * Bean field editor using a JCheckBox to handle boolean and Boolean field types
+ *
+ * @author Tom Oinn
+ */
+public class BeanCheckBox extends BeanComponent implements AlignableComponent {
+
+ private static final long serialVersionUID = -2842617445268734650L;
+ private JCheckBox value;
+
+ public BeanCheckBox(Object target, String propertyName, Properties props)
+ throws NoSuchMethodException {
+ this(target, propertyName, true, props);
+ }
+
+ public BeanCheckBox(Object target, String propertyName, boolean useLabel,
+ Properties props) throws NoSuchMethodException {
+ super(target, propertyName, useLabel, props);
+ setLayout(new BorderLayout());
+ value = new JCheckBox();
+ value.setSelected(getBooleanProperty());
+ value.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ boolean isSelected = value.isSelected();
+ currentObjectValue = isSelected;
+ setProperty();
+ }
+ });
+ addLabel();
+ value.setOpaque(false);
+ add(Box.createHorizontalGlue(), BorderLayout.CENTER);
+ add(value, BorderLayout.EAST);
+ }
+
+ @Override
+ protected void updateComponent() {
+ value.setSelected(getBooleanProperty());
+ }
+
+ private boolean getBooleanProperty() {
+ return (Boolean) getProperty();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java
new file mode 100644
index 0000000..27014e1
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java
@@ -0,0 +1,420 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Properties;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Superclass of all bean component editors acting on a single (non collection)
+ * property within a POJO. This handles bound properties, using reflection to
+ * determine whether an appropriate addPropertyChangeListener method exists on
+ * the target bean and registering a listener if so. It also registers an
+ * ancestor listener to de-register this property listener if the component is
+ * removed from the display heirarchy and to re-attach it if the component is
+ * added. Implement updateComponent with the code to update the UI state from
+ * the underlying object, this is called when a property change is received by
+ * the listener (but not used for unbound properties).
+ * <p>
+ * This superclass also defines the name property:
+ * <ul>
+ * <li><code>name=SomeName</code> by default field labels use the field name
+ * defined in the configuration, including any case used. If this property is
+ * defined then the specified name is used instead</li>
+ * </ul>
+ *
+ * @author Tom Oinn
+ */
+public abstract class BeanComponent extends JPanel {
+
+ private static Logger logger = Logger
+ .getLogger(BeanComponent.class);
+
+ private static final long serialVersionUID = -6044009506938335937L;
+ protected Object target;
+ protected String propertyName;
+ protected Method getMethod, setMethod = null;
+ protected boolean editable = true;
+ protected Class<?> propertyType;
+ protected static Color invalidColour = Color.red;
+ protected static Color validColour = Color.black;
+ protected static Color uneditedColour = Color.white;
+ protected static Color editedColour = new Color(255, 245, 200);
+ protected boolean currentValueValid = true;
+ protected Object currentObjectValue = null;
+ protected JLabel label;
+ private boolean useLabel = false;
+ protected static int height = 16;
+ private Properties properties;
+ private PropertyChangeListener propertyListener = null;
+
+ /**
+ * Equivalent to BeanComponent(target, propertyName, true, props)
+ */
+ public BeanComponent(Object target, String propertyName, Properties props)
+ throws NoSuchMethodException {
+ this(target, propertyName, true, props);
+ }
+
+ /**
+ * Superclass constructor for BeanComponent instances.
+ *
+ * @param target
+ * the object containing the property this component acts as a
+ * view and controller for
+ * @param propertyName
+ * name of the property in the target object
+ * @param useLabel
+ * whether to show the label component (we set this to false for
+ * wrapped lists, for example)
+ * @param props
+ * a component specific properties object, passing in any
+ * properties defined in the configuration passed to UIBuilder
+ * @throws NoSuchMethodException
+ * if the appropriate get method for the named property can't be
+ * found
+ */
+ public BeanComponent(Object target, String propertyName, boolean useLabel,
+ Properties props) throws NoSuchMethodException {
+ super();
+ setOpaque(false);
+ // Find methods
+ this.properties = props;
+ this.useLabel = useLabel;
+ this.target = target;
+ this.propertyName = propertyName;
+
+ // If the target implements property change support then we can attach a
+ // listener to update the UI if the bound property changes. This
+ // listener is attached and detached in response to an ancestor listener
+ // so the bound property is only monitored when the component is visible
+ // in the UI.
+ try {
+ target.getClass().getMethod("addPropertyChangeListener",
+ String.class, PropertyChangeListener.class);
+ setUpPropertyListener();
+ addAncestorListener(new AncestorListener() {
+ public void ancestorAdded(AncestorEvent event) {
+ setUpPropertyListener();
+ }
+
+ public void ancestorMoved(AncestorEvent event) {
+ // Ignore
+ }
+
+ public void ancestorRemoved(AncestorEvent event) {
+ tearDownPropertyListener();
+ }
+ });
+ } catch (NoSuchMethodException nsme) {
+ // Means we don't have a bound property listener
+ }
+
+ getMethod = findMethodWithPrefix("get");
+ try {
+ setMethod = findMethodWithPrefix("set");
+ } catch (NoSuchMethodException nsme) {
+ logger.error("Unable to find set method", nsme);
+ editable = false;
+ }
+ propertyType = getPropertyType(target, propertyName);
+ }
+
+ /**
+ * Attempts to create and bind a property change listener to the target
+ * bean, failing silently if the bean doesn't implement the appropriate
+ * methods
+ */
+ private synchronized void setUpPropertyListener() {
+ if (propertyListener == null) {
+ Method addListener = null;
+ try {
+ addListener = target.getClass().getMethod(
+ "addPropertyChangeListener", String.class,
+ PropertyChangeListener.class);
+ } catch (NoSuchMethodException nsme) {
+ return;
+ }
+ propertyListener = new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent evt) {
+ Object newValue = evt.getNewValue();
+ if (currentObjectValue == null
+ || (newValue != currentObjectValue && !newValue
+ .equals(currentObjectValue))) {
+ // System.out.println("Property change, source was "+evt.getSource());
+ if (SwingUtilities.isEventDispatchThread()) {
+ updateComponent();
+ } else {
+ // try {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateComponent();
+ }
+ });
+ }
+ }
+ }
+ };
+ try {
+ addListener.invoke(target, propertyName, propertyListener);
+ } catch (IllegalArgumentException e) {
+ logger.error("Unable to set up property listener", e);
+ } catch (IllegalAccessException e) {
+ logger.error("Unable to set up property listener", e);
+ } catch (InvocationTargetException e) {
+ logger.error("Unable to set up property listener", e);
+ }
+ }
+ }
+
+ /**
+ * If the property listener for bound properties exists then this method
+ * unregisters it from the target, attempting to use a variety of the
+ * standard method names to do so
+ */
+ private synchronized void tearDownPropertyListener() {
+ if (propertyListener == null) {
+ return;
+ }
+ try {
+ Method removeListener = null;
+ try {
+ removeListener = target.getClass().getMethod(
+ "removePropertyChangeListener", String.class,
+ PropertyChangeListener.class);
+ removeListener.invoke(target, propertyName, propertyListener);
+ } catch (NoSuchMethodException nsme) {
+ try {
+ removeListener = target.getClass().getMethod(
+ "removePropertyChangeListener",
+ PropertyChangeListener.class);
+ removeListener.invoke(target, propertyListener);
+ } catch (NoSuchMethodException nsme2) {
+ return;
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ logger.error("Unable to remove property listener", e);
+ } catch (IllegalAccessException e) {
+ logger.error("Unable to remove property listener", e);
+ } catch (InvocationTargetException e) {
+ logger.error("Unable to remove property listener", e);
+ }
+ propertyListener = null;
+ }
+
+ /**
+ * Called by the bound property listener, implementing components must
+ * update their UI state in this method to match the value of the underlying
+ * component. This method is always called in the AWT dispatch thread so
+ * implementations can safely modify the state of UI components without
+ * worrying about swing thread handling
+ */
+ protected abstract void updateComponent();
+
+ /**
+ * Adds the label to the component if labels are enabled
+ */
+ protected void addLabel() {
+ if (useLabel) {
+ String labelName = propertyName;
+ if (getProperties().containsKey("name")) {
+ labelName = getProperties().getProperty("name");
+ }
+ label = new JLabel(labelName);
+ label.setOpaque(false);
+ label.setPreferredSize(new Dimension(
+ label.getPreferredSize().width, height));
+ JPanel labelPanel = new JPanel();
+ labelPanel.setOpaque(false);
+ labelPanel.add(label);
+ labelPanel.add(Box.createHorizontalStrut(5));
+ add(labelPanel, BorderLayout.WEST);
+ }
+ }
+
+ /**
+ * Return the type of the property on the target object, trying to locate a
+ * matching 'get' method for the supplied property name and returning the
+ * class of that method's return type.
+ * <p>
+ * Attempts to, in order :
+ * <ol>
+ * <li>Call the method and get the concrete type of the returned value</li>
+ * <li>Get the declared return type of the get method</li>
+ * </ol>
+ *
+ * @param target
+ * @param propertyName
+ * @return
+ * @throws NoSuchMethodException
+ * if the get method can't be found for the given property name
+ * on the target
+ */
+ public static Class<?> getPropertyType(Object target, String propertyName)
+ throws NoSuchMethodException {
+ Method getMethod = findMethodWithPrefix("get", target, propertyName);
+ try {
+ Object value = getMethod.invoke(target);
+ if (value != null) {
+ return value.getClass();
+ }
+ } catch (InvocationTargetException ite) {
+ //
+ } catch (IllegalArgumentException e) {
+ //
+ } catch (IllegalAccessException e) {
+ //
+ }
+ // if (target instanceof ListHandler.ListItem) {
+ // return ((ListHandler.ListItem) target).getTargetClass();
+ // }
+ return getMethod.getReturnType();
+ }
+
+ /**
+ * Searches for the specified method on the target object, throwing an
+ * exception if it can't be found. Searches for
+ * target.[prefix][propertyname]()
+ *
+ * @throws NoSuchMethodException
+ */
+ static Method findMethodWithPrefix(String prefix, Object target,
+ String propertyName) throws NoSuchMethodException {
+ for (Method m : target.getClass().getMethods()) {
+ if (m.getName().equalsIgnoreCase(prefix + propertyName)) {
+ return m;
+ }
+ }
+ throw new NoSuchMethodException("Can't find method matching '" + prefix
+ + propertyName + "' in " + target.getClass().getCanonicalName());
+ }
+
+ /**
+ * Calls the static findMethodWithPrefix passing in the property name and
+ * the target assigned to this instance
+ *
+ * @param prefix
+ * a string prefix to use when finding the name, searches for
+ * [prefix][propertyname]()
+ * @return a Method matching the query
+ * @throws NoSuchMethodException
+ * if the method can't be found.
+ */
+ protected final Method findMethodWithPrefix(String prefix)
+ throws NoSuchMethodException {
+ return findMethodWithPrefix(prefix, target, propertyName);
+ }
+
+ /**
+ * Returns the toString value of the current bean property, or the empty
+ * string if the get method returns null
+ */
+ protected final String getPropertyAsString() {
+ Object value = getProperty();
+ if (value == null) {
+ return "";
+ }
+ currentObjectValue = value;
+ return value.toString();
+ }
+
+ /**
+ * Uses reflection to call the get[property name] method on the target bean
+ * and returns the result
+ *
+ * @return current value of the bean property
+ */
+ protected final Object getProperty() {
+ try {
+ Object value = getMethod.invoke(target);
+ return value;
+ } catch (Exception ex) {
+ logger.error("Unable to get property", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the property on the object to the current object value for this UI
+ * component, effectively pushing any changes into the underlying target
+ * bean
+ */
+ protected final void setProperty() {
+ if (currentValueValid && editable) {
+ if (setMethod != null) {
+ try {
+ setMethod.invoke(target, currentObjectValue);
+ } catch (IllegalArgumentException e) {
+ logger.error("Unable to set property", e);
+ } catch (IllegalAccessException e) {
+ logger.error("Unable to set property", e);
+ } catch (InvocationTargetException e) {
+ logger.error("Unable to set property", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * If using labels (as determined by the useLabel property) this sets the
+ * preferred width of the label for this component
+ *
+ * @param newWidth
+ * new label width in pixels
+ */
+ public void setLabelWidth(int newWidth) {
+ if (useLabel) {
+ label.setPreferredSize(new Dimension(newWidth, height));
+ }
+ }
+
+ /**
+ * If using labels (as determined by the useLabel property) this returns the
+ * current preferred size of the label associated with this component
+ *
+ * @return label width in pixels
+ */
+ public int getLabelWidth() {
+ if (useLabel) {
+ return this.label.getPreferredSize().width;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * If using the label then set its text (foreground) colour
+ *
+ * @param colour
+ */
+ public void setLabelColour(Color colour) {
+ if (useLabel) {
+ this.label.setForeground(colour);
+ }
+ }
+
+ /**
+ * Get the properties object associated with this component. This is
+ * generated from the configuration passed to UIBuilder
+ *
+ * @return a Properties object containing named properties applying to this
+ * component in the UI
+ */
+ public Properties getProperties() {
+ return this.properties;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java
new file mode 100644
index 0000000..34646a4
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Properties;
+
+import javax.swing.JComboBox;
+
+/**
+ * Bean property editor for enumerated property types, rendering the enumeration
+ * as a combo box
+ *
+ * @author Tom Oinn
+ *
+ */
+public class BeanEnumComboBox extends BeanComponent implements
+ AlignableComponent {
+
+ private static final long serialVersionUID = -6892016525599793149L;
+
+ private Object[] possibleValues;
+ private static int height = 24;
+ private JComboBox value;
+
+ public BeanEnumComboBox(Object target, String propertyName, Properties props)
+ throws NoSuchMethodException {
+ this(target, propertyName, true, props);
+ }
+
+ public BeanEnumComboBox(Object target, String propertyName,
+ boolean useLabel, Properties props) throws NoSuchMethodException {
+ super(target, propertyName, useLabel, props);
+ setLayout(new BorderLayout());
+ // Check that this is actually an enumeration type
+ if (!propertyType.isEnum()) {
+ throw new IllegalArgumentException(
+ "Can't use BeanEnumComboBox on a non Enumeration property");
+ }
+ possibleValues = propertyType.getEnumConstants();
+ value = new JComboBox(possibleValues) {
+
+ private static final long serialVersionUID = -7712225463703816146L;
+
+ @Override
+ public Dimension getMinimumSize() {
+ return new Dimension(super.getMinimumSize().width, height);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(super.getPreferredSize().width, height);
+ }
+
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension(super.getMaximumSize().width, height);
+ }
+ };
+ value.setSelectedIndex(currentValueIndex());
+ value.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ synchronized (this) {
+ currentObjectValue = value.getSelectedItem();
+ setProperty();
+ }
+ }
+ });
+ addLabel();
+ add(value, BorderLayout.CENTER);
+ }
+
+ private int currentValueIndex() {
+ Object currentValue = getProperty();
+ for (int i = 0; i < possibleValues.length; i++) {
+ if (currentValue.equals(possibleValues[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ protected void updateComponent() {
+ value.setSelectedIndex(currentValueIndex());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java
new file mode 100644
index 0000000..740a592
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Dimension;
+import java.awt.KeyboardFocusManager;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.text.JTextComponent;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+
+/**
+ * Bean editor based on a DialogTextArea for use with longer strings such as
+ * descriptions. Supports the 'nofilter' property, if this is not specified then
+ * the text inserted initially (but not on subsequent events such as property
+ * change messages) will be filtered to remove multiple whitespace elements,
+ * replacing them with spaces, and to trim leading and trailing whitespace.
+ *
+ * @author Tom Oinn
+ *
+ */
+public class BeanTextArea extends BeanTextComponent implements
+ AlignableComponent {
+
+ private static final long serialVersionUID = 6418526320837944375L;
+ boolean initialized = false;
+
+ public BeanTextArea(Object target, String propertyName, Properties props)
+ throws NoSuchMethodException {
+ super(target, propertyName, props);
+ initialized = true;
+ }
+
+ @SuppressWarnings( { "serial", "unchecked" })
+ @Override
+ protected JTextComponent getTextComponent() {
+ DialogTextArea result = new DialogTextArea() {
+ @Override
+ public void setText(String text) {
+ if (!initialized && !getProperties().containsKey("nofilter")) {
+ super.setText(text.replaceAll("[ \\t\\n\\x0B\\f\\r]+", " ")
+ .trim());
+ } else {
+ super.setText(text);
+ }
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(0, super.getPreferredSize().height);
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return new Dimension(0, super.getPreferredSize().height);
+ }
+ };
+ // Fix to add borders to DialogTextArea on old look and feel implementations,
+ // the new one (Nimbus) already has this
+ if (!UIManager.getLookAndFeel().getName().equals("Nimbus")) {
+ result.setBorder(UIManager.getBorder("TextField.border"));
+ result.setFont(UIManager.getFont("TextField.font"));
+ }
+ // Change tab behaviour to allow tab to move to the next field - this
+ // effectively prevents a tab being placed in the text area but hey, we
+ // don't really want people doing that anyway in these cases.
+ Set set = new HashSet(
+ result
+ .getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
+ set.add(KeyStroke.getKeyStroke("TAB"));
+ result.setFocusTraversalKeys(
+ KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);
+
+ set = new HashSet(
+ result
+ .getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
+ set.add(KeyStroke.getKeyStroke("shift TAB"));
+ result.setFocusTraversalKeys(
+ KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set);
+
+ result.setLineWrap(true);
+ result.setWrapStyleWord(true);
+ return result;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java
new file mode 100644
index 0000000..791f1a8
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java
@@ -0,0 +1,174 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.lang.reflect.Constructor;
+import java.util.Properties;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+/**
+ * Abstract superclass for all bean property editors based on text components
+ *
+ * @author Tom Oinn
+ */
+public abstract class BeanTextComponent extends BeanComponent {
+
+ private static final long serialVersionUID = -9133735782847457090L;
+ private String regex = null;
+ private JTextComponent text;
+ private String originalValue = null;
+ private boolean edited = false;
+
+ protected BeanTextComponent(Object target, String propertyName,
+ Properties props) throws NoSuchMethodException {
+ this(target, propertyName, true, props);
+ }
+
+ protected BeanTextComponent(Object target, String propertyName,
+ boolean useLabel, Properties props) throws NoSuchMethodException {
+ super(target, propertyName, useLabel, props);
+ setLayout(new BorderLayout());
+ if (propertyType.equals(Boolean.class)) {
+ setRegex("\\A((true)|(false))\\Z");
+ } else if (propertyType.equals(Character.class)) {
+ setRegex("\\A.\\Z");
+ }
+ text = getTextComponent();
+ originalValue = getPropertyAsString();
+ text.setText(originalValue);
+ text.setEditable(editable);
+ add(text, BorderLayout.CENTER);
+ if (editable) {
+ text.getDocument().addDocumentListener(new DocumentListener() {
+ public void changedUpdate(DocumentEvent e) {
+ valueChangedInEditor();
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ valueChangedInEditor();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ valueChangedInEditor();
+ }
+
+ private void valueChangedInEditor() {
+ BeanTextComponent.this.valueChangedInEditor();
+ }
+ });
+ text.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ //
+ }
+
+ public void focusLost(FocusEvent e) {
+ // System.out.println("Focus lost : valid = "
+ // + currentValueValid);
+ if (currentValueValid) {
+ if (isEdited()) {
+ BeanTextComponent.this.setProperty();
+ originalValue = text.getText();
+ }
+ } else {
+ originalValue = getPropertyAsString();
+ text.setText(originalValue);
+ }
+ setEdited(false);
+ }
+
+ });
+ }
+ addLabel();
+ }
+
+ private boolean isEdited() {
+ return this.edited;
+ }
+
+ private void setEdited(boolean edited) {
+ if (edited == this.edited) {
+ return;
+ }
+ this.edited = edited;
+ text.setBackground(edited ? editedColour : uneditedColour);
+ if (!edited) {
+ setValid(true);
+ }
+ }
+
+ public void updateComponent() {
+ originalValue = getPropertyAsString();
+ text.setText(originalValue);
+ setEdited(false);
+ }
+
+ private void valueChangedInEditor() {
+ if (text.getText().equals(originalValue)) {
+ setEdited(false);
+ return;
+ }
+ setEdited(true);
+ // Check for regex
+ if (regex != null) {
+ if (text.getText().matches(regex) == false) {
+ // System.out.println(text.getText() + ".matches(" + regex
+ // + ")==false");
+ setValid(false);
+ return;
+ }
+ }
+ // Delegate to constructor for non-string classes
+ if (!propertyType.equals(String.class)) {
+ try {
+ Constructor<?> cons = null;
+ if (propertyType.equals(Character.class)
+ && text.getText().length() > 0) {
+ currentObjectValue = text.getText().toCharArray()[0];
+ } else {
+ cons = propertyType.getConstructor(String.class);
+ currentObjectValue = cons.newInstance(text.getText());
+ }
+ } catch (Throwable t) {
+ setValid(false);
+ return;
+ }
+ } else {
+ currentObjectValue = text.getText();
+ }
+ setValid(true);
+ }
+
+ private void setValid(final boolean valid) {
+ if (valid == currentValueValid) {
+ return;
+ }
+ currentValueValid = valid;
+ text.setForeground(valid ? validColour : invalidColour);
+ setLabelColour(valid ? validColour : invalidColour);
+ text.repaint();
+ }
+
+ /**
+ * Implement this to provide the actual UI component used to show the
+ * content of the field. Done this way so you can choose what component to
+ * use.
+ *
+ * @return
+ */
+ protected abstract JTextComponent getTextComponent();
+
+ /**
+ * Set the regular expression used to validate the contents of the text
+ * field
+ *
+ * @param regex
+ */
+ public void setRegex(String regex) {
+ this.regex = regex;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java
new file mode 100644
index 0000000..63ed107
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java
@@ -0,0 +1,60 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Properties;
+
+import javax.swing.JTextField;
+import javax.swing.text.JTextComponent;
+
+/**
+ * Bean field editor using a JTextField for short values
+ *
+ * @author Tom Oinn
+ */
+public class BeanTextField extends BeanTextComponent implements
+ AlignableComponent {
+
+ private static final long serialVersionUID = 5968203948656812060L;
+
+ public BeanTextField(Object target, String propertyName, boolean useLabel,
+ Properties props) throws NoSuchMethodException {
+ super(target, propertyName, useLabel, props);
+ }
+
+ public BeanTextField(Object target, String propertyName, Properties props)
+ throws NoSuchMethodException {
+ this(target, propertyName, true, props);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected JTextComponent getTextComponent() {
+ final JTextField result = new JTextField() {
+
+ @Override
+ public Dimension getMinimumSize() {
+ return new Dimension(0, height);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(0, height);
+ }
+
+ @Override
+ public Dimension getMaximumSize() {
+ return new Dimension(super.getMaximumSize().width, height);
+ }
+ };
+ result.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setProperty();
+ result.transferFocus();
+ }
+ });
+ return new JTextField();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java
new file mode 100644
index 0000000..b8fc58e
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java
@@ -0,0 +1,41 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Manage icons for the UIBuilder
+ *
+ * @author Tom Oinn
+ *
+ */
+public abstract class Icons {
+
+ private static Logger logger = Logger
+ .getLogger(Icons.class);
+
+ private static Map<String, ImageIcon> icons;
+
+ static {
+ icons = new HashMap<String, ImageIcon>();
+ }
+
+ static synchronized ImageIcon getIcon(String iconName) {
+ String iconNameLC = iconName.toLowerCase();
+ if (!icons.containsKey(iconNameLC)) {
+ try {
+ URL iconURL = Icons.class.getResource(iconName + ".png");
+ icons.put(iconNameLC, new ImageIcon(iconURL));
+ } catch (Exception ex) {
+ logger.error("Unable to get icon resource", ex);
+ }
+ }
+ return icons.get(iconNameLC);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java
new file mode 100644
index 0000000..e7a2db8
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java
@@ -0,0 +1,67 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The list handler is used to allow the reflection based UI builder to handle
+ * lists of non-bean value types such as String etc.
+ *
+ * @author Tom Oinn
+ *
+ */
+public class ListHandler extends
+ ArrayList<ListHandler.ListItem> {
+
+ private static Logger logger = Logger
+ .getLogger(ListHandler.class);
+
+ private static final long serialVersionUID = -1361470859975889856L;
+
+ private List<Object> wrappedList;
+
+ public ListHandler(List<Object> theList) {
+ this.wrappedList = theList;
+ for (Object o : wrappedList) {
+ this.add(new ListItem(o));
+ }
+ }
+
+ /**@Override
+ public boolean add(ListHandler.ListItem newItem) {
+ wrappedList.add((T) newItem.getValue());
+ return super.add(newItem);
+ }*/
+
+ /**
+ * Simple container class to handle list items, allowing them to present a
+ * bean interface
+ *
+ * @author Tom Oinn
+ *
+ */
+ class ListItem {
+ Object value;
+
+ public ListItem(Object o) {
+ this.value = o;
+ }
+
+ public void setValue(Object o) {
+ try {
+ wrappedList.set(indexOf(this), o);
+ this.value = o;
+ }
+ catch (Exception ex) {
+ logger.error("Unable to set value", ex);
+ }
+ }
+
+ public Object getValue() {
+ return this.value;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java
new file mode 100644
index 0000000..7db727d
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java
@@ -0,0 +1,98 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Component;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+
+/**
+ * Handles lists where the elements in the list are beans with declared fields
+ * in the UIBuilder configuration.
+ *
+ * @author Tom Oinn
+ *
+ */
+public class RecursiveListComponent extends AbstractListComponent {
+
+ private static final long serialVersionUID = -3760308074241973969L;
+
+ @SuppressWarnings("unchecked")
+ public RecursiveListComponent(String fieldName, List theList,
+ Properties props, Map<String, Properties> fieldProps,
+ Class<?> newItemClass, List<String> subFields, String parent)
+ throws NoSuchMethodException, IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ super(fieldName, theList, props, fieldProps, newItemClass, subFields,
+ parent);
+ // TODO Auto-generated constructor stub
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void addNewItemToList(Object o) throws IllegalArgumentException,
+ NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException, ClassNotFoundException {
+ getUnderlyingList().add(0, o);
+ updateListContents();
+ }
+
+ @Override
+ protected void deleteItemAtIndex(int index)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ getUnderlyingList().remove(index);
+ updateListContents();
+ }
+
+ @Override
+ protected List<JComponent> getListComponents()
+ throws NoSuchMethodException, IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ List<JComponent> result = new ArrayList<JComponent>();
+ for (Object target : getUnderlyingList()) {
+ JPanel listItem = new JPanel();
+ listItem.setOpaque(false);
+ listItem.setLayout(new BoxLayout(listItem, BoxLayout.PAGE_AXIS));
+ UIBuilder.buildEditor(target, getSubFields(), getFieldProperties(),
+ listItem, getParentFieldName());
+ result.add(listItem);
+ }
+ return result;
+ }
+
+ @Override
+ public List<Component> getSeparatorComponents() {
+ List<Component> result = new ArrayList<Component>();
+ result.add(Box.createVerticalStrut(3));
+ result.add(new JSeparator(SwingConstants.HORIZONTAL));
+ result.add(Box.createVerticalStrut(3));
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void itemMoved(int fromIndex, int toIndex)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ Object toMove = getUnderlyingList().remove(fromIndex);
+ getUnderlyingList().add(toIndex, toMove);
+ updateListContents();
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java
new file mode 100644
index 0000000..2501046
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java
@@ -0,0 +1,236 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+
+/**
+ * Static methods to build bean editor UIs through reflection and (minimal)
+ * configuration
+ *
+ * @author Tom Oinn
+ *
+ */
+public abstract class UIBuilder {
+
+ /**
+ * Build an editor component for the specified target, the configuration
+ * string determines which properties are exposed to the UI and whether to
+ * drill into nested collections and composite bean types
+ *
+ * @param target
+ * @param configuration
+ * @return
+ * @throws NoSuchMethodException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ * @throws ClassNotFoundException
+ */
+ public static JPanel buildEditor(Object target, String configuration)
+ throws UIConstructionException {
+ String[] fields = configuration.split(";");
+ return buildEditor(target, fields);
+ }
+
+ public static JPanel buildEditor(Object target, String[] fields)
+ throws UIConstructionException {
+ // Now go through the configuration...
+ JPanel result = new JPanel();
+ result.setLayout(new BorderLayout());
+ JPanel contents = new JPanel();
+ contents.setOpaque(false);
+ contents.setLayout(new BoxLayout(contents, BoxLayout.PAGE_AXIS));
+ List<String> fieldNames = new ArrayList<String>();
+ Map<String, Properties> fieldProps = new HashMap<String, Properties>();
+ for (String field : fields) {
+ String fieldName = field.split(":")[0];
+ fieldNames.add(fieldName);
+ if (field.split(":").length > 1) {
+ String propertiesString = field.split(":")[1];
+ String[] props = propertiesString.split(",");
+ if (props.length == 0) {
+ props = new String[] { propertiesString };
+ }
+ Properties properties = new Properties();
+ for (String prop : props) {
+ if (prop.contains("=")) {
+ String[] p = prop.split("=");
+ properties.put(p[0], p[1]);
+ } else {
+ properties.put(prop, "true");
+ }
+ }
+ fieldProps.put(fieldName, properties);
+ }
+ }
+ try {
+ buildEditor(target, fieldNames, fieldProps, contents, "");
+ } catch (Exception ex) {
+ throw new UIConstructionException(
+ "Unable to construct UI from POJO", ex);
+ }
+ result.add(contents, BorderLayout.NORTH);
+ result.add(Box.createVerticalGlue(), BorderLayout.CENTER);
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ static void buildEditor(Object target, List<String> fieldNames,
+ Map<String, Properties> fieldProps, Container contents,
+ String parent) throws NoSuchMethodException,
+ IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException, ClassNotFoundException {
+
+ // Get all top level fields to render in this pass
+ List<String> activeFields = new ArrayList<String>();
+ for (String field : fieldNames) {
+ if (!field.contains(".")) {
+ activeFields.add(field);
+ }
+ }
+ // For each field generate the appropriate component
+ for (String field : activeFields) {
+
+ // Fetch the properties block for this field
+ Properties props = getProperties(field, parent, fieldProps);
+
+ // First check whether there are any subfields for this field, in
+ // which case we need to treat it differently
+ boolean simpleField = true;
+ for (String subField : fieldNames) {
+ if (!subField.equals(field) && subField.startsWith(field + ".")) {
+ simpleField = false;
+ break;
+ }
+ }
+ List<String> subFields = new ArrayList<String>();
+ // Create filtered list of the field names
+ // to ensure that this is now the top level
+ // field and recurse
+ String newParent = field;
+ if (!parent.equals("")) {
+ newParent = parent + "." + field;
+ }
+ for (String f : fieldNames) {
+ if (f.startsWith(field + ".")) {
+ subFields.add(f.substring(field.length() + 1));
+ }
+ }
+
+ // Secondly check whether this is a list
+ boolean listField = false;
+ Class<?> fieldType = BeanComponent.getPropertyType(target, field);
+ if (List.class.isAssignableFrom(fieldType)) {
+ listField = true;
+ }
+
+ // Now handle the four possible cases. If a non-list non-compound
+ // field we have a terminator and can obtain an appropriate subclass
+ // of BeanComponent to render it
+ if (!listField) {
+ // If a non-list non-compound field we have a terminator and can
+ // obtain an appropriate subclass of BeanComponent to render it
+ if (simpleField) {
+ if (fieldType.isEnum()) {
+ contents
+ .add(new BeanEnumComboBox(target, field, props));
+ } else if (Boolean.class.isAssignableFrom(fieldType)
+ || boolean.class.isAssignableFrom(fieldType)) {
+ contents.add(new BeanCheckBox(target, field, props));
+ } else {
+ if (props.get("type") != null) {
+ if (props.get("type").equals("textarea")) {
+ BeanTextArea bta = new BeanTextArea(target,
+ field, props);
+ contents.add(bta);
+ } else {
+ contents.add(new BeanTextField(target, field,
+ props));
+ }
+ } else {
+ contents
+ .add(new BeanTextField(target, field, props));
+ }
+ }
+ } else {
+ Object value = BeanComponent.findMethodWithPrefix("get",
+ target, field).invoke(target);
+ if (value != null) {
+ String displayName = field;
+ if (props.containsKey("name")) {
+ displayName = props.getProperty("name");
+ }
+ JPanel itemContainer = new JPanel();
+ itemContainer.setOpaque(false);
+ itemContainer.setBorder(BorderFactory
+ .createTitledBorder(displayName));
+ itemContainer.setLayout(new BoxLayout(itemContainer,
+ BoxLayout.PAGE_AXIS));
+ buildEditor(value, subFields, fieldProps,
+ itemContainer, newParent);
+ contents.add(itemContainer);
+ }
+ }
+ } else {
+ // Handle the case where this is a simple field (i.e. no
+ // sub-fields defined) but is a list type. In this case we need
+ // to build an appropriate wrapper list panel around this
+ // returned list.
+ List value = (List) BeanComponent.findMethodWithPrefix("get",
+ target, field).invoke(target);
+ // If the 'new' property is defined then fetch the Class object
+ // to be used to instantiate new list items
+ Class<?> newItemClass = null;
+ if (props.containsKey("new")) {
+ String newItemClassName = props.getProperty("new");
+ newItemClass = target.getClass().getClassLoader()
+ .loadClass(newItemClassName);
+ }
+ if (value != null) {
+ if (simpleField) {
+ contents.add(new WrappedListComponent(field, value,
+ props, fieldProps, newItemClass, subFields,
+ newParent));
+ // contents.add(buildWrappedList(field, value, props,
+ // newItemClass));
+ } else {
+ contents.add(new RecursiveListComponent(field, value,
+ props, fieldProps, newItemClass, subFields,
+ newParent));
+ // contents.add(buildList(field, value, props,
+ // fieldProps,
+ // newItemClass, subFields, newParent));
+ }
+ }
+ }
+ }
+ // Finally align any labels where appropriate
+ Alignment.alignInContainer(contents);
+ }
+
+ private static Properties getProperties(String field, String parent,
+ Map<String, Properties> props) {
+ String fullName = parent;
+ if (!parent.equals("")) {
+ fullName = fullName + ".";
+ }
+ fullName = fullName + field;
+ if (props.containsKey(fullName)) {
+ return props.get(fullName);
+ } else {
+ return new Properties();
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java
new file mode 100644
index 0000000..54167db
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java
@@ -0,0 +1,29 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+/**
+ * Used to wrap checked exceptions from the various reflection based UI
+ * construction methods
+ *
+ * @author Tom Oinn
+ */
+public class UIConstructionException extends RuntimeException {
+
+ private static final long serialVersionUID = 3396809563793962316L;
+
+ public UIConstructionException() {
+ //
+ }
+
+ public UIConstructionException(String message) {
+ super(message);
+ }
+
+ public UIConstructionException(Throwable cause) {
+ super(cause);
+ }
+
+ public UIConstructionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java
new file mode 100644
index 0000000..e44b036
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java
@@ -0,0 +1,105 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/**
+ * Models lists requiring use of the ListHandler helper class within the UI
+ *
+ * @author Tom Oinn
+ */
+public class WrappedListComponent extends AbstractListComponent {
+
+ private static final long serialVersionUID = -4457073442579747674L;
+ private ListHandler lh = null;
+
+ @SuppressWarnings("unchecked")
+ public WrappedListComponent(String fieldName, List theList,
+ Properties props, Map<String, Properties> fieldProps,
+ Class<?> newItemClass, List<String> subFields, String parent)
+ throws NoSuchMethodException, IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ super(fieldName, theList, props, fieldProps, newItemClass, subFields,
+ parent);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void addNewItemToList(Object o) throws IllegalArgumentException,
+ NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException, ClassNotFoundException {
+ // Keep lists in sync
+ getListHandler();
+ synchronized (lh) {
+ getUnderlyingList().add(0, o);
+ lh.add(0, lh.new ListItem(o));
+ }
+ updateListContents();
+ }
+
+ @Override
+ protected void deleteItemAtIndex(int index)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ getListHandler();
+ synchronized (lh) {
+ getUnderlyingList().remove(index);
+ lh.remove(index);
+ }
+ updateListContents();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void itemMoved(int fromIndex, int toIndex)
+ throws IllegalArgumentException, NoSuchMethodException,
+ IllegalAccessException, InvocationTargetException,
+ ClassNotFoundException {
+ getListHandler();
+ synchronized (lh) {
+ Object toMove = getUnderlyingList().remove(fromIndex);
+ ListHandler.ListItem wrapperToMove = lh.remove(fromIndex);
+ getUnderlyingList().add(toIndex, toMove);
+ lh.add(toIndex, wrapperToMove);
+ }
+ updateListContents();
+ }
+
+ @Override
+ protected List<JComponent> getListComponents() throws NoSuchMethodException {
+ getListHandler();
+ List<JComponent> result = new ArrayList<JComponent>();
+ for (ListHandler.ListItem item : lh) {
+ JPanel listItem = new JPanel();
+ listItem.setOpaque(false);
+ listItem.setLayout(new BoxLayout(listItem, BoxLayout.PAGE_AXIS));
+ Class<?> itemClass = item.getValue().getClass();
+ if (itemClass.isEnum()) {
+ result.add(new BeanEnumComboBox(item, "value", false,
+ new Properties()));
+ } else {
+ result.add(new BeanTextField(item, "value", false,
+ new Properties()));
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private ListHandler getListHandler() {
+ if (this.lh == null) {
+ lh = new ListHandler(getUnderlyingList());
+ }
+ return lh;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html
new file mode 100644
index 0000000..7ee4684
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html
@@ -0,0 +1,4 @@
+<body>
+Swing components to control and render the PluginManager and associated
+bean classes, most obviously PluginDescription
+</body>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png
new file mode 100644
index 0000000..4ad6a58
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png differ
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png
new file mode 100644
index 0000000..c7b2f03
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png differ
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png
new file mode 100644
index 0000000..7c437cf
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png differ
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png
new file mode 100644
index 0000000..bace5c1
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png differ
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java
new file mode 100644
index 0000000..3f90241
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java
@@ -0,0 +1,48 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Color;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+
+/**
+ * Torture test for the UIBuilder, run this as an application
+ *
+ * @author Tom Oinn
+ *
+ */
+public class Application {
+
+ private static final String NUMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+
+ public static void main(String[] args) throws ClassNotFoundException,
+ InstantiationException, IllegalAccessException,
+ UnsupportedLookAndFeelException {
+ try {
+ UIManager.setLookAndFeel(NUMBUS);
+ } catch (ClassNotFoundException ex) {
+ // ignore
+ }
+ Object bean = new TopLevelBean();
+ JFrame win = new JFrame();
+ JPanel contents = UIBuilder.buildEditor(bean, new String[] {
+ "boundbean", "boundbean.string:type=textarea", "boundbean.url",
+ "boundbean.uri",
+ "boundbean.string:type=textarea", "boundbean.url",
+ "enumeratedfield", "nest", "nest.list", "nest.list.list1",
+ "nest.list.list2", "nest.list.list2.string",
+ "nest.list.list2.url" });
+ contents.setBackground(new Color(240, 230, 200));
+ win.setContentPane(new JScrollPane(contents));
+ win.setTitle("Bean test");
+ win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ win.pack();
+ win.setVisible(true);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java
new file mode 100644
index 0000000..bf689c9
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java
@@ -0,0 +1,44 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Color;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+
+/**
+ * Torture test for the UIBuilder, run this as an application
+ *
+ * @author Tom Oinn
+ *
+ */
+public class Application2 {
+
+ private static final String NUMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+
+ public static void main(String[] args) throws ClassNotFoundException,
+ InstantiationException, IllegalAccessException,
+ UnsupportedLookAndFeelException {
+ try {
+ UIManager.setLookAndFeel(NUMBUS);
+ } catch (ClassNotFoundException ex) {
+ // ignore
+ }
+ Object bean = new PrimitiveTypeBean();
+ JFrame win = new JFrame();
+ JPanel contents = UIBuilder.buildEditor(bean, new String[] {
+ "intvalue", "shortValue", "longValue", "doubleValue",
+ "booleanValue", "byteValue", "floatValue", "charValue" });
+ contents.setBackground(new Color(240, 230, 200));
+ win.setContentPane(new JScrollPane(contents));
+ win.setTitle("Primitive type test");
+ win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ win.pack();
+ win.setVisible(true);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java
new file mode 100644
index 0000000..3f91ea2
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java
@@ -0,0 +1,74 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * Example bean with string and URL properties, both of which fire property
+ * change events
+ *
+ * @author Tom Oinn
+ *
+ */
+public class BeanWithBoundProps {
+
+ private String string = "Default value";
+ private URL url;
+ private PropertyChangeSupport pcs;
+ private URI uri;
+
+
+ public BeanWithBoundProps() {
+ try {
+ this.url = new URL("http://some.default.url");
+ this.pcs = new PropertyChangeSupport(this);
+ this.uri = URI.create("http://google.com/");
+ } catch (MalformedURLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public void setUrl(URL url) {
+ URL old = this.url;
+ this.url = url;
+ this.pcs.firePropertyChange("url", old, url);
+ }
+
+ public URL getUrl() {
+ return url;
+ }
+
+
+
+ public void setString(String string) {
+ String old = this.string;
+ this.string = string;
+ this.pcs.firePropertyChange("string", old, string);
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public void addPropertyChangeListener(String propertyName,
+ PropertyChangeListener l) {
+ pcs.addPropertyChangeListener(propertyName, l);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener l) {
+ pcs.removePropertyChangeListener(l);
+ }
+
+ public void setUri(URI uri) {
+ this.uri = uri;
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java
new file mode 100644
index 0000000..90d7c6a
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java
@@ -0,0 +1,35 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Sample bean with a couple of list properties
+ *
+ * @author Tom Oinn
+ *
+ */
+public class BeanWithListProperties {
+
+ private List<String> list1;
+ private List<BeanWithBoundProps> list2;
+
+ public BeanWithListProperties() {
+ this.list1 = new ArrayList<String>();
+ this.list2 = new ArrayList<BeanWithBoundProps>();
+ list1.add("A list item");
+ list1.add("Another item");
+ for (int i = 0; i < 10; i++) {
+ list2.add(new BeanWithBoundProps());
+ }
+ }
+
+ public List<String> getList1() {
+ return this.list1;
+ }
+
+ public List<BeanWithBoundProps> getList2() {
+ return this.list2;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java
new file mode 100644
index 0000000..231f068
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java
@@ -0,0 +1,28 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple bean with a list of the BeanWithListProperties to check nested list
+ * management
+ *
+ * @author Tom Oinn
+ *
+ */
+public class BeanWithNestedList {
+
+ private List<BeanWithListProperties> list;
+
+ public BeanWithNestedList() {
+ this.list = new ArrayList<BeanWithListProperties>();
+ for (int i = 0; i < 3; i++) {
+ list.add(new BeanWithListProperties());
+ }
+ }
+
+ public List<BeanWithListProperties> getList() {
+ return this.list;
+ }
+
+}