http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/TableTreeNodeRenderer.java
----------------------------------------------------------------------
diff --git 
a/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/TableTreeNodeRenderer.java
 
b/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/TableTreeNodeRenderer.java
new file mode 100644
index 0000000..12fd2a4
--- /dev/null
+++ 
b/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/TableTreeNodeRenderer.java
@@ -0,0 +1,554 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.partition.ui;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.UIManager;
+import javax.swing.plaf.TreeUI;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+
+public abstract class TableTreeNodeRenderer implements TreeCellRenderer {
+
+       private static final long serialVersionUID = -7291631337751330696L;
+
+       // The difference in indentation between a node and its child nodes, 
there
+       // isn't an easy way to get this other than constructing a JTree and
+       // measuring it - you'd think it would be a property of TreeUI but
+       // apparently not.
+       private static int perNodeOffset = -1;
+
+       // Use this to rubber stamp the original node renderer in before 
rendering
+       // the table
+       private TreeCellRenderer nodeRenderer;
+
+       // Determines the space allocated to leaf nodes and their parents when
+       // applying the stamp defined by the nodeRenderer
+       private int nodeWidth;
+
+       // Number of pixels of space to leave between the node label and the 
table
+       // header or rows
+       private int labelToTablePad = 3;
+
+       // Number of pixels to leave around the label rendered into the table 
cells
+       private int cellPadding = 4;
+
+       // Drawing borders?
+       private boolean drawingBorders = true;
+
+       // The number of pixels by which the height of the header is reduced
+       // compared to the row height, this leaves a small border above the 
header
+       // and separates it from the last row of the table above, if any.
+       private int headerTopPad = 4;
+
+       // The proportion of colour : black or colour : white used to create the
+       // darker or lighter shades is blendFactor : 1
+       private int blendFactor = 2;
+
+       // Colour to use to draw the table borders when they're enabled
+       private Color borderColour = Color.black;
+
+       /**
+        * Set the colour to be used to draw the borders if they are displayed 
at
+        * all. Defaults to black.
+        */
+       public void setBorderColour(Color borderColour) {
+               this.borderColour = borderColour;
+       }
+
+       /**
+        * The blend factor determines how strong the colour component is in the
+        * shadow and highlight colours used in the bevelled boxes, the ratio of
+        * black / white to colour is 1 : blendFactor
+        * 
+        * @param blendFactor
+        */
+       public void setBlendFactor(int blendFactor) {
+               this.blendFactor = blendFactor;
+       }
+
+       /**
+        * Set whether the renderer will draw borders around the table cells - 
if
+        * this is false the table still has the bevelled edges of the cell 
painters
+        * so will still look semi-bordered. Defaults to true if not otherwise 
set.
+        * 
+        * @param drawingBorders
+        */
+       public void setDrawBorders(boolean drawingBorders) {
+               this.drawingBorders = drawingBorders;
+       }
+
+       /**
+        * Override and implement to get the list of columns for a given 
partition
+        * node - currently assumes all partitions use the same column structure
+        * which I need to fix so it doesn't take a partition as argument (yet).
+        * 
+        * @return an array of column specifications used to drive the renderer
+        */
+       public abstract TableTreeNodeColumn[] getColumns();
+
+       /**
+        * Construct a new TableTreeNodeRenderer
+        * 
+        * @param nodeRenderer
+        *            The inner renderer used to render the node labels
+        * @param nodeWidth
+        *            Width of the cell space into which the node label is 
rendered
+        *            in the table header and row nodes
+        */
+       public TableTreeNodeRenderer(TreeCellRenderer nodeRenderer, int 
nodeWidth) {
+               super();
+               this.nodeRenderer = nodeRenderer;
+               this.nodeWidth = nodeWidth;
+       }
+
+       /**
+        * Do the magic!
+        */
+       public Component getTreeCellRendererComponent(final JTree tree,
+                       final Object value, final boolean selected, final 
boolean expanded,
+                       final boolean leaf, final int row, final boolean 
hasFocus) {
+               final Component nodeLabel = 
nodeRenderer.getTreeCellRendererComponent(
+                               tree, value, false, expanded, leaf, row, false);
+               final int nodeLabelHeight = (int) nodeLabel.getPreferredSize()
+                               .getHeight();
+               if (leaf) {
+                       // Rendering the leaf nodes, therefore use the table 
rendering
+                       // strategy
+                       getPerNodeIndentation(tree, row);
+                       return new JComponent() {
+                               private static final long serialVersionUID = 
4993815558563895266L;
+
+                               @Override
+                               public Dimension getPreferredSize() {
+                                       int width = nodeWidth + labelToTablePad;
+                                       for (TableTreeNodeColumn column : 
getColumns()) {
+                                               width += 
column.getColumnWidth();
+                                       }
+                                       return new Dimension(width, 
nodeLabelHeight);
+                               }
+
+                               @Override
+                               protected void paintComponent(Graphics g) {
+
+                                       Graphics2D g2d = (Graphics2D) 
g.create();
+                                       AffineTransform originalTransform = 
g2d.getTransform();
+                                       // Enable anti-aliasing for the curved 
lines
+                                       
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                                       
RenderingHints.VALUE_ANTIALIAS_ON);
+
+                                       // This method should paint a bevelled 
container for the
+                                       // original label but it doesn't really 
work terribly well
+                                       // as we can't ensure that the original 
label is actually
+                                       // honouring any opaque flags.
+                                       if (drawingBorders) {
+                                               paintRectangleWithBevel(g2d, 
nodeWidth
+                                                               + 
labelToTablePad, getHeight(), Color.white);
+                                       }
+
+                                       // Paint original node label
+                                       nodeLabel.setSize(new Dimension(
+                                                       nodeWidth - cellPadding 
* 2, getHeight()
+                                                                       - 
(drawingBorders ? 2 : 1)));
+                                       g2d.translate(cellPadding, 0);
+                                       nodeLabel.paint(g2d);
+                                       g2d.translate(-cellPadding, 0);
+
+                                       if (drawingBorders) {
+                                               paintRectangleBorder(g2d, 
nodeWidth + labelToTablePad,
+                                                               getHeight(), 0, 
0, 1, 1, borderColour);
+                                       }
+
+                                       g2d.translate(nodeWidth + 
labelToTablePad, 0);
+                                       boolean first = true;
+                                       for (TableTreeNodeColumn column : 
getColumns()) {
+
+                                               Color fillColour = 
column.getColour().brighter();
+                                               Object parentNode = 
tree.getPathForRow(row)
+                                                               
.getParentPath().getLastPathComponent();
+                                               int indexInParent = 
tree.getModel().getIndexOfChild(
+                                                               parentNode, 
value);
+                                               if ((indexInParent & 1) == 1) {
+                                                       fillColour = new Color(
+                                                                       
(fillColour.getRed() + column.getColour()
+                                                                               
        .getRed()) / 2, (fillColour
+                                                                               
        .getGreen() + column.getColour()
+                                                                               
        .getGreen()) / 2, (fillColour
+                                                                               
        .getBlue() + column.getColour()
+                                                                               
        .getBlue()) / 2);
+                                               }
+
+                                               // Paint background and bevel
+                                               paintRectangleWithBevel(g2d, 
column.getColumnWidth(),
+                                                               getHeight(), 
fillColour);
+
+                                               // Paint cell component
+                                               Component cellComponent = 
column.getCellRenderer(value);
+                                               cellComponent.setSize(new 
Dimension(column
+                                                               
.getColumnWidth()
+                                                               - cellPadding * 
2, getHeight()));
+                                               g2d.translate(cellPadding, 0);
+                                               cellComponent.paint(g2d);
+                                               g2d.translate(-cellPadding, 0);
+
+                                               // Draw border
+                                               if (drawingBorders) {
+                                                       
paintRectangleBorder(g2d, column.getColumnWidth(),
+                                                                       
getHeight(), 0, 1, 1, first ? 1 : 0,
+                                                                       
borderColour);
+                                               }
+                                               first = false;
+
+                                               
g2d.translate(column.getColumnWidth(), 0);
+
+                                       }
+                                       if (selected) {
+                                               
g2d.setTransform(originalTransform);
+                                               g2d.translate(2, 0);
+                                               
paintRectangleWithHighlightColour(g2d, getWidth()
+                                                               - 
(drawingBorders ? 4 : 2), getHeight()
+                                                               - 
(drawingBorders ? 2 : 0));
+                                       }
+                               }
+                       };
+               } else {
+                       // If there are no child nodes, or there are child 
nodes but they
+                       // aren't leaves then we render the cell as normal. If 
there are
+                       // child nodes and the first one is a leaf (we assume 
this means
+                       // they all are!) then we render the table header after 
the label.
+                       if (!expanded) {
+                               return getLabelWithHighlight(nodeLabel, 
selected);
+                       }
+                       // Expanded so do the model check...
+                       TreeModel model = tree.getModel();
+                       int childCount = model.getChildCount(value);
+                       if (childCount == 0) {
+                               return getLabelWithHighlight(nodeLabel, 
selected);
+                       }
+                       Object childNode = model.getChild(value, 0);
+                       if (!model.isLeaf(childNode)) {
+                               return getLabelWithHighlight(nodeLabel, 
selected);
+                       }
+                       getPerNodeIndentation(tree, row);
+                       // Got to here so we need to render a table header.
+                       return new JComponent() {
+                               private static final long serialVersionUID = 
-4923965850510357216L;
+
+                               @Override
+                               public Dimension getPreferredSize() {
+                                       int width = nodeWidth + labelToTablePad 
+ perNodeOffset;
+                                       for (TableTreeNodeColumn column : 
getColumns()) {
+                                               width += 
column.getColumnWidth();
+                                       }
+                                       return new Dimension(width, 
nodeLabelHeight);
+                               }
+
+                               @Override
+                               protected void paintComponent(Graphics g) {
+
+                                       Graphics2D g2d = (Graphics2D) 
g.create();
+                                       AffineTransform originalTransform = 
g2d.getTransform();
+                                       // Enable anti-aliasing for the curved 
lines
+                                       
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                                       
RenderingHints.VALUE_ANTIALIAS_ON);
+
+                                       // Paint original node label
+                                       nodeLabel.setSize(new 
Dimension(nodeWidth + perNodeOffset,
+                                                       getHeight()));
+                                       nodeLabel.paint(g2d);
+
+                                       // Draw line under label to act as line 
above table row
+                                       // below
+                                       if (drawingBorders) {
+                                               GeneralPath path = new 
GeneralPath();
+                                               path.moveTo(perNodeOffset, 
getHeight() - 1);
+                                               path.lineTo(
+                                                               perNodeOffset + 
nodeWidth + labelToTablePad,
+                                                               getHeight() - 
1);
+                                               g2d.setStroke(new 
BasicStroke(1, BasicStroke.CAP_BUTT,
+                                                               
BasicStroke.JOIN_MITER));
+                                               g2d.setPaint(borderColour);
+                                               g2d.draw(path);
+                                       }
+
+                                       // Move painting origin to the start of 
the header row
+                                       g2d.translate(nodeWidth + perNodeOffset 
+ labelToTablePad,
+                                                       0);
+
+                                       // Paint columns
+                                       boolean first = true;
+                                       for (TableTreeNodeColumn column : 
getColumns()) {
+
+                                               // Paint header cell background 
with bevel
+                                               g2d.translate(0, headerTopPad);
+                                               paintRectangleWithBevel(g2d, 
column.getColumnWidth(),
+                                                               getHeight() - 
headerTopPad, column.getColour());
+
+                                               // Paint header label
+                                               JLabel columnLabel = new 
JLabel(column.getShortName());
+                                               columnLabel.setSize(new 
Dimension(column
+                                                               
.getColumnWidth()
+                                                               - cellPadding * 
2, getHeight() - headerTopPad));
+                                               g2d.translate(cellPadding, 0);
+                                               columnLabel.paint(g2d);
+                                               g2d.translate(-cellPadding, 0);
+
+                                               // Paint header borders
+                                               if (drawingBorders) {
+                                                       
paintRectangleBorder(g2d, column.getColumnWidth(),
+                                                                       
getHeight() - headerTopPad, 1, 1, 1,
+                                                                       first ? 
1 : 0, borderColour);
+                                               }
+                                               g2d.translate(0, -headerTopPad);
+                                               first = false;
+                                               
g2d.translate(column.getColumnWidth(), 0);
+
+                                       }
+                                       if (selected) {
+                                               
g2d.setTransform(originalTransform);
+                                               g2d.translate(1, headerTopPad);
+                                               
paintRectangleWithHighlightColour(g2d, perNodeOffset
+                                                               + nodeWidth + 
labelToTablePad
+                                                               - 
(drawingBorders ? 2 : 0), getHeight()
+                                                               - (headerTopPad 
+ 2));
+                                       }
+                               }
+                       };
+
+               }
+
+       }
+
+       private static Component getLabelWithHighlight(final Component c,
+                       boolean selected) {
+               if (!selected) {
+                       return c;
+               } else {
+                       return new JComponent() {
+                               private static final long serialVersionUID = 
-9175635475959046704L;
+
+                               @Override
+                               public Dimension getPreferredSize() {
+                                       return c.getPreferredSize();
+                               }
+
+                               @Override
+                               protected void paintComponent(Graphics g) {
+                                       Graphics2D g2d = (Graphics2D) 
g.create();
+                                       c.setSize(new Dimension(getWidth(), 
getHeight()));
+                                       c.paint(g2d);
+                                       
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                                       
RenderingHints.VALUE_ANTIALIAS_ON);
+                                       g2d.translate(1, 1);
+                                       paintRectangleWithHighlightColour(g2d, 
getWidth() - 2,
+                                                       getHeight() - 2);
+                               }
+                       };
+               }
+       }
+
+       private static void paintRectangleBorder(Graphics2D g2d, int width,
+                       int height, int north, int east, int south, int west, 
Color c) {
+               Paint oldPaint = g2d.getPaint();
+               Stroke oldStroke = g2d.getStroke();
+
+               g2d.setPaint(c);
+
+               GeneralPath path;
+
+               if (north > 0) {
+                       g2d.setStroke(new BasicStroke(north, 
BasicStroke.CAP_BUTT,
+                                       BasicStroke.JOIN_MITER));
+                       path = new GeneralPath();
+                       path.moveTo(0, north - 1);
+                       path.lineTo(width - 1, north - 1);
+                       g2d.draw(path);
+               }
+               if (east > 0) {
+                       g2d.setStroke(new BasicStroke(east, 
BasicStroke.CAP_BUTT,
+                                       BasicStroke.JOIN_MITER));
+                       path = new GeneralPath();
+                       path.moveTo(width - east, 0);
+                       path.lineTo(width - east, height - 1);
+                       g2d.draw(path);
+               }
+               if (south > 0) {
+                       g2d.setStroke(new BasicStroke(south, 
BasicStroke.CAP_BUTT,
+                                       BasicStroke.JOIN_MITER));
+                       path = new GeneralPath();
+                       path.moveTo(0, height - south);
+                       path.lineTo(width - 1, height - south);
+                       g2d.draw(path);
+               }
+               if (west > 0) {
+                       g2d.setStroke(new BasicStroke(west, 
BasicStroke.CAP_BUTT,
+                                       BasicStroke.JOIN_MITER));
+                       path = new GeneralPath();
+                       path.moveTo(west - 1, 0);
+                       path.lineTo(west - 1, height - 1);
+                       g2d.draw(path);
+               }
+
+               g2d.setPaint(oldPaint);
+               g2d.setStroke(oldStroke);
+       }
+
+       /**
+        * Paint a rectangle with the border colour set from the UIManager
+        * 'textHighlight' property and filled with the same colour at alpha 
50/255.
+        * Paints from 0,0 to width-1,height-1 into the specified Graphics2D,
+        * preserving the existing paint and stroke properties on exit.
+        */
+       private static void paintRectangleWithHighlightColour(Graphics2D g2d,
+                       int width, int height) {
+               GeneralPath path = new GeneralPath();
+               path.moveTo(0, 0);
+               path.lineTo(width - 1, 0);
+               path.lineTo(width - 1, height - 1);
+               path.lineTo(0, height - 1);
+               path.closePath();
+
+               Paint oldPaint = g2d.getPaint();
+               Stroke oldStroke = g2d.getStroke();
+
+               Color c = UIManager.getColor("textHighlight");
+
+               g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT,
+                               BasicStroke.JOIN_MITER));
+               g2d.setPaint(c);
+               g2d.draw(path);
+
+               Color alpha = new Color(c.getRed(), c.getGreen(), c.getBlue(), 
50);
+               g2d.setPaint(alpha);
+               g2d.fill(path);
+
+               g2d.setPaint(oldPaint);
+               g2d.setStroke(oldStroke);
+
+       }
+
+       /**
+        * Paint a bevelled rectangle into the specified Graphics2D with shape 
from
+        * 0,0 to width-1,height-1 using the specified colour as a base and
+        * preserving colour and stroke in the Graphics2D
+        */
+       private void paintRectangleWithBevel(Graphics2D g2d, int width, int 
height,
+                       Color c) {
+               if (drawingBorders) {
+                       width = width - 1;
+                       height = height - 1;
+               }
+
+               GeneralPath path = new GeneralPath();
+               path.moveTo(0, 0);
+               path.lineTo(width - 1, 0);
+               path.lineTo(width - 1, height - 1);
+               path.lineTo(0, height - 1);
+               path.closePath();
+
+               Paint oldPaint = g2d.getPaint();
+               Stroke oldStroke = g2d.getStroke();
+
+               g2d.setPaint(c);
+               g2d.fill(path);
+
+               g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
+                               BasicStroke.JOIN_MITER));
+
+               // Draw highlight (Northeast)
+               path = new GeneralPath();
+               path.moveTo(0, 0);
+               path.lineTo(width - 1, 0);
+               path.lineTo(width - 1, height - 1);
+               Color highlightColour = new Color((c.getRed() * blendFactor + 
255)
+                               / (blendFactor + 1), (c.getGreen() * 
blendFactor + 255)
+                               / (blendFactor + 1), (c.getBlue() * blendFactor 
+ 255)
+                               / (blendFactor + 1));
+               g2d.setPaint(highlightColour);
+               g2d.draw(path);
+
+               // Draw shadow (Southwest)
+               path = new GeneralPath();
+               path.moveTo(0, 0);
+               path.lineTo(0, height - 1);
+               path.lineTo(width - 1, height - 1);
+               Color shadowColour = new Color((c.getRed() * blendFactor)
+                               / (blendFactor + 1), (c.getGreen() * 
blendFactor)
+                               / (blendFactor + 1), (c.getBlue() * blendFactor)
+                               / (blendFactor + 1));
+               g2d.setPaint(shadowColour);
+               g2d.draw(path);
+
+               g2d.setPaint(oldPaint);
+               g2d.setStroke(oldStroke);
+
+       }
+
+       /**
+        * The TreeUI which was used to determine the per node indentation in 
the
+        * JTree for which this is a renderer. If this hasn't been set yet then 
this
+        * is null.
+        */
+       private static TreeUI cachedTreeUI = null;
+
+       /**
+        * Use the current TreeUI to determine the indentation per-node in the 
tree,
+        * this only works when the treeRow passed in is not the root as it has 
to
+        * traverse up to the parent to do anything sensible. Cached and 
associated
+        * with the TreeUI so in theory if the look and feel changes the UI 
changes
+        * and this is re-generated within the renderer code.
+        * 
+        * @param tree
+        * @param treeRow
+        * @return
+        */
+       private static int getPerNodeIndentation(JTree tree, int treeRow) {
+               if (perNodeOffset > 0 && tree.getUI() == cachedTreeUI) {
+                       return perNodeOffset;
+               }
+               TreeUI uiModel = tree.getUI();
+               cachedTreeUI = uiModel;
+               TreePath path = tree.getPathForRow(treeRow);
+               Rectangle nodeBounds = uiModel.getPathBounds(tree, path);
+               Rectangle parentNodeBounds = uiModel.getPathBounds(tree, path
+                               .getParentPath());
+               perNodeOffset = (int) nodeBounds.getMinX()
+                               - (int) parentNodeBounds.getMinX();
+               return perNodeOffset;
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/UITest.java
----------------------------------------------------------------------
diff --git 
a/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/UITest.java 
b/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/UITest.java
new file mode 100644
index 0000000..05a6112
--- /dev/null
+++ b/taverna-partition/src/main/java/net/sf/taverna/t2/partition/ui/UITest.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.partition.ui;
+
+import java.awt.BorderLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+import net.sf.taverna.t2.partition.PartitionAlgorithm;
+import net.sf.taverna.t2.partition.algorithms.LiteralValuePartitionAlgorithm;
+
+public class UITest extends JFrame {
+
+       private static final long serialVersionUID = -734851883737477053L;
+
+       public UITest() {
+               super();
+               getContentPane().setLayout(new BorderLayout());
+               List<PartitionAlgorithm<?>> paList = new 
ArrayList<PartitionAlgorithm<?>>();
+               paList.add(new LiteralValuePartitionAlgorithm());
+               paList.add(new LiteralValuePartitionAlgorithm());
+               paList.add(new LiteralValuePartitionAlgorithm());
+               PartitionAlgorithmListEditor pale = new 
PartitionAlgorithmListEditor(paList);
+               getContentPane().add(pale, BorderLayout.NORTH);
+               setVisible(true);
+               
+       }
+       
+       public static void main(String[] args) {
+               JLabel l = new JLabel("Foo");
+               System.out.println(l.getPreferredSize());
+               System.out.println(l.getWidth());
+               
+               new UITest();
+       }
+       
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-partition/src/test/java/net/sf/taverna/t2/partition/PartitionTestApplication.java
----------------------------------------------------------------------
diff --git 
a/taverna-partition/src/test/java/net/sf/taverna/t2/partition/PartitionTestApplication.java
 
b/taverna-partition/src/test/java/net/sf/taverna/t2/partition/PartitionTestApplication.java
new file mode 100644
index 0000000..07364f1
--- /dev/null
+++ 
b/taverna-partition/src/test/java/net/sf/taverna/t2/partition/PartitionTestApplication.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.partition;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.partition.algorithms.LiteralValuePartitionAlgorithm;
+import net.sf.taverna.t2.partition.ui.TableTreeNodeColumn;
+import net.sf.taverna.t2.partition.ui.TableTreeNodeRenderer;
+
+/**
+ * Exercise the partition algorithm codes
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class PartitionTestApplication {
+
+       final static PropertyExtractorRegistry reg = new 
ExampleExtractorRegistry();
+
+       public static void main(String[] args) throws InterruptedException {
+               try {
+                       // Set System L&F
+                       
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+               } catch (Exception e) {
+                       //
+               }
+
+               JFrame frame = new JFrame("Partition test");
+               frame.addWindowListener(new WindowAdapter() {
+                       @Override
+                       public void windowClosing(WindowEvent e) {
+                               System.exit(0);
+                       }
+
+               });
+
+               RootPartition<ExampleItem> partition = new 
RootPartition<ExampleItem>(
+                               getAlgorithms(), reg);
+               JTree partitionTree = new AlwaysOpenJTree(partition);
+               partitionTree.setRowHeight(24);
+               TreeCellRenderer oldRenderer = partitionTree.getCellRenderer();
+               TableTreeNodeRenderer ttnr = new 
TableTreeNodeRenderer(oldRenderer, 50) {
+                       @Override
+                       public TableTreeNodeColumn[] getColumns() {
+                               return new TableTreeNodeColumn[] {
+                                               new 
TableTreeNodeColumnImpl("int", new Color(150, 150,
+                                                               210), 60,reg),
+                                               new 
TableTreeNodeColumnImpl("float", new Color(150,
+                                                               210, 150), 
60,reg),
+                                               new 
TableTreeNodeColumnImpl("name", new Color(210, 150,
+                                                               150), 60,reg) ,
+                                               new 
TableTreeNodeColumnImpl("Fish", new Color(210, 150,
+                                               150), 60,reg) };
+                       }
+               };
+
+               ttnr.setBorderColour(new Color(150, 150, 150));
+
+               partitionTree.setCellRenderer(ttnr);
+
+               frame.getContentPane().setLayout(new 
BoxLayout(frame.getContentPane(),BoxLayout.Y_AXIS));
+               frame.getContentPane().add(new JScrollPane(partitionTree));
+               frame.getContentPane().add(new JScrollPane(partitionTree));
+               frame.setSize(400, 200);
+               frame.setVisible(true);
+               boolean showFrames = false;
+               //while (true) {
+                       ttnr.setDrawBorders(showFrames);
+                       showFrames = !showFrames;
+                       for (ExampleItem item : exampleItems) {
+                               Thread.sleep(200);
+                               partition.addOrUpdateItem(item);
+                       }
+//                     Thread.sleep(1000);
+//                     for (ExampleItem item : exampleItems) {
+//                             Thread.sleep(400);
+//                             partition.removeItem(item);
+//                     }
+               //}
+       }
+
+       static ExampleItem[] exampleItems = new ExampleItem[] {
+                       new ExampleItem("foo", 1, 2.0f), new ExampleItem("bar", 
1, 2.0f),
+                       new ExampleItem("foo", 4, 3.7f), new ExampleItem("foo", 
3, 2.0f),
+                       new ExampleItem("bar", 1, 3.5f), new ExampleItem("bar", 
1, 7.5f),
+                       new ExampleItem("foo", 1, 2.1f), new ExampleItem("bar", 
1, 2.3f),
+                       new ExampleItem("foo", 4, 3.8f), new ExampleItem("foo", 
3, 2.4f) };
+
+       static class TableTreeNodeColumnImpl implements TableTreeNodeColumn {
+
+               private String propertyName;
+               private Color colour;
+               private int columnWidth;
+               private PropertyExtractorRegistry reg;
+
+               public TableTreeNodeColumnImpl(String propertyName, Color 
colour,
+                               int width,PropertyExtractorRegistry reg) {
+                       this.propertyName = propertyName;
+                       this.colour = colour;
+                       this.columnWidth = width;
+                       this.reg=reg;
+               }
+
+               public Component getCellRenderer(Object value) {
+                       Object propertyValue = 
reg.getAllPropertiesFor(value).get(
+                                       propertyName);
+                       if (propertyValue == null) {
+                               propertyValue = "Not defined";
+                       }
+                       return new JLabel(propertyValue.toString());
+               }
+
+               public Color getColour() {
+                       return this.colour;
+               }
+
+               public int getColumnWidth() {
+                       return columnWidth;
+               }
+
+               public String getDescription() {
+                       return "A description...";
+               }
+
+               public String getShortName() {
+                       return propertyName;
+               }
+
+       }
+
+       static List<PartitionAlgorithm<?>> getAlgorithms() {
+               List<PartitionAlgorithm<?>> paList = new 
ArrayList<PartitionAlgorithm<?>>();
+               LiteralValuePartitionAlgorithm lvpa = new 
LiteralValuePartitionAlgorithm();
+               lvpa.setPropertyName("float");
+               
+               LiteralValuePartitionAlgorithm lvpa2 = new 
LiteralValuePartitionAlgorithm();
+               lvpa2.setPropertyName("int");
+               LiteralValuePartitionAlgorithm lvpa3 = new 
LiteralValuePartitionAlgorithm();
+               lvpa3.setPropertyName("name");
+               
+               paList.add(lvpa2);
+               paList.add(lvpa);
+               paList.add(lvpa3);
+               
+               return paList;
+       }
+
+       static class ExampleItem implements Comparable<Object>{
+               private String name;
+               private int intValue;
+               private float floatValue;
+
+               public String getName() {
+                       return this.name;
+               }
+
+               public int getIntValue() {
+                       return this.intValue;
+               }
+
+               public float getFloatValue() {
+                       return this.floatValue;
+               }
+
+               public ExampleItem(String name, int intValue, float floatValue) 
{
+                       this.name = name;
+                       this.intValue = intValue;
+                       this.floatValue = floatValue;
+               }
+
+               @Override
+               public String toString() {
+                       return this.name;
+               }
+
+               public int compareTo(Object o) {
+                       // TODO Auto-generated method stub
+                       return 0;
+               }
+       }
+
+       static class ExampleExtractorRegistry implements 
PropertyExtractorRegistry {
+               public Map<String, Object> getAllPropertiesFor(Object target) {
+                       Map<String, Object> properties = new HashMap<String, 
Object>();
+                       if (target instanceof ExampleItem) {
+                               ExampleItem item = (ExampleItem) target;
+                               properties.put("name", item.getName());
+                               properties.put("int", item.getIntValue());
+                               properties.put("float", item.getFloatValue());
+                               properties.put("Fish", "Soup");
+                       }
+                       
+                       return properties;
+               }
+       }
+
+       static class AlwaysOpenJTree extends JTree {
+
+               private static final long serialVersionUID = 
-3769998854485605447L;
+
+               public AlwaysOpenJTree(TreeModel newModel) {
+                       super(newModel);
+                       setEditable(false);
+                       setExpandsSelectedPaths(false);
+                       setDragEnabled(false);
+                       setScrollsOnExpand(false);
+                       // 
setSelectionModel(SingleSelectionModel.sharedInstance());
+               }
+
+               @Override
+               public void setModel(TreeModel model) {
+                       if (treeModel == model)
+                               return;
+                       if (treeModelListener == null)
+                               treeModelListener = new TreeModelHandler() {
+                                       @Override
+                                       public void treeNodesInserted(final 
TreeModelEvent ev) {
+                                               if (ev.getChildren()[0] 
instanceof Partition == false) {
+                                                       
SwingUtilities.invokeLater(new Runnable() {
+                                                               public void 
run() {
+                                                                       
TreePath path = ev.getTreePath();
+                                                                       
setExpandedState(path, true);
+                                                                       
fireTreeExpanded(path);
+                                                               }
+                                                       });
+                                               }
+
+                                       }
+
+                               };
+                       if (model != null) {
+                               model.addTreeModelListener(treeModelListener);
+                       }
+                       TreeModel oldValue = treeModel;
+                       treeModel = model;
+                       firePropertyChange(TREE_MODEL_PROPERTY, oldValue, 
model);
+               }
+
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-partition/src/test/java/net/sf/taverna/t2/partition/algorithms/LiteralValuePartitionAlgorithmTest.java
----------------------------------------------------------------------
diff --git 
a/taverna-partition/src/test/java/net/sf/taverna/t2/partition/algorithms/LiteralValuePartitionAlgorithmTest.java
 
b/taverna-partition/src/test/java/net/sf/taverna/t2/partition/algorithms/LiteralValuePartitionAlgorithmTest.java
new file mode 100644
index 0000000..4e8f93c
--- /dev/null
+++ 
b/taverna-partition/src/test/java/net/sf/taverna/t2/partition/algorithms/LiteralValuePartitionAlgorithmTest.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.partition.algorithms;
+
+import static org.junit.Assert.*;
+import net.sf.taverna.t2.partition.algorithms.LiteralValuePartitionAlgorithm;
+
+import org.junit.Test;
+
+public class LiteralValuePartitionAlgorithmTest {
+       
+       @Test
+       public void testEquals() {
+               LiteralValuePartitionAlgorithm a = new 
LiteralValuePartitionAlgorithm();
+               LiteralValuePartitionAlgorithm b = new 
LiteralValuePartitionAlgorithm();
+               LiteralValuePartitionAlgorithm c = new 
LiteralValuePartitionAlgorithm();
+               
+               a.setPropertyName("cheese");
+               b.setPropertyName("cheese");
+               c.setPropertyName("butter");
+               
+               assertEquals("They should be equal",a,a);
+               assertEquals("They should be equal",a,b);
+               assertFalse("They should not be equal",a.equals(c));
+               assertFalse("They should not be equal",a.equals("cheese"));
+       }
+       
+       @Test
+       public void testHashcode() {
+               LiteralValuePartitionAlgorithm a = new 
LiteralValuePartitionAlgorithm();
+               LiteralValuePartitionAlgorithm b = new 
LiteralValuePartitionAlgorithm();
+               LiteralValuePartitionAlgorithm c = new 
LiteralValuePartitionAlgorithm();
+               
+               a.setPropertyName("cheese");
+               b.setPropertyName("cheese");
+               c.setPropertyName("Z");
+               
+               assertEquals("They should have the same 
hashcode",a.hashCode(),b.hashCode());
+       }
+       
+       @Test
+       public void testConstructor() {
+               LiteralValuePartitionAlgorithm p = new 
LiteralValuePartitionAlgorithm();
+               assertNull("The property shoudl default to 
null",p.getPropertyName());
+               
+               p=new LiteralValuePartitionAlgorithm("pea");
+               assertEquals("The property name should default to 
'pea'","pea",p.getPropertyName());
+       }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-ui/pom.xml b/taverna-ui/pom.xml
new file mode 100644
index 0000000..6d42dc3
--- /dev/null
+++ b/taverna-ui/pom.xml
@@ -0,0 +1,36 @@
+<?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>ui</artifactId>
+       <packaging>bundle</packaging>
+       <name>User interface (Swing) utility classes</name>
+       <dependencies>
+               <dependency>
+                       <groupId>net.sf.taverna.t2.lang</groupId>
+                       <artifactId>observer</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>net.sf.taverna.jedit</groupId>
+                       <artifactId>jedit-syntax</artifactId>
+                       <version>${jedit.syntax.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>commons-io</groupId>
+                       <artifactId>commons-io</artifactId>
+                       <version>${commons.io.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/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CArrowImage.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CArrowImage.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CArrowImage.java
new file mode 100644
index 0000000..6a258de
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CArrowImage.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.SystemColor;
+import java.awt.RenderingHints.Key;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A BufferedImage of one of four types of arrow (up, down, left or right) 
drawn
+ * to the size specified on the constructor.
+ */
+public class CArrowImage extends BufferedImage {
+       // Constants...
+       public static final int ARROW_UP = 0;
+
+       public static final int ARROW_DOWN = 1;
+
+       public static final int ARROW_LEFT = 2;
+
+       public static final int ARROW_RIGHT = 3;
+
+       // Member variables...
+       private GeneralPath _pathArrow = new GeneralPath();
+
+       // Constructor...
+       public CArrowImage(int nArrowDirection) {
+               this(15, 9, nArrowDirection);
+       }
+
+       public CArrowImage(int nWidth, int nHeight, int nArrowDirection) {
+               super(nWidth, nHeight, TYPE_INT_ARGB_PRE); // Set the width, 
height and
+               // image type
+
+               Map<Key, Object> map = new HashMap<Key, Object>();
+               map.put(RenderingHints.KEY_ANTIALIASING,
+                               RenderingHints.VALUE_ANTIALIAS_ON);
+               map.put(RenderingHints.KEY_RENDERING,
+                               RenderingHints.VALUE_RENDER_QUALITY);
+               RenderingHints hints = new RenderingHints(map);
+
+               Graphics2D g2 = this.createGraphics(); // Create a graphics 
context for
+               // this buffered image
+               g2.setRenderingHints(hints);
+
+               float h = getHeight();
+               float w = getWidth();
+               float w13 = w / 3;
+               float w12 = w / 2;
+               float w23 = w * 2 / 3;
+               float h13 = h / 3;
+               float h12 = h / 2;
+               float h23 = h * 2 / 3;
+
+               switch (nArrowDirection) {
+               case ARROW_UP:
+                       _pathArrow.moveTo(w12, h12);
+                       _pathArrow.lineTo(w12, 0);
+                       _pathArrow.lineTo(w, h - 1);
+                       _pathArrow.lineTo(0, h - 1);
+                       _pathArrow.closePath();
+                       g2.setPaint(new GradientPaint(w13, h13,
+                                       SystemColor.controlLtHighlight, w, h - 
1,
+                                       SystemColor.controlShadow));
+
+                       g2.fill(_pathArrow);
+
+                       g2.setColor(SystemColor.controlDkShadow);
+                       g2.draw(new Line2D.Float(0, h - 1, w, h - 1));
+                       g2.setColor(SystemColor.controlShadow);
+                       g2.draw(new Line2D.Float(w12, 0, w, h - 1));
+                       g2.setColor(SystemColor.controlLtHighlight);
+                       g2.draw(new Line2D.Float(0, h - 1, w12, 0));
+                       break;
+
+               case ARROW_DOWN:
+                       _pathArrow.moveTo(w12, h12);
+                       _pathArrow.lineTo(w, 0);
+                       _pathArrow.lineTo(w12, h - 1);
+                       _pathArrow.closePath();
+                       g2.setPaint(new GradientPaint(0, 0, 
SystemColor.controlLtHighlight,
+                                       w23, h23, SystemColor.controlShadow));
+                       g2.fill(_pathArrow);
+
+                       g2.setColor(SystemColor.controlDkShadow);
+                       g2.draw(new Line2D.Float(w, 0, w12, h - 1));
+                       g2.setColor(SystemColor.controlShadow);
+                       g2.draw(new Line2D.Float(w12, h - 1, 0, 0));
+                       g2.setColor(SystemColor.controlLtHighlight);
+                       g2.draw(new Line2D.Float(0, 0, w, 0));
+                       break;
+
+               case ARROW_LEFT:
+                       _pathArrow.moveTo(w - 1, h13);
+                       _pathArrow.lineTo(w13, h13);
+                       _pathArrow.lineTo(w13, 0);
+                       _pathArrow.lineTo(0, h12);
+                       _pathArrow.lineTo(w13, h - 1);
+                       _pathArrow.lineTo(w13, h23);
+                       _pathArrow.lineTo(w - 1, h23);
+                       _pathArrow.closePath();
+                       g2.setPaint(new GradientPaint(0, 0, Color.white, // 
SystemColor.
+                                                                               
                                                // controlLtHighlight
+                                                                               
                                                // ,
+                                       0, h, SystemColor.controlShadow));
+                       g2.fill(_pathArrow);
+
+                       _pathArrow.reset();
+                       _pathArrow.moveTo(w13, 0);
+                       _pathArrow.lineTo(w13, h13);
+                       _pathArrow.moveTo(w - 1, h13);
+                       _pathArrow.lineTo(w - 1, h23);
+                       _pathArrow.lineTo(w13, h23);
+                       _pathArrow.lineTo(w13, h - 1);
+                       g2.setColor(SystemColor.controlDkShadow);
+                       g2.draw(_pathArrow);
+
+                       g2.setColor(SystemColor.controlShadow);
+                       g2.draw(new Line2D.Float(0, h12, w13, h - 1));
+
+                       _pathArrow.reset();
+                       _pathArrow.moveTo(0, h12);
+                       _pathArrow.lineTo(w13, 0);
+                       _pathArrow.moveTo(w13, h13);
+                       _pathArrow.lineTo(w - 1, h13);
+                       g2.setColor(SystemColor.controlLtHighlight);
+                       g2.draw(_pathArrow);
+                       break;
+
+               case ARROW_RIGHT:
+               default: {
+                       _pathArrow.moveTo(0, h13);
+                       _pathArrow.lineTo(w23, h13);
+                       _pathArrow.lineTo(w23, 0);
+                       _pathArrow.lineTo(w - 1, h12);
+                       _pathArrow.lineTo(w23, h - 1);
+                       _pathArrow.lineTo(w23, h23);
+                       _pathArrow.lineTo(0, h23);
+                       _pathArrow.closePath();
+                       g2.setPaint(new GradientPaint(0, 0, Color.white, // 
SystemColor.
+                                                                               
                                                // controlLtHighlight
+                                                                               
                                                // ,
+                                       0, h, SystemColor.controlShadow));
+                       g2.fill(_pathArrow);
+
+                       _pathArrow.reset();
+                       _pathArrow.moveTo(0, h23);
+                       _pathArrow.lineTo(w23, h23);
+                       _pathArrow.moveTo(w23, h - 1);
+                       _pathArrow.lineTo(w - 1, h12);
+                       g2.setColor(SystemColor.controlDkShadow);
+                       g2.draw(_pathArrow);
+
+                       g2.setColor(SystemColor.controlShadow);
+                       g2.draw(new Line2D.Float(w - 1, h12, w23, 0));
+
+                       _pathArrow.reset();
+                       _pathArrow.moveTo(w23, 0);
+                       _pathArrow.lineTo(w23, h13);
+                       _pathArrow.lineTo(0, h13);
+                       _pathArrow.lineTo(0, h23);
+                       _pathArrow.moveTo(w23, h23);
+                       _pathArrow.lineTo(w23, h - 1);
+                       g2.setColor(SystemColor.controlLtHighlight);
+                       g2.draw(_pathArrow);
+                       break;
+               }
+               }
+
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CTransferableTreePath.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CTransferableTreePath.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CTransferableTreePath.java
new file mode 100644
index 0000000..2d85203
--- /dev/null
+++ 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/CTransferableTreePath.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+
+import javax.swing.tree.TreePath;
+
+/**
+ * This represents a TreePath (a node in a JTree) that can be transferred
+ * between a drag source and a drop target.
+ */
+public class CTransferableTreePath implements Transferable {
+       // The type of DnD object being dragged...
+       public static final DataFlavor TREEPATH_FLAVOR = new DataFlavor(
+                       DataFlavor.javaJVMLocalObjectMimeType, "TreePath");
+
+       private TreePath _path;
+
+       private DataFlavor[] _flavors = { TREEPATH_FLAVOR };
+
+       /**
+        * Constructs a transferrable tree path object for the specified path.
+        */
+       public CTransferableTreePath(TreePath path) {
+               _path = path;
+       }
+
+       // Transferable interface methods...
+       public DataFlavor[] getTransferDataFlavors() {
+               return _flavors;
+       }
+
+       public boolean isDataFlavorSupported(DataFlavor flavor) {
+               return java.util.Arrays.asList(_flavors).contains(flavor);
+       }
+
+       public synchronized Object getTransferData(DataFlavor flavor)
+                       throws UnsupportedFlavorException {
+               if (flavor.isMimeTypeEqual(TREEPATH_FLAVOR.getMimeType())) // 
DataFlavor.javaJVMLocalObjectMimeType))
+                       return _path;
+               else
+                       throw new UnsupportedFlavorException(flavor);
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DeselectingButton.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DeselectingButton.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DeselectingButton.java
new file mode 100644
index 0000000..ea29adb
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DeselectingButton.java
@@ -0,0 +1,45 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+
+/**
+ * @author alanrw
+ *
+ */
+public class DeselectingButton extends JButton {
+       
+       public DeselectingButton(String name, final ActionListener action, 
String toolTip) {
+               super();
+               this.setAction(new AbstractAction() {
+
+                       public void actionPerformed(ActionEvent e) {
+                               Component parent = 
DeselectingButton.this.getParent();
+                               action.actionPerformed(e);
+                               parent.requestFocusInWindow();
+                       }               
+               });
+               this.setText(name);
+               this.setToolTipText(toolTip);
+       }
+
+       public DeselectingButton(String name, final ActionListener action) {
+               this(name, action, null);
+       }
+       
+       public DeselectingButton(final Action action, String toolTip) {
+               this((String) action.getValue(Action.NAME), action, toolTip);
+       }
+
+       public DeselectingButton(final Action action) {
+               this(action, null);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DialogTextArea.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DialogTextArea.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DialogTextArea.java
new file mode 100644
index 0000000..faf2643
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/DialogTextArea.java
@@ -0,0 +1,83 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Font;
+
+import javax.swing.JTextArea;
+import javax.swing.text.Document;
+
+/**
+ * @author alanrw
+ *
+ */
+public class DialogTextArea extends JTextArea {
+
+       private static Font newFont = Font.decode("Dialog");
+       
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 2329063139827993252L;
+
+       /**
+        * 
+        */
+       public DialogTextArea() {
+               updateFont();
+       }
+
+       /**
+        * @param text
+        */
+       public DialogTextArea(String text) {
+               super(text);
+               updateFont();
+       }
+
+       /**
+        * @param doc
+        */
+       public DialogTextArea(Document doc) {
+               super(doc);
+               updateFont();
+       }
+
+       /**
+        * @param rows
+        * @param columns
+        */
+       public DialogTextArea(int rows, int columns) {
+               super(rows, columns);
+               updateFont();
+       }
+
+       /**
+        * @param text
+        * @param rows
+        * @param columns
+        */
+       public DialogTextArea(String text, int rows, int columns) {
+               super(text, rows, columns);
+               updateFont();
+       }
+
+       /**
+        * @param doc
+        * @param text
+        * @param rows
+        * @param columns
+        */
+       public DialogTextArea(Document doc, String text, int rows, int columns) 
{
+               super(doc, text, rows, columns);
+               updateFont();
+       }
+       
+       private void updateFont() {
+               if (newFont != null) {
+                       this.setFont(newFont);
+               }
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EdgeLineBorder.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EdgeLineBorder.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EdgeLineBorder.java
new file mode 100644
index 0000000..f49faa1
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EdgeLineBorder.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
+
+/**
+ *
+ *
+ * @author David Withers
+ */
+public class EdgeLineBorder implements Border {
+
+       public static final int TOP = 1;
+       public static final int BOTTOM = 2;
+       public static final int LEFT = 3;
+       public static final int RIGHT = 4;
+       private final int edge;
+       private final Color color;
+
+       public EdgeLineBorder(int edge, Color color) {
+               this.edge = edge;
+               this.color = color;
+       }
+
+       @Override
+       public void paintBorder(Component c, Graphics g, int x, int y, int 
width, int height) {
+               Color oldColor = g.getColor();
+               g.setColor(color);
+               switch (edge) {
+               case TOP:
+                       g.drawLine(x, y, x+width, y);
+                       break;
+               case BOTTOM:
+                       g.drawLine(x, y+height-2, x+width, y+height-2);
+                       break;
+               case LEFT:
+                       g.drawLine(x, y, x+width, y+height);
+                       break;
+               case RIGHT:
+                       g.drawLine(x+width, y, x+width, y+height);
+                       break;
+               }
+               g.setColor(oldColor);
+       }
+
+       @Override
+       public Insets getBorderInsets(Component c) {
+               return new Insets(0, 0, 0, 0);
+       }
+
+       @Override
+       public boolean isBorderOpaque() {
+               return false;
+       }
+
+       public static void main(String[] args) {
+               JFrame frame = new JFrame();
+               frame.setSize(500, 500);
+               JPanel panel = new JPanel();
+               panel.setBorder(new EdgeLineBorder(TOP, Color.GRAY));
+               frame.add(panel);
+               frame.setVisible(true);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EditorKeySetUtil.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EditorKeySetUtil.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EditorKeySetUtil.java
new file mode 100644
index 0000000..0e8d908
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/EditorKeySetUtil.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2009 Ingo Wassink of University of Twente, Netherlands and
+ * The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+
+/**
+ * @author Ingo Wassink
+ * @author Ian Dunlop
+ * @author Alan R Williams
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Manager for reading key set file
+ * 
+ * @author WassinkI
+ * @author alanrw
+ * 
+ */
+public class EditorKeySetUtil {
+       
+       private static Logger logger = Logger.getLogger(EditorKeySetUtil.class);
+
+
+       public static Set<String> loadKeySet(InputStream stream) {
+               Set<String> result = new TreeSet<String>();
+                               try {
+                       BufferedReader reader = new BufferedReader(
+                                       new InputStreamReader(stream));
+                                                                            
+                       String line;
+                       while ((line = reader.readLine()) != null) {
+                               result.add(line.trim());
+                       }
+                       reader.close();
+               } catch (Exception e) {
+                       logger.error(e);
+               }
+               return result;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/ExtensionFileFilter.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/ExtensionFileFilter.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/ExtensionFileFilter.java
new file mode 100644
index 0000000..35e2417
--- /dev/null
+++ 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/ExtensionFileFilter.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+/**
+ * This file is a component of the Taverna project,
+ * and is licensed under the GNU LGPL.
+ * Copyright Tom Oinn, EMBL-EBI
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A FileFilter implementation that can be configured to show only specific 
file
+ * suffixes.
+ * 
+ * @author Tom Oinn
+ * @author Stian Soiland-Reyes
+ */
+public class ExtensionFileFilter extends FileFilter {
+       List<String> allowedExtensions;
+
+       public ExtensionFileFilter(List<String> extensions) {
+           setAllowedExtensions(extensions);
+       }
+
+       public ExtensionFileFilter(String[] allowedExtensions) {
+           setAllowedExtensions(Arrays.asList(allowedExtensions));
+       }
+
+    private void setAllowedExtensions(List<String> extensions) {
+           this.allowedExtensions = new ArrayList<String>();
+            for (String e : extensions) {
+               if (e.startsWith(".")) {
+                    if (e.length() > 1) {
+                       allowedExtensions.add(e.substring(1));
+                   }
+               }
+               else {
+                   allowedExtensions.add(e);
+               }
+           }
+    }
+
+       @Override
+       public boolean accept(File f) {
+               if (f.isDirectory()) {
+                       return true;
+               }
+               String extension = getExtension(f);
+               if (extension != null) {
+                       for (String allowedExtension : allowedExtensions) {
+                               if 
(extension.equalsIgnoreCase(allowedExtension)) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       String getExtension(File f) {
+               String ext = null;
+               String s = f.getName();
+               int i = s.lastIndexOf('.');
+               if (i > 0 && i < s.length() - 1) {
+                       ext = s.substring(i + 1).toLowerCase();
+               }
+               return ext;
+       }
+
+       @Override
+       public String getDescription() {
+               StringBuffer sb = new StringBuffer();
+               sb.append("Filter for extensions : " );
+               for (int i = 0; i < allowedExtensions.size(); i++) {
+                       sb.append(allowedExtensions.get(i));
+                       if (i < allowedExtensions.size() - 1) {
+                               sb.append(", ");
+                       }
+               }
+               return sb.toString();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/FileTools.java
----------------------------------------------------------------------
diff --git a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/FileTools.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/FileTools.java
new file mode 100644
index 0000000..4aa5bb2
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/FileTools.java
@@ -0,0 +1,117 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Component;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.prefs.Preferences;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * @author alanrw
+ *
+ */
+public class FileTools {
+       
+       private static Logger logger = Logger.getLogger(FileTools.class);
+       
+       
+
+       public static boolean saveStringToFile(Component parent, String 
dialogTitle, String extension, String content) {
+               JFileChooser fileChooser = new JFileChooser();
+               fileChooser.setDialogTitle(dialogTitle);
+
+               fileChooser.resetChoosableFileFilters();
+               fileChooser.setAcceptAllFileFilterUsed(true);
+               
+               fileChooser.setFileFilter(new ExtensionFileFilter(new String[] 
{ extension }));
+
+               Preferences prefs = 
Preferences.userNodeForPackage(FileTools.class);
+               String curDir = prefs
+                               .get("currentDir", 
System.getProperty("user.home"));
+               fileChooser.setCurrentDirectory(new File(curDir));
+
+               boolean tryAgain = true;
+               while (tryAgain) {
+                       tryAgain = false;
+                       int returnVal = fileChooser.showSaveDialog(parent);
+                       if (returnVal == JFileChooser.APPROVE_OPTION) {
+                               prefs.put("currentDir", 
fileChooser.getCurrentDirectory()
+                                               .toString());
+                               File file = fileChooser.getSelectedFile();
+                               if (!file.getName().contains(".")) {
+                                       String newName = file.getName() + 
extension;
+                                       file = new File(file.getParentFile(), 
newName);
+                               }
+
+                               // TODO: Open in separate thread to avoid 
hanging UI
+                               try {
+                                       if (file.exists()) {
+                                               logger.info("File already 
exists: " + file);
+                                               String msg = "Are you sure you 
want to overwrite existing file "
+                                                               + file + "?";
+                                               int ret = 
JOptionPane.showConfirmDialog(
+                                                               parent, msg, 
"File already exists",
+                                                               
JOptionPane.YES_NO_CANCEL_OPTION);
+                                               if (ret == 
JOptionPane.YES_OPTION) {
+                                                       
+                                               } else if (ret == 
JOptionPane.NO_OPTION) {
+                                                       tryAgain = true;
+                                                       continue;
+                                               } else {
+                                                       logger.info("Aborted 
overwrite of " + file);
+                                                       return false;
+                                               }
+                                       }
+                                       FileUtils.writeStringToFile(file, 
content, StandardCharsets.UTF_8.name());
+                                       logger.info("Saved content by 
overwriting " + file);
+                                       return true;
+                               } catch (IOException ex) {
+                                       logger.warn("Could not save content to 
" + file, ex);
+                                       JOptionPane.showMessageDialog(parent,
+                                                       "Could not save to " + 
file + ": \n\n"
+                                                                       + 
ex.getMessage(), "Warning",
+                                                       
JOptionPane.WARNING_MESSAGE);
+                                       return false;
+                               }
+                       }
+               }
+               return false;
+       }
+       
+    public static String readStringFromFile(Component parent, String 
dialogTitle, String extension) {
+               JFileChooser fileChooser = new JFileChooser();
+               fileChooser.setDialogTitle(dialogTitle);
+               fileChooser.resetChoosableFileFilters();
+               fileChooser.setAcceptAllFileFilterUsed(true);
+               
+               fileChooser.setFileFilter(new ExtensionFileFilter(new String[] 
{ extension }));
+               
+               Preferences prefs = 
Preferences.userNodeForPackage(FileTools.class);
+               String curDir = prefs
+                               .get("currentDir", 
System.getProperty("user.home"));
+               fileChooser.setCurrentDirectory(new File(curDir));
+
+               if (fileChooser.showOpenDialog(parent) == 
JFileChooser.APPROVE_OPTION) {
+                       File selectedFile = fileChooser.getSelectedFile();
+                       
+                       try {
+                               return FileUtils.readFileToString(selectedFile, 
StandardCharsets.UTF_8.name());
+                       } catch (IOException ioe) {
+                               JOptionPane.showMessageDialog(parent, "Can not 
read file '"
+                                               + selectedFile.getName() + "'", 
"Can not read file",
+                                               JOptionPane.ERROR_MESSAGE);
+                       }
+
+               }
+               return null;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/HtmlUtils.java
----------------------------------------------------------------------
diff --git a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/HtmlUtils.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/HtmlUtils.java
new file mode 100644
index 0000000..a30d36f
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/HtmlUtils.java
@@ -0,0 +1,87 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.BorderLayout;
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JEditorPane;
+import javax.swing.JPanel;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author alanrw
+ *
+ */
+public class HtmlUtils {
+       
+       private static Logger logger = getLogger(HtmlUtils.class);
+
+
+       
+       public static JEditorPane createEditorPane(String html) {
+               JEditorPane result = new JEditorPane("text/html", html);
+               result.addHyperlinkListener(new HyperlinkListener() {
+
+                       @Override
+                       public void hyperlinkUpdate(HyperlinkEvent arg0) {
+                               if (HyperlinkEvent.EventType.ACTIVATED == 
arg0.getEventType()) {
+                       try {
+                           Desktop.getDesktop().browse(arg0.getURL().toURI());
+                       } catch (IOException | URISyntaxException e1) {
+                           logger.error(e1);
+                       }
+                   }
+                       }});
+               result.setEditable(false);
+               return result;
+       }
+       
+       public static JPanel panelForHtml(JEditorPane editorPane) {
+               JPanel result = new JPanel();
+
+               result.setLayout(new BorderLayout());
+
+               result.add(editorPane, BorderLayout.CENTER);
+               return result;
+       }
+
+       public static String getStyle(String backgroundColour) {
+               String style = "<style type='text/css'>";
+               style += "table {align:center; border:solid black 1px; 
background-color:"
+                               + backgroundColour
+                               + ";width:100%; height:100%; overflow:auto;}";
+               style += "</style>";
+               return style;
+       }
+       
+       public static String buildTableOpeningTag() {
+               String result = "<table ";
+               Map<String, String> props = getTableProperties();
+               for (String key : props.keySet()) {
+                       result += key + "=\"" + props.get(key) + "\" ";
+               }
+               result += ">";
+               return result;
+       }
+
+       public static Map<String, String> getTableProperties() {
+               Map<String, String> result = new HashMap<String, String>();
+               result.put("border", "1");
+               return result;
+       }
+
+       public static String getHtmlHead(String backgroundColour) {
+               return "<html><head>" + getStyle(backgroundColour) + 
"</head><body>";
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/JSplitPaneExt.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/JSplitPaneExt.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/JSplitPaneExt.java
new file mode 100644
index 0000000..e656c36
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/JSplitPaneExt.java
@@ -0,0 +1,56 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Graphics;
+
+import javax.swing.JSplitPane;
+
+/**
+ * Copied from code found on http://www.jguru.com
+ *
+ */
+public class JSplitPaneExt extends JSplitPane {
+       
+       protected boolean m_fIsPainted = false;
+       protected double m_dProportionalLocation = -1;
+
+       public JSplitPaneExt() {
+               super();
+       }
+
+       public JSplitPaneExt(int iOrientation) {
+               super(iOrientation);
+       }
+
+       protected boolean hasProportionalLocation() {
+               return (m_dProportionalLocation != -1);
+       }
+
+       public void cancelDividerProportionalLocation() {
+               m_dProportionalLocation = -1;
+       }
+
+       public void setDividerLocation(double dProportionalLocation) {
+               if (dProportionalLocation < 0 || dProportionalLocation > 1) {
+                       throw new IllegalArgumentException(
+                                       "Illegal value for divider location: "
+                                                       + 
dProportionalLocation);
+               }
+               m_dProportionalLocation = dProportionalLocation;
+               if (m_fIsPainted) {
+                       super.setDividerLocation(m_dProportionalLocation);
+               }
+       }
+
+       public void paint(Graphics g) {
+               super.paint(g);
+               if (hasProportionalLocation()) {
+                       super.setDividerLocation(m_dProportionalLocation);
+               }
+               m_fIsPainted=true; 
+
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/KeywordDocument.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/KeywordDocument.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/KeywordDocument.java
new file mode 100644
index 0000000..e8fae14
--- /dev/null
+++ b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/KeywordDocument.java
@@ -0,0 +1,568 @@
+/*******************************************************************************
+ * Copyright (C) 2009 Ingo Wassink of University of Twente, Netherlands and
+ * The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ 
******************************************************************************/
+
+/**
+ * @author Ingo Wassink
+ * @author Ian Dunlop
+ * @author Alan R Williams
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.Color;
+import java.util.Set;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultEditorKit;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Element;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+
+import org.apache.log4j.Logger;
+
+public class KeywordDocument extends DefaultStyledDocument {
+
+       private static Logger logger = Logger
+       .getLogger(KeywordDocument.class);
+
+       private DefaultStyledDocument doc;
+       private Element rootElement;
+
+       private boolean multiLineComment;
+       private MutableAttributeSet normal;
+       private MutableAttributeSet keyword;
+       private MutableAttributeSet comment;
+       private MutableAttributeSet quote;
+       private MutableAttributeSet port;
+
+       private Set<String> keywords;
+
+       private Set<String> ports;
+
+
+
+       public KeywordDocument(Set<String> keywords, Set<String> ports) {
+               doc = this;
+               rootElement = doc.getDefaultRootElement();
+               putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
+
+               normal = new SimpleAttributeSet();
+               StyleConstants.setForeground(normal, Color.black);
+
+               comment = new SimpleAttributeSet();
+               StyleConstants.setForeground(comment, new Color(0, 139, 69, 
255));
+               StyleConstants.setItalic(comment, true);
+
+               keyword = new SimpleAttributeSet();
+               StyleConstants.setForeground(keyword, Color.blue);
+               StyleConstants.setBold(keyword, true);
+
+
+               port = new SimpleAttributeSet();
+               StyleConstants.setForeground(port, Color.magenta);
+
+               quote = new SimpleAttributeSet();
+               StyleConstants.setForeground(quote, Color.red);
+
+               this.keywords = keywords;
+               this.ports = ports;
+       }
+
+       /**
+       * Method for adding an port
+       * @param name the name of the  port
+       */
+       public void addPort(String name){
+         ports.add(name);
+         updateText();
+       }
+
+       /**
+        * Method for removing an  port
+        * @param name the name of the  port
+        */
+       public void removePort(String name){
+         ports.remove(name);
+         updateText();
+       }
+
+       /**
+        * Method for checking whether the name represents an input port
+        * @param name the name of the  port
+        * @return true if true
+        */
+       private boolean isPort(String name){
+         return ports.contains(name);
+       }
+
+
+       /**
+        * Method for updating the whole text
+        */
+       private void updateText(){
+         try{
+           processChangedLines(0, getLength() );
+         } catch(Exception e){
+                 logger.error("Unable to update text", e);
+         }
+       }
+
+       /*
+        * Override to apply syntax highlighting after the document has been 
updated
+        */
+       public void insertString(int offset, String str, AttributeSet a)
+                       throws BadLocationException {
+               if (str.equals("{"))
+                       str = addMatchingBrace(offset);
+
+               super.insertString(offset, str, a);
+               processChangedLines(offset, str.length());
+       }
+
+       /*
+        * Override to apply syntax highlighting after the document has been 
updated
+        */
+       public void remove(int offset, int length) throws BadLocationException {
+               super.remove(offset, length);
+               processChangedLines(offset, 0);
+       }
+
+       /*
+        * Determine how many lines have been changed, then apply highlighting 
to
+        * each line
+        */
+       public void processChangedLines(int offset, int length)
+                       throws BadLocationException {
+               String content = doc.getText(0, doc.getLength());
+
+               // The lines affected by the latest document update
+
+               int startLine = rootElement.getElementIndex(offset);
+               int endLine = rootElement.getElementIndex(offset + length);
+
+               // Make sure all comment lines prior to the start line are 
commented
+               // and determine if the start line is still in a multi line 
comment
+
+               setMultiLineComment(commentLinesBefore(content, startLine));
+
+               // Do the actual highlighting
+
+               for (int i = startLine; i <= endLine; i++) {
+                       applyHighlighting(content, i);
+               }
+
+               // Resolve highlighting to the next end multi line delimiter
+
+               if (isMultiLineComment())
+                       commentLinesAfter(content, endLine);
+               else
+                       highlightLinesAfter(content, endLine);
+       }
+
+       /*
+        * Highlight lines when a multi line comment is still 'open' (ie. 
matching
+        * end delimiter has not yet been encountered)
+        */
+       private boolean commentLinesBefore(String content, int line) {
+               int offset = rootElement.getElement(line).getStartOffset();
+
+               // Start of comment not found, nothing to do
+
+               int startDelimiter = lastIndexOf(content, getStartDelimiter(),
+                               offset - 2);
+
+               if (startDelimiter < 0)
+                       return false;
+
+               // Matching start/end of comment found, nothing to do
+
+               int endDelimiter = indexOf(content, getEndDelimiter(), 
startDelimiter);
+
+               if (endDelimiter < offset & endDelimiter != -1)
+                       return false;
+
+               // End of comment not found, highlight the lines
+
+               doc.setCharacterAttributes(startDelimiter, offset - 
startDelimiter + 1,
+                               comment, false);
+               return true;
+       }
+
+       /*
+        * Highlight comment lines to matching end delimiter
+        */
+       private void commentLinesAfter(String content, int line) {
+               int offset = rootElement.getElement(line).getEndOffset();
+
+               // End of comment not found, nothing to do
+
+               int endDelimiter = indexOf(content, getEndDelimiter(), offset);
+
+               if (endDelimiter < 0)
+                       return;
+
+               // Matching start/end of comment found, comment the lines
+
+               int startDelimiter = lastIndexOf(content, getStartDelimiter(),
+                               endDelimiter);
+
+               if (startDelimiter < 0 || startDelimiter <= offset) {
+                       doc.setCharacterAttributes(offset, endDelimiter - 
offset + 1,
+                                       comment, false);
+               }
+       }
+
+       /*
+        * Highlight lines to start or end delimiter
+        */
+       private void highlightLinesAfter(String content, int line)
+                       throws BadLocationException {
+               int offset = rootElement.getElement(line).getEndOffset();
+
+               // Start/End delimiter not found, nothing to do
+
+               int startDelimiter = indexOf(content, getStartDelimiter(), 
offset);
+               int endDelimiter = indexOf(content, getEndDelimiter(), offset);
+
+               if (startDelimiter < 0)
+                       startDelimiter = content.length();
+
+               if (endDelimiter < 0)
+                       endDelimiter = content.length();
+
+               int delimiter = Math.min(startDelimiter, endDelimiter);
+
+               if (delimiter < offset)
+                       return;
+
+               // Start/End delimiter found, reapply highlighting
+
+               int endLine = rootElement.getElementIndex(delimiter);
+
+               for (int i = line + 1; i < endLine; i++) {
+                       Element branch = rootElement.getElement(i);
+                       Element leaf = 
doc.getCharacterElement(branch.getStartOffset());
+                       AttributeSet as = leaf.getAttributes();
+
+                       if (as.isEqual(comment))
+                               applyHighlighting(content, i);
+               }
+       }
+
+       /*
+        * Parse the line to determine the appropriate highlighting
+        */
+       private void applyHighlighting(String content, int line)
+                       throws BadLocationException {
+               int startOffset = rootElement.getElement(line).getStartOffset();
+               int endOffset = rootElement.getElement(line).getEndOffset() - 1;
+
+               int lineLength = endOffset - startOffset;
+               int contentLength = content.length();
+
+               if (endOffset >= contentLength)
+                       endOffset = contentLength - 1;
+
+               // check for multi line comments
+               // (always set the comment attribute for the entire line)
+
+               if (endingMultiLineComment(content, startOffset, endOffset)
+                               || isMultiLineComment()
+                               || startingMultiLineComment(content, 
startOffset, endOffset)) {
+                       doc.setCharacterAttributes(startOffset,
+                                       endOffset - startOffset + 1, comment, 
false);
+                       return;
+               }
+
+               // set normal attributes for the line
+
+               doc.setCharacterAttributes(startOffset, lineLength, normal, 
true);
+
+               // check for single line comment
+
+               int index = content.indexOf(getSingleLineDelimiter(), 
startOffset);
+
+               if ((index > -1) && (index < endOffset)) {
+                       doc.setCharacterAttributes(index, endOffset - index + 
1, comment,
+                                       false);
+                       endOffset = index - 1;
+               }
+
+               // check for tokens
+
+               checkForTokens(content, startOffset, endOffset);
+       }
+
+       /*
+        * Does this line contain the start delimiter
+        */
+       private boolean startingMultiLineComment(String content, int 
startOffset,
+                       int endOffset) throws BadLocationException {
+               int index = indexOf(content, getStartDelimiter(), startOffset);
+
+               if ((index < 0) || (index > endOffset))
+                       return false;
+               else {
+                       setMultiLineComment(true);
+                       return true;
+               }
+       }
+
+       /*
+        * Does this line contain the end delimiter
+        */
+       private boolean endingMultiLineComment(String content, int startOffset,
+                       int endOffset) throws BadLocationException {
+               int index = indexOf(content, getEndDelimiter(), startOffset);
+
+               if ((index < 0) || (index > endOffset))
+                       return false;
+               else {
+                       setMultiLineComment(false);
+                       return true;
+               }
+       }
+
+       /*
+        * We have found a start delimiter and are still searching for the end
+        * delimiter
+        */
+       private boolean isMultiLineComment() {
+               return false;//multiLineComment;
+       }
+
+       private void setMultiLineComment(boolean value) {
+               multiLineComment = value;
+       }
+
+       /*
+        * Parse the line for tokens to highlight
+        */
+       private void checkForTokens(String content, int startOffset, int 
endOffset) {
+               while (startOffset <= endOffset) {
+                       // skip the delimiters to find the start of a new token
+
+                       while (isDelimiter(content.substring(startOffset, 
startOffset + 1))) {
+                               if (startOffset < endOffset)
+                                       startOffset++;
+                               else
+                                       return;
+                       }
+
+                       // Extract and process the entire token
+
+                       if (isQuoteDelimiter(content
+                                       .substring(startOffset, startOffset + 
1)))
+                               startOffset = getQuoteToken(content, 
startOffset, endOffset);
+                       else
+                               startOffset = getOtherToken(content, 
startOffset, endOffset);
+               }
+       }
+
+       /*
+        *
+        */
+       private int getQuoteToken(String content, int startOffset, int 
endOffset) {
+               String quoteDelimiter = content.substring(startOffset, 
startOffset + 1);
+               String escapeString = getEscapeString(quoteDelimiter);
+
+               int index;
+               int endOfQuote = startOffset;
+
+               // skip over the escape quotes in this quote
+
+               index = content.indexOf(escapeString, endOfQuote + 1);
+
+               while ((index > -1) && (index < endOffset)) {
+                       endOfQuote = index + 1;
+                       index = content.indexOf(escapeString, endOfQuote);
+               }
+
+               // now find the matching delimiter
+
+               index = content.indexOf(quoteDelimiter, endOfQuote + 1);
+
+               if ((index < 0) || (index > endOffset))
+                       endOfQuote = endOffset;
+               else
+                       endOfQuote = index;
+
+               doc.setCharacterAttributes(startOffset, endOfQuote - 
startOffset + 1,
+                               quote, false);
+
+               return endOfQuote + 1;
+       }
+
+       /*
+        *
+        */
+       private int getOtherToken(String content, int startOffset, int 
endOffset) {
+               int endOfToken = startOffset + 1;
+
+               while (endOfToken <= endOffset) {
+                       if (isDelimiter(content.substring(endOfToken, 
endOfToken + 1)))
+                               break;
+
+                       endOfToken++;
+               }
+
+               String token = content.substring(startOffset, endOfToken);
+
+               if (isKeyword(token)) {
+                       doc.setCharacterAttributes(startOffset, endOfToken - 
startOffset,
+                                       keyword, false);
+               } else if(isPort(token)){
+                       doc.setCharacterAttributes(startOffset, endOfToken - 
startOffset,
+                               port, false);
+               }
+
+               return endOfToken + 1;
+       }
+
+       /*
+        * Assume the needle will the found at the start/end of the line
+        */
+       private int indexOf(String content, String needle, int offset) {
+               int index;
+
+               while ((index = content.indexOf(needle, offset)) != -1) {
+                       String text = getLine(content, index).trim();
+
+                       if (text.startsWith(needle) || text.endsWith(needle))
+                               break;
+                       else
+                               offset = index + 1;
+               }
+
+               return index;
+       }
+
+       /*
+        * Assume the needle will the found at the start/end of the line
+        */
+       private int lastIndexOf(String content, String needle, int offset) {
+               int index;
+
+               while ((index = content.lastIndexOf(needle, offset)) != -1) {
+                       String text = getLine(content, index).trim();
+
+                       if (text.startsWith(needle) || text.endsWith(needle))
+                               break;
+                       else
+                               offset = index - 1;
+               }
+
+               return index;
+       }
+
+       private String getLine(String content, int offset) {
+               int line = rootElement.getElementIndex(offset);
+               Element lineElement = rootElement.getElement(line);
+               int start = lineElement.getStartOffset();
+               int end = lineElement.getEndOffset();
+               return content.substring(start, end - 1);
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected boolean isDelimiter(String character) {
+               String operands = ";:{}()[]+-/%<=>!&|^~*,.";
+
+               if (Character.isWhitespace(character.charAt(0))
+                               || operands.indexOf(character) != -1)
+                       return true;
+               else
+                       return false;
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected boolean isQuoteDelimiter(String character) {
+               String quoteDelimiters = "\"'";
+
+               if (quoteDelimiters.indexOf(character) < 0)
+                       return false;
+               else
+                       return true;
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected boolean isKeyword(String token) {
+               return keywords.contains(token);
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected String getStartDelimiter() {
+               return "/*";
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected String getEndDelimiter() {
+               return "*/";
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected String getSingleLineDelimiter() {
+               return "#";
+       }
+
+       /*
+        * Override for other languages
+        */
+       protected String getEscapeString(String quoteDelimiter) {
+               return "\\" + quoteDelimiter;
+       }
+
+       /*
+        *
+        */
+       protected String addMatchingBrace(int offset) throws 
BadLocationException {
+               StringBuffer whiteSpace = new StringBuffer();
+               int line = rootElement.getElementIndex(offset);
+               int i = rootElement.getElement(line).getStartOffset();
+
+               while (true) {
+                       String temp = doc.getText(i, 1);
+
+                       if (temp.equals(" ") || temp.equals("\t")) {
+                               whiteSpace.append(temp);
+                               i++;
+                       } else
+                               break;
+               }
+
+               return "{\n" + whiteSpace.toString() + "\t\n" + 
whiteSpace.toString()
+                               + "}";
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/f676ef35/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/LineEnabledTextPanel.java
----------------------------------------------------------------------
diff --git 
a/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/LineEnabledTextPanel.java 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/LineEnabledTextPanel.java
new file mode 100644
index 0000000..389b9b3
--- /dev/null
+++ 
b/taverna-ui/src/main/java/net/sf/taverna/t2/lang/ui/LineEnabledTextPanel.java
@@ -0,0 +1,106 @@
+/**
+ * 
+ */
+package net.sf.taverna.t2.lang.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Event;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.KeyStroke;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.JTextComponent;
+
+
+/**
+ * @author alanrw
+ *
+ */
+public class LineEnabledTextPanel extends JPanel {
+       
+       private JTextComponent textComponent = null;
+       private Document document;
+       private GotoLineAction gotoLineAction = null;
+
+       public LineEnabledTextPanel(final JTextComponent component) {
+               
+               this.setLayout(new BorderLayout());
+               textComponent = component;
+               updateDocument();
+               
+               JScrollPane scrollPane = new JScrollPane(textComponent );
+               scrollPane.setPreferredSize(textComponent.getPreferredSize() );
+
+               this.add(scrollPane, BorderLayout.CENTER);;
+               
+               final JLabel caretLabel = new JLabel("Line: 1 Column: 0");
+               
+               setCaretListener(new CaretListener() {
+
+                       public void caretUpdate(CaretEvent e) {
+                               int caretPosition = getCaretPosition();
+                               Element root = document.getRootElements()[0];
+                               int elementIndex = 
root.getElementIndex(caretPosition);
+                               int relativeOffset = caretPosition - 
root.getElement(elementIndex).getStartOffset();
+                       caretLabel.setText("Line: " + (elementIndex + 1) + " 
Column: " + relativeOffset);
+
+                       }});
+               this.add(caretLabel, BorderLayout.SOUTH);
+
+               KeyStroke gotoLineKeystroke = 
KeyStroke.getKeyStroke(KeyEvent.VK_L, Event.META_MASK);
+
+               gotoLineAction = new GotoLineAction();
+               textComponent.getInputMap().put(gotoLineKeystroke, 
"gotoLineKeystroke");
+               textComponent.getActionMap().put("gotoLineKeystroke", 
gotoLineAction);
+
+       }
+       
+       private void updateDocument() {
+           document = ((JTextComponent) textComponent).getDocument();
+       }
+       
+       private void setCaretListener(CaretListener listener) {
+           ((JTextComponent) textComponent).addCaretListener(listener);
+       }
+       
+       private int getCaretPosition() {
+           return ((JTextComponent) textComponent).getCaretPosition();
+       }
+       
+       private void setCaretPosition(int position) {
+           ((JTextComponent) textComponent).setCaretPosition(position);
+           textComponent.requestFocus();
+       }
+       
+       class GotoLineAction extends AbstractAction
+       {
+
+               public GotoLineAction() {       
+               }
+
+               public void actionPerformed(ActionEvent e) {
+                       String inputString = JOptionPane.showInputDialog(null, 
"Enter line number", "Line number", JOptionPane.QUESTION_MESSAGE);
+                       if (inputString != null) {
+                               try {
+                                       int lineNumber = 
Integer.parseInt(inputString);
+                                       Element root = 
document.getDefaultRootElement();
+                                       lineNumber = Math.max(lineNumber, 1);
+                                       lineNumber = Math.min(lineNumber, 
root.getElementCount());
+                                       setCaretPosition( root.getElement( 
lineNumber - 1 ).getStartOffset() );
+
+                               } catch (NumberFormatException e1){
+                                       // do nothing
+                               }
+                       }
+               }
+       }
+}

Reply via email to