You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 11:22:34 UTC

[11/51] [partial] incubator-taverna-workbench git commit: Revert "temporarily empty repository"

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java
new file mode 100644
index 0000000..eafe095
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ServiceListCellRenderer.java
@@ -0,0 +1,291 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.biocatalogue.x2009.xml.rest.Location;
+import org.biocatalogue.x2009.xml.rest.ResourceLinkWithString;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceSummary.Provider;
+
+import net.sf.taverna.biocatalogue.model.LoadingExpandedResource;
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+
+/**
+ * This list cell renderer is will display (web) Service items in the search results
+ * list box.
+ * 
+ * In the collapsed state, four data items will be shown per service: status (as per monitoring data),
+ * service type (SOAP / REST), the name of the service and its description.
+ * 
+ * In the expanded state, other details are added to the ones above: service categories, locations,
+ * endpoints and providers.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class ServiceListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+  private JLabel jlTypeIcon;
+  private JLabel jlItemStatus;
+  private JLabel jlItemTitle;
+  private JLabel jlDescription;
+  
+  private GridBagConstraints c;
+  
+  
+  public ServiceListCellRenderer() {
+    /* do nothing */
+  }
+  
+  
+  /**
+   * This entry can be in one of two states:
+   * -- containing only the name of the resource and NOT loading further details;
+   * -- containing only the name of the resource and LOADING further details.
+   * 
+   * @param itemToRender
+   * @return
+   */
+  protected GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender)
+  {
+    TYPE resourceType = determineResourceType(itemToRender);
+    LoadingResource resource = (LoadingResource)itemToRender;
+    
+    jlTypeIcon = new JLabel(resourceType.getIcon());
+    jlItemStatus = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNKNOWN_ICON));
+    
+    jlItemTitle = new JLabel(Resource.getDisplayNameForResource(resource), JLabel.LEFT);
+    jlItemTitle.setForeground(Color.decode("#AD0000"));  // very dark red
+    jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+    
+    jlDescription = resource.isLoading() ? loaderBarAnimationGrey : loaderBarAnimationGreyStill;
+    
+    return (arrangeLayout(false));
+  }
+  
+  
+  /**
+   * 
+   * @param itemToRender
+   * @param expandedView <code>true</code> to indicate that this method generates the top
+   *                     fragment of the expanded list entry for this SOAP operation / REST method.
+   * @return
+   */
+  protected GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean selected)
+  {
+    TYPE resourceType = determineResourceType(itemToRender);
+    Service service = (Service)itemToRender;;
+    
+    // service type
+    if (service.getServiceTechnologyTypes() != null && service.getServiceTechnologyTypes().getTypeList().size() > 0)
+    {
+      if (service.getServiceTechnologyTypes().getTypeList().size() > 1 &&
+           !(service.getServiceTechnologyTypes().getTypeList().get(0).toString().equalsIgnoreCase("SOAP")) && 
+             service.getServiceTechnologyTypes().getTypeList().get(1).toString().equalsIgnoreCase("SOAPLAB")
+         )
+      {
+        jlTypeIcon = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_TYPE_MULTITYPE_ICON));
+      }
+      else if (service.getServiceTechnologyTypes().getTypeArray(0).toString().equalsIgnoreCase("SOAP")) {
+        jlTypeIcon = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_TYPE_SOAP_ICON));
+      }
+      else if (service.getServiceTechnologyTypes().getTypeArray(0).toString().equalsIgnoreCase("REST")) {
+        jlTypeIcon = new JLabel(ResourceManager.getImageIcon(ResourceManager.SERVICE_TYPE_REST_ICON));
+      }
+    }
+    else {
+      // can't tell the type - just show as a service of no particular type
+      jlTypeIcon = new JLabel(resourceType.getIcon());
+    }
+    
+    
+    // service status
+    jlItemStatus = new JLabel(ServiceMonitoringStatusInterpreter.getStatusIcon(service, true));
+    
+    jlItemTitle = new JLabel(Resource.getDisplayNameForResource(service), JLabel.LEFT);
+    jlItemTitle.setForeground(Color.decode("#AD0000"));  // very dark red
+    jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+    
+    int descriptionMaxLength = DESCRIPTION_MAX_LENGTH_EXPANDED;
+    String strDescription = Util.stripAllHTML(service.getDescription());
+    strDescription = (strDescription == null || strDescription.length() == 0 ?
+                             "<font color=\"gray\">no description</font>" :
+                            	 StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(strDescription, LINE_LENGTH, false)));
+    
+    if (strDescription.length() > descriptionMaxLength) {
+      strDescription = strDescription.substring(0, descriptionMaxLength) + "<font color=\"gray\">(...)</font>";
+    }
+    strDescription = "<html><b>Description: </b>" + strDescription + "</html>";
+    jlDescription = new JLabel(strDescription);
+    
+    return (arrangeLayout(true));
+  }
+  
+  
+  /**
+   * @return Final state of the {@link GridBagConstraints} instance
+   *         that was used to lay out components in the panel.
+   */
+  private GridBagConstraints arrangeLayout(boolean showActionButtons)
+  {
+    // POPULATE PANEL WITH PREPARED COMPONENTS
+    this.setLayout(new GridBagLayout());
+    c = new GridBagConstraints();
+    c.anchor = GridBagConstraints.NORTHWEST;
+    c.fill = GridBagConstraints.HORIZONTAL;
+    
+    c.gridx = 0;
+    c.gridy = 0;
+    c.weightx = 0;
+    c.insets = new Insets(10, 6, 6, 3);
+    this.add(jlTypeIcon, c);
+    
+    c.gridx++;
+    c.insets = new Insets(10, 3, 6, 3);
+    this.add(jlItemStatus, c);
+    
+    c.gridx++;
+    c.weightx = 1.0;
+    c.insets = new Insets(10, 3, 6, 3);
+    this.add(jlItemTitle, c);
+    
+    if (showActionButtons) {
+      c.gridx++;
+      c.gridheight = 3;
+      c.weightx = 0;
+      c.weighty = 1.0;
+      jlExpand = new JLabel(ResourceManager.getImageIcon(ResourceManager.FOLD_ICON));
+      this.add(jlExpand, c);
+    }
+    
+    c.gridx = 2;
+    c.gridy++;
+    c.gridheight = 1;
+    c.weightx = 1.0;
+    c.weighty = 0;
+    c.insets = new Insets(3, 3, 3, 3);
+    this.add(jlDescription, c);
+    
+    return (c);
+  }
+  
+  
+  
+  protected void prepareLoadingExpandedEntry(Object itemToRender)
+  {
+    LoadingExpandedResource expandedResource = (LoadingExpandedResource) itemToRender;
+    GridBagConstraints c = prepareLoadedEntry(expandedResource.getAssociatedObj(), false);
+    
+    if (expandedResource.isLoading())
+    {
+      c.gridx = 0;
+      c.gridy++;
+      c.gridwidth = 3;
+      c.anchor = GridBagConstraints.CENTER;
+      c.fill = GridBagConstraints.HORIZONTAL;
+      c.weightx = 1.0;
+      this.add(loaderBarAnimationOrange, c);
+    }
+    else
+    {
+      // *** additional data for this Web Service operations ***
+      Service service = (Service) expandedResource.getAssociatedObj();
+      
+      
+      // -- categories --
+      int categoryCount = service.getSummary().getCategoryList().size();
+      String categoryString = "";
+      if (categoryCount > 0) {
+        List<String> categoryNames = new ArrayList<String>();
+        for (ResourceLinkWithString category : service.getSummary().getCategoryList()) {
+          categoryNames.add(category.getStringValue());
+        }
+        categoryString = "<html><b>" + Util.pluraliseNoun("Category", categoryCount) + ": </b>" + StringEscapeUtils.escapeHtml(Util.join(categoryNames, ", ")) + "</html>";
+      }
+      else {
+        categoryString = "<html><b>Category: </b><font color=\"gray\">unknown</font></html>";
+      }
+      
+      c.gridy++;
+      this.add(new JLabel(categoryString),c);
+      
+      
+      // -- endpoints --
+      int endpointCount = service.getSummary().getEndpointList().size();
+      String endpointString = "";
+      if (endpointCount > 0) {
+        endpointString = "<html><b>" + Util.pluraliseNoun("Endpoint", endpointCount) + ": </b>" +
+        StringEscapeUtils.escapeHtml(Util.join(service.getSummary().getEndpointList(), ", ")) + "</html>";
+      }
+      else {
+        endpointString = "<html><b>Endpoint: </b><font color=\"gray\">unknown</font></html>";
+      }
+      
+      c.gridy++;
+      this.add(new JLabel(endpointString), c);
+      
+      
+      // -- providers --
+      int providerCount = service.getSummary().getProviderList().size();
+      String providerString = "";
+      if (providerCount > 0) {
+        List<String> providerNames = new ArrayList<String>();
+        for (Provider serviceProvider : service.getSummary().getProviderList()) {
+          providerNames.add(serviceProvider.getName());
+        }
+        providerString = "<html><b>" + Util.pluraliseNoun("Provider", providerCount) + ": </b>" + StringEscapeUtils.escapeHtml(Util.join(providerNames, ", ")) + "</html>";
+      }
+      else {
+        providerString = "<html><b>Provider: </b><font color=\"gray\">unknown</font></html>";
+      }
+      
+      c.gridy++;
+      this.add(new JLabel(providerString),c);
+      
+      
+      // -- locations --
+      int locationCount = service.getSummary().getLocationList().size();
+      String locationString = "";
+      List<String> locations = new ArrayList<String>();
+      if (locationCount > 0) {
+        for (Location location : service.getSummary().getLocationList()) {
+          List<String> locationNameFragments = new ArrayList<String>();
+          locationNameFragments.add(location.getCity());
+          locationNameFragments.add(location.getCountry());
+          locations.add(Util.join(locationNameFragments, ", "));
+        }
+      }
+      locationString = "<html><b>" + Util.pluraliseNoun("Location", locations.size()) + ": </b>" +
+      (locations.size() > 0 ? StringEscapeUtils.escapeHtml(Util.join(locations, "; ")) : "<font color=\"gray\">unknown</font>") +
+      "</html>";
+      
+      c.gridy++;
+      c.insets = new Insets(3, 3, 12, 3);
+      this.add(new JLabel(locationString),c);
+    }
+  }
+
+
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+	return false;
+}
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java
new file mode 100644
index 0000000..304e50a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/JTriStateTree.java
@@ -0,0 +1,631 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class JTriStateTree extends JTree
+{
+  // This is used to manage location and padding of tooltips on long items
+  // that don't fit into the visible part of this tree.
+  private static final int JCHECKBOX_WIDTH = 16;
+  
+  private JTriStateTree instanceOfSelf;
+  private JPopupMenu contextualMenu;
+  
+  private TriStateTreeNode root;
+  
+  // will enable/disable checkboxes - when disabled the selection
+  // will remain, but will appear as read-only
+  private boolean bCheckingEnabled;
+  
+  private List<Action> contextualMenuActions; 
+  private Action expandAllAction;
+  private Action collapseAllAction;
+  private Action selectAllAction;
+  private Action deselectAllAction;
+  
+  private Set<TriStateTreeCheckingListener> checkingListeners;
+  
+  
+  @SuppressWarnings("serial")
+public JTriStateTree(TriStateTreeNode root)
+  {
+    super(root);
+    this.root = root;
+    this.instanceOfSelf = this;
+    
+    // by default checking is allowed
+    this.bCheckingEnabled = true;
+    
+    // initially, no checking listeners
+    checkingListeners = new HashSet<TriStateTreeCheckingListener>();
+    
+    // initially set to show the [+]/[-] icons for expanding collapsing top-level nodes
+    this.setShowsRootHandles(true);
+    
+    // use the cell rendered which understands the three states of the
+    // nodes of this tree
+    this.setCellRenderer(new TriStateCheckBoxTreeCellRenderer());
+    
+    
+    // create all necessary actions for the popup menu: selecting/deselecting and expanding/collapsing all nodes
+    this.selectAllAction = new AbstractAction("Select all", ResourceManager.getImageIcon(ResourceManager.SELECT_ALL_ICON))
+    {
+      // Tooltip
+      { this.putValue(SHORT_DESCRIPTION, "Select all nodes in the tree"); }
+      
+      public void actionPerformed(ActionEvent e) {
+        selectAllNodes(true);
+      }
+    };
+    
+    // Use the Taverna untick icon
+    this.deselectAllAction = new AbstractAction("Deselect all", ResourceManager.getImageIcon(ResourceManager.UNCHECKED_ICON))
+    {
+      // Tooltip
+      { this.putValue(SHORT_DESCRIPTION, "Deselect all nodes in the tree"); }
+      
+      public void actionPerformed(ActionEvent e) {
+        selectAllNodes(false);
+      }
+    };
+    
+    
+//    this.expandAllAction = new AbstractAction("Expand all", ResourceManager.getImageIcon(ResourceManager.EXPAND_ALL_ICON))
+    // Use the standard Taverna plus icon
+    this.expandAllAction = new AbstractAction("Expand all", WorkbenchIcons.plusIcon)
+    {
+      // Tooltip
+      { this.putValue(SHORT_DESCRIPTION, "Expand all nodes in the tree"); }
+      
+      public void actionPerformed(ActionEvent e) {
+        expandAll();
+      }
+    };
+    
+//    this.collapseAllAction = new AbstractAction("Collapse all", ResourceManager.getImageIcon(ResourceManager.COLLAPSE_ALL_ICON))
+    // Use the standard Taverna minus icon
+    this.collapseAllAction = new AbstractAction("Collapse all", WorkbenchIcons.minusIcon)
+    {
+      // Tooltip
+      { this.putValue(SHORT_DESCRIPTION, "Collapse all expanded nodes in the tree"); }
+      
+      public void actionPerformed(ActionEvent e) {
+        collapseAll();
+      }
+    };
+    
+    
+    // populate the popup menu with created menu items
+    contextualMenuActions = Arrays.asList(new Action[] {expandAllAction, collapseAllAction, deselectAllAction});
+    
+    contextualMenu = new JPopupMenu();
+    contextualMenu.add(expandAllAction);
+    contextualMenu.add(collapseAllAction);
+    contextualMenu.add(new JPopupMenu.Separator());
+    //contextualMenu.add(selectAllAction);
+    contextualMenu.add(deselectAllAction);
+    
+    
+    this.addMouseListener(new MouseAdapter() {
+      // use mousePressed, not mouseClicked to make sure that
+      // quick successive clicks get processed correctly, otherwise
+      // some clicks are disregarded
+      public void mousePressed(MouseEvent e)
+      {
+        // only listen to checkbox checking requests if this is
+        // a correct type of mouse event for this
+        if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1)
+        {
+          int clickedRow = instanceOfSelf.getRowForLocation(e.getX(), e.getY());
+          
+          // only make changes to node selections if checking is enabled in the tree and
+          // it was a node which was clicked, not [+]/[-] or blank space
+          if (bCheckingEnabled && clickedRow != -1)
+          {
+            Object clickedObject = instanceOfSelf.getPathForRow(clickedRow).getLastPathComponent();
+            if (clickedObject instanceof TriStateTreeNode) {
+              TriStateTreeNode node = ((TriStateTreeNode)clickedObject);
+              
+              // toggle state of the clicked node + propagate the changes to
+              // the checking state of all nodes
+              node.toggleState(true);
+              
+              // repaint the whole tree
+              SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                  instanceOfSelf.repaint();
+                }
+              });
+              
+              // notify all listeners
+              notifyCheckingListeners();
+            }
+          }
+        }
+        else {
+          // not a checking action - instead, bring up a popup menu
+          contextualMenu.show(instanceOfSelf, e.getX(), e.getY());
+        }
+      }
+      
+      public void mouseReleased(MouseEvent e)
+      {
+        if (e.isPopupTrigger()) {
+          // another way a popup menu may be called on different systems
+          contextualMenu.show(instanceOfSelf, e.getX(), e.getY());
+        }
+      }
+      
+      
+      /**
+       * This method enables tooltips on this instance of JTriStateTree
+       * when mouse enters its bounds. Custom tooltips will be used, but
+       * this notifies ToolTipManager that tooltips must be shown on this
+       * tree. 
+       */
+      public void mouseEntered(MouseEvent e) {
+        instanceOfSelf.setToolTipText("Filter tree");
+      }
+      
+      /**
+       * Removes tooltips from this JTriStateTree when mouse leaves its bounds.
+       */
+      public void mouseExited(MouseEvent e) {
+        instanceOfSelf.setToolTipText(null);
+      }
+      
+    });
+    
+  }
+  
+  
+  /**
+   * This method is used to determine tooltip location.
+   * 
+   * Helps to ensure that the tooltip appears directly over the
+   * text in the row over which the mouse currently hovers.
+   */
+  public Point getToolTipLocation(MouseEvent e)
+  {
+    int iRowIndex = this.getRowForLocation(e.getX(), e.getY());
+    if (iRowIndex != -1) {
+      // mouse hovers over one of the rows - make top-left corner of
+      // the tooltip to appear over the top-left corner of that row
+      Rectangle bounds = this.getRowBounds(iRowIndex);
+      return (new Point(bounds.x + JCHECKBOX_WIDTH, bounds.y));
+    }
+    else {
+      // let ToolTipManager determine where to show the tooltip (if it will be shown)
+      return null;
+    }
+  }
+  
+  
+  /**
+   * Supports dynamic tooltips for the contents of this JTriStateTree -
+   * the tooltips will only be shown for those tree nodes that don't
+   * fully fit within the visible bounds of the tree.
+   * 
+   * For other nodes no tooltip will be shown.
+   */
+  public String getToolTipText(MouseEvent e)
+  {
+    String strTooltip = null;
+    
+    Object correspondingObject = getTreeNodeObject(e);
+    if (correspondingObject != null) {
+      // mouse is hovering over some row in the tree, not a blank space --
+      // obtain a component that is identical to the one which is currently displayed at the identified row in the tree
+      Component rendering = this.getCellRenderer().getTreeCellRendererComponent(this, correspondingObject, false, false,
+                                                                true, this.getRowForLocation(e.getX(), e.getY()), false);
+      
+      if (rendering.getPreferredSize().width + getToolTipLocation(e).x - JCHECKBOX_WIDTH > this.getVisibleRect().width) {
+        // if the component is not fully visible, the tooltip will be displayed -
+        // tooltip text matches the one for this row in the tree, will just be shown in full
+        strTooltip = correspondingObject.toString();
+      }
+    }
+    
+    // return either tooltip text or 'null' if no tooltip is currently required
+    return (strTooltip);
+  }
+  
+  
+  /**
+   * Check whether a {@link MouseEvent} happened in such a location
+   * in the {@link JTriStateTree} that corresponds to some node or a
+   * blank space.
+   * 
+   * @param e
+   * @return Object contained in the tree node that corresponds to the
+   *         location of specified {@link MouseEvent} <code>e</code>;
+   *         or <code>null</code> if the event happened over a blank space.
+   */
+  public Object getTreeNodeObject(MouseEvent e)
+  {
+    int iRowIndex = this.getRowForLocation(e.getX(), e.getY());
+    if (iRowIndex != -1) {
+      // mouse is hovering over some row in the tree, not a blank space
+      return (this.getPathForRow(iRowIndex).getLastPathComponent());
+    }
+    
+    return (null);
+  }
+  
+  
+  /**
+   * @return List of the highest-level nodes of the tree that have full (not partial) selection and,
+   *         therefore, act as roots of checked paths.
+   */
+  public List<TreePath> getRootsOfCheckedPaths()
+  {
+    return getRootsOfCheckedPaths(this.root);
+  }
+  
+  /**
+   * A recursive version of the getCheckedRootsOfCheckedPaths().
+   * Performs all the work for a given node and returns result to
+   * the caller.
+   * 
+   * @param startNode Node to start with.
+   * @return
+   */
+  private List<TreePath> getRootsOfCheckedPaths(TriStateTreeNode startNode)
+  {
+    ArrayList<TreePath> pathsToRootsOfCheckings = new ArrayList<TreePath>();
+    
+    Object currentNode = null;
+    for (Enumeration e = startNode.children(); e.hasMoreElements(); )
+    {
+      currentNode = e.nextElement();
+      if (currentNode instanceof TriStateTreeNode) {
+        TriStateTreeNode curTriStateNode = (TriStateTreeNode)currentNode;
+        
+        if (curTriStateNode.getState().equals(TriStateCheckBox.State.CHECKED)) {
+          pathsToRootsOfCheckings.add(new TreePath(curTriStateNode.getPath()));
+        }
+        else if (curTriStateNode.getState().equals(TriStateCheckBox.State.PARTIAL)) {
+          pathsToRootsOfCheckings.addAll(getRootsOfCheckedPaths(curTriStateNode));
+        }
+      }
+    }
+    
+    return (pathsToRootsOfCheckings);
+  }
+  
+  
+  /**
+   * @return List of TreePath objects, where the last component in each
+   *         path is the root of an unchecked path in this tree. In other
+   *         words each of those last components is either an unchecked
+   *         leaf node or a node, none of whose children (and itself as
+   *         well) are checked.
+   */
+  public List<TreePath> getRootsOfUncheckedPaths()
+  {
+    return (getRootsOfUncheckedPaths(this.root));
+  }
+  
+  
+  /**
+   * Recursive worker method for <code>getRootsOfUncheckedPaths()</code>.
+   */
+  private List<TreePath> getRootsOfUncheckedPaths(TreeNode startNode)
+  {
+    List<TreePath> rootNodesOfUncheckedPaths = new ArrayList<TreePath>();
+    
+    Object currentNode = null;
+    for (Enumeration e = startNode.children(); e.hasMoreElements(); )
+    {
+      currentNode = e.nextElement();
+      if (!(currentNode instanceof TriStateTreeNode) ||
+          ((TriStateTreeNode)currentNode).getState().equals(TriStateCheckBox.State.UNCHECKED))
+      {
+        rootNodesOfUncheckedPaths.add(new TreePath(((DefaultMutableTreeNode)currentNode).getPath()));
+      }
+      else {
+        rootNodesOfUncheckedPaths.addAll(getRootsOfUncheckedPaths((TreeNode)currentNode));
+      }
+    }
+    
+    return (rootNodesOfUncheckedPaths);
+  }
+  
+  
+  /**
+   * @return List of TreePath objects, that point to all "leaf"
+   *         nodes in the tree that are checked - in other words
+   *         this method returns a collection of paths to all "deepest"
+   *         nodes in this tree that are checked and do not have any
+   *         (checked) children.
+   */
+  public List<TreePath> getLeavesOfCheckedPaths() {
+    return (getLeavesOfCheckedPaths(this.root));
+  }
+  
+  
+  /**
+   * Recursive worker method for {@link JTriStateTree#getLeavesOfCheckedPaths()}
+   */
+  private List<TreePath> getLeavesOfCheckedPaths(TriStateTreeNode startNode)
+  {
+    List<TreePath> leavesOfCheckedPaths = new ArrayList<TreePath>();
+    
+    // this node is only relevant if it is checked itself - if not,
+    // it must be the first-level child of another node that is checked
+    // and is only considered here on the recursive pass (but will be discarded)
+    if (startNode.getState().equals(TriStateCheckBox.State.CHECKED) ||
+        startNode.getState().equals(TriStateCheckBox.State.PARTIAL))
+    {
+      // "ask" all children to do the same...
+      Object currentNode = null;
+      for (Enumeration e = startNode.children(); e.hasMoreElements(); ) {
+        currentNode = e.nextElement();
+        if (currentNode instanceof TriStateTreeNode) {
+          leavesOfCheckedPaths.addAll(getLeavesOfCheckedPaths((TriStateTreeNode)currentNode));
+        }
+      }
+      
+      // ...if we have a list of leaf nodes, then this node can't be a leaf;
+      // -> but alternatively, if the list is empty, it means that this node is
+      //    itself a leaf node and must be added to the result
+      if (leavesOfCheckedPaths.isEmpty()) {
+        leavesOfCheckedPaths.add(new TreePath(startNode.getPath()));
+      }
+    }
+    
+    return (leavesOfCheckedPaths);
+  }
+  
+  
+  /**
+   * @return List of all contextual menu actions that are available for this tree.
+   */
+  public List<Action> getContextualMenuActions() {
+    return this.contextualMenuActions;
+  }
+  
+  
+  /**
+   * Enables or disables all actions in the contextual menu
+   * @param actionsAreEnabled
+   */
+  public void enableAllContextualMenuAction(boolean actionsAreEnabled) {
+    for (Action a : getContextualMenuActions()) {
+      a.setEnabled(actionsAreEnabled);
+    }
+  }
+  
+  
+  /**
+   * Selects or deselects all nodes.
+   * @param selectAll True - to select all; false - to reset all selections.
+   */
+  public void selectAllNodes(boolean selectAll) {
+    root.setState(selectAll ? TriStateCheckBox.State.CHECKED : TriStateCheckBox.State.UNCHECKED);
+    root.updateStateOfRelatedNodes();
+    this.repaint();
+    
+    // even though this isn't a click in the tree, the selection has changed -
+    // notify all listeners
+    notifyCheckingListeners();
+  }
+  
+  
+  /**
+   * TODO - this method doesn't take into account a possibility that the
+   *        filter tree might have changed
+   * 
+   * @param rootsOfCheckedPaths A list of TreePath objects which represent a checking state of
+   * the nodes in this tree (as returned by <code>getRootsOfCheckedPaths()</code>).
+   * 
+   * The last node of each path is the one that should have <code>TriStateCheckBox.State.CHECKED</code>
+   * state (so that last node is a root of checked path that start at that node). Related partial
+   * checkings for the UI can be computed from that by the tree checking model.
+   * 
+   * Therefore, a single "real" checking per provided TreePath from <code>rootsOfCheckedPaths</code> is
+   * made.
+   */
+  public void restoreFilterCheckingSettings(List<TreePath> rootsOfCheckedPaths)
+  {
+    // start with removing all selections
+    this.selectAllNodes(false);
+    
+    for (TreePath p : rootsOfCheckedPaths) {
+      restoreTreePathCheckingSettings(this.root, p);
+    }
+  }
+  
+  /**
+   * A worker method for <code>restoreFilterCheckingSettings(List<TreePath> rootsOfCheckedPaths)</code>.
+   * See that method for further details.
+   * 
+   * @param startNode A node of this tree.
+   * @param pathFromStartNode A TreePath object from the stored filter, where the first node must be 
+   *                          equals to <code>startNode</code> (based on the <code>userObject</code>,
+   *                          but not the checking state), should the traversal of the tree result in
+   *                          checking the last node of this TreePath eventually - which is the goal
+   *                          of this method.
+   * @return True if traversal of <code>pathFromStartNode</code> succeeded and a node in this tree was checked;
+   *         false if traversal couldn't find a matching node in this tree, and so no checking was made.
+   */
+  private boolean restoreTreePathCheckingSettings(TriStateTreeNode startNode, TreePath pathFromStartNode)
+  {
+    if (startNode == null || pathFromStartNode == null || pathFromStartNode.getPathCount() == 0) {
+      // no match - no data to work with
+      return (false);
+    }
+    
+    // compare the "roots" - provided start node and the root of the provided path
+    // (based on the 'user object', but not the selection state)
+    if (startNode.equals(pathFromStartNode.getPathComponent(0)))
+    {
+      if (pathFromStartNode.getPathCount() == 1) {
+        // provided startNode is equals to the only node in the provided tree path -
+        // so it is the node to mark as checked; also - make sure that this selection
+        // propagates through tree
+        startNode.setState(TriStateCheckBox.State.CHECKED, true);
+        
+        // we've found the required node in this path - no further search needed,
+        // so terminate this method
+        return (true);
+      }
+      else {
+        // provided startNode is equals to the first node of the provided tree path -
+        // meaning that at this stage we need to traverse all children of the startNode
+        // and look for the child that would match the next element in the provided tree path
+        //
+        // to do this, produce a new tree path from the provided one that doesn't contain
+        // the first node - then proceed recursively
+        Object[] currentPathComponents = pathFromStartNode.getPath();
+        Object[] reducedPathComponents = new Object[currentPathComponents.length - 1];
+        System.arraycopy(currentPathComponents, 1, reducedPathComponents, 0, currentPathComponents.length - 1);
+        
+        Enumeration children = startNode.children();
+        while (children.hasMoreElements()) {
+          TriStateTreeNode currentChild = (TriStateTreeNode)children.nextElement();
+          
+          // if recursive call succeeds, no need to iterate any further
+          if (restoreTreePathCheckingSettings(currentChild, new TreePath(reducedPathComponents))) return (true);
+        }
+      }
+    }
+    
+    // the startNode doesn't match the the first element in the provided tree path
+    // or no match could be found during recursive search for the node to "check"
+    return (false);
+  }
+  
+  
+  /**
+   * Expands all paths in this tree.
+   */
+  public void expandAll()
+  {
+    // this simply expands all tree nodes
+    // TODO - this actually "freezes" the UI if there are many nodes in the tree
+    //        some better solution to be found (e.g. expand the nodes in the model, then update UI, or similar)
+    for (int i = 0; i < getRowCount(); i++) {
+      instanceOfSelf.expandRow(i);
+    }
+  }
+  
+  
+  /**
+   * Collapses all paths in this tree.
+   */
+  public void collapseAll()
+  {
+    // this simply collapses all expanded nodes - this is very quick, execute just as it is
+    for (int i = getRowCount() - 1; i >= 0; i--) {
+      instanceOfSelf.collapseRow(i);
+    }
+  }
+  
+  
+  /**
+   * Removes all nodes in this tree that are unchecked.
+   * 
+   * It doesn't iterate through *all* nodes - if some node is
+   * indeed unchecked, it removes that node and any children that
+   * it has (because unchecked node is the root of an unchecked path).
+   */
+  public void removeAllUncheckedNodes()
+  {
+    // get the tree model first - will be used to remove the nodes
+    DefaultTreeModel theTreeModel = (DefaultTreeModel)this.treeModel;
+    
+    // remove unchecked nodes
+    List<TreePath> allNodesToRemove = this.getRootsOfUncheckedPaths();
+    for (TreePath p : allNodesToRemove) {
+      theTreeModel.removeNodeFromParent((MutableTreeNode)p.getLastPathComponent());
+    }
+  }
+  
+  
+  /**
+   * Provides access to the contextual menu of this JTriStateTree.
+   * 
+   * @return Reference to the contextual menu.
+   */
+  public JPopupMenu getContextualMenu() {
+    return contextualMenu;
+  }
+  
+  
+  public void setCheckingEnabled(boolean bCheckingEnabled) {
+    this.bCheckingEnabled = bCheckingEnabled;
+  }
+  
+  /**
+   * @return True if the current state of this JTriStateTree
+   *         allows making changes to checking of checkboxes
+   *         in its nodes.
+   */
+  public boolean isCheckingEnabled() {
+    return bCheckingEnabled;
+  }
+  
+  
+  /**
+   * @param listener New tree checking listener to register for updates
+   *                 to tree node selections.
+   */
+  public void addCheckingListener(TriStateTreeCheckingListener listener) {
+    if (listener != null) {
+      this.checkingListeners.add(listener);
+    }
+  }
+  
+  
+  /**
+   * @param listener Tree checking listener to remove.
+   */
+  public void removeCheckingListener(TriStateTreeCheckingListener listener) {
+    if (listener != null) {
+      this.checkingListeners.remove(listener);
+    }
+  }
+  
+  
+  /**
+   * Sends a signal to all listeners to check the state of the tree,
+   * as it has changed. 
+   */
+  private void notifyCheckingListeners() {
+    for (TriStateTreeCheckingListener listener : this.checkingListeners) {
+      listener.triStateTreeCheckingChanged(instanceOfSelf);
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z
new file mode 100644
index 0000000..8c60d87
Binary files /dev/null and b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Swing - Tristate CheckBox.7z differ

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java
new file mode 100644
index 0000000..6d58db3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/Test.java
@@ -0,0 +1,67 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class Test extends JFrame
+{
+  public Test() {
+    this.setLayout(new BorderLayout());
+    
+    this.add(new TriStateCheckBox("LOL", TriStateCheckBox.State.PARTIAL), BorderLayout.NORTH);
+    
+    TriStateTreeNode root = new TriStateTreeNode("root");
+    TriStateTreeNode c1 = new TriStateTreeNode("child 1");
+    TriStateTreeNode c2 = new TriStateTreeNode("child 2");
+    TriStateTreeNode c3 = new TriStateTreeNode("child 3");
+    
+    TriStateTreeNode c1_1 = new TriStateTreeNode("child 1_1");
+    TriStateTreeNode c1_2 = new TriStateTreeNode("child 1_2");
+    TriStateTreeNode c1_3 = new TriStateTreeNode("child 1_3");
+    
+    TriStateTreeNode c2_1 = new TriStateTreeNode("child 2_1");
+    TriStateTreeNode c2_2 = new TriStateTreeNode("child 2_2");
+    TriStateTreeNode c2_3 = new TriStateTreeNode("child 2_3");
+    
+    TriStateTreeNode c3_1 = new TriStateTreeNode("child 3_1");
+    TriStateTreeNode c3_2 = new TriStateTreeNode("child 3_2");
+    TriStateTreeNode c3_3 = new TriStateTreeNode("child 3_3");
+    
+    TriStateTreeNode c1_1_1 = new TriStateTreeNode("child 1_1_1");
+    TriStateTreeNode c1_1_2 = new TriStateTreeNode("child 1_1_2");
+    TriStateTreeNode c1_1_3 = new TriStateTreeNode("child 1_1_3");
+    
+    // adding second level children
+    root.add(c1); root.add(c2); root.add(c3);
+    
+    // adding third-level children
+    c1.add(c1_1); c1.add(c1_2); c1.add(c1_3);
+    c2.add(c2_1); c2.add(c2_2); c2.add(c2_3);
+    c3.add(c3_1); c3.add(c3_2); c3.add(c3_3);
+    
+    // adding fourth-level children
+    c1_1.add(c1_1_1); c1_1.add(c1_1_2); c1_1.add(c1_1_3);
+    
+    
+    // NB! important to create the tree when 'root' is already populated with children
+    JTriStateTree tree = new JTriStateTree(root);
+    tree.setRootVisible(false);
+    tree.setShowsRootHandles(true);
+    this.add(new JScrollPane(tree), BorderLayout.CENTER);
+    
+    this.setDefaultCloseOperation(EXIT_ON_CLOSE);
+    this.pack();
+  }
+  
+
+  public static void main(String[] args) {
+    JFrame a = new Test();
+    a.setVisible(true);
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java
new file mode 100644
index 0000000..361dfc3
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBox.java
@@ -0,0 +1,172 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+/*
+ * Taken from: http://72.5.124.102/thread.jspa?threadID=721308&messageID=9955637
+ * Data webpage accessed: 07/February/2010
+ * 
+ * Modified by Sergejs Aleksejevs
+ */
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+ 
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JToggleButton;
+import javax.swing.UIManager;
+ 
+public class TriStateCheckBox extends JCheckBox
+{
+  private static final long serialVersionUID = 1L;
+  
+  public static enum State {
+    CHECKED, UNCHECKED, PARTIAL
+  };
+  
+  
+  // these colors will be used for painting the 'partial' state of the checkbox -
+  // a gradient painting will be made from top-left to bottom-right
+  private Color partialStateTopLeftColor = Color.GREEN.darker().darker().darker();
+  private Color partialStateBottomRightColor = Color.GREEN.brighter().brighter().brighter();
+  
+  
+  /**
+   * Creates an initially unselected check box button with no text, no icon.
+   */
+  public TriStateCheckBox() {
+    this(null, State.UNCHECKED);
+  }
+ 
+  /**
+   * Creates a check box with text and icon, and specifies whether or not it is initially
+   * selected.
+   * 
+   * @param text
+   *            The text of the check box.
+   * @param initial
+   *            The initial state
+   */
+  public TriStateCheckBox(String text, State initial) {
+    super.setText(text);
+    setModel(new TriStateModel(initial));
+    setIcon(new TriStateIcon(this));
+    // some UI settings
+    setRolloverEnabled(true);
+  }
+ 
+  /**
+   * Set the new state to either CHECKED, PARTIAL or UNCHECKED.
+   */
+  public void setState(State state) {
+    ((TriStateModel) model).setState(state);
+  }
+ 
+  /**
+   * Return the current state, which is determined by the selection status of the model.
+   */
+  public State getState() {
+    return ((TriStateModel) model).getState();
+  }
+ 
+  public void setSelected(boolean selected) {
+    ((TriStateModel) model).setSelected(selected);
+  }
+ 
+  /** The model for the button */
+  private static class TriStateModel extends JToggleButton.ToggleButtonModel {
+ 
+    private static final long serialVersionUID = 1L;
+    protected State state;
+ 
+    public TriStateModel(State state) {
+      this.state = state;
+    }
+ 
+    public boolean isSelected() {
+      return state == State.CHECKED;
+    }
+ 
+    public State getState() {
+      return state;
+    }
+ 
+    public void setState(State state) {
+      this.state = state;
+      fireStateChanged();
+    }
+ 
+    public void setPressed(boolean pressed) {
+      if (pressed) {
+        switch (state) {
+          case UNCHECKED:
+            state = State.CHECKED;
+            break;
+          case PARTIAL:
+            state = State.UNCHECKED;
+            break;
+          case CHECKED:
+            state = State.PARTIAL;
+            break;
+        }
+      }
+ 
+    }
+ 
+    public void setSelected(boolean selected) {
+      if (selected) {
+        this.state = State.CHECKED;
+      } else {
+        this.state = State.UNCHECKED;
+      }
+    }
+  }
+ 
+  private class TriStateIcon implements Icon
+  {
+    private Icon checkBoxIcon;
+    private TriStateCheckBox triStateCheckBox;
+    public TriStateIcon(TriStateCheckBox triStateCheckBox) {
+      this.triStateCheckBox = triStateCheckBox;
+      this.checkBoxIcon = UIManager.getIcon("CheckBox.icon");
+      
+      return;
+    }
+ 
+    public final int getIconHeight() {
+      return this.checkBoxIcon.getIconHeight();
+ 
+    }
+ 
+    public final int getIconWidth() {
+      return this.checkBoxIcon.getIconWidth();
+ 
+    }
+    
+    
+    public void paintIcon(Component c, Graphics g, int x, int y)
+		{
+			checkBoxIcon.paintIcon(triStateCheckBox, g, x, y);
+			if (triStateCheckBox.getState().equals(TriStateCheckBox.State.PARTIAL))
+			{
+			  // this is changed to create the gradient paint dynamically every time;
+			  // this makes sure that the gradient is relative to the actual position of the checkbox,
+			  // rather than in the absolute coordinates of the parent component
+			  GradientPaint gradient = new GradientPaint(x, y, partialStateTopLeftColor, x + 8, y + 8, partialStateBottomRightColor, false);
+      
+				Graphics2D g2d = (Graphics2D) g;
+				g2d.setPaint(gradient);
+				final int deltaX = 2;
+				final int deltaY = 2;
+				final int xNew = x + deltaX;
+				final int yNew = y + deltaY;
+				final int width = getIconWidth() - 2*deltaX;
+				final int height = getIconHeight() - 2*deltaY;
+				g2d.fillRect(xNew, yNew, width, height);
+			}
+		}
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java
new file mode 100644
index 0000000..dd78bf7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateCheckBoxTreeCellRenderer.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.border.Border;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+
+
+/**
+ * Provides a mechanism for rendering tri-state tree nodes.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class TriStateCheckBoxTreeCellRenderer extends DefaultTreeCellRenderer
+{
+  public Component getTreeCellRendererComponent(JTree tree, Object value,
+      boolean selected, boolean expanded, boolean leaf, int row,
+      boolean hasFocus)
+  {
+    Border treeNodePanelBorder = null; // will be obtained from default rendering and applied to the new one
+    Color backgroundColor = null;      // likewise: will be applied to all constituents of the new rendering
+    
+    // obtain the default rendering, we'll then customize it
+    Component defaultRendering = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+    
+    // it is most likely that the default rendering will be a JLabel, check just to be safe
+    if (defaultRendering instanceof JLabel)
+    {
+      JLabel defaultRenderedLabel = ((JLabel)defaultRendering);
+      
+      // if this is not the case, it kind of undermines the whole purpose
+      // of using this tree cell renderer, but check just to be sure
+      if (value instanceof TriStateTreeNode) {
+        // a state value from within the TriStateTreeNode will be used to
+        // set the correct state in its rendering
+        switch (((TriStateTreeNode)value).getState()) {
+          case CHECKED: 
+            if (((TriStateTreeNode)value).getPath().length > 2) {
+              // only allow CHECKED state icon for nodes that are deeper than second
+              // level in the tree - that is for any nodes that do not represent categories
+              // in the tree (root is not shown, so nodes that represent categories are
+              // effectively multiple category "roots" that have actual contents inside them)
+              defaultRenderedLabel.setIcon(ResourceManager.getImageIcon(ResourceManager.TRISTATE_CHECKBOX_CHECKED_ICON));
+              break;
+            }
+            // else -- 'fall through' to PARTIAL icon: this was a CHECKED state for the category node
+          case PARTIAL: defaultRenderedLabel.setIcon(ResourceManager.getImageIcon(ResourceManager.TRISTATE_CHECKBOX_PARTIAL_ICON)); break;
+          case UNCHECKED: defaultRenderedLabel.setIcon(ResourceManager.getImageIcon(ResourceManager.TRISTATE_CHECKBOX_UNCHECKED_ICON)); break;
+          default: defaultRenderedLabel.setIcon(null); break;
+        }
+      }
+    }
+    
+    return (defaultRendering);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java
new file mode 100644
index 0000000..e612e54
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeCheckingListener.java
@@ -0,0 +1,12 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+/**
+ * A simple interface to enable tracking tree checking
+ * changes.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public interface TriStateTreeCheckingListener
+{
+  public void triStateTreeCheckingChanged(JTriStateTree source);
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java
new file mode 100644
index 0000000..248cdf8
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/tristatetree/TriStateTreeNode.java
@@ -0,0 +1,246 @@
+package net.sf.taverna.biocatalogue.ui.tristatetree;
+
+import java.util.Enumeration;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+/**
+ * This class models tri-state nodes in the tree. Effectively
+ * it associates a tri-state checkbox with each tree node.
+ * 
+ * Useful for partial selections of hierarchical data -
+ * partial selection of a node indicates that some of the
+ * children of that node are selected.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class TriStateTreeNode extends DefaultMutableTreeNode
+{
+  private TriStateCheckBox.State state;
+  
+  /**
+   * Creates a regular tree node; associated tri-state checkbox state is set to UNCHECKED.
+   *  
+   * @param userObject The object this tree node will represent.
+   */
+  public TriStateTreeNode(Object userObject) {
+    this(userObject, TriStateCheckBox.State.UNCHECKED);
+  }
+  
+  /**
+   * Creates a regular tree node; associated tri-state checkbox state is set to the provided <code>initialState</code> value.
+   *  
+   * @param userObject The object this tree node will represent.
+   * @param initialState One of the enum values of <code>TriStateCheckBox.State</code>.
+   */
+  public TriStateTreeNode(Object userObject, TriStateCheckBox.State initialState) {
+    super(userObject);
+    this.state = initialState;
+  }
+  
+  
+  /**
+   * Compares based on the user object, not the state of this node.
+   */
+  public boolean equals(Object other) {
+    if (other instanceof TriStateTreeNode) {
+      return (this.userObject.equals(((TriStateTreeNode)other).userObject));
+    }
+    else {
+      return (false);
+    }
+  }
+  
+  
+  /**
+   * Sets the state of the current node and (optionally) propagates
+   * those changes through the tree.
+   * 
+   * @param state The new state to set - value from <code>TriStateCheckBox.State</code> enum.
+   * @param propagateChangesToRelatedNodes True - to use the tree checking model to
+   *                 propagate changes of the state of the current tree node to the
+   *                 other related tree nodes (e.g. all descendants and ancestors) -
+   *                 up and down the tree hierarchy. False - to only update the current
+   *                 node and make no changes to the rest of the tree.
+   */
+  public void setState(TriStateCheckBox.State state, boolean propagateChangesToRelatedNodes)
+  {
+    this.state = state;
+    
+    // check if the tree checking model should be activated
+    if (propagateChangesToRelatedNodes) {
+      updateStateOfRelatedNodes();
+    }
+  }
+  
+  
+  /**
+   * Sets the state of the current node.
+   * 
+   * @param state The new state to set - value from <code>TriStateCheckBox.State</code> enum.
+   */
+  public void setState(TriStateCheckBox.State state) {
+    setState(state, false);
+  }
+  
+  
+  public TriStateCheckBox.State getState() {
+    return state;
+  }
+  
+  
+  /**
+   * Toggles the state of the associated tri-state checkbox.
+   * State transitions are as follows:</br>
+   * <code>
+   * TriStateCheckBox.State.CHECKED -> TriStateCheckBox.State.UNCHECKED
+   * TriStateCheckBox.State.PARTIAL -> TriStateCheckBox.State.UNCHECKED
+   * TriStateCheckBox.State.UNCHECKED -> TriStateCheckBox.State.CHECKED
+   * </code>
+   *
+   * @param propagateChangesToRelatedNodes True - to use the tree checking model to
+   *                 propagate changes of the state of the current tree node to the
+   *                 other related tree nodes (e.g. all descendants and ancestors) -
+   *                 up and down the tree hierarchy. False - to only update the current
+   *                 node and make no changes to the rest of the tree. 
+   * @return The value of the new state.
+   */
+  public TriStateCheckBox.State toggleState(boolean propagateChangesToRelatedNodes)
+  {
+    if (state.equals(TriStateCheckBox.State.CHECKED) || state.equals(TriStateCheckBox.State.PARTIAL)) {
+      state = TriStateCheckBox.State.UNCHECKED;
+    }
+    else if (state.equals(TriStateCheckBox.State.UNCHECKED)) {
+      state = TriStateCheckBox.State.CHECKED;
+    }
+    
+    // check if the tree checking model should be activated
+    if (propagateChangesToRelatedNodes) {
+      updateStateOfRelatedNodes();
+    }
+    
+    return (state);
+  }
+  
+  
+  /* 
+   * === The tree CHECKING MODEL ===
+   * 
+   * Effectively, this defines the way the tree reacts to it's nodes
+   * being checked / unchecked. Only one model is implemented at the
+   * moment, therefore it's not extracted into a separate class, but
+   * remains to be a part of the TriStateTreeNode.
+   * 
+   * Could possibly be better placed within the JTriStateTree, rather
+   * than TriStateTreeNode.
+   */
+  
+  /**
+   * The entry point - must be invoked to traverse the tree and make
+   * changes to checking states of related tree nodes. 
+   */
+  public void updateStateOfRelatedNodes()
+  {
+    updateStateOfAncestors(this.getParent());
+    updateStateOfDescendants(this);
+  }
+  
+  
+  /**
+   * Recursively visits all ancestors of the <code>parentNode</code>
+   * and decides on their checking states based on the states of their
+   * children nodes.
+   * 
+   * @param parentNode Initially - parent node of the current node (i.e. the one,
+   *                   for which a state update has been made); then updated for
+   *                   recursive calls.
+   */
+  private void updateStateOfAncestors(TreeNode parentNode)
+  {
+    // reached root of the tree, do nothing - return
+    if (parentNode == null) {
+      return;
+    }
+    
+    if (parentNode instanceof TriStateTreeNode) {
+      TriStateTreeNode parentTriStateNode = (TriStateTreeNode)parentNode;
+      
+      // explicitly fetch children into a new enumeration - this is
+      // to make sure that we work with the same enumeration, rather
+      // than obtaining a fresh one with every reference to 'parentTriStateNode.children()'
+      Enumeration childNodes = parentTriStateNode.children();
+      
+      // go through all the children and count the number of selected ones
+      int iChildrenCount = 0;
+      int iPartiallySelectedChildren = 0;
+      int iSelectedChildren = 0;
+      
+      while(childNodes.hasMoreElements()) {
+        Object node = childNodes.nextElement();
+        if (node instanceof TriStateTreeNode) {
+          TriStateTreeNode currentNode = (TriStateTreeNode)node;
+          iChildrenCount++;
+          if (currentNode.getState().equals(TriStateCheckBox.State.CHECKED)) {
+            iSelectedChildren++;
+          }
+          else if (currentNode.getState().equals(TriStateCheckBox.State.PARTIAL)) {
+            iPartiallySelectedChildren++;
+          }
+        }
+      }
+      
+      
+      // decide on the state of the 'parentNode' based on the checking state of its children
+      if (iSelectedChildren == 0 && iPartiallySelectedChildren == 0) {
+        // no children are selected
+        parentTriStateNode.setState(TriStateCheckBox.State.UNCHECKED);
+      }
+      else if ((iSelectedChildren + iPartiallySelectedChildren) > 0 && iSelectedChildren < iChildrenCount) {
+        // some children are selected (either partially or fully)
+        parentTriStateNode.setState(TriStateCheckBox.State.PARTIAL);
+      }
+      else if (iSelectedChildren > 0 && iSelectedChildren == iChildrenCount) {
+        // all children are selected
+        parentTriStateNode.setState(TriStateCheckBox.State.CHECKED);
+      }
+      
+      
+      // repeat the same recursively up the hierarchy
+      updateStateOfAncestors(parentTriStateNode.getParent());
+    }
+  }
+
+  /**
+   * Recursively traverses all descendants of the <code>parentNode</code>
+   * to set their checking state to the value of the state of the <code>parentNode</code>. 
+   * 
+   * @param parentNode Initially - the tree node for which the state
+   *                   change was made; then updated for recursive calls.
+   */
+  private void updateStateOfDescendants(TriStateTreeNode parentNode)
+  {
+    // explicitly fetch children into a new enumeration - this is
+    // to make sure that we work with the same enumeration, rather
+    // than obtaining a fresh one with every reference to 'parentNode.children()'
+    Enumeration childNodes = parentNode.children();
+    
+    // for all child nodes do 2 things:
+    // - set their state as that of the parent;
+    // - repeat the same recursively with their children
+    while(childNodes.hasMoreElements()) {
+      Object node = childNodes.nextElement();
+      if (node instanceof TriStateTreeNode) {
+        TriStateTreeNode currentNode = (TriStateTreeNode) node; 
+        currentNode.setState(parentNode.getState());
+        currentNode.updateStateOfDescendants(currentNode);
+      }
+    }
+  }
+  
+  /*
+   * === End of CHECKING MODEL implementation.
+   */
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java
new file mode 100644
index 0000000..ac170dc
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/BioCataloguePerspective.java
@@ -0,0 +1,70 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.jdom.Element;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class BioCataloguePerspective implements PerspectiveSPI
+{
+  private MainComponent perspectiveMainComponent;
+	private boolean visible = true;
+
+	public ImageIcon getButtonIcon()
+	{
+		return ResourceManager.getImageIcon(ResourceManager.FAVICON);
+	}
+
+	public InputStream getLayoutInputStream() {
+	  return getClass().getResourceAsStream("biocatalogue-perspective.xml");
+	}
+
+	public String getText() {
+		return "Service Catalogue";
+	}
+
+	public boolean isVisible() {
+		return visible;
+	}
+
+	public int positionHint()
+	{
+	  // this determines position of perspective in the
+    // bar with perspective buttons (currently makes it the last in
+    // the list)
+    return 40;
+	}
+
+	public void setVisible(boolean visible) {
+		this.visible = visible;
+		
+	}
+
+	public void update(Element layoutElement) {
+		// TODO Auto-generated method stub
+		
+		// Not sure what to do here
+	}
+	
+  public void setMainComponent(MainComponent component)
+  {
+    this.perspectiveMainComponent = component;
+  }
+  
+  /**
+   * Returns the instance of the main component of this perspective.
+   */
+  public MainComponent getMainComponent()
+  {
+    return this.perspectiveMainComponent;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java
new file mode 100644
index 0000000..eeb1cba
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponent.java
@@ -0,0 +1,285 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.GridLayout;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.ToolTipManager;
+import javax.swing.border.LineBorder;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.ui.BioCatalogueExplorationTab;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+import org.apache.log4j.Logger;
+
+/*
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public final class MainComponent extends JPanel implements UIComponentSPI //, ChangeListener
+{
+//  private static final String windowBaseTitle = "BioCatalogue API Demo";
+//  private HashMap<String, String> windowTitleMap;
+  
+  private MainComponent pluginPerspectiveMainComponent;
+  private final Logger logger = Logger.getLogger(MainComponent.class);
+  
+  //private JTabbedPane tpMainTabs;
+  private BioCatalogueExplorationTab jpBioCatalogueExplorationTab;
+//  private BioCataloguePluginAbout jpAboutTab;
+  
+  public static JFrame dummyOwnerJFrame;
+  static {
+    // this is only to have a nice icon on all Dialog boxes - can be removed at any time
+    dummyOwnerJFrame = new JFrame();
+    dummyOwnerJFrame.setIconImage(ResourceManager.getImageIcon(ResourceManager.FAVICON).getImage());
+  }
+  
+  
+  /**
+   * This constructor is protected, and so is only available to the classes in its package -
+   * i.e. Taverna integration classes. Other parts of the plugin must use <code>MainComponentFactory.getSharedInstance()</code>
+   * to get the shared instance of this class.
+   */
+	protected MainComponent()
+	{
+	  super();
+	  initialiseEnvironment();
+	  initialisePerspectiveUI();
+	}
+	
+	
+  // *** Methods implementing UIComponentSPI interface ***
+	
+	public ImageIcon getIcon() {
+		return WorkbenchIcons.databaseIcon;
+	}
+
+	@Override
+	public String getName() {
+		return "Service Catalogue Perspective Main Component";
+	}
+
+	public void onDisplay() {
+		// TODO Auto-generated method stub
+	}
+
+	public void onDispose() {
+		// TODO Auto-generated method stub
+	}
+	
+	// *** End of methods implementing UIComponentSPI interface ***
+	
+	
+	private void initialiseEnvironment()
+	{
+	  // before anything else - store a reference to self for use during
+	  // initialisation of other components
+	  pluginPerspectiveMainComponent = this;
+	  
+	  
+	  // pre-load classes for FlyingSaucer XHTML renderer - this will make sure
+    // that the first time it is used, there will be no delay while classes
+    // are loaded by Java
+    new Thread("class pre-loading") {
+      public void run() {
+        try {
+          Class.forName("org.xhtmlrenderer.simple.FSScrollPane");
+          Class.forName("org.xhtmlrenderer.simple.XHTMLPanel");
+        }
+        catch (ClassNotFoundException e) {
+          logger.error("Problem while pre-loading classes for FlyingSaucer XHTML renderer", e);
+        }
+      }
+    }.start();
+    
+    
+    // determine what folder is to be used for config files
+    if (!Util.isRunningInTaverna()) {
+      // running outside Taverna, place config file and log into the user's home directory
+      BioCataloguePluginConstants.CONFIG_FILE_FOLDER = 
+        new java.io.File(System.getProperty("user.home"), BioCataloguePluginConstants.CONFIG_FILE_FOLDER_WHEN_RUNNING_STANDALONE);
+      BioCataloguePluginConstants.LOG_FILE_FOLDER = 
+        new java.io.File(System.getProperty("user.home"), BioCataloguePluginConstants.CONFIG_FILE_FOLDER_WHEN_RUNNING_STANDALONE);
+    }
+    
+    
+    // this makes sure that tooltips will stay displayed for longer than default 
+    ToolTipManager.sharedInstance().setDismissDelay(BioCataloguePluginConstants.DEFAULT_TOOLTIP_DURATION);
+    
+    // these components must be accessed by all other components, hence need
+    // to be initialised before any other initialisation is done
+
+//    windowTitleMap = new HashMap<String,String>();
+	}
+	
+	private void initialisePerspectiveUI()
+	{
+	  // set the loader icon to show that the perspective is loading
+	  this.setLayout(new GridLayout(1,1));
+	  this.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE)));
+	  
+	  new Thread("Initialise Service Catalogue Perspective UI")
+	  {
+	    public void run() {
+	      // create all tabs prior to putting them inside the tabbed pane
+	      jpBioCatalogueExplorationTab = new BioCatalogueExplorationTab();
+//	      jpServiceFilteringTab = new ServiceFilteringTab(pluginPerspectiveMainComponent, client, logger);
+//	      jpSearchTab = new SearchTab(pluginPerspectiveMainComponent, client, logger);
+//	      jpAboutTab = new BioCataloguePluginAbout(pluginPerspectiveMainComponent, client, logger);
+	      
+	      // create main tabs
+//	      tpMainTabs = new JTabbedPane();
+//	      tpMainTabs.add("Explore BioCatalogue", jpBioCatalogueExplorationTab);
+//	      tpMainTabs.add("Search", jpSearchTab);
+//	      tpMainTabs.add("Filter Services", jpServiceFilteringTab);
+//	      tpMainTabs.add("About", jpAboutTab);
+	      
+	      SwingUtilities.invokeLater(new Runnable() {
+          public void run()
+          {
+            // add main tabs and the status bar into the perspective
+            pluginPerspectiveMainComponent.removeAll();
+            pluginPerspectiveMainComponent.setLayout(new BorderLayout());
+            pluginPerspectiveMainComponent.setBorder(new LineBorder(Color.BLACK));
+            pluginPerspectiveMainComponent.add(jpBioCatalogueExplorationTab, BorderLayout.CENTER);
+            
+            // everything is prepared -- need to focus default component on the first tab
+            // (artificially generate change event on the tabbed pane to perform focus request)
+//            tpMainTabs.setSelectedIndex(1);
+//            tpMainTabs.addChangeListener(pluginPerspectiveMainComponent);
+//           tpMainTabs.setSelectedIndex(0);
+          }
+        });
+	    }
+	  }.start();
+	}
+	
+	/**
+   * Determines whether the specified tab is currently active in the main tabbed pane.
+   * @param strTabClassName Class name of the tab which is tested for being active.
+   * @return True if specified tab is currently active.
+   */
+/*  
+ private boolean isTabActive(String strTabClassName)
+  {
+    if (tpMainTabs == null) return (false);
+    
+    // if an anonymous thread within the main tab component class will call
+    // this method, we want to store the main tab's class, rather than
+    // the full class name of the anonymous worker thread
+    String strCurSelectedTabClassName = tpMainTabs.getSelectedComponent().getClass().getName();
+    
+    // get the real class name to match
+    String strBaseClassName = Util.getBaseClassName(strTabClassName);
+    
+    // compare the two class names
+    return (strBaseClassName.equals(strCurSelectedTabClassName));
+  }
+  */
+  
+  
+  /**
+   * This method "selects" the tab represented by the component provided as a parameter.
+   * 
+   * @param c Component that represents one of the tabs in the main tabbed pane.
+   *          If <code>c</code> is not found within the components of the tabbed pane, nothing will happen.
+   */
+/*  
+  public void setTabActive(Component c)
+  {
+    try {
+      tpMainTabs.setSelectedComponent(c);
+    }
+    catch (IllegalArgumentException e) {
+      // do nothing, can't activate component which is not in the tabbed pane
+    }
+  }
+  */
+  
+  
+  /**
+   * Sets title of the main perspective window for a specified tab,
+   * thus supporting different window titles for different tabs;
+   * new title will only be displayed immediately if the specified tab.
+   * 
+   * @param strTabClassName Class name of the tab, for which window title should be updated.
+   * @param strTitle New title to set.
+   */
+/* 
+  public void setWindowTitle(String strTabClassName, String strTitle)
+  {
+    // if an anonymous thread within the main tab component class will call
+    // this method, we want to store the main tab's class, rather than
+    // the full class name of the anonymous worker thread
+    String strBaseClassName = Util.getBaseClassName(strTabClassName);
+    
+    // store the new title for for the specified tab
+    windowTitleMap.put(strBaseClassName, strTitle);
+    
+    // if the specified tab is active, update window title immediately
+    if (isTabActive(strBaseClassName)) displayWindowTitle(strBaseClassName);
+  }
+  */
+  
+  
+  /** 
+   * Displays window title that corresponds to specified tab.
+   * 
+   * @param strTabClassName Class name of the tab for which the window title is to be set.
+   */
+/*
+  public void displayWindowTitle(String strTabClassName)
+  {
+    // if an anonymous thread within the main tab component class will call
+    // this method, we want to store the main tab's class, rather than
+    // the full class name of the anonymous worker thread
+    String strBaseClassName = Util.getBaseClassName(strTabClassName);
+    
+    if (windowTitleMap.containsKey(strBaseClassName)) {
+      // title for specified tab found - show it
+      // TODO - disabled until this info will be shown in the status bar
+      //this.setTitle(windowBaseTitle + " :: " + windowTitleMap.get(strBaseClassName));
+    }
+    else {
+      // tab not found - display standard title
+      // TODO - disabled until this info will be shown in the status bar
+      //this.setTitle(windowBaseTitle);
+    }
+  }
+  */
+  
+  // *** Getters for various components ***
+  
+  /**
+   * @return Reference to the component that represents the BioCatalogue Exploration
+   *         tab in the BioCatalogue Perspective.
+   */
+  public BioCatalogueExplorationTab getBioCatalogueExplorationTab() {
+    return (this.jpBioCatalogueExplorationTab);
+  }
+    
+  // *** Callbacks for ChangeListener interface ***
+  
+/*  public void stateChanged(ChangeEvent e)
+  {
+    if (e.getSource().equals(tpMainTabs)) {
+      // will get called when selected tab changes - need to focus default component in the active tab
+      // and also set a correct window title
+      if (tpMainTabs.getSelectedComponent() instanceof HasDefaultFocusCapability) {
+        ((HasDefaultFocusCapability)tpMainTabs.getSelectedComponent()).focusDefaultComponent();
+        this.displayWindowTitle(tpMainTabs.getSelectedComponent().getClass().getName());
+      }
+    }
+  }*/
+	
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java
new file mode 100644
index 0000000..c213600
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentFactory.java
@@ -0,0 +1,46 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class MainComponentFactory implements UIComponentFactorySPI
+{
+  // this is to ensure that the whole perspective is not re-created
+  // each time it is being activated in Taverna, rather it will only
+  // happen once during the execution
+  private static MainComponent mainPerspectiveComponent = null;
+  
+	public static MainComponent getSharedInstance()
+	{
+	  // double-check on existence of the 'mainPerspectiveComponent' ensures
+    // that it is really created only once
+    if (mainPerspectiveComponent == null) {
+      synchronized(MainComponentFactory.class) {
+        if (mainPerspectiveComponent == null) {
+          mainPerspectiveComponent = new MainComponent();
+        }
+      }
+    }
+    return (mainPerspectiveComponent);
+	}
+	
+	public UIComponentSPI getComponent() {
+    return (getSharedInstance());
+  }
+	
+	
+	public ImageIcon getIcon() {
+		return WorkbenchIcons.databaseIcon;
+	}
+
+	public String getName() {
+		return "Service Catalogue Main Component Factory";
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java
new file mode 100644
index 0000000..6c2e844
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/MainComponentShutdownHook.java
@@ -0,0 +1,49 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import com.thoughtworks.xstream.XStream;
+
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config.BioCataloguePluginConfiguration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.service_panel.BioCatalogueServiceProvider;
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class MainComponentShutdownHook implements ShutdownSPI
+{
+  public int positionHint()
+  {
+    // all custom plugins are suggested to return a value of > 100;
+    // this affects when in the termination process will this plugin
+    // be shutdown;
+    return 100;
+  }
+  
+  public boolean shutdown()
+  {
+      // Do not save service providers in BioCatalogue's conf file - they should be saved by Taverna together with 
+      // other service providers
+	  
+//      // store services that were added to the Service Panel - both REST and SOAP
+//      XStream xstream = new XStream();
+//      
+//	  BioCataloguePluginConfiguration configuration = BioCataloguePluginConfiguration.getInstance();
+//      
+//      configuration.setProperty(
+//          BioCataloguePluginConfiguration.SOAP_OPERATIONS_IN_SERVICE_PANEL,
+//          xstream.toXML(BioCatalogueServiceProvider.getRegisteredSOAPOperations()));
+//      configuration.setProperty(
+//          BioCataloguePluginConfiguration.REST_METHODS_IN_SERVICE_PANEL,
+//          xstream.toXML(BioCatalogueServiceProvider.getRegisteredRESTMethods()));
+//      
+//      // save all the plugin's configuration 
+//      configuration.store();
+//      
+//      
+//      // close API operation log
+//      MainComponentFactory.getSharedInstance().getBioCatalogueClient().getAPILogWriter().close();
+//      
+      return true;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java
new file mode 100644
index 0000000..28a52c7
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/t2/ui/perspectives/biocatalogue/TestJFrameForLocalLaunch.java
@@ -0,0 +1,68 @@
+package net.sf.taverna.t2.ui.perspectives.biocatalogue;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+
+import javax.swing.JFrame;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class TestJFrameForLocalLaunch extends JFrame implements ComponentListener
+{
+  private static final int DEFAULT_POSITION_X = 225;
+  private static final int DEFAULT_POSITION_Y = 150;
+  
+  private static final int DEFAULT_WIDTH = 800;
+  private static final int DEFAULT_HEIGHT = 500;
+  
+  
+	private TestJFrameForLocalLaunch()
+  {
+    // set window title and icon
+	  this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+	  this.setTitle("Service Catalogue API Demo"/* TODO - windowBaseTitle */);
+    this.setIconImage(ResourceManager.getImageIcon(ResourceManager.FAVICON).getImage());
+    this.addComponentListener(this);
+    
+    // get content pane
+    Container contentPane = this.getContentPane();
+    contentPane.setLayout(new BorderLayout());
+    
+    // put main tabs into the content pane
+    contentPane.add(MainComponentFactory.getSharedInstance(), BorderLayout.CENTER);
+    
+    this.pack();
+  }
+	
+	
+  // *** Callbacks for ComponentListener interface ***
+  
+  public void componentResized(ComponentEvent e) { /* do nothing */ }
+  public void componentMoved(ComponentEvent e) { /* do nothing */ }
+  public void componentHidden(ComponentEvent e) { /* do nothing */ }
+  public void componentShown(ComponentEvent e) {
+    this.setLocation(DEFAULT_POSITION_X, DEFAULT_POSITION_Y);
+  }
+	
+  
+  /**
+   * This is a simple test class for launching BioCatalogue perspective
+   * from outside Taverna. At some point it will be not usable anymore,
+   * when proper integration of BioCatalogue plugin is made.
+   * 
+   * @author Sergejs Aleksejevs
+   */
+  public static void main(String[] args)
+  {
+    TestJFrameForLocalLaunch standaloneFrame = new TestJFrameForLocalLaunch();
+    standaloneFrame.setMinimumSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
+    standaloneFrame.setVisible(true);
+  }
+}