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 <[email protected]> Authored: Fri Mar 6 22:28:51 2015 +0000 Committer: Stian Soiland-Reyes <[email protected]> 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; + } + +}
