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

[15/51] [abbrv] [partial] incubator-taverna-workbench git commit: taverna-workbench-* -> taverna-*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
new file mode 100644
index 0000000..a223fc8
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
@@ -0,0 +1,220 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+
+/**
+ * 
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public abstract class ExpandableOnDemandLoadedListCellRenderer extends JPanel implements ListCellRenderer
+{
+  protected static final int DESCRIPTION_MAX_LENGTH_COLLAPSED = 90;
+  protected static final int DESCRIPTION_MAX_LENGTH_EXPANDED = 500;
+  
+  protected static final int LINE_LENGTH = 90;
+  
+  
+  protected static final int TOOLTIP_DESCRIPTION_LENGTH = 150;
+  protected static final int TOOLTIP_LINE_LENGTH = 60;
+  
+  // list cells are not repainted by Swing by default - hence to use animated GIFs inside cells,
+  // need to have a special class that takes care of changing the frames as necessary
+  protected JLabel loaderBarAnimationOrange = new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE), JLabel.CENTER);
+  protected JLabel loaderBarAnimationGrey = new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_GREY), JLabel.CENTER);
+  protected JLabel loaderBarAnimationGreyStill = new JLabel (ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_GREY_STILL), JLabel.CENTER);
+  
+  
+  protected JPanel thisPanel;
+  private List<Class<? extends ResourceLink>> resourceClasses;
+  
+  
+  protected JLabel jlExpand;
+  protected static Rectangle expandRect;
+    
+  public ExpandableOnDemandLoadedListCellRenderer()
+  {
+    this.thisPanel = this;
+    
+    resourceClasses = new ArrayList<Class<? extends ResourceLink>>();
+    try {
+      for (Resource.TYPE resourceType : Resource.TYPE.values()) {
+        resourceClasses.add(resourceType.getXmlBeansGeneratedClass());
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+      
+  }
+  
+  
+  public static Rectangle getExpandRect() {
+    return (expandRect == null ? new Rectangle() : expandRect);
+  }
+  
+  
+  public Component getListCellRendererComponent(JList list, Object itemToRender, int itemIndex, boolean isSelected, boolean cellHasFocus)
+  {
+    // the same instance of the cell renderer is used for all cells, so
+    // need to remove everything from the current panel to ensure clean
+    // painting of the current cell
+    this.removeAll();
+    
+    // GET THE DATA
+    
+    // LoadingResource is a placeholder for the detailed data on the resource --
+    // it is being quickly fetched from the API and contanins just the name and the URL
+    // of the actual resource;
+    // 
+    // these entries will be placed into the list when the initial part of the search
+    // is complete, further details will be loaded asynchronously and inserted into
+    // the same area
+    if (itemToRender instanceof LoadingResource) {
+      prepareInitiallyLoadingEntry(itemToRender);
+    }
+    
+    // real data about some resource: details, but in the collapsed form
+    else if (isInstanceOfResourceType(itemToRender)) {
+      prepareLoadedEntry(itemToRender, isSelected);
+    }
+       
+    // error case - unknown resource...
+    else {
+      prepareUnknownResourceTypeEntry();
+    }
+    
+    
+    // MAKE SURE CELL SELECTION WORKS AS DESIRED
+    if (shouldBeHidden(itemToRender)) {
+        this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+                BorderFactory.createLineBorder(Color.DARK_GRAY)));
+        setBackground(list.getBackground());
+        setForeground(list.getBackground());
+    }
+    else if (isSelected) {
+      this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+                                                        BorderFactory.createLineBorder(Color.DARK_GRAY)));
+        setBackground(Color.decode("#BAE8FF"));         // very light blue colour
+        setForeground(list.getSelectionForeground());
+    } else {
+        this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+                                                          BorderFactory.createLineBorder(Color.DARK_GRAY)));
+        setBackground(Color.WHITE);
+        setForeground(list.getForeground());
+    }
+    
+    this.revalidate();
+    
+    if (expandRect == null && jlExpand != null) {
+      SwingUtilities.invokeLater(new Runnable() {
+        public void run() {
+          expandRect = jlExpand.getBounds();
+          expandRect.x -= Math.abs(thisPanel.getBounds().x);
+        }
+      });
+    }
+    
+    return (this);
+  }
+  
+  
+  /**
+   * 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 abstract GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender);
+  
+  
+  /**
+   * 
+   * @param itemToRender
+ * @param isSelected 
+   * @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 abstract GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean isSelected);
+  
+  
+  private void prepareUnknownResourceTypeEntry()
+  {
+    this.setLayout(new GridBagLayout());
+    GridBagConstraints c = new GridBagConstraints();
+    c.anchor = GridBagConstraints.NORTHWEST;
+    c.fill = GridBagConstraints.HORIZONTAL;
+    
+    c.gridx = 0;
+    c.gridy = 0;
+    c.weightx = 0;
+    c.insets = new Insets(8, 6, 6, 3);
+    this.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.UNKNOWN_RESOURCE_TYPE_ICON)), c);
+    
+    c.gridx++;
+    c.weightx = 1.0;
+    c.insets = new Insets(8, 3, 6, 3);
+    this.add(new JLabel("<html><font color=\"#FF0000\">ERROR: This item shoulnd't have been here...</font></html>"), c);
+    
+    c.gridx = 1;
+    c.gridy++;
+    c.gridheight = 1;
+    c.weightx = 1.0;
+    c.weighty = 0;
+    c.insets = new Insets(3, 3, 3, 3);
+    this.add(new JLabel(" "), c);
+    
+    c.gridy++;
+    c.insets = new Insets(3, 3, 8, 3);
+    this.add(new JLabel(" "), c);
+  }
+  
+  
+  private boolean isInstanceOfResourceType(Object itemToRender)
+  {
+    for (Class<? extends ResourceLink> resourceClass : resourceClasses) {
+      if (resourceClass.isInstance(itemToRender)) {
+        return (true);
+      }
+    }
+    
+    return (false);
+  }
+  
+  protected TYPE determineResourceType(Object itemToRender) {
+    if (itemToRender instanceof ResourceLink) {
+      return (Resource.getResourceTypeFromResourceURL(((ResourceLink)itemToRender).getHref()));
+    }
+    else {
+      return (null);
+    }
+  }
+  
+  abstract boolean shouldBeHidden(Object itemToRender);
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
new file mode 100644
index 0000000..64bdfbf
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
@@ -0,0 +1,248 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+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.JLabel;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestParameter;
+import org.biocatalogue.x2009.xml.rest.RestRepresentation;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.RestMethod.Ancestors;
+
+
+/**
+ * 
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class RESTMethodListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+  private JLabel jlTypeIcon = new JLabel();
+  private JLabel jlItemStatus = new JLabel();
+  private JLabel jlItemTitle = new JLabel("X");
+  private JLabel jlPartOf = new JLabel("X");
+  private ReadOnlyTextArea jtDescription = new ReadOnlyTextArea(5, 80);
+  private JLabel jlMethodType = new JLabel("X");
+  private JLabel jlUrlTemplate = new JLabel("X");
+  private JLabel jlMethodParameters = new JLabel("X");
+  private JLabel jlInputRepresentations = new JLabel("X");
+  private JLabel jlOutputRepresentations = new JLabel("X");
+  
+  private GridBagConstraints c;
+  
+  private static Resource.TYPE resourceType = Resource.TYPE.RESTMethod;
+
+  
+  
+  public RESTMethodListCellRenderer() {
+	   jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+	    jtDescription.setOpaque(false);
+	    jtDescription.setLineWrap(true);
+	    jtDescription.setWrapStyleWord(true);
+  }
+  
+  
+  
+  /**
+   * 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)
+  {
+    LoadingResource resource = (LoadingResource)itemToRender;
+    
+    jlTypeIcon.setIcon(resourceType.getIcon());
+    jlItemStatus.setIcon(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+    
+    jlItemTitle.setText("<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(resource)) + "<font color=\"gray\"><i>- fetching more information</i></font></html>");
+    
+    jlPartOf.setText("");
+    jtDescription.setText(" ");
+    jlMethodType.setText(" ");
+    jlUrlTemplate.setText(" ");
+    jlMethodParameters.setText(" ");
+    jlInputRepresentations.setText(" ");
+    jlOutputRepresentations.setText(" ");
+    
+    return (arrangeLayout());
+  }
+  
+  
+  /**
+   * 
+   * @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)
+  {
+    RestMethod restMethod = (RestMethod)itemToRender;;
+    
+    Ancestors ancestors = restMethod.getAncestors();
+    Service service = ancestors.getService();
+    String title = "<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(restMethod));
+
+    if (restMethod.isSetArchived() || service.isSetArchived()) {
+    	jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+    	title = title + "<i> - this operation is archived and probably cannot be used</i></html>";
+    }
+    else {
+    	jlTypeIcon.setIcon(resourceType.getIcon());
+    	title = title + "</html>";
+    }
+    
+    // service status
+    jlItemStatus.setIcon(ServiceMonitoringStatusInterpreter.getStatusIcon(service, false));
+    jlItemTitle.setText(title);
+     
+    jlPartOf.setText("<html><b>Part of: </b>" + restMethod.getAncestors().getRestService().getResourceName() + "</html>");
+    
+    String strDescription = (restMethod.getDescription() == null || restMethod.getDescription().length() == 0 ?
+                             "No description" :
+                            	 Util.stripAllHTML(restMethod.getDescription()));
+    jtDescription.setText(strDescription);
+    
+    jlMethodType.setText("<html><b>HTTP Method: </b>" + StringEscapeUtils.escapeHtml(restMethod.getHttpMethodType().toString()) + "</html>");
+    jlUrlTemplate.setText("<html><b>URL Template: </b>" + StringEscapeUtils.escapeHtml(restMethod.getUrlTemplate()) + "</html>");
+    
+    List<String> names = new ArrayList<String>();
+    for (RestParameter restParameter : restMethod.getInputs().getParameters().getRestParameterList()) {
+      names.add(restParameter.getName() + (restParameter.getIsOptional() ? " (optional)" : ""));
+    }
+    
+    String methodParameters = "<b>" + names.size() + " " + Util.pluraliseNoun("Parameter", names.size()) + "</b>";
+    if(names.size() > 0) {
+      methodParameters += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+    }
+    methodParameters = "<html>" + methodParameters + "</html>";
+    jlMethodParameters.setText(methodParameters);
+    
+       names.clear();
+      for (RestRepresentation restRepresentation : restMethod.getInputs().getRepresentations().getRestRepresentationList()) {
+        names.add(restRepresentation.getContentType());
+      }
+      
+      String inputRepresentations = "<b>" + names.size() + " " + Util.pluraliseNoun("Input representation", names.size()) + "</b>";
+      if(names.size() > 0) {
+        inputRepresentations += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+      }
+      inputRepresentations = "<html>" + inputRepresentations + "</html>";
+      
+      jlInputRepresentations.setText(inputRepresentations);
+
+      // output representations
+      names.clear();
+      for (RestRepresentation restRepresentation : restMethod.getOutputs().getRepresentations().getRestRepresentationList()) {
+        names.add(restRepresentation.getContentType());
+      }
+      
+      String outputRepresentations = "<b>" + names.size() + " " + Util.pluraliseNoun("Output representation", names.size()) + "</b>";
+      if(names.size() > 0) {
+        outputRepresentations += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+      }
+      outputRepresentations = "<html>" + outputRepresentations + "</html>";
+      
+      jlOutputRepresentations.setText(outputRepresentations);
+    
+    return (arrangeLayout());
+  }
+  
+  
+  /**
+   * @return Final state of the {@link GridBagConstraints} instance
+   *         that was used to lay out components in the panel.
+   */
+  private GridBagConstraints arrangeLayout()
+  {
+    // 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(8, 6, 6, 3);
+    this.add(jlTypeIcon, c);
+    
+    c.gridx++;
+    c.weightx = 1.0;
+    c.insets = new Insets(8, 3, 6, 3);
+    this.add(jlItemTitle, c);
+    
+    c.gridx++;
+    c.gridheight = 8;
+    c.weightx = 0;
+    c.weighty = 1.0;
+    this.add(jlItemStatus, c);
+    
+    c.gridx = 1;
+    c.gridy++;
+    c.gridheight = 1;
+    c.weightx = 1.0;
+    c.weighty = 0;
+    this.add(jlPartOf, c);
+    
+    c.fill = GridBagConstraints.NONE;
+    c.gridy++;
+    this.add(jtDescription, c);
+    
+    c.fill = GridBagConstraints.HORIZONTAL;
+    c.gridy++;
+    this.add(jlMethodType, c);
+    
+    c.gridy++;
+    this.add(jlUrlTemplate, c);
+    
+    c.gridy++;
+    this.add(jlMethodParameters, c);
+    
+    c.gridy++;
+    this.add(jlInputRepresentations, c);
+    
+    c.gridy++;
+    this.add(jlOutputRepresentations, c);
+    return (c);
+  }
+  
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+	if (!(itemToRender instanceof RestMethod)) {
+		return false;
+	}
+    RestMethod restMethod = (RestMethod)itemToRender;;
+    
+    Ancestors ancestors = restMethod.getAncestors();
+    Service service = ancestors.getService();
+    String title = Resource.getDisplayNameForResource(restMethod);
+
+    if (restMethod.isSetArchived() || service.isSetArchived()) {
+    	return true;
+    }
+    else {
+    	return false;
+    }
+
+}
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
new file mode 100644
index 0000000..77939c7
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
@@ -0,0 +1,257 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+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 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.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+import org.biocatalogue.x2009.xml.rest.SoapInput;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapOutput;
+import org.biocatalogue.x2009.xml.rest.SoapService;
+import org.biocatalogue.x2009.xml.rest.Service.ServiceTechnologyTypes;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType.Enum;
+import org.biocatalogue.x2009.xml.rest.SoapOperation.Ancestors;
+
+
+/**
+ * 
+ * 
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SOAPOperationListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+	
+	private JLabel jlTypeIcon = new JLabel();
+  private JLabel jlItemStatus = new JLabel();
+  private JLabel jlItemTitle = new JLabel("X");
+  private JLabel jlPartOf = new JLabel("X");
+  private JLabel jlWsdlLocation = new JLabel("X");
+  private ReadOnlyTextArea jtDescription = new ReadOnlyTextArea(5,80);
+  private JLabel jlSoapInputs = new JLabel("X");
+  private JLabel jlSoapOutputs = new JLabel("X");
+  
+  private GridBagConstraints c;
+  
+  private static Resource.TYPE resourceType = Resource.TYPE.SOAPOperation;
+  
+  
+  public SOAPOperationListCellRenderer() {
+    jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+    jtDescription.setOpaque(false);
+    jtDescription.setLineWrap(true);
+    jtDescription.setWrapStyleWord(true);
+  }
+  
+  
+  /**
+   * 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)
+  {
+    LoadingResource resource = (LoadingResource)itemToRender;
+    
+    jlTypeIcon.setIcon(resourceType.getIcon());
+    jlItemStatus.setIcon(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+       
+    jlItemTitle.setText("<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(resource)) + "<font color=\"gray\"><i>- fetching more information</i></font></html>");
+   
+    jlPartOf.setText(" ");
+    jlWsdlLocation.setText(" ");
+    jtDescription.setText("");
+    jlSoapInputs.setText(" ");
+    jlSoapOutputs.setText(" ");
+   
+    return (arrangeLayout());
+  }
+  
+  
+  /**
+   * 
+   * @param itemToRender
+ * @param selected 
+   * @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)
+  {
+    SoapOperation soapOp = (SoapOperation)itemToRender;
+    
+    Ancestors ancestors = soapOp.getAncestors();
+    SoapService soapService = ancestors.getSoapService();
+    Service service = ancestors.getService();
+    String title = StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(soapOp));
+    
+    if (soapOp.isSetArchived() || service.isSetArchived()) {
+    	jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+    	title = "<html>" + title + "<i> - this operation is archived and probably cannot be used</i></html>";
+    } else if (isSoapLab(service)) {
+       	jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+    	title = "<html>" + title + "<i> - this operation can only be used as part of a SoapLab service</i></html>";
+    }
+    else {
+    	jlTypeIcon.setIcon(resourceType.getIcon());
+    	title = "<html>" + title + "</html>";
+   }
+    
+    // service status
+    jlItemStatus.setIcon(ServiceMonitoringStatusInterpreter.getStatusIcon(service, false));
+    jlItemTitle.setText(title);
+    
+    jlPartOf.setText("<html><b>Part of: </b>" + StringEscapeUtils.escapeHtml(soapOp.getAncestors().getSoapService().getResourceName()) + "</html>");
+    
+    jlWsdlLocation.setText("<html><b>WSDL location: </b>" + soapService.getWsdlLocation() + "</html>");
+    
+        String strDescription = (soapOp.getDescription() == null || soapOp.getDescription().length() == 0 ?
+                             "No description" :
+                            	 Util.stripAllHTML(soapOp.getDescription()));
+    
+            jtDescription.setText(strDescription);
+    
+    // add SOAP inputs
+    List<String> names = new ArrayList<String>();
+    for (SoapInput soapInput : soapOp.getInputs().getSoapInputList()) {
+      names.add(soapInput.getName());
+    }
+    
+    String soapInputs = "<b>" + names.size() + " " + Util.pluraliseNoun("Input", names.size()) + "</b>";
+    if(names.size() > 0) {
+      soapInputs += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+    }
+    soapInputs = "<html>" + soapInputs + "</html>";
+    jlSoapInputs.setText(soapInputs);
+    
+    c.gridy++;
+    this.add(jlSoapInputs, c);
+    
+    
+    // add SOAP outputs
+    names.clear();
+    for (SoapOutput soapOutput : soapOp.getOutputs().getSoapOutputList()) {
+      names.add(soapOutput.getName());
+    }
+    
+    String soapOutputs = "<b>" + names.size() + " " + Util.pluraliseNoun("Output", names.size()) + "</b>";
+    if(names.size() > 0) {
+      soapOutputs += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+    }
+    soapOutputs = "<html>" + soapOutputs + "</html>";
+    jlSoapOutputs.setText(soapOutputs);
+   
+    return (arrangeLayout());
+  }
+
+
+private boolean isSoapLab(Service service) {
+	boolean result = false;
+	ServiceTechnologyTypes serviceTechnologyTypes = service.getServiceTechnologyTypes();
+	if (serviceTechnologyTypes == null) {
+		return result;
+	}
+	List<Enum> typeList = serviceTechnologyTypes.getTypeList();
+	if (typeList == null) {
+		return result;
+	}
+	result = typeList.contains(ServiceTechnologyType.SOAPLAB);
+	return result;
+}
+  
+  
+  /**
+   * @return Final state of the {@link GridBagConstraints} instance
+   *         that was used to lay out components in the panel.
+   */
+  private GridBagConstraints arrangeLayout()
+  {
+	   // 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(8, 6, 6, 3);
+	    this.add(jlTypeIcon, c);
+	    
+	    c.gridx++;
+	    c.weightx = 1.0;
+	    c.insets = new Insets(8, 3, 6, 3);
+	    this.add(jlItemTitle, c);
+	    
+	    c.gridx++;
+	    c.gridheight = 7;
+	    c.weightx = 0;
+	    c.weighty = 1.0;
+	    this.add(jlItemStatus, c);
+	    
+	    c.gridx = 1;
+	    c.gridy++;
+	    c.gridheight = 1;
+	    c.weightx = 0;
+	    c.weighty = 0;
+	    this.add(jlPartOf, c);
+	    
+	    c.gridy++;
+	    this.add(jlWsdlLocation, c);
+	    
+	    c.fill = GridBagConstraints.NONE;
+	    c.gridy++;
+	    this.add(jtDescription, c);
+	    
+	    c.fill = GridBagConstraints.HORIZONTAL;
+	    c.gridy++;
+	    this.add(jlSoapInputs, c);
+	    
+	    c.fill = GridBagConstraints.HORIZONTAL;
+	    c.gridy++;
+	    this.add(jlSoapOutputs, c);
+	    
+	    return (c);
+  }
+  
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+	if (!(itemToRender instanceof SoapOperation)) {
+		return false;
+	}
+	SoapOperation soapOp = (SoapOperation) itemToRender;
+	   Ancestors ancestors = soapOp.getAncestors();
+	    Service service = ancestors.getService();
+	    if (soapOp.isSetArchived() || service.isSetArchived()) {
+	    	return true;
+	    } else if (isSoapLab(service)) {
+	       	return true;
+	    }
+	    else {
+	    	return false;
+	   }
+
+}
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
new file mode 100644
index 0000000..c88de40
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
@@ -0,0 +1,870 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+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.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.ui.JWaitDialog;
+import net.sf.taverna.t2.lang.ui.ModelMap;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceHealthChecker;
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.ModelMapConstants;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+
+/**
+ * This class is responsible for producing search results listing panel. It only
+ * shows a single listing for a specified type. Multiple types are handled by
+ * having different tabs in {@link SearchResultsMainPanel} with instances of
+ * this class in each.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SearchResultsListingPanel extends JPanel implements MouseListener,
+		SearchResultsRenderer, MouseMotionListener {
+	public static final int SEARCH_STATUS_TOOLTIP_LINE_LENGTH = 65;
+
+	private static final Logger logger = Logger.getLogger(SearchResultsListingPanel.class);
+	private final SearchResultsMainPanel parentMainSearchResultsPanel;
+
+	// currently displayed search results
+	SearchInstance searchInstance;
+
+	// main UI components
+	private SearchResultsListingPanel thisPanel;
+	private DefaultListModel resultsListingModel;
+	private JList jlResultsListing;
+	private JScrollPane spResultsListing;
+
+	// contextual menu
+	private JPopupMenu contextualMenu;
+	private Action addToServicePanelAction;
+	private Action addToWorkflowDiagramAction;
+	private Action openInBioCatalogueAction;
+	private Action doHealthCheckAction;
+	private Action addAllOperationsToServicePanelAction;
+
+	// search status and actions on selected items in the list
+	private JToolBar tbSelectedItemActions;
+	protected JPanel jpSearchStatus;
+	private JLabel jlSearchStatus;
+
+	// this is used for previewing items from the result listing through
+	// contextual menu -
+	// value will be updated by mouse event accordingly
+	private ResourceLink potentialObjectToPreview;
+	private final TYPE typeToPreview;
+
+	// Design perspective - some actions require switching to it
+	PerspectiveSPI designPerspective;
+
+	private ListCellRenderer listingCellRenderer;
+	
+	/**
+	 * @param typeToPreview
+	 *            Resource type that will be previewed in this panel.
+	 * @param parentMainSearchResultsPanel
+	 *            Reference to a "parent" of this panel - this is needed to
+	 *            notify the main results panel with the
+	 */
+	public SearchResultsListingPanel(TYPE typeToPreview,
+			SearchResultsMainPanel parentMainSearchResultsPanel) {
+		this.thisPanel = this;
+
+		this.typeToPreview = typeToPreview;
+		listingCellRenderer = this.typeToPreview
+		.getResultListingCellRenderer();
+		this.parentMainSearchResultsPanel = parentMainSearchResultsPanel;
+		MainComponentFactory
+				.getSharedInstance();
+
+		initialiseUI();
+
+		this.setPreferredSize(new Dimension(800, 400));
+	}
+
+	private void initialiseUI() {
+
+		this.addToServicePanelAction = new AbstractAction(
+				"Add to Service Panel",
+				ResourceManager
+						.getImageIcon(ResourceManager.ADD_PROCESSOR_AS_FAVOURITE_ICON)) {
+			// Tooltip
+			{
+				this.putValue(SHORT_DESCRIPTION, "Add selected "
+						+ typeToPreview.getTypeName()
+						+ " to the Service Panel");
+			}
+
+			public void actionPerformed(ActionEvent e) {
+				final JWaitDialog jwd = new JWaitDialog(
+						MainComponent.dummyOwnerJFrame,
+						"Service Catalogue Plugin - Adding "
+								+ typeToPreview.getTypeName(),
+						"<html><center>Please wait for selected "
+								+ typeToPreview.getTypeName()
+								+ " details to be fetched from the Service Catalogue<br>"
+								+ "and to be added into the Service Panel.</center></html>");
+
+				new Thread("Adding " + typeToPreview.getTypeName()
+						+ " into Service Panel") {
+					public void run() {
+						// if it is the expanded that we are looking at, need to extract
+						// the 'associated' object
+						ResourceLink processorResourceToAdd = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+								.getAssociatedObj()
+								: potentialObjectToPreview);
+
+						JComponent insertionOutcome = Integration
+								.insertProcesorIntoServicePanel(processorResourceToAdd);
+						jwd.waitFinished(insertionOutcome);
+						
+						// Switch to Design Perspective
+						switchToDesignPerspective();
+					}
+				}.start();
+
+				// NB! The modal dialog window needs to be made visible after
+				// the background
+				// process (i.e. adding a processor) has already been started!
+				jwd.setVisible(true);
+			}
+		};
+
+		// For a parent Web service, action to add all operations to the Service Panel.
+		// Works for SOAP services at the moment.
+		this.addAllOperationsToServicePanelAction = new AbstractAction(
+				"Add all operations to Service Panel",
+				ResourceManager
+						.getImageIcon(ResourceManager.ADD_ALL_SERVICES_AS_FAVOURITE_ICON)) {
+			// Tooltip
+			{
+				this.putValue(SHORT_DESCRIPTION, "Add all associated services to the Service Panel");
+			}
+
+			public void actionPerformed(ActionEvent e) {
+				final JWaitDialog jwd = new JWaitDialog(
+						MainComponent.dummyOwnerJFrame,
+						"Service Catalogue Plugin - Adding "
+								+ typeToPreview.getTypeName(),
+						"<html><center>Please wait for selected "
+								+ typeToPreview.getTypeName()
+								+ " details to be fetched from the Service Catalogue<br>"
+								+ "and to be added into the Service Panel.</center></html>");
+
+				new Thread("Adding all operations of " + typeToPreview.getTypeName()
+						+ " to the Service Panel") {
+					public void run() {
+						// if it is the expanded that we are looking at, need to extract
+						// the 'associated' object
+						ResourceLink resourceLink = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+								.getAssociatedObj()
+								: potentialObjectToPreview);						
+
+						JComponent insertionOutcome = Integration
+								.insertAllOperationsIntoServicePanel(resourceLink);
+						jwd.waitFinished(insertionOutcome);
+						
+						// Switch to Design Perspective
+						switchToDesignPerspective();
+					}
+				}.start();
+
+				// NB! The modal dialog window needs to be made visible after
+				// the background
+				// process (i.e. adding a processor) has already been started!
+				jwd.setVisible(true);
+			}
+		};
+
+		this.addToWorkflowDiagramAction = new AbstractAction(
+				"Add to workflow",
+				ResourceManager
+						.getImageIcon(ResourceManager.ADD_PROCESSOR_TO_WORKFLOW_ICON)) {
+			// Tooltip
+			{
+				this.putValue(SHORT_DESCRIPTION, "<html>Insert selected "
+						+ typeToPreview.getTypeName()
+						+ " into the current workflow</html>");
+			}
+
+			public void actionPerformed(ActionEvent e) {
+				final JWaitDialog jwd = new JWaitDialog(
+						MainComponent.dummyOwnerJFrame,
+						"Service Catalogue Plugin - Adding "
+								+ typeToPreview.getTypeName(),
+						"<html><center>Please wait for selected "
+								+ typeToPreview.getTypeName()
+								+ " details to be fetched from the Service Catalogue<br>"
+								+ "and to be added into the current workflow.</center></html>");
+
+				new Thread("Adding " + typeToPreview.getTypeName()
+						+ " into workflow") {
+					public void run() {
+						// if it is the expanded that we are looking at, need to extract
+						// the 'associated' object
+						ResourceLink processorResourceToAdd = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+								.getAssociatedObj()
+								: potentialObjectToPreview);
+
+						JComponent insertionOutcome = Integration
+								.insertProcessorIntoCurrentWorkflow(processorResourceToAdd);
+						jwd.waitFinished(insertionOutcome);
+
+						// Switch to Design Perspective
+						switchToDesignPerspective();
+					}
+				}.start();
+
+				// NB! The modal dialog window needs to be made visible after
+				// the background
+				// process (i.e. adding a processor) has already been started!
+				jwd.setVisible(true);
+			}
+		};
+
+		this.openInBioCatalogueAction = new AbstractAction(
+				"Open in the Service Catalogue",
+				ResourceManager
+						.getImageIcon(ResourceManager.OPEN_IN_BIOCATALOGUE_ICON)) {
+			// Tooltip
+			{
+				this.putValue(SHORT_DESCRIPTION, "<html>View selected "
+						+ typeToPreview.getTypeName()
+						+ " on the Service Catalogue Web site.<br>"
+						+ "This will open your standard Web browser.</html>");
+			}
+
+			public void actionPerformed(ActionEvent e) {
+				String hrefString = potentialObjectToPreview.getHref();
+				   try {
+						Desktop.getDesktop().browse(new URI(hrefString));
+					    }
+					    catch (Exception ex) {
+					      logger.error("Failed while trying to open the URL in a standard browser; URL was: " +
+					           hrefString + "\nException was: " + ex + "\n" + ex.getStackTrace());
+					    };
+			}
+		};
+
+		this.doHealthCheckAction = new AbstractAction(
+				"Check monitoring status",
+				ResourceManager
+						.getImageIcon(ResourceManager.EXECUTE_HEALTH_CHECK_ICON)) {
+			// Tooltip
+			{
+				this
+						.putValue(
+								SHORT_DESCRIPTION,
+								"<html>Fetch the latest monitoring data for selected "
+										+ typeToPreview.getTypeName()
+										+ ".<br>"
+										+ "Data will be obtained from the Service Catalogue and displayed in a popup window.</html>");
+			}
+
+			public void actionPerformed(ActionEvent e) {
+				// if it is the expanded that we are looking at, need to extract
+				// the 'associated' object
+				ResourceLink resourceForHealthCheck = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+						.getAssociatedObj()
+						: potentialObjectToPreview);
+
+				ServiceHealthChecker.checkResource(resourceForHealthCheck);
+			}
+		};
+
+		tbSelectedItemActions = new JToolBar(JToolBar.HORIZONTAL);
+		tbSelectedItemActions.setBorderPainted(true);
+		tbSelectedItemActions.setBorder(BorderFactory.createEmptyBorder(5, 5,
+				5, 3));
+		tbSelectedItemActions.setFloatable(false);
+		if (typeToPreview.isSuitableForAddingToServicePanel()) {
+			tbSelectedItemActions.add(addToServicePanelAction);
+		}
+		if (typeToPreview.isSuitableForAddingAllToServicePanel()) {
+			tbSelectedItemActions.add(addAllOperationsToServicePanelAction);
+		}
+		if (typeToPreview.isSuitableForAddingToWorkflowDiagram()) {
+			tbSelectedItemActions.add(addToWorkflowDiagramAction);
+		}
+		if (typeToPreview.isSuitableForHealthCheck()) {
+			tbSelectedItemActions.add(doHealthCheckAction);
+		}
+		tbSelectedItemActions.add(openInBioCatalogueAction);
+
+		// *** Prepare search results status panel ***
+
+		GridBagConstraints c = new GridBagConstraints();
+		jpSearchStatus = new JPanel(new GridBagLayout());
+		c.anchor = GridBagConstraints.WEST;
+		c.weightx = 0;
+		jpSearchStatus.add(tbSelectedItemActions, c);
+
+		jlSearchStatus = new JLabel();
+		jlSearchStatus.setIconTextGap(20);
+		c.weightx = 1.0;
+		c.insets = new Insets(0, 20, 0, 0);
+		jpSearchStatus.add(jlSearchStatus, c);
+
+		if (parentMainSearchResultsPanel.getFilterTreePaneFor(typeToPreview) != null) {
+			Dimension preferredSize = new Dimension(200,
+					parentMainSearchResultsPanel.getFilterTreePaneFor(
+							typeToPreview).getTreeToolbarPreferredSize().height);
+
+			// HACK: due to concurrency issues, sometimes this doesn't work
+			// correctly -
+			// to rectify the problem using the hard-coded value that was
+			// correct at
+			// the time of coding...
+			if (preferredSize.height < 30) {
+				preferredSize.height = 33;
+			}
+
+			jpSearchStatus.setPreferredSize(preferredSize);
+		}
+
+		// *** Create list to hold search results and wrap it into a scroll pane
+		// ***
+		resultsListingModel = new DefaultListModel();
+		jlResultsListing = new JList(resultsListingModel);
+		jlResultsListing.setDoubleBuffered(true);
+		jlResultsListing.setCellRenderer(listingCellRenderer);
+		jlResultsListing.addMouseListener(this);
+		jlResultsListing.addMouseMotionListener(this);
+		jlResultsListing.setBackground(thisPanel.getBackground());
+
+		jlResultsListing.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+		jlResultsListing.addListSelectionListener(new ListSelectionListener() {
+			public void valueChanged(ListSelectionEvent e) {
+				if (!e.getValueIsAdjusting()) {
+					// update value to be used in contextual menu click handler
+					// to act on the just-selected entry
+					potentialObjectToPreview = getResourceSelectedInJList();
+
+					if (potentialObjectToPreview != null) {
+
+						// only enable actions in the menu if the list entry
+						// that is being
+						// clicked on is beyond the initial 'loading' state
+						boolean shown = !isListEntryOnlyWithInitialDetails(potentialObjectToPreview);
+						boolean shownAndNotArchived = shown && !isArchived(potentialObjectToPreview);
+						addToServicePanelAction
+								.setEnabled(shownAndNotArchived);
+						addAllOperationsToServicePanelAction
+						.setEnabled(shownAndNotArchived && !(potentialObjectToPreview instanceof RestMethod));
+						addToWorkflowDiagramAction
+								.setEnabled(shownAndNotArchived);
+						openInBioCatalogueAction
+								.setEnabled(shown);
+						doHealthCheckAction
+								.setEnabled(shown);
+					    
+						return;
+					}
+				}
+
+				// disable actions if nothing is selected in the list or if
+				// selection is still "adjusting"
+				addToServicePanelAction.setEnabled(false);
+				addAllOperationsToServicePanelAction.setEnabled(false);
+				addToWorkflowDiagramAction.setEnabled(false);
+				openInBioCatalogueAction.setEnabled(false);
+				doHealthCheckAction.setEnabled(false);
+			}
+		});
+
+		spResultsListing = new JScrollPane(jlResultsListing);
+		spResultsListing.getVerticalScrollBar().addAdjustmentListener(
+				new AdjustmentListener() {
+					public void adjustmentValueChanged(AdjustmentEvent e) {
+						if (!e.getValueIsAdjusting()) {
+							// load missing details on adjusting the scroll bar
+							//
+							// only start loading more results in case if the
+							// value is "not adjusting" -
+							// this means that the mouse has been released and
+							// is not dragging the scroll bar
+							// any more, so effectively the user has stopped
+							// scrolling
+							checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+						}
+					}
+				});
+
+		// tie components to the class panel itself
+		this.resetSearchResultsListing(true);
+
+		// *** Create CONTEXTUAL MENU ***
+
+		contextualMenu = new JPopupMenu();
+		if (typeToPreview.isSuitableForAddingToServicePanel()) {
+			contextualMenu.add(addToServicePanelAction);
+			contextualMenu.add(addAllOperationsToServicePanelAction);
+		}
+		if (typeToPreview.isSuitableForAddingToWorkflowDiagram()) {
+			contextualMenu.add(addToWorkflowDiagramAction);
+		}
+		if (typeToPreview.isSuitableForHealthCheck()) {
+			contextualMenu.add(doHealthCheckAction);
+		}
+		contextualMenu.add(openInBioCatalogueAction);
+	}
+
+	/**
+	 * Allows to set the search status by supplying the message to display.
+	 */
+	protected void setSearchStatusText(final String statusString,
+			final boolean spinnerActive) {
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+				jlSearchStatus
+						.setIcon(spinnerActive ? ResourceManager
+								.getImageIcon(ResourceManager.BAR_LOADER_ORANGE)
+								: null);
+
+				jlSearchStatus.setText(statusString);
+				jlSearchStatus.setToolTipText("<html>"
+						+ Util.ensureLineLengthWithinString(statusString,
+								SEARCH_STATUS_TOOLTIP_LINE_LENGTH, false)
+						+ "</html>");
+			}
+		});
+	}
+
+	/**
+	 * This helper method is used to initialise this panel. Also invoked when
+	 * search results need to be cleared.
+	 * 
+	 * @param showSuggestion
+	 *            <code>true</code> should be used on first load of the panel -
+	 *            in that case a suggestion would be displayed to perform a
+	 *            search, tag search or start directly with filtering;<br/>
+	 *            <code>false</code> to be used when resetting the panel after
+	 *            perfoming the search, but not finding any results.
+	 */
+	public void resetSearchResultsListing(boolean showSuggestion) {
+		setSearchStatusText("No searches were made yet", false);
+
+		String labelText = "<html><center>"
+				+ (showSuggestion ? "You can find "
+						+ this.typeToPreview.getCollectionName()
+						+ " by typing a search query."
+						+ (this.typeToPreview.isSuitableForFiltering() ? "<br><br>Alternatively, you can select some filters from the tree on the left."
+								: "")
+						: "There are no "
+								+ this.typeToPreview.getCollectionName()
+								+ " that match your search criteria<br><br>"
+								+ "Please try making the search query shorter or selecting fewer filters")
+				+ "</center></html>";
+
+		JLabel jlMainLabel = new JLabel(labelText, JLabel.CENTER);
+		jlMainLabel.setFont(jlMainLabel.getFont().deriveFont(Font.PLAIN, 16));
+		jlMainLabel.setBorder(BorderFactory.createEtchedBorder());
+
+		this.removeAll();
+		this.setLayout(new BorderLayout(0, 0));
+		this.add(jpSearchStatus, BorderLayout.NORTH);
+		this.add(jlMainLabel, BorderLayout.CENTER);
+		this.validate();
+
+		// disable the toolbar actions
+		this.addToServicePanelAction.setEnabled(false);
+		this.addToWorkflowDiagramAction.setEnabled(false);
+		this.openInBioCatalogueAction.setEnabled(false);
+		this.doHealthCheckAction.setEnabled(false);
+		this.addAllOperationsToServicePanelAction.setEnabled(false);
+	}
+
+	/**
+	 * Statistics will be rendered along with the collection of found items.
+	 * 
+	 * @param searchInstance
+	 *            SearchInstance containing search results to render.
+	 */
+	public void renderResults(SearchInstance searchInstance) {
+		// make the current search instance available globally within this class
+		this.searchInstance = searchInstance;
+
+		// stop spinner icon on the tab that is populated and add number of
+		// results
+		parentMainSearchResultsPanel.setDefaultIconForTab(typeToPreview);
+		parentMainSearchResultsPanel.setDefaultTitleForTabWithSuffix(
+				typeToPreview, " ("
+						+ searchInstance.getSearchResults()
+								.getTotalMatchingItemCount() + ")");
+
+		// if nothing was found - display notification and finish result
+		// processing
+		if (searchInstance.getSearchResults().getTotalMatchingItemCount() == 0) {
+			resetSearchResultsListing(false);
+
+			// must happen after resetting the listing, as it replaces the
+			// default status text
+			setSearchStatusText("No results found for "
+					+ searchInstance.getDescriptionStringForSearchStatus(),
+					false);
+			return;
+		}
+
+		// populate results
+		if (searchInstance.getSearchResults().getTotalMatchingItemCount() > 0) {
+			// populate the list box with users
+
+			List<? extends ResourceLink> foundItems = searchInstance
+					.getSearchResults().getFoundItems();
+			for (ResourceLink item : foundItems) {
+				resultsListingModel.addElement(item);
+			}
+		}
+
+		// update the UI once contents are ready
+		thisPanel.removeAll();
+		thisPanel.setLayout(new BorderLayout(0, 0));
+		thisPanel.add(jpSearchStatus, BorderLayout.NORTH);
+		thisPanel.add(spResultsListing, BorderLayout.CENTER);
+		thisPanel.repaint();
+
+		// automatically start loading details for the first section of result
+		// listing
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+				checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+			}
+		});
+
+		// *** Also update status text ***
+
+		setSearchStatusText("Search results for "
+				+ searchInstance.getDescriptionStringForSearchStatus(), false);
+	}
+
+	/**
+	 * Check if details are fetched for all result entries that are currently
+	 * visible in the JList.
+	 * 
+	 * If some are not yet loaded, identifies the page in the index of
+	 * corresponding resources to fetch details.
+	 * 
+	 * When done, recursively calls itself again to verify that no more entries
+	 * need further details loaded.
+	 */
+	private void checkAllEntriesInTheVisiblePartOfJListAreLoaded() {
+		int firstVisibleIndex = jlResultsListing.getFirstVisibleIndex();
+
+		if (firstVisibleIndex >= 0) {
+			int lastVisibleIndex = jlResultsListing.getLastVisibleIndex();
+
+			final int firstNotFetchedMatchingItemIndex = searchInstance
+					.getSearchResults().getFirstMatchingItemIndexNotYetFetched(
+							firstVisibleIndex, lastVisibleIndex);
+			final int pageToFetchNumber = searchInstance.getSearchResults()
+					.getMatchingItemPageNumberFor(
+							firstNotFetchedMatchingItemIndex);
+
+			// check if found a valid page to load
+			if (pageToFetchNumber != -1) {
+				int numberOfResourcesPerPageForThisResourceType = searchInstance
+						.getSearchResults().getTypeOfResourcesInTheResultSet()
+						.getApiResourceCountPerIndexPage();
+
+				int firstListIndexToLoad = searchInstance.getSearchResults()
+						.getFirstItemIndexOn(pageToFetchNumber); // first
+																	// element
+																	// on the
+																	// page that
+																	// is about
+																	// to be
+																	// loaded
+				int countToLoad = Math.min(
+						numberOfResourcesPerPageForThisResourceType, // if the
+																		// last
+																		// page
+																		// isn't
+																		// full,
+																		// need
+																		// to
+																		// mark
+																		// less
+																		// items
+																		// than
+																		// the
+																		// full
+																		// page
+						resultsListingModel.getSize() - firstListIndexToLoad);
+
+				// mark the next "page" of items in the JList as "loading" -
+				// but also mark them in the SearchResults backing list, so
+				// that next calls to this listener are aware of the previous
+				// items that were marked as "loading"
+				for (int i = firstListIndexToLoad; i < firstListIndexToLoad
+						+ countToLoad; i++) {
+					((LoadingResource) searchInstance.getSearchResults()
+							.getFoundItems().get(i)).setLoading(true);
+				}
+
+				// update the UI to show 'loading' state on relevant entries
+				renderFurtherResults(searchInstance, firstListIndexToLoad,
+						countToLoad);
+
+				// now start loading data for the 'loading' entries
+				final CountDownLatch latch = new CountDownLatch(1);
+				new Thread("Search via the API") {
+					public void run() {
+						try {
+							searchInstance.fetchMoreResults(
+									parentMainSearchResultsPanel, latch,
+									thisPanel, pageToFetchNumber);
+						} catch (Exception e) {
+							logger.error("Error while searching via the Service Catalogue API", e);
+
+						}
+					}
+				}.start();
+
+				// wait for the previous portion of results to load, then fetch
+				// the next portion
+				new Thread(
+						"Fetch more another page of details for search results") {
+					public void run() {
+						try {
+							latch.await();
+							checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+						} catch (InterruptedException e) {
+							logger
+									.error(
+											"Failed to wait for the previous page of results to load to check if "
+													+ "another one needs loading as well. Details in the attache exception.",
+											e);
+						}
+					}
+				}.start();
+
+			}
+		}
+	}
+
+	/**
+	 * Tests whether {@link ResourceLink} object corresponding to an entry in
+	 * the search results list is in the state where only the first (initial)
+	 * fragment of data was loaded (through BioCatalogue LITE JSON API) that
+	 * contains just the title + URL of the resource.
+	 * 
+	 * @param resource
+	 * @return
+	 */
+	private boolean isListEntryOnlyWithInitialDetails(ResourceLink resource) {
+		return (resource instanceof LoadingResource);
+	}
+	
+	private boolean isArchived(ResourceLink resource) {
+		if (listingCellRenderer instanceof ExpandableOnDemandLoadedListCellRenderer) {
+			ExpandableOnDemandLoadedListCellRenderer r = (ExpandableOnDemandLoadedListCellRenderer) listingCellRenderer;
+			return r.shouldBeHidden(resource);
+		}
+		return false;
+	}
+
+
+	// ***** Callbacks for MouseListener *****
+
+	public void mouseClicked(MouseEvent e) {
+	}
+
+	public void mouseEntered(MouseEvent e) { /* NOT IN USE */
+	}
+
+	public void mouseExited(MouseEvent e) { /* NOT IN USE */
+	}
+
+	public void mousePressed(MouseEvent e) {
+		// checked in both mousePressed() & mouseReleased() for cross-platform
+		// operation
+		maybeShowPopupMenu(e);
+	}
+
+	public void mouseReleased(MouseEvent e) {
+		// checked in both mousePressed() & mouseReleased() for cross-platform
+		// operation
+		maybeShowPopupMenu(e);
+	}
+
+	// ***** Callbacks for MouseMotionListener *****
+
+	public void mouseMoved(MouseEvent e) {
+	}
+
+	public void mouseDragged(MouseEvent e) { /* do nothing */
+	}
+
+	/**
+	 * Gets the selected object from the specified list. Used for previewing
+	 * items through double-clicks and contextual menu.
+	 * 
+	 * @return <code>null</code> if no selection in the list,
+	 *         <code>ResourceLink</code> object that is currently selected
+	 *         otherwise.
+	 */
+	private ResourceLink getResourceSelectedInJList() {
+		return (jlResultsListing.getSelectedIndex() == -1 ? null
+				: (ResourceLink) jlResultsListing.getSelectedValue());
+	}
+
+	private void maybeShowPopupMenu(MouseEvent e) {
+		if (e.getSource().equals(jlResultsListing) && e.isPopupTrigger()
+				&& jlResultsListing.locationToIndex(e.getPoint()) != -1) {
+			// select the entry in the list that triggered the event to show
+			// this popup menu
+			jlResultsListing.setSelectedIndex(jlResultsListing
+					.locationToIndex(e.getPoint()));
+
+			// update value to be used in contextual menu click handler to act
+			// on the just-selected entry
+			potentialObjectToPreview = getResourceSelectedInJList();
+
+			// show the contextual menu
+			this.contextualMenu.show(e.getComponent(), e.getX(), e.getY());
+		}
+	}
+
+	// *** Callbacks for SearchResultsRenderer ***
+
+	public void renderInitialResults(final SearchInstance si) {
+		// NB! critical to have UI update done within the invokeLater()
+		// method - this is to prevent UI from 'flashing' and to
+		// avoid concurrency-related errors
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+				// make sure to remove any old results from the list model!
+				resultsListingModel.clear();
+
+				// display the partial search results
+				logger.debug("Started rendering initial search results for "
+						+ si.getResourceTypeToSearchFor().getCollectionName());
+				renderResults(si);
+				logger.debug("Finished rendering initial search results for "
+						+ si.getResourceTypeToSearchFor().getCollectionName());
+			}
+		});
+	}
+
+	public void renderFurtherResults(SearchInstance si, int startIndex,
+			int count) {
+		renderFurtherResults(si, startIndex, count, false);
+	}
+
+	public void renderFurtherResults(final SearchInstance si,
+			final int startIndex, final int count,
+			final boolean disableListDataListeners) {
+		logger.debug("Started rendering further search results for "
+				+ si.getResourceTypeToSearchFor().getCollectionName());
+
+		// NB! very important to remove all listeners here, so that the JList
+		// won't "freeze"
+		// on updating the components
+		ListDataListener[] listeners = null;
+		if (disableListDataListeners) {
+			listeners = resultsListingModel.getListDataListeners();
+			for (ListDataListener listener : listeners) {
+				resultsListingModel.removeListDataListener(listener);
+			}
+		}
+
+		for (int i = startIndex; i < startIndex + count
+				&& i < resultsListingModel.getSize(); i++) {
+			resultsListingModel.set(i, searchInstance.getSearchResults()
+					.getFoundItems().get(i));
+		}
+
+		// reset all listeners in case they were removed
+		if (disableListDataListeners) {
+			for (ListDataListener listener : listeners) {
+				resultsListingModel.addListDataListener(listener);
+			}
+		}
+
+		// NB! critical to have UI update done within the invokeLater()
+		// method - this is to prevent UI from 'flashing' and to
+		// avoid some weird errors
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+				jlResultsListing.validate();
+				jlResultsListing.repaint();
+
+				logger.debug("Finished rendering further search results for "
+						+ si.getResourceTypeToSearchFor().getCollectionName());
+			}
+		});
+	}
+
+	private void switchToDesignPerspective() {
+		if (designPerspective == null) {
+			for (PerspectiveSPI perspective : Workbench.getInstance()
+					.getPerspectives().getPerspectives()) {
+				if (perspective.getText().equalsIgnoreCase("design")) {
+					designPerspective = perspective;
+					break;
+				}
+			}
+		}
+		
+		if (designPerspective != null) {
+			ModelMap.getInstance().setModel(
+					ModelMapConstants.CURRENT_PERSPECTIVE, designPerspective);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
new file mode 100644
index 0000000..a3fca27
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
@@ -0,0 +1,498 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JToggleButton;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.model.search.SearchInstanceTracker;
+import net.sf.taverna.biocatalogue.model.search.SearchOptions;
+import net.sf.taverna.biocatalogue.model.search.ServiceFilteringSettings;
+import net.sf.taverna.biocatalogue.ui.filtertree.FilterTreePane;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+import org.apache.log4j.Logger;
+
+/**
+ * This class represents the main panel that deals with the status
+ * and results of the current search.
+ * 
+ * It has a status label, spinner to depict search in progress,
+ * actual search results split into tabs by their type, a toolbar
+ * with search history, favourite searches settings, favourite filters,
+ * ability to restart last search, etc.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResultsMainPanel extends JPanel implements ActionListener, SearchInstanceTracker
+{
+  private final SearchResultsMainPanel instanceOfSelf;
+  private Logger logger;
+  
+  private LinkedHashMap<TYPE, JComponent> searchResultTabs;
+  private Map<TYPE, SearchResultsListingPanel> searchResultListings;
+  
+  // holds a reference to the instance of the search instances in the current context
+  // that should be active at the moment (will aid early termination of older searches
+  // when new ones are started)
+  private Map<TYPE, SearchInstance> currentSearchInstances;
+  
+  // holds a map of references to the current instances of filter trees per resource type
+  private Map<TYPE, FilterTreePane> currentFilterPanes;
+  
+  
+  // COMPONENTS
+  private JTabbedPane tabbedSearchResultPanel;
+  
+  protected JToggleButton bToggleSearchHistory;
+  protected JButton bRefreshLastSearch;
+  protected JButton bClearSearchResults;
+  
+  
+  public SearchResultsMainPanel()
+  {
+    this.instanceOfSelf = this;
+    MainComponentFactory.getSharedInstance();
+    this.logger = Logger.getLogger(SearchResultsMainPanel.class);
+    
+    this.currentSearchInstances = new HashMap<TYPE,SearchInstance>();
+    
+    this.searchResultListings = new HashMap<TYPE, SearchResultsListingPanel>();
+    this.currentFilterPanes = new HashMap<TYPE,FilterTreePane>();
+    this.searchResultTabs = new LinkedHashMap<TYPE, JComponent>(); // crucial to preserve the order -- so that these tabs always appear in the UI in the same order!
+    initialiseResultTabsMap();
+    
+    initialiseUI();
+  }
+  
+  
+  private void initialiseUI()
+  {
+    // create a panel for tabbed listings of search results
+    this.tabbedSearchResultPanel = new JTabbedPane();
+    reloadResultTabsFromMap();
+       
+    // pack all main components together
+    JPanel jpMainResultsPanel = new JPanel(new BorderLayout());
+    jpMainResultsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 3));
+    
+    jpMainResultsPanel.add(tabbedSearchResultPanel, BorderLayout.CENTER);
+    
+    
+    // --- Put together all parts ---
+    // main components in the middle, toolbar on the right
+    this.setMinimumSize(new Dimension(450, 50));
+    this.setLayout(new BorderLayout());
+    this.add(jpMainResultsPanel, BorderLayout.CENTER);
+    
+    // FIXME - add toolbar to the main window!
+//    this.add(tbSearchActions, BorderLayout.EAST);
+  }
+  
+  
+  
+  // ----- Hiding / Showing tabs for various search result types -----
+  
+  /**
+   * Dynamically populates the map of resource types and components that represent these types
+   * in the tabbed pane -- this is only to be done once during the initialisation.
+   */
+  private void initialiseResultTabsMap()
+  {
+    for (TYPE t : TYPE.values()) {
+      toggleResultTabsInMap(t, t.isDefaultSearchType());
+    }
+  }
+  
+  
+  /**
+   * Adds or removes a tab for a specified type of resource.
+   * 
+   * @param type Resource type for which the tab is to be added / removed.
+   * @param doShowTab Defines whether to add or remove tab for this resource type.
+   */
+  public void toggleResultTabsInMap(TYPE type, boolean doShowTab)
+  {
+    JPanel jpResultTabContent = null;
+    
+    if (doShowTab)
+    {
+      jpResultTabContent = new JPanel(new GridLayout());
+      
+      // decide if this resource type supports filtering
+      if (type.isSuitableForFiltering()) {
+          FilterTreePane filterTreePane = new FilterTreePane(type);
+          this.currentFilterPanes.put(type, filterTreePane);
+      }
+      else {
+        // not suitable for filtering - record this in a map
+        this.currentFilterPanes.put(type, null);
+      }
+      
+      
+      SearchResultsListingPanel resultsListingPanel = new SearchResultsListingPanel(type, this);
+      this.searchResultListings.put(type, resultsListingPanel);
+      
+      if (this.currentFilterPanes.get(type) == null) {
+        jpResultTabContent.add(resultsListingPanel);
+      }
+      else {
+        JSplitPane spFiltersAndResultListing = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+        spFiltersAndResultListing.setLeftComponent(this.currentFilterPanes.get(type));
+        spFiltersAndResultListing.setRightComponent(resultsListingPanel);
+        jpResultTabContent.add(spFiltersAndResultListing);
+      }
+    }
+    else {
+      // tab for this type is being hidden - just remove the references
+      // to the search result listing and to filter pane 
+      this.searchResultListings.put(type, null);
+      this.currentFilterPanes.put(type, null);
+    }
+    
+    this.searchResultTabs.put(type, jpResultTabContent);
+  }
+  
+  
+  /**
+   * (Re-)loads the user interface from the internal map.
+   */
+  public void reloadResultTabsFromMap()
+  {
+    Component selectedTabsComponent = tabbedSearchResultPanel.getSelectedComponent();
+    tabbedSearchResultPanel.removeAll();
+    for (TYPE type : this.searchResultTabs.keySet()) {
+      JComponent c = this.searchResultTabs.get(type);
+      if (c != null) {
+        tabbedSearchResultPanel.addTab(type.getCollectionName(), type.getIcon(), c, type.getCollectionTabTooltip());
+      }
+    }
+    
+    // attempt to re-select the same tab that was open before reloading
+    try {
+      tabbedSearchResultPanel.setSelectedComponent(selectedTabsComponent);
+    }
+    catch (IllegalArgumentException e) {
+      // failed - probably previously selected tab got removed - select the first one
+      tabbedSearchResultPanel.setSelectedIndex(0);
+    }
+  }
+  
+  
+  /**
+   * @param resourceType Resource type to look for.
+   * @return Current index of the tab in the results tabbed pane view
+   *         that holds a component showing search results for this type.
+   *         Returns <code>-1</code> if requested type is not currently displayed.
+   */
+  protected int getTabIndexForResourceType(TYPE resourceType) {
+    return (tabbedSearchResultPanel.indexOfComponent(searchResultTabs.get(resourceType)));
+  }
+  
+  
+  // ----- ------
+  
+  
+  /**
+   * This method is intended to be called when filter options in one of the tabs change.
+   * It starts the new filtering operation.
+   * 
+   * Effectively it sets the filtering parameters for the SearchInstance
+   * and then starts a new search with that {@link SearchInstance} wrapped into {@link SearchOptions}.
+   * 
+   * @param resourceType Resource type for which the new filtering operation is started
+   * @param filteringSettings Filtering settings for the current filtering operation
+   *                          obtained from the filter tree (or favourite filters).
+   */
+  public void startNewFiltering(TYPE resourceType, ServiceFilteringSettings filteringSettings)
+  {
+    SearchInstance siPreviousSearchForThisType = getCurrentSearchInstance(resourceType);
+    
+    // pass on the filtering parameters to the relevant search instance (this will overwrite the old ones if any were present!)
+    if (siPreviousSearchForThisType == null) {
+      // no filterings have been done earlier for this resource type;
+      // we'll need a new (blank) query search SearchInstance and
+      // wrap it into a service filtering SearchInstance
+      siPreviousSearchForThisType = new SearchInstance(new SearchInstance("", resourceType), filteringSettings);
+    }
+    else {
+      if (!siPreviousSearchForThisType.isServiceFilteringSearch()) {
+        // just wrap existing search instance that was (probably) transferred from the Search tab
+        // into another SearchInstance that explicitly deals with service filtering
+        siPreviousSearchForThisType = new SearchInstance(siPreviousSearchForThisType, filteringSettings);
+      }
+      else {
+        // previous search instance dealt with filtering -
+        // simply update the filtering settings (but before that
+        // run a 'deep copy' of the original search instance, so
+        // that the new one gets a new reference; this will aid
+        // in early termination of older filterings)
+        siPreviousSearchForThisType = siPreviousSearchForThisType.deepCopy();
+        siPreviousSearchForThisType.setFilteringSettings(filteringSettings);
+      }
+    }
+    
+    // proceed with "search" as usual - it will treat this search instance differently
+    // from "ordinary" search
+    startNewSearch(new SearchOptions(siPreviousSearchForThisType));
+  }
+  
+  
+  /**
+   * Worker method responsible for starting a new search via the API.
+   * 
+   * This method is to be used when a *new* search is started. It will
+   * mainly make updates to the UI and store the new search in the history.
+   */
+  public void startNewSearch(final SearchOptions searchOptions)
+  {
+    try
+    {
+      for (final TYPE resourceType : searchOptions.getResourceTypesToSearchFor())
+      {
+        SearchInstance si = null;
+        switch (searchOptions.getSearchType()) {
+          case QuerySearch: si = new SearchInstance(searchOptions.getSearchString(), resourceType);
+                            resetAllFilterPanes(searchOptions.getSearchString());
+                            break;
+                            
+          case TagSearch:   if (resourceType.isSuitableForTagSearch()) {
+                              si = new SearchInstance(searchOptions.getSearchTags(), resourceType);
+                              resetAllFilterPanes(searchOptions.getSearchString());
+                            }
+                            else {
+                              // FIXME implement this... - show "no results" in the appropriate tab
+                              JOptionPane.showMessageDialog(null, "'" + resourceType.getTypeName() + "' resource type is not suitable for tag search");
+                            }
+                            break;
+                            
+          case Filtering:   if (resourceType.isSuitableForFiltering()) {
+                              si = searchOptions.getPreconfiguredSearchInstance();
+                            }
+                            else {
+                              // FIXME implement this... - show "no results" in the appropriate tab
+                              JOptionPane.showMessageDialog(null, "'" + resourceType.getTypeName() + "' resource type is not suitable for filtering");
+                            }
+                            break;
+        }
+        
+        if (si.isEmptySearch()) {
+        	clearListingPanels();
+        	return;
+        }
+        
+        // Record 'this' search instance and set it as the new "primary" one for
+        // this resource type;
+        // (this way it if a new search thread starts afterwards, it is possible to
+        //  detect this and stop the 'older' search, because it is no longer relevant)
+        registerSearchInstance(resourceType, si);
+        
+        // start spinner icon for this tab to indicate search in progress - also show status message
+        setSpinnerIconForTab(resourceType);
+        setDefaultTitleForTab(resourceType);
+        searchResultListings.get(resourceType).setSearchStatusText("Searching for " + si.getDescriptionStringForSearchStatus() + "...", true);
+        
+        
+        // start the actual search
+        final SearchInstance siToStart = si;
+        new Thread(searchOptions.getSearchType() + " of " + resourceType.getCollectionName() + " via the API") {
+          public void run() {
+            siToStart.startNewSearch(instanceOfSelf, new CountDownLatch(1), searchResultListings.get(resourceType));  // FIXME - the new countdown latch is never used...
+          }
+        }.start();
+      }
+    }
+    catch (Exception e) {
+      logger.error("Error while searching via the Service Catalogue API. Error details attached.", e);
+    }
+    
+}
+  
+  
+  
+  /**
+   * Clears selection of filtering criteria and collapses any expanded nodes
+   * in all filter tree panes.<br/><br/>
+   * 
+   * To be used for resetting all filter panes when the new query / tag
+   * search starts.
+ * @param queryString 
+   */
+  private void resetAllFilterPanes(String queryString) {
+    for (FilterTreePane filterTreePane : this.currentFilterPanes.values()) {
+      if (filterTreePane != null) {
+        filterTreePane.clearSelection();
+        filterTreePane.collapseAll();
+        if ((queryString != null) && !queryString.isEmpty()) {
+        	filterTreePane.applyQueryString(queryString);
+        }
+      }
+    }
+  }
+  
+  
+  protected void setSpinnerIconForTab(TYPE resourceType) {
+    tabbedSearchResultPanel.setIconAt(getTabIndexForResourceType(resourceType), ResourceManager.getImageIcon(ResourceManager.SPINNER));
+  }
+  
+  protected void setDefaultIconForTab(TYPE resourceType) {
+    this.tabbedSearchResultPanel.setIconAt(getTabIndexForResourceType(resourceType), resourceType.getIcon());
+  }
+  
+  
+  /**
+   * Same as {@link SearchResultsMainPanel#setDefaultTitleForTab(TYPE)},
+   * but allows to append a specified string at the end of the default title.
+   * 
+   * @param resourceType
+   * @param suffix
+   */
+  protected void setDefaultTitleForTabWithSuffix(TYPE resourceType, String suffix) {
+    tabbedSearchResultPanel.setTitleAt(getTabIndexForResourceType(resourceType),
+        resourceType.getCollectionName() + (suffix == null ? "" : suffix) );
+  }
+  
+  
+  /**
+   * Sets default title for a tab that contains panel representing 
+   * search results of the specified resource type. Default title
+   * is just a name of the collections of resources in that tab. 
+   * 
+   * @param resourceType 
+   */
+  protected void setDefaultTitleForTab(TYPE resourceType) {
+    setDefaultTitleForTabWithSuffix(resourceType, null);
+  }
+  
+  
+  /**
+   * @param resourceType Resource type for which the search result listing panel is requested.
+   * @return Reference to the requested panel or <code>null</code> if a tab for the specified
+   *         <code>resourceType</code> does not exist.
+   */
+  protected SearchResultsListingPanel getResultsListingFor(TYPE resourceType) {
+    return (this.searchResultListings.get(resourceType));
+  }
+  
+  
+  /**
+   * @param resourceType Resource type for which filter tree pane is to be returned.
+   * @return Reference to the requested filter tree pane or <code>null</code> if
+   *         there is no search result tab for the specified <code>resourceType</code>
+   *         (or if that <code>resourceType</code> does not support filtering).
+   */
+  protected FilterTreePane getFilterTreePaneFor(TYPE resourceType) {
+    return (this.currentFilterPanes.get(resourceType));
+  }
+    
+  
+  // *** Callback for ActionListener interface ***
+  
+  public void actionPerformed(ActionEvent e)
+  {
+    // FIXME -- remove this...
+//    if (e.getSource().equals(bRefreshLastSearch))
+//    {
+//      // restore state of the search options panel
+//      pluginPerspectiveMainComponent.getSearchTab().restoreSearchOptions(siPreviousSearch);
+//      
+//      // completely re-run the previous search
+//      startNewSearch(siPreviousSearch);
+//    }
+//    else if (e.getSource().equals(bClearSearchResults))
+//    {
+//      // manual request to clear results of previous search
+//      
+//      // if any search thread was running, deactivate it as well
+//      if (isSearchThreadRunning()) {
+//        vCurrentSearchThreadID.set(0, null);
+//      }
+//      
+//      // changing both - spinner image and the status text simultaneously
+//      setSearchStatusText("No searches were made yet", false);
+//      
+//      // removed the previous search, hence makes no sense to allow to clear "previous" results again
+//      bClearSearchResults.setEnabled(false);
+//      
+//      // only remove data about previous search and disable refresh button 
+//      // if no search thread is currently running - otherwise keep the button
+//      // enabled in case there is a need to re-start the search if it's frozen
+//      if (!isSearchThreadRunning()) {
+//        siPreviousSearch = null;
+//        bRefreshLastSearch.setEnabled(false);
+//      }
+//      
+//      // also notify tabbed results panel, so that it removes the actual search results 
+//      searchResultsPanel.clearPreviousSearchResults();
+//    }
+//    else if (e.getSource().equals(this.jclPreviewCurrentFilteringCriteria))
+//    {
+//      // open a preview window showing current filtering settings
+//      SwingUtilities.invokeLater(new Runnable()
+//      {
+//        public void run() {
+//          ServiceFilteringSettingsPreview p = new ServiceFilteringSettingsPreview(siPreviousSearch.getFilteringSettings());
+//          p.setVisible(true);
+//        }
+//      });
+//      
+//    }
+  }
+  
+  
+  // *** Callbacks for SearchInstanceTracker interface ***
+  
+  public synchronized void clearPreviousSearchInstances() {
+    this.currentSearchInstances.clear();
+  }
+  
+  public synchronized boolean isCurrentSearchInstance(TYPE searchType, SearchInstance searchInstance) {
+    // NB! it is crucial to perform test by reference here (hence the use of "==", not equals()!)
+    return (this.currentSearchInstances.get(searchType) == searchInstance);
+  }
+  
+  public synchronized void registerSearchInstance(TYPE searchType, SearchInstance searchInstance) {
+    this.currentSearchInstances.put(searchType, searchInstance);
+  }
+  
+  public synchronized SearchInstance getCurrentSearchInstance(TYPE searchType) {
+    return this.currentSearchInstances.get(searchType);
+  }
+
+public void clearListingPanels() {
+    for (SearchResultsListingPanel listingPanel : searchResultListings.values()) {
+    	listingPanel.resetSearchResultsListing(true);
+    }
+    for (TYPE t : searchResultListings.keySet()) {
+    	setDefaultTitleForTab(t);
+    }
+	
+}
+
+public void clearSearch() {
+	clearPreviousSearchInstances();
+	clearListingPanels();
+    for (FilterTreePane treePanel : currentFilterPanes.values()) {
+    	treePanel.reset();
+    }
+}
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
new file mode 100644
index 0000000..58ee5fd
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
@@ -0,0 +1,47 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+
+/**
+ * This interfaces avoids coupling of search engine classes
+ * directly with the UI classes.
+ * 
+ * Search engines would send new chunks of search results
+ * to the <code>SearchResultsRenderer</code> as soon as
+ * they become available.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public interface SearchResultsRenderer
+{
+  /**
+   * Render initial fragment of search results. This includes
+   * creating a new <code>ListModel</code> in the results listing
+   * and populating it with the first chunk of results and placeholders
+   * for those results that haven't been fetched yet.
+   * 
+//   * @param searchThreadID This is the ID of the thread that initiated search  // FIXME
+//   *                       from within the UI component, rather than the ID of
+//   *                       the real worker search engine's search thread.
+//   *                       It is used to test whether that thread is still active -
+//   *                       to determine whether the partial results need to be rendered.
+   * @param si The search instance containing partial search results to be rendered. 
+   */
+  void renderInitialResults(SearchInstance si);
+  
+  
+  /**
+   * Update the results listing with a specific fragment of the collection
+   * of search results.
+   * 
+   * @param si The search instance containing partial search results to be rendered.
+   * @param startIndex First index in the result collection to update.
+   * @param count Number of result listing entries to update.
+   *              <br/>
+   *              At most <code>count</code> results will be rendered - less can be rendered
+   *              if end of result list is reached earlier. <code>count</code> is normally
+   *              just a page size for a specific resource type.
+   */
+  void renderFurtherResults(SearchInstance si, int startIndex, int count);
+  
+}