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:18 UTC

[04/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-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java
new file mode 100644
index 0000000..128cfd3
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/ResourcePreviewFactory.java
@@ -0,0 +1,1359 @@
+/*******************************************************************************
+ * Copyright (C) 2009 The University of Manchester
+ * 
+ * Modifications to the initial code base are copyright of their respective
+ * authors, or their employers as appropriate.
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.JTextPane;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Comment;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.File;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Group;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Pack;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.PackItem;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.User;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class ResourcePreviewFactory {
+  // CONSTANTS
+  private static final int PREFERRED_LOWER_TABBED_PANE_HEIGHT = 250; // used for
+  // all
+  // tabbed
+  // views
+  // inside
+  // preview
+  // for
+  // every
+  // resource
+  // type
+
+  private final MainComponent pluginMainComponent;
+  private final MyExperimentClient myExperimentClient;
+  private final Logger logger;
+
+  // icons which are used in several places in the preview factory
+  private final ImageIcon iconWorkflow;
+  private final ImageIcon iconFile;
+  private final ImageIcon iconPack;
+  private final ImageIcon iconUser;
+  private final ImageIcon iconGroup;
+
+  public ResourcePreviewFactory(MainComponent component, MyExperimentClient client, Logger logger) {
+	super();
+
+	// set main variables to ensure access to myExperiment, logger and the
+	// parent component
+	this.pluginMainComponent = component;
+	this.myExperimentClient = client;
+	this.logger = logger;
+
+	// set up the icons
+	iconWorkflow = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.WORKFLOW));
+	iconFile = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.FILE));
+	iconPack = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.PACK));
+	iconUser = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.USER));
+	iconGroup = new ImageIcon(MyExperimentPerspective.getLocalIconURL(Resource.GROUP));
+  }
+
+  // main worker method - generates the content to be shown in the preview;
+  // responsible for parsing the preview action request, fetching data and
+  // generating all the content (via helpers)
+  public ResourcePreviewContent createPreview(String action, EventListener eventHandler) {
+	JPanel panelToPopulate = new JPanel();
+
+	// === PREPROCESSING ===
+
+	// return error message if the action string isn't actually a request for
+	// preview
+	if (!action.startsWith("preview:")) {
+	  this.logger.error("Bad preview request: \"" + action + "\"");
+	  panelToPopulate.add(new JLabel("An error has occurred."));
+	  Resource r = new Resource();
+	  r.setItemType(Resource.UNEXPECTED_TYPE);
+	  r.setTitle("Bad preview request");
+	  r.setURI(action);
+	  return (new ResourcePreviewContent(r, panelToPopulate));
+	}
+
+	// parse the action string - we are now sure that it starts with a
+	// 'preview:'
+	action = action.substring(action.indexOf(":") + 1); // remove "preview:"
+	int iType = Integer.parseInt(action.substring(0, action.indexOf(":"))); // get
+	// type
+	action = action.substring(action.indexOf(":") + 1); // remove type
+	String strURI = action; // get URI
+
+	// === FETCHING RESOURCE DATA ===
+	Document doc = null;
+	try {
+	  // the resource type is known at this point, hence can use specialist
+	  // method
+	  // that only fetches required metadata for (individual for each resource
+	  // type)
+	  doc = this.myExperimentClient.getResource(iType, strURI, Resource.REQUEST_FULL_PREVIEW);
+	} catch (Exception e) {
+	  logger.error("Error while fetching resource data from myExperiment to generate a preview.\nResource type: "
+		  + Resource.getResourceTypeName(iType)
+		  + "\nResource URI: "
+		  + strURI
+		  + "\nException: " + e);
+	}
+
+	// === GENERATING PREVIEW ===
+	Resource resource = null;
+	switch (iType) {
+	  case Resource.WORKFLOW:
+		Workflow w = Workflow.buildFromXML(doc, this.logger);
+		resource = w;
+		this.generateWorkflowPreviewContent(w, panelToPopulate, eventHandler);
+		break;
+
+	  case Resource.FILE:
+		File f = File.buildFromXML(doc, this.logger);
+		resource = f;
+		this.generateFilePreviewContent(f, panelToPopulate, eventHandler);
+		break;
+
+	  case Resource.PACK:
+		Pack p = Pack.buildFromXML(doc, this.myExperimentClient, this.logger);
+		resource = p;
+		this.generatePackPreviewContent(p, panelToPopulate, eventHandler);
+		break;
+
+	  case Resource.USER:
+		User u = User.buildFromXML(doc, logger);
+		resource = u;
+		this.generateUserPreviewContent(u, panelToPopulate, eventHandler);
+		break;
+
+	  case Resource.GROUP:
+		Group g = Group.buildFromXML(doc, logger);
+		resource = g;
+		this.generateGroupPreviewContent(g, panelToPopulate, eventHandler);
+		break;
+
+	  default:
+		// unexpected resource type - can't generate preview
+		this.logger.error("Failed generating preview. Reason: unknown resource type - \""
+			+ Resource.getResourceTypeName(iType) + "\"");
+		panelToPopulate.add(new JLabel("Cannot generate preview for unknown resource types."));
+		Resource r = new Resource();
+		r.setItemType(iType);
+		r.setTitle("Error: unknown resource type");
+		r.setURI(strURI);
+		return (new ResourcePreviewContent(r, panelToPopulate));
+	}
+
+	// format output data
+	return (new ResourcePreviewContent(resource, panelToPopulate));
+  }
+
+  private void generateWorkflowPreviewContent(Workflow w, JPanel panelToPopulate, EventListener eventHandler) {
+	if (w != null) {
+	  try {
+		StringBuffer content = new StringBuffer();
+		content.append("<div class='outer'>");
+		content.append("<div class='workflow'>");
+
+		content.append("<br>");
+
+		content.append("<p class='title'>");
+		content.append("Workflow Entry: <a href='preview:" + Resource.WORKFLOW
+			+ ":" + w.getURI() + "'>" + w.getTitle() + "</a> (version "
+			+ w.getVersion() + ")");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<p class='info'>");
+		content.append("<b>Type:</b> " + w.getVisibleType() + "<br><br>");
+		content.append("<b>Uploader:</b> <a href='preview:" + Resource.USER
+			+ ":" + w.getUploader().getURI() + "'>" + w.getUploader().getName()
+			+ "</a><br>");
+		content.append("<b>Created at: </b> " + w.getCreatedAt() + "<br>");
+		content.append("<b>License: </b> <a href='"
+			+ w.getLicense().getLink()
+			+ "'>"
+			+ w.getLicense().getText()
+			+ "</a>"
+			+ "&nbsp;<img src='"
+			+ MyExperimentPerspective.getLocalResourceURL("external_link_small_icon")
+			+ "' />");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<a href='" + w.getPreview() + "'>");
+		content.append("<img class='preview' src='" + w.getThumbnailBig()
+			+ "'></img>");
+		content.append("</a>");
+
+		content.append("<br>");
+		content.append("<br>");
+
+		if (!w.getDescription().equals("")) {
+		  content.append("<p class='desc'>");
+		  content.append("<br>");
+		  content.append(Util.stripHTML(w.getDescription()));
+		  content.append("<br>");
+		  content.append("</p>");
+		} else {
+		  content.append("<span class='none_text'>No description</span>");
+		}
+
+		content.append("<br>");
+		content.append("</div>");
+		content.append("</div>");
+
+		HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+		HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+		doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+		// === Now render user's items as Swing components ===
+		// .. TABS for components, tags, comments, credits, attributions ..
+		JScrollPane spComponentsTab = createWorkflowComponentPreviewTab(w);
+		JScrollPane spTagsTab = createTagPreviewTab(w.getTags());
+		JScrollPane spCommentsTab = createCommentsPreviewTab(w.getComments());
+		JScrollPane spCreditsTab = createCreditsPreviewTab(w.getCredits());
+		JScrollPane spAttributionsTab = createAttributionsPreviewTab(w.getAttributions());
+
+		// .. ASSEMBLE ALL TABS together
+		JTabbedPane tpTabbedView = new JTabbedPane();
+		tpTabbedView.add("Components", spComponentsTab);
+		tpTabbedView.add("Tags (" + w.getTags().size() + ")", spTagsTab);
+		tpTabbedView.add("Comments (" + w.getComments().size() + ")", spCommentsTab);
+		tpTabbedView.addTab("Credits (" + w.getCredits().size() + ")", spCreditsTab);
+		tpTabbedView.addTab("Attributions (" + w.getAttributions().size() + ")", spAttributionsTab);
+
+		// PUT EVERYTHING TOGETHER
+		JTextPane tpWorkflowPreview = new JTextPane();
+		tpWorkflowPreview.setEditable(false);
+		tpWorkflowPreview.setEditorKit(kit);
+		tpWorkflowPreview.setDocument(doc);
+		tpWorkflowPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+		JPanel jpFullWorkflowPreview = wrapTextPaneAndTabbedViewIntoFullPreview(tpWorkflowPreview, tpTabbedView);
+
+		// POPULATE THE GIVEN PANEL
+		panelToPopulate.setLayout(new BorderLayout());
+		panelToPopulate.add(jpFullWorkflowPreview, BorderLayout.CENTER);
+
+		// this.statusLabel.setText("Workflow information found. Last fetched: "
+		// + new Date().toString());
+
+		// this.clearButton.setEnabled(true);
+		// this.refreshButton.setEnabled(true);
+		// this.loadButton.setEnabled(true);
+		// this.importButton.setEnabled(true);
+	  } catch (Exception e) {
+		logger.error("Failed to populate Workflow Preview pane", e);
+	  }
+	} else {
+	  // statusLabel.setText("Could not find information for workflow ID: " +
+	  // currentWorkflowId);
+	  // clearContentTextPane();
+	  // disableButtons();
+	}
+  }
+
+  private void generateFilePreviewContent(File f, JPanel panelToPopulate, EventListener eventHandler) {
+	if (f != null) {
+	  try {
+		StringBuffer content = new StringBuffer();
+		content.append("<div class='outer'>");
+		content.append("<div class='file'>");
+
+		content.append("<br>");
+
+		content.append("<p class='title'>");
+		content.append("File: <a href='preview:" + Resource.FILE + ":"
+			+ f.getURI() + "'>" + f.getTitle() + "</a>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<p class='info'>");
+		content.append("<b>Type:</b> " + f.getVisibleType() + "<br>");
+		content.append("<b>Filename:</b> " + f.getFilename() + "<br><br>");
+		content.append("<b>Uploader:</b> <a href='preview:" + Resource.USER
+			+ ":" + f.getUploader().getURI() + "'>" + f.getUploader().getName()
+			+ "</a><br>");
+		content.append("<b>Created at: </b> " + f.getCreatedAt() + "<br>");
+		content.append("<b>Last updated at: </b> " + f.getUpdatedAt() + "<br>");
+		content.append("<b>License: </b> <a href='"
+			+ f.getLicense().getLink()
+			+ "'>"
+			+ f.getLicense().getText()
+			+ "</a>"
+			+ "&nbsp;<img src='"
+			+ MyExperimentPerspective.getLocalResourceURL("external_link_small_icon")
+			+ "' />");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		if (!f.getDescription().equals("")) {
+		  content.append("<p class='desc'>");
+		  content.append("<br>");
+		  content.append(Util.stripHTML(f.getDescription()));
+		  content.append("<br>");
+		  content.append("</p>");
+		} else {
+		  content.append("<span class='none_text'>No description</span>");
+		}
+
+		content.append("<br>");
+		content.append("</div>");
+		content.append("</div>");
+
+		HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+		HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+		doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+		// === Now render group's items as Swing components ===
+		// TABS FOR file's tags, credits, etc
+		JScrollPane spTagsTab = createTagPreviewTab(f.getTags());
+		JScrollPane spCommentsTab = createCommentsPreviewTab(f.getComments());
+		JScrollPane spCreditsTab = createCreditsPreviewTab(f.getCredits());
+		JScrollPane spAttributionsTab = createAttributionsPreviewTab(f.getAttributions());
+
+		// ASSEMBLE tabs into tabbed view
+		JTabbedPane tpTabbedView = new JTabbedPane();
+		tpTabbedView.add("Tags (" + f.getTags().size() + ")", spTagsTab);
+		tpTabbedView.add("Comments (" + f.getComments().size() + ")", spCommentsTab);
+		tpTabbedView.add("Credits (" + f.getCredits().size() + ")", spCreditsTab);
+		tpTabbedView.add("Attributions (" + f.getAttributions().size() + ")", spAttributionsTab);
+
+		// PUT EVERYTHING TOGETHER
+		JTextPane tpFilePreview = new JTextPane();
+		tpFilePreview.setEditable(false);
+		tpFilePreview.setEditorKit(kit);
+		tpFilePreview.setDocument(doc);
+		tpFilePreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+		JPanel jpFullFilePreview = new JPanel();
+		jpFullFilePreview.setBackground(Color.WHITE); // white background for
+		// the whole file preview
+		// panel
+		jpFullFilePreview.setLayout(new GridBagLayout());
+		GridBagConstraints c = new GridBagConstraints();
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 0;
+		c.weighty = 0; // will not change size when the window is resized
+		jpFullFilePreview.add(tpFilePreview, c);
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 1;
+		c.weighty = 1; // will grow in size when the window is resized..
+		c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+		// vertically
+		c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+		// bottom
+		jpFullFilePreview.add(tpTabbedView, c);
+
+		// POPULATE THE GIVEN PANEL
+		panelToPopulate.setLayout(new BorderLayout());
+		panelToPopulate.add(jpFullFilePreview, BorderLayout.CENTER);
+
+		// this.statusLabel.setText("File information found. Last fetched: " +
+		// new Date().toString());
+
+		// this.clearButton.setEnabled(true);
+		// this.refreshButton.setEnabled(true);
+		// this.loadButton.setEnabled(true);
+		// this.importButton.setEnabled(true);
+	  } catch (Exception e) {
+		logger.error("Failed to populate File Preview pane", e);
+	  }
+	} else {
+	  // statusLabel.setText("Could not find information for file ID: " +
+	  // currentFileId);
+	  // clearContentTextPane();
+	  // disableButtons();
+	}
+  }
+
+  private void generatePackPreviewContent(Pack p, JPanel panelToPopulate, EventListener eventHandler) {
+	if (p != null) {
+	  try {
+		// === Render pack details in HTML format ===
+		StringBuffer content = new StringBuffer();
+		content.append("<div class='outer'>");
+		content.append("<div class='pack'>");
+
+		content.append("<br>");
+
+		content.append("<p class='title'>");
+		content.append("Pack: <a href='preview:" + Resource.PACK + ":"
+			+ p.getURI() + "'>" + p.getTitle() + "</a>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<p class='info'>");
+		content.append("<b>Creator:</b> <a href='preview:" + Resource.USER
+			+ ":" + p.getCreator().getURI() + "'>" + p.getCreator().getName()
+			+ "</a><br>");
+		content.append("<b>Created at: </b> " + p.getCreatedAt() + "<br>");
+		content.append("<b>Last updated at: </b> " + p.getUpdatedAt() + "<br>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		if (!p.getDescription().equals("")) {
+		  content.append("<p class='desc'>");
+		  content.append("<br>");
+		  content.append(Util.stripHTML(p.getDescription()));
+		  content.append("<br>");
+		  content.append("<br>");
+		  content.append("</p>");
+		} else {
+		  content.append("<span class='none_text'>No description</span>");
+		}
+
+		content.append("<br>");
+		content.append("</div>");
+		content.append("</div>");
+
+		HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+		HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+		doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+		// === Now render group's items as Swing components ===
+		// TABS FOR pack items, tags, etc
+		JScrollPane spPackItemsTab = createPackItemPreviewTab(p);
+		JScrollPane spTagsTab = createTagPreviewTab(p.getTags());
+		JScrollPane spCommentsTab = createCommentsPreviewTab(p.getComments());
+
+		// ASSEMBLE tabs into tabbed view
+		JTabbedPane tpTabbedView = new JTabbedPane();
+		tpTabbedView.addTab("Pack Items (" + p.getItemCount() + ")", spPackItemsTab);
+		tpTabbedView.add("Tags (" + p.getTags().size() + ")", spTagsTab);
+		tpTabbedView.add("Comments (" + p.getComments().size() + ")", spCommentsTab);
+
+		// PUT EVERYTHING TOGETHER
+		JTextPane tpPackPreview = new JTextPane();
+		tpPackPreview.setEditable(false);
+		tpPackPreview.setEditorKit(kit);
+		tpPackPreview.setDocument(doc);
+		tpPackPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+		JPanel jpFullPackPreview = new JPanel();
+		jpFullPackPreview.setBackground(Color.WHITE); // white background for
+		// the whole pack preview
+		// panel
+		jpFullPackPreview.setLayout(new GridBagLayout());
+		GridBagConstraints c = new GridBagConstraints();
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 0;
+		c.weighty = 0; // will not change size when the window is resized
+		jpFullPackPreview.add(tpPackPreview, c);
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 1;
+		c.weighty = 1; // will grow in size when the window is resized..
+		c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+		// vertically
+		c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+		// bottom
+		jpFullPackPreview.add(tpTabbedView, c);
+
+		// POPULATE THE GIVEN PANEL
+		panelToPopulate.setLayout(new BorderLayout());
+		panelToPopulate.add(jpFullPackPreview, BorderLayout.CENTER);
+
+		// this.statusLabel.setText("Pack information found. Last fetched: " +
+		// new Date().toString());
+
+		// this.clearButton.setEnabled(true);
+		// this.refreshButton.setEnabled(true);
+		// this.loadButton.setEnabled(true);
+		// this.importButton.setEnabled(true);
+	  } catch (Exception e) {
+		logger.error("Failed to populate Pack Preview pane", e);
+	  }
+	} else {
+	  // statusLabel.setText("Could not find information for pack ID: " +
+	  // currentPackId);
+	  // clearContentTextPane();
+	  // disableButtons();
+	}
+  }
+
+  private void generateUserPreviewContent(User u, JPanel panelToPopulate, EventListener eventHandler) {
+	if (u != null) {
+	  try {
+		// === Render user details in HTML format ===
+		StringBuffer content = new StringBuffer();
+		content.append("<div class='outer'>");
+		content.append("<div class='user'>");
+
+		content.append("<br>");
+
+		content.append("<p class='name'>");
+		content.append("User: <a href=preview:" + Resource.USER + ":"
+			+ u.getURI() + "'>" + u.getName() + "</a>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<p class='info'>");
+		String strLocation;
+		if (u.getCity().length() == 0 && u.getCountry().length() == 0)
+		  strLocation = "<span class='none_text'>Not specified</span>";
+		else
+		  strLocation = u.getCity()
+			  + (u.getCity().length() == 0 || u.getCountry().length() == 0 ? "" : ", ")
+			  + u.getCountry();
+		content.append("<b>Location:</b> " + strLocation + "<br>");
+		content.append("<b>Joined at: </b> " + u.getCreatedAt() + "<br>");
+		content.append("<b>Last seen at: </b> " + u.getUpdatedAt() + "<br>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<a href='" + u.getAvatarResource() + "'>");
+		content.append("<img class='avatar' src='" + u.getAvatarResource()
+			+ "'></img>");
+		content.append("</a>");
+
+		content.append("<br>");
+		content.append("<br>");
+
+		if (!u.getDescription().equals("")) {
+		  // HACK: the way JAVA renders html causes styling not to be inherited;
+		  // hence need to
+		  // remove any nested <p> or <div> tags to get a proper layout
+		  content.append("<p class='desc'>"
+			  + Util.stripHTML(u.getDescription()) + "<br><br></p>");
+		} else {
+		  content.append("<span class='none_text'>No description</span>");
+		}
+
+		content.append("<p class='contact_details_header'>Contact Details</p>");
+		content.append("<p class='contact_details'>");
+		content.append("<b>Email: </b>"
+			+ (u.getEmail().length() == 0 ? "<span class='none_text'>Not specified</span>" : u.getEmail())
+			+ "<br>");
+		content.append("<b>Website: </b>"
+			+ (u.getWebsite().length() == 0 ? "<span class='none_text'>Not specified</span>" : u.getWebsite()));
+		content.append("</p>");
+
+		content.append("</div>");
+		content.append("</div>");
+
+		HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+		HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+		doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+		// === Now render user's items as Swing components ===
+		// .. WORKFLOWS ..
+		JPanel jpWorkflowsTabContent = new JPanel();
+		jpWorkflowsTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+		jpWorkflowsTabContent.setLayout(new BoxLayout(jpWorkflowsTabContent, BoxLayout.Y_AXIS));
+
+		// iterate through all workflows and add all to the panel
+		Iterator<HashMap<String, String>> iWorkflows = u.getWorkflows().iterator();
+		while (iWorkflows.hasNext()) {
+		  HashMap<String, String> hmCurWF = iWorkflows.next();
+		  jpWorkflowsTabContent.add(new JClickableLabel(hmCurWF.get("name"), "preview:"
+			  + Resource.WORKFLOW + ":" + hmCurWF.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconWorkflow));
+		}
+
+		// .. FILES ..
+		JPanel jpFilesTabContent = new JPanel();
+		jpFilesTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+		jpFilesTabContent.setLayout(new BoxLayout(jpFilesTabContent, BoxLayout.Y_AXIS));
+
+		// iterate through all files and add all to the panel
+		Iterator<HashMap<String, String>> iFiles = u.getFiles().iterator();
+		while (iFiles.hasNext()) {
+		  HashMap<String, String> hmCurFile = iFiles.next();
+		  jpFilesTabContent.add(new JClickableLabel(hmCurFile.get("name"), "preview:"
+			  + Resource.FILE + ":" + hmCurFile.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconFile));
+		}
+
+		// .. PACKS ..
+		JPanel jpPacksTabContent = new JPanel();
+		jpPacksTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+		jpPacksTabContent.setLayout(new BoxLayout(jpPacksTabContent, BoxLayout.Y_AXIS));
+
+		// iterate through all packs and add all to the panel
+		Iterator<HashMap<String, String>> iPacks = u.getPacks().iterator();
+		while (iPacks.hasNext()) {
+		  HashMap<String, String> hmCurPack = iPacks.next();
+		  jpPacksTabContent.add(new JClickableLabel(hmCurPack.get("name"), "preview:"
+			  + Resource.PACK + ":" + hmCurPack.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconPack));
+		}
+
+		// .. FRIENDS ..
+		JPanel jpFriendsTabContent = new JPanel();
+		jpFriendsTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+		jpFriendsTabContent.setLayout(new BoxLayout(jpFriendsTabContent, BoxLayout.Y_AXIS));
+
+		// iterate through all friends and add all to the panel
+		Iterator<HashMap<String, String>> iFriends = u.getFriends().iterator();
+		while (iFriends.hasNext()) {
+		  HashMap<String, String> hmCurFriend = iFriends.next();
+		  jpFriendsTabContent.add(new JClickableLabel(hmCurFriend.get("name"), "preview:"
+			  + Resource.USER + ":" + hmCurFriend.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconUser));
+		}
+
+		// .. GROUPS ..
+		JPanel jpGroupsTabContent = new JPanel();
+		jpGroupsTabContent.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+		jpGroupsTabContent.setLayout(new BoxLayout(jpGroupsTabContent, BoxLayout.Y_AXIS));
+
+		// iterate through all groups and add all to the panel
+		Iterator<HashMap<String, String>> iGroups = u.getGroups().iterator();
+		while (iGroups.hasNext()) {
+		  HashMap<String, String> hmCurGroup = iGroups.next();
+		  jpGroupsTabContent.add(new JClickableLabel(hmCurGroup.get("name"), "preview:"
+			  + Resource.GROUP + ":" + hmCurGroup.get("uri"), pluginMainComponent.getPreviewBrowser(), this.iconGroup));
+		}
+
+		// .. WRAP EVERY TAB content into it's own scroll pane ..
+		Dimension dPreferredTabSize = new Dimension(ResourcePreviewBrowser.PREFERRED_WIDTH - 50, PREFERRED_LOWER_TABBED_PANE_HEIGHT);
+
+		JScrollPane spWorkflowsTab = new JScrollPane(jpWorkflowsTabContent);
+		spWorkflowsTab.setBorder(BorderFactory.createEmptyBorder());
+		spWorkflowsTab.setPreferredSize(dPreferredTabSize);
+		spWorkflowsTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+		JScrollPane spFilesTab = new JScrollPane(jpFilesTabContent);
+		spFilesTab.setBorder(BorderFactory.createEmptyBorder());
+		spFilesTab.setPreferredSize(dPreferredTabSize);
+		spFilesTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+		JScrollPane spPacksTab = new JScrollPane(jpPacksTabContent);
+		spPacksTab.setBorder(BorderFactory.createEmptyBorder());
+		spPacksTab.setPreferredSize(dPreferredTabSize);
+		spPacksTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+		JScrollPane spFriendsTab = new JScrollPane(jpFriendsTabContent);
+		spFriendsTab.setBorder(BorderFactory.createEmptyBorder());
+		spFriendsTab.setPreferredSize(dPreferredTabSize);
+		spFriendsTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+		JScrollPane spGroupsTab = new JScrollPane(jpGroupsTabContent);
+		spGroupsTab.setBorder(BorderFactory.createEmptyBorder());
+		spGroupsTab.setPreferredSize(dPreferredTabSize);
+		spGroupsTab.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+		// .. ASSEMBLE ALL TABS together
+		JTabbedPane tpTabbedItems = new JTabbedPane();
+		tpTabbedItems.addTab("Workflows (" + u.getWorkflows().size() + ")", spWorkflowsTab);
+		tpTabbedItems.addTab("Files (" + u.getFiles().size() + ")", spFilesTab);
+		tpTabbedItems.addTab("Packs (" + u.getPacks().size() + ")", spPacksTab);
+		tpTabbedItems.addTab("Friends (" + u.getFriends().size() + ")", spFriendsTab);
+		tpTabbedItems.addTab("Groups (" + u.getGroups().size() + ")", spGroupsTab);
+
+		// === PUT EVERYTHING TOGETHER ===
+		JTextPane tpUserPreview = new JTextPane();
+		tpUserPreview.setEditable(false);
+		tpUserPreview.setEditorKit(kit);
+		tpUserPreview.setDocument(doc);
+		tpUserPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+		JPanel jpFullUserPreview = new JPanel();
+		jpFullUserPreview.setBackground(Color.WHITE); // white background for
+		// the whole user preview
+		// panel
+		jpFullUserPreview.setLayout(new GridBagLayout());
+		GridBagConstraints c = new GridBagConstraints();
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 0;
+		c.weighty = 0; // will not change size when the window is resized
+		jpFullUserPreview.add(tpUserPreview, c);
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 1;
+		c.weighty = 1; // will grow in size when the window is resized..
+		c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+		// vertically
+		c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+		// bottom
+		jpFullUserPreview.add(tpTabbedItems, c);
+
+		// POPULATE THE GIVEN PANEL
+		panelToPopulate.setLayout(new BorderLayout());
+		panelToPopulate.add(jpFullUserPreview, BorderLayout.CENTER);
+
+		// this.statusLabel.setText("User information found. Last fetched: " +
+		// new Date().toString());
+
+		// this.clearButton.setEnabled(true);
+		// this.refreshButton.setEnabled(true);
+		// this.loadButton.setEnabled(true);
+		// this.importButton.setEnabled(true);
+	  } catch (Exception e) {
+		logger.error("Failed to populate Workflow Preview pane", e);
+	  }
+	} else {
+	  // statusLabel.setText("Could not find information for workflow ID: " +
+	  // currentWorkflowId);
+	  // clearContentTextPane();
+	  // disableButtons();
+	}
+  }
+
+  private void generateGroupPreviewContent(Group g, JPanel panelToPopulate, EventListener eventHandler) {
+	if (g != null) {
+	  try {
+		// === Render group details in HTML format ===
+		StringBuffer content = new StringBuffer();
+		content.append("<div class='outer'>");
+		content.append("<div class='group'>");
+
+		content.append("<br>");
+
+		content.append("<p class='title'>");
+		content.append("Group: <a href='preview:" + Resource.GROUP + ":"
+			+ g.getURI() + "'>" + g.getTitle() + "</a>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		content.append("<p class='info'>");
+		content.append("<b>Administrator:</b> <a href='preview:"
+			+ Resource.USER + ":" + g.getAdmin().getURI() + "'>"
+			+ g.getAdmin().getName() + "</a><br>");
+		content.append("<b>Created at: </b> " + g.getCreatedAt() + "<br>");
+		content.append("</p>");
+
+		content.append("<br>");
+
+		if (!g.getDescription().equals("")) {
+		  content.append("<p class='desc'>");
+		  content.append("<br>");
+		  content.append(Util.stripHTML(g.getDescription()));
+		  content.append("<br>");
+		  content.append("</p>");
+		} else {
+		  content.append("<span class='none_text'>No description</span>");
+		}
+
+		content.append("<br>");
+		content.append("</div>");
+		content.append("</div>");
+
+		HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+		HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+		doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+		// === Now render group's items as Swing components ===
+
+		// .. MEMBERS ..
+		JPanel jpMembersTabContent = createStandardTabContentPanel();
+
+		// iterate through all shared items and add all to the panel
+		Iterator<User> iMembers = g.getMembers().iterator();
+		while (iMembers.hasNext()) {
+		  User uCurMember = iMembers.next();
+		  jpMembersTabContent.add(new JClickableLabel(uCurMember.getName(), "preview:"
+			  + uCurMember.getItemType() + ":" + uCurMember.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(uCurMember.getItemType()))));
+		}
+
+		// wrap into a standard scroll pane
+		JScrollPane spMembersTabContent = wrapPreviewTabContentIntoScrollPane(jpMembersTabContent);
+
+		// .. SHARED ITEMS ..
+		JPanel jpSharedItemsTabContent = createStandardTabContentPanel();
+
+		// iterate through all shared items and add all to the panel
+		Iterator<Resource> iSharedItems = g.getSharedItems().iterator();
+		while (iSharedItems.hasNext()) {
+		  Resource rCurItem = iSharedItems.next();
+		  jpSharedItemsTabContent.add(new JClickableLabel(rCurItem.getTitle(), "preview:"
+			  + rCurItem.getItemType() + ":" + rCurItem.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(rCurItem.getItemType()))));
+		}
+
+		// wrap into a standard scroll pane
+		JScrollPane spSharedItemsTabContent = wrapPreviewTabContentIntoScrollPane(jpSharedItemsTabContent);
+
+		// .. TAGS, COMMENTS ..
+		JScrollPane spTagsTabContent = createTagPreviewTab(g.getTags());
+		JScrollPane spCommentsTab = createCommentsPreviewTab(g.getComments());
+
+		// ASSEMBLE tabs together
+		JTabbedPane tpTabbedItems = new JTabbedPane();
+		tpTabbedItems.addTab("Members (" + g.getMemberCount() + ")", spMembersTabContent);
+		tpTabbedItems.addTab("Shared Items (" + g.getSharedItemCount() + ")", spSharedItemsTabContent);
+		tpTabbedItems.addTab("Tags (" + g.getTags().size() + ")", spTagsTabContent);
+		tpTabbedItems.addTab("Comments (" + g.getComments().size() + ")", spCommentsTab);
+
+		// PUT EVERYTHING TOGETHER
+		JTextPane tpGroupPreview = new JTextPane();
+		tpGroupPreview.setEditable(false);
+		tpGroupPreview.setEditorKit(kit);
+		tpGroupPreview.setDocument(doc);
+		tpGroupPreview.addHyperlinkListener((HyperlinkListener) eventHandler);
+
+		JPanel jpFullGroupPreview = new JPanel();
+		jpFullGroupPreview.setBackground(Color.WHITE); // white background for
+		// the whole group
+		// preview panel
+		jpFullGroupPreview.setLayout(new GridBagLayout());
+		GridBagConstraints c = new GridBagConstraints();
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 0;
+		c.weighty = 0; // will not change size when the window is resized
+		jpFullGroupPreview.add(tpGroupPreview, c);
+
+		c.gridx = GridBagConstraints.REMAINDER;
+		c.gridy = 1;
+		c.weighty = 1; // will grow in size when the window is resized..
+		c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+		// vertically
+		c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top &
+		// bottom
+		jpFullGroupPreview.add(tpTabbedItems, c);
+
+		// POPULATE THE GIVEN PANEL
+		panelToPopulate.setLayout(new BorderLayout());
+		panelToPopulate.add(jpFullGroupPreview, BorderLayout.CENTER);
+
+		// this.statusLabel.setText("Group information found. Last fetched: " +
+		// new Date().toString());
+
+		// this.clearButton.setEnabled(true);
+		// this.refreshButton.setEnabled(true);
+		// this.loadButton.setEnabled(true);
+		// this.importButton.setEnabled(true);
+	  } catch (Exception e) {
+		logger.error("Failed to populate Group Preview pane", e);
+	  }
+	} else {
+	  // statusLabel.setText("Could not find information for group ID: " +
+	  // currentGroupId);
+	  // clearContentTextPane();
+	  // disableButtons();
+	}
+  }
+
+  // *** Helper methods follow that generate particular reusable pieces of the
+  // previews ***
+
+  private JScrollPane createWorkflowComponentPreviewTab(Workflow w) {
+	final JPanel jpWorkflowComponentsTabContent = createStandardTabContentPanel();
+
+	if (!w.isTavernaWorkflow()) {
+	  // can only display components for Taverna 1 workflows at the moment
+	  JLabel lNotSupported = new JLabel("<html>This is a "
+		  + w.getVisibleType()
+		  + " workflow;<br>myExperiment can only display Taverna workflow components at the moment.</html>");
+	  lNotSupported.setFont(lNotSupported.getFont().deriveFont(Font.ITALIC));
+	  lNotSupported.setForeground(Color.GRAY);
+	  jpWorkflowComponentsTabContent.add(lNotSupported);
+	} else if (!w.isDownloadAllowed()) {
+	  // can display components for workflow of this type, but current user is
+	  // not
+	  // allowed to download this workflow - and, hence, to view its components
+	  JLabel lNotAuthorized = new JLabel("You are not authorised to download this workflow, "
+		  + "and hence component preview is not available.");
+	  lNotAuthorized.setFont(lNotAuthorized.getFont().deriveFont(Font.ITALIC));
+	  lNotAuthorized.setForeground(Color.GRAY);
+	  jpWorkflowComponentsTabContent.add(lNotAuthorized);
+	} else {
+	  // can display the components
+
+	  // storage for table column names
+	  Vector<String> vColumnNames = new Vector<String>();
+
+	  // ** inputs **
+	  vColumnNames.clear();
+	  vColumnNames.addAll(Arrays.asList(new String[] { "Name", "Description" }));
+
+	  Vector<Vector<String>> vInputsData = new Vector<Vector<String>>();
+	  ArrayList<HashMap<String, String>> inputs = w.getComponents().get("inputs");
+	  if (inputs != null) {
+		for (HashMap<String, String> curInput : inputs) {
+		  Vector<String> vCurData = new Vector<String>();
+		  vCurData.add(curInput.get("name"));
+		  vCurData.add(curInput.get("description"));
+
+		  vInputsData.add(vCurData);
+		}
+	  }
+
+	  JTable jtInputs = new JTable(vInputsData, vColumnNames);
+	  jtInputs.getColumnModel().getColumn(0).setPreferredWidth(100);
+	  jtInputs.getTableHeader().setFont(jtInputs.getTableHeader().getFont().deriveFont(Font.BOLD));
+	  JPanel jpInputs = new JPanel();
+	  jpInputs.setLayout(new BorderLayout());
+	  jpInputs.add(jtInputs.getTableHeader(), BorderLayout.NORTH);
+	  jpInputs.add(jtInputs, BorderLayout.CENTER);
+
+	  JPanel jpInputsWithTitle = new JPanel();
+	  jpInputsWithTitle.setBorder(BorderFactory.createEtchedBorder());
+	  jpInputsWithTitle.setLayout(new BorderLayout());
+	  jpInputsWithTitle.add(new ShadedLabel("Workflow input ports ("
+		  + vInputsData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+	  if (vInputsData.size() > 0) {
+		jpInputsWithTitle.add(jpInputs, BorderLayout.CENTER);
+	  }
+
+	  // ** processors **
+	  vColumnNames.clear();
+	  vColumnNames.addAll(Arrays.asList(new String[] { "Name", "Type", "Description" }));
+
+	  Vector<Vector<String>> vProcessorsData = new Vector<Vector<String>>();
+	  ArrayList<HashMap<String, String>> processors = w.getComponents().get("processors");
+	  if (processors != null) {
+		for (HashMap<String, String> curProcessor : processors) {
+		  Vector<String> vCurData = new Vector<String>();
+		  vCurData.add(curProcessor.get("name"));
+		  vCurData.add(curProcessor.get("type"));
+		  vCurData.add(curProcessor.get("description"));
+
+		  vProcessorsData.add(vCurData);
+		}
+	  }
+
+	  JTable jtProcessors = new JTable(vProcessorsData, vColumnNames);
+	  jtProcessors.getTableHeader().setFont(jtProcessors.getTableHeader().getFont().deriveFont(Font.BOLD));
+	  JPanel jpProcessors = new JPanel();
+	  jpProcessors.setLayout(new BorderLayout());
+	  jpProcessors.add(jtProcessors.getTableHeader(), BorderLayout.NORTH);
+	  jpProcessors.add(jtProcessors, BorderLayout.CENTER);
+
+	  JPanel jpProcessorsWithTitle = new JPanel();
+	  jpProcessorsWithTitle.setBorder(BorderFactory.createEtchedBorder());
+	  jpProcessorsWithTitle.setLayout(new BorderLayout());
+	  jpProcessorsWithTitle.add(new ShadedLabel("Services ("
+		  + vProcessorsData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+	  if (vProcessorsData.size() > 0) {
+		jpProcessorsWithTitle.add(jpProcessors, BorderLayout.CENTER);
+	  }
+
+	  // ** links **
+	  vColumnNames.clear();
+	  vColumnNames.addAll(Arrays.asList(new String[] { "Source", "Sink" }));
+
+	  Vector<Vector<String>> vLinksData = new Vector<Vector<String>>();
+	  ArrayList<HashMap<String, String>> links = w.getComponents().get("links");
+	  if (links != null) {
+		for (HashMap<String, String> curLink : links) {
+		  Vector<String> vCurData = new Vector<String>();
+		  vCurData.add(curLink.get("source"));
+		  vCurData.add(curLink.get("sink"));
+
+		  vLinksData.add(vCurData);
+		}
+	  }
+
+	  JTable jtLinks = new JTable(vLinksData, vColumnNames);
+	  jtLinks.getColumnModel().getColumn(0).setPreferredWidth(100);
+	  jtLinks.getTableHeader().setFont(jtLinks.getTableHeader().getFont().deriveFont(Font.BOLD));
+	  JPanel jpLinks = new JPanel();
+	  jpLinks.setLayout(new BorderLayout());
+	  jpLinks.add(jtLinks.getTableHeader(), BorderLayout.NORTH);
+	  jpLinks.add(jtLinks, BorderLayout.CENTER);
+
+	  JPanel jpLinksWithTitle = new JPanel();
+	  jpLinksWithTitle.setBorder(BorderFactory.createEtchedBorder());
+	  jpLinksWithTitle.setLayout(new BorderLayout());
+	  jpLinksWithTitle.add(new ShadedLabel("Links (" + vLinksData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+	  if (vLinksData.size() > 0) {
+		jpLinksWithTitle.add(jpLinks, BorderLayout.CENTER);
+	  }
+
+	  // ** outputs **
+	  vColumnNames.clear();
+	  vColumnNames.addAll(Arrays.asList(new String[] { "Name", "Description" }));
+
+	  Vector<Vector<String>> vOutputsData = new Vector<Vector<String>>();
+	  ArrayList<HashMap<String, String>> outputs = w.getComponents().get("outputs");
+	  if (outputs != null) {
+		for (HashMap<String, String> curOutput : outputs) {
+		  Vector<String> vCurData = new Vector<String>();
+		  vCurData.add(curOutput.get("name"));
+		  vCurData.add(curOutput.get("description"));
+
+		  vOutputsData.add(vCurData);
+		}
+	  }
+
+	  JTable jtOutputs = new JTable(vOutputsData, vColumnNames);
+	  jtOutputs.getColumnModel().getColumn(0).setPreferredWidth(100);
+	  jtOutputs.getTableHeader().setFont(jtOutputs.getTableHeader().getFont().deriveFont(Font.BOLD));
+	  JPanel jpOutputs = new JPanel();
+	  jpOutputs.setLayout(new BorderLayout());
+	  jpOutputs.add(jtOutputs.getTableHeader(), BorderLayout.NORTH);
+	  jpOutputs.add(jtOutputs, BorderLayout.CENTER);
+
+	  JPanel jpOutputsWithTitle = new JPanel();
+	  jpOutputsWithTitle.setBorder(BorderFactory.createEtchedBorder());
+	  jpOutputsWithTitle.setLayout(new BorderLayout());
+	  jpOutputsWithTitle.add(new ShadedLabel("Workflow output ports ("
+		  + vOutputsData.size() + ")", ShadedLabel.BLUE, true), BorderLayout.NORTH);
+	  if (vOutputsData.size() > 0) {
+		jpOutputsWithTitle.add(jpOutputs, BorderLayout.CENTER);
+	  }
+
+	  // PUT EVERYTHING TOGETHER
+	  jpWorkflowComponentsTabContent.setLayout(new GridBagLayout());
+	  GridBagConstraints c = new GridBagConstraints();
+
+	  c.gridx = 0;
+	  c.gridy = GridBagConstraints.RELATIVE;
+	  c.weightx = 1.0;
+	  c.fill = GridBagConstraints.HORIZONTAL;
+	  c.anchor = GridBagConstraints.NORTH;
+	  jpWorkflowComponentsTabContent.add(jpInputsWithTitle, c);
+
+	  c.insets = new Insets(10, 0, 0, 0);
+	  jpWorkflowComponentsTabContent.add(jpProcessorsWithTitle, c);
+
+	  jpWorkflowComponentsTabContent.add(jpLinksWithTitle, c);
+
+	  c.weighty = 1.0; // ONLY FOR THE LAST ELEMENT
+	  jpWorkflowComponentsTabContent.add(jpOutputsWithTitle, c);
+	}
+
+	return (wrapPreviewTabContentIntoScrollPane(jpWorkflowComponentsTabContent));
+  }
+
+  private JScrollPane createPackItemPreviewTab(Pack p) {
+	JPanel jpPackItemsTabContent = createStandardTabContentPanel();
+	GridBagConstraints c = new GridBagConstraints();
+	jpPackItemsTabContent.setLayout(new GridBagLayout());
+	c.anchor = GridBagConstraints.NORTHWEST;
+
+	// iterate through all internal and external items and add all to the panel
+	if (p.getItems().size() > 0) {
+	  int iCnt = 0;
+	  boolean bNoCommentForPrevItem = false;
+
+	  for (PackItem piCurItem : p.getItems()) {
+		c.gridx = 0;
+		c.gridy = 3 * iCnt;
+		c.weightx = 1.0;
+		c.insets = (bNoCommentForPrevItem ? new Insets(7, 0, 0, 0) : new Insets(0, 0, 0, 0));
+		c.fill = GridBagConstraints.NONE;
+		// item data is stored differently whether the item is internal or
+		// external
+		if (piCurItem.isInternalItem()) {
+		  jpPackItemsTabContent.add(new JClickableLabel(piCurItem.getItem().getTitle(), "preview:"
+			  + piCurItem.getItem().getItemType()
+			  + ":"
+			  + piCurItem.getItem().getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(piCurItem.getItem().getItemType()))), c);
+		} else {
+		  jpPackItemsTabContent.add(new JClickableLabel(piCurItem.getTitle(), piCurItem.getLink(), // link should open up directly in the web
+		  // browser
+		  pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(piCurItem.getItemType())), SwingConstants.LEFT, piCurItem.getTitle()
+			  + " (link: " + piCurItem.getLink() + ")"), c);
+		}
+
+		// prepare comment before populating the metadata
+		String strComment = Util.stripAllHTML(piCurItem.getComment());
+		bNoCommentForPrevItem = (strComment == null || strComment.length() == 0);
+
+		// add metadata to the item ..
+		// .. who and when added the item ..
+		JPanel jpWhoAddedTheItem = new JPanel();
+		jpWhoAddedTheItem.setLayout(new GridBagLayout());
+		GridBagConstraints c1 = new GridBagConstraints();
+		c1.anchor = GridBagConstraints.NORTHWEST;
+		jpWhoAddedTheItem.setBorder(BorderFactory.createEmptyBorder());
+		jpWhoAddedTheItem.add(new JLabel("Added by "), c1);
+		jpWhoAddedTheItem.add(new JClickableLabel(piCurItem.getUserWhoAddedTheItem().getName(), "preview:"
+			+ Resource.USER + ":" + piCurItem.getUserWhoAddedTheItem().getURI(), pluginMainComponent.getPreviewBrowser()), c1);
+
+		String strAddedOnDate = MyExperimentClient.formatDate(piCurItem.getCreatedAt());
+		c1.weightx = 1.0;
+		jpWhoAddedTheItem.add(new JLabel(" [" + strAddedOnDate + "]"), c1);
+
+		c.gridx = 0;
+		c.gridy = 3 * iCnt + 1;
+		c.insets = new Insets(0, 25, 0, 0);
+		c.weightx = 1.0;
+		if (bNoCommentForPrevItem && (iCnt + 1 == p.getItems().size()))
+		  c.weighty = 1.0;
+		jpPackItemsTabContent.add(jpWhoAddedTheItem, c);
+
+		// .. and the comment
+		if (!bNoCommentForPrevItem) {
+		  c.gridx = 0;
+		  c.gridy = 3 * iCnt + 2;
+		  c.fill = GridBagConstraints.HORIZONTAL;
+		  c.insets = new Insets(0, 25, 7, 25);
+		  c.weightx = 1.0;
+		  if (iCnt + 1 == p.getItems().size())
+			c.weighty = 1.0; // only if this is the comment for the last item,
+		  // shift all items to the top of the panel
+
+		  DialogTextArea taCommentText = new DialogTextArea("Comment: "
+			  + strComment);
+		  taCommentText.setOpaque(false);
+		  taCommentText.setEditable(false);
+		  taCommentText.setLineWrap(true);
+		  taCommentText.setWrapStyleWord(true);
+		  jpPackItemsTabContent.add(taCommentText, c);
+		}
+
+		// update the item counter
+		iCnt++;
+	  }
+	} else {
+	  c.weighty = 1.0;
+	  c.weightx = 1.0;
+	  jpPackItemsTabContent.add(Util.generateNoneTextLabel("None"), c);
+	}
+
+	return (wrapPreviewTabContentIntoScrollPane(jpPackItemsTabContent));
+  }
+
+  private JScrollPane createTagPreviewTab(List<Tag> lTags) {
+	TagCloudPanel jpTagTabContent = new TagCloudPanel("Resource tag cloud", TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW, pluginMainComponent.getPreviewBrowser(), pluginMainComponent, myExperimentClient, logger);
+	jpTagTabContent.getTagCloudData().clear();
+	jpTagTabContent.getTagCloudData().addAll(lTags);
+	jpTagTabContent.refresh();
+
+	// tag cloud panel itself already has a scroll pane in it; hence the outer
+	// scroll pane
+	// is only used for consistency across the user interface (contents of all
+	// tabs in the
+	// preview window are scroll panes); the preferred size of the tag cloud
+	// panel should
+	// still be adjusted accordingly to the preferred size of the outer scroll
+	// pane
+	JScrollPane spTagTabContent = wrapPreviewTabContentIntoScrollPane(jpTagTabContent);
+	spTagTabContent.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+	spTagTabContent.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+
+	jpTagTabContent.setPreferredSize(spTagTabContent.getPreferredSize());
+
+	return (spTagTabContent);
+  }
+
+  private JScrollPane createCommentsPreviewTab(List<Comment> comments) {
+	final List<Comment> lComments = comments;
+	final JPanel jpCommentsTabContent = createStandardTabContentPanel();
+
+	if (lComments.size() > 0) {
+	  final GridBagConstraints c = new GridBagConstraints();
+	  jpCommentsTabContent.setLayout(new GridBagLayout());
+	  c.anchor = GridBagConstraints.NORTHWEST;
+
+	  // a placeholder for comments while they are loading
+	  JLabel lLoading = new JLabel("Loading comments...", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("spinner")), SwingConstants.LEFT);
+	  lLoading.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 10));
+	  c.weightx = 1.0;
+	  c.weighty = 1.0;
+	  jpCommentsTabContent.add(lLoading, c);
+
+	  new Thread("Load comments for preview") {
+		@Override
+		public void run() {
+		  myExperimentClient.updateCommentListWithExtraData(lComments);
+
+		  SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+			  // remove 'loading...' placeholder
+			  jpCommentsTabContent.removeAll();
+
+			  int iCnt = 0;
+			  for (Comment comment : lComments) {
+				c.gridx = 0;
+				c.gridy = 2 * iCnt;
+				c.weightx = 0;
+				c.weighty = 0;
+				c.gridwidth = 1;
+				JClickableLabel lCommentAuthor = new JClickableLabel(comment.getUser().getName(), "preview:"
+					+ comment.getUser().getItemType()
+					+ ":"
+					+ comment.getUser().getURI(), pluginMainComponent.getPreviewBrowser(), iconUser);
+				jpCommentsTabContent.add(lCommentAuthor, c);
+
+				c.gridx = 1;
+				c.gridy = 2 * iCnt;
+				c.weightx = 1.0;
+				String strCommentDate = MyExperimentClient.formatDate(comment.getCreatedAt());
+				JLabel lCommentDate = new JLabel(" - [" + strCommentDate + "]");
+				lCommentDate.setBorder(lCommentAuthor.getBorder());
+				jpCommentsTabContent.add(lCommentDate, c);
+
+				c.gridx = 0;
+				c.gridy = 2 * iCnt + 1;
+				c.weightx = 1.0;
+				c.gridwidth = 2;
+				c.fill = GridBagConstraints.HORIZONTAL;
+				if (iCnt + 1 == lComments.size())
+				  c.weighty = 1.0;
+
+				DialogTextArea taCommentText = new DialogTextArea(Util.stripAllHTML(comment.getComment()));
+				taCommentText.setBorder(BorderFactory.createEmptyBorder(0, 25, 10, 25));
+				taCommentText.setOpaque(false);
+				taCommentText.setEditable(false);
+				taCommentText.setLineWrap(true);
+				taCommentText.setWrapStyleWord(true);
+				jpCommentsTabContent.add(taCommentText, c);
+
+				iCnt++;
+			  }
+
+			  jpCommentsTabContent.validate();
+			  jpCommentsTabContent.repaint();
+
+			  // this will ensure that even if there are many comments,
+			  // the comment panel is still shown starting at the top comment
+			  SwingUtilities.invokeLater(new Runnable() {
+				public void run() {
+				  jpCommentsTabContent.scrollRectToVisible(new Rectangle());
+				}
+			  });
+			}
+		  });
+		}
+	  }.start();
+	} else {
+	  jpCommentsTabContent.add(Util.generateNoneTextLabel("None"));
+	}
+
+	JScrollPane spCommentsTabContent = wrapPreviewTabContentIntoScrollPane(jpCommentsTabContent);
+	spCommentsTabContent.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+	return (spCommentsTabContent);
+  }
+
+  private JScrollPane createCreditsPreviewTab(List<Resource> lCreditedUsersOrGroups) {
+	JPanel jpCreditsTabContent = createStandardTabContentPanel();
+
+	if (lCreditedUsersOrGroups.size() > 0) {
+	  for (Resource r : lCreditedUsersOrGroups) {
+		jpCreditsTabContent.add(new JClickableLabel(r.getTitle(), "preview:"
+			+ r.getItemType() + ":" + r.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(r.getItemType()))));
+	  }
+	} else {
+	  jpCreditsTabContent.add(Util.generateNoneTextLabel("None"));
+	}
+
+	return (wrapPreviewTabContentIntoScrollPane(jpCreditsTabContent));
+  }
+
+  private JScrollPane createAttributionsPreviewTab(List<Resource> lAttributions) {
+	JPanel jpAttributionsTabContent = createStandardTabContentPanel();
+
+	if (lAttributions.size() > 0) {
+	  for (Resource r : lAttributions) {
+		jpAttributionsTabContent.add(new JClickableLabel(r.getTitle(), "preview:"
+			+ r.getItemType() + ":" + r.getURI(), pluginMainComponent.getPreviewBrowser(), new ImageIcon(MyExperimentPerspective.getLocalIconURL(r.getItemType()))));
+	  }
+	} else {
+	  jpAttributionsTabContent.add(Util.generateNoneTextLabel("None"));
+	}
+
+	return (wrapPreviewTabContentIntoScrollPane(jpAttributionsTabContent));
+  }
+
+  /**
+   * A standard starting point for all preview window tabs.
+   */
+  private JPanel createStandardTabContentPanel() {
+	JPanel jpTabContentPanel = new JPanel();
+	jpTabContentPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+	jpTabContentPanel.setLayout(new BoxLayout(jpTabContentPanel, BoxLayout.Y_AXIS));
+
+	return (jpTabContentPanel);
+  }
+
+  private JScrollPane wrapPreviewTabContentIntoScrollPane(JPanel jpTabContentPanel) {
+	// WRAPS TAB CONTENT into it's own SCROLL PANE ..
+	Dimension dPreferredTabSize = new Dimension(ResourcePreviewBrowser.PREFERRED_WIDTH - 50, PREFERRED_LOWER_TABBED_PANE_HEIGHT);
+
+	JScrollPane spTabContent = new JScrollPane(jpTabContentPanel);
+	spTabContent.setBorder(BorderFactory.createEmptyBorder());
+	spTabContent.setPreferredSize(dPreferredTabSize);
+	spTabContent.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+
+	return (spTabContent);
+  }
+
+  private JPanel wrapTextPaneAndTabbedViewIntoFullPreview(JTextPane tpHTMLPreview, JTabbedPane tpTabbedView) {
+	// WRAPS HTML JTextPane PREVIEW AND A JTabbedPane WITH DETAILS INTO A SINGLE
+	// PREVIEW PANEL
+	JPanel jpFullPreview = new JPanel();
+	jpFullPreview.setBackground(Color.WHITE); // white background for the whole
+	// preview panel
+	jpFullPreview.setLayout(new GridBagLayout());
+	GridBagConstraints c = new GridBagConstraints();
+
+	c.gridx = GridBagConstraints.REMAINDER;
+	c.gridy = 0;
+	c.weighty = 0; // will not change size when the window is resized
+	jpFullPreview.add(tpHTMLPreview, c);
+
+	c.gridx = GridBagConstraints.REMAINDER;
+	c.gridy = 1;
+	c.weighty = 1; // will grow in size when the window is resized..
+	c.fill = GridBagConstraints.VERTICAL; // ..and fill all available space
+	// vertically
+	c.insets = new Insets(20, 0, 5, 0); // a bit of margin at the top & bottom
+	jpFullPreview.add(tpTabbedView, c);
+
+	return (jpFullPreview);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java
new file mode 100644
index 0000000..b2fd9f5
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchOptionsPanel.java
@@ -0,0 +1,317 @@
+/*******************************************************************************
+ * Copyright (C) 2009 The University of Manchester
+ * 
+ * Modifications to the initial code base are copyright of their respective
+ * authors, or their employers as appropriate.
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.SpinnerNumberModel;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs, Emmanuel Tagarira
+ */
+public class SearchOptionsPanel extends JPanel implements ItemListener, KeyListener {
+  // CONSTANTS
+  protected static final int SEARCH_RESULT_LIMIT_MIN = 1;
+  protected static final int SEARCH_RESULT_LIMIT_INIT = 20;
+  protected static final int SEARCH_RESULT_LIMIT_MAX = 100;
+
+  private final MainComponent pluginMainComponent;
+  private final MyExperimentClient myExperimentClient;
+  private final Logger logger;
+  private final ActionListener clickHandler;
+
+  // COMPONENTS
+  protected JButton bSearch;
+  private JTextField tfSearchQuery;
+  private JCheckBox cbSearchAllTypes;
+  private JCheckBox cbWorkflows;
+  private JCheckBox cbFiles;
+  private JCheckBox cbPacks;
+  private JCheckBox cbUsers;
+  private JCheckBox cbGroups;
+  protected JSpinner jsResultLimit;
+  protected JTextField tfResultLimitTextField;
+
+  // Data
+  ArrayList<JCheckBox> alDataTypeCheckboxes;
+
+  public SearchOptionsPanel(ActionListener actionListener, MainComponent component, MyExperimentClient client, Logger logger) {
+	super();
+
+	// set main variables to ensure access to myExperiment, logger and the parent component
+	this.pluginMainComponent = component;
+	this.myExperimentClient = client;
+	this.logger = logger;
+	this.clickHandler = actionListener;
+
+	this.initialiseUI();
+
+	// this will hold the collection of all checkboxes that correspond to data types (will be used in item event handling)
+	alDataTypeCheckboxes = new ArrayList<JCheckBox>(Arrays.asList(new JCheckBox[] { cbWorkflows, cbFiles, cbPacks, cbUsers, cbGroups }));
+  }
+
+  private void initialiseUI() {
+	this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Search Settings "), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
+
+	this.setLayout(new GridBagLayout());
+	GridBagConstraints c = new GridBagConstraints();
+
+	c.gridx = 0;
+	c.gridy = 0;
+	c.anchor = GridBagConstraints.WEST;
+	this.add(new JLabel("Query"), c);
+
+	c.gridx = 0;
+	c.gridy = 1;
+	c.gridwidth = 4;
+	c.fill = GridBagConstraints.HORIZONTAL;
+	c.weightx = 1.0;
+	tfSearchQuery = new JTextField();
+	tfSearchQuery.setToolTipText("<html>Tips for creating search queries:<br>1) Use wildcards to make more "
+		+ "flexible queries. Asterisk (<b>*</b>) matches any zero or more<br>&nbsp;&nbsp;&nbsp;&nbsp;characters (e.g. "
+		+ "<b><i>Seq*</i></b> would match <b><i>Sequence</i></b>), question mark (<b>?</b>) matches any single<br>"
+		+ "&nbsp;&nbsp;&nbsp;&nbsp;character (e.g. <b><i>Tave?na</i></b> would match <b><i>Taverna</i></b>).<br>"
+		+ "2) Enclose the <b><i>\"search query\"</i></b> in double quotes to make exact phrase matching, otherwise<br>"
+		+ "&nbsp;&nbsp;&nbsp;&nbsp;items that contain any (or all) words in the <b><i>search query</i></b> will be found.</html>");
+	tfSearchQuery.addKeyListener(this);
+	this.add(tfSearchQuery, c);
+
+	c.gridx = 4;
+	c.gridy = 1;
+	c.gridwidth = 1;
+	c.fill = GridBagConstraints.NONE;
+	c.weightx = 0;
+	c.insets = new Insets(0, 5, 0, 0);
+	bSearch = new JButton("Search", WorkbenchIcons.searchIcon);
+	bSearch.addActionListener(this.clickHandler);
+	bSearch.addKeyListener(this);
+	this.add(bSearch, c);
+
+	c.gridx = 0;
+	c.gridy = 2;
+	c.insets = new Insets(10, 0, 3, 0);
+	this.add(new JLabel("Search for..."), c);
+
+	c.gridx = 0;
+	c.gridy = 3;
+	c.gridwidth = 2;
+	c.insets = new Insets(0, 0, 0, 0);
+	cbSearchAllTypes = new JCheckBox("all resource types", true);
+	cbSearchAllTypes.addItemListener(this);
+	cbSearchAllTypes.addKeyListener(this);
+	this.add(cbSearchAllTypes, c);
+
+	c.gridx = 0;
+	c.gridy = 4;
+	c.gridwidth = 1;
+	c.ipady = 0;
+	cbWorkflows = new JCheckBox("workflows", true);
+	cbWorkflows.addItemListener(this);
+	cbWorkflows.addKeyListener(this);
+	this.add(cbWorkflows, c);
+
+	c.gridx = 0;
+	c.gridy = 5;
+	cbFiles = new JCheckBox("files", true);
+	cbFiles.addItemListener(this);
+	cbFiles.addKeyListener(this);
+	this.add(cbFiles, c);
+
+	c.gridx = 0;
+	c.gridy = 6;
+	cbPacks = new JCheckBox("packs", true);
+	cbPacks.addItemListener(this);
+	cbPacks.addKeyListener(this);
+	this.add(cbPacks, c);
+
+	c.gridx = 1;
+	c.gridy = 4;
+	cbUsers = new JCheckBox("users", true);
+	cbUsers.addItemListener(this);
+	cbUsers.addKeyListener(this);
+	this.add(cbUsers, c);
+
+	c.gridx = 1;
+	c.gridy = 5;
+	cbGroups = new JCheckBox("groups", true);
+	cbGroups.addItemListener(this);
+	cbGroups.addKeyListener(this);
+	this.add(cbGroups, c);
+
+	c.gridx = 3;
+	c.gridy = 2;
+	c.insets = new Insets(10, 25, 3, 0);
+	JLabel jlResultLimit = new JLabel("Result limit");
+	this.add(jlResultLimit, c);
+
+	c.gridx = 3;
+	c.gridy = 3;
+	c.insets = new Insets(0, 25, 0, 0);
+	jsResultLimit = new JSpinner(new SpinnerNumberModel(SEARCH_RESULT_LIMIT_INIT, SEARCH_RESULT_LIMIT_MIN, SEARCH_RESULT_LIMIT_MAX, 1));
+	jsResultLimit.setPreferredSize(new Dimension(jlResultLimit.getPreferredSize().width, jsResultLimit.getPreferredSize().height));
+	this.add(jsResultLimit, c);
+
+	// adding KeyListener to JSpinner directly doesn't make sense; need to attach KeyListener to the text field that is associated with spinner
+	tfResultLimitTextField = ((JSpinner.DefaultEditor) this.jsResultLimit.getEditor()).getTextField();
+	tfResultLimitTextField.addKeyListener(this);
+  }
+
+  public String getSearchQuery() {
+	return (this.tfSearchQuery.getText());
+  }
+
+  public void setSearchQuery(String strSearchQuery) {
+	this.tfSearchQuery.setText(strSearchQuery);
+  }
+
+  public void focusSearchQueryField() {
+	this.tfSearchQuery.selectAll();
+	this.tfSearchQuery.requestFocusInWindow();
+  }
+
+  public void setSearchAllResourceTypes(boolean bSearchAllTypes) {
+	this.cbSearchAllTypes.setSelected(bSearchAllTypes);
+  }
+
+  public boolean getSearchWorkflows() {
+	return (this.cbWorkflows.isSelected());
+  }
+
+  public void setSearchWorkflows(boolean bSearchWorkflows) {
+	this.cbWorkflows.setSelected(bSearchWorkflows);
+  }
+
+  public boolean getSearchFiles() {
+	return (this.cbFiles.isSelected());
+  }
+
+  public void setSearchFiles(boolean bSearchFiles) {
+	this.cbFiles.setSelected(bSearchFiles);
+  }
+
+  public boolean getSearchPacks() {
+	return (this.cbPacks.isSelected());
+  }
+
+  public void setSearchPacks(boolean bSearchPacks) {
+	this.cbPacks.setSelected(bSearchPacks);
+  }
+
+  public boolean getSearchUsers() {
+	return (this.cbUsers.isSelected());
+  }
+
+  public void setSearchUsers(boolean bSearchUsers) {
+	this.cbUsers.setSelected(bSearchUsers);
+  }
+
+  public boolean getSearchGroups() {
+	return (this.cbGroups.isSelected());
+  }
+
+  public void setSearchGroups(boolean bSearchGroups) {
+	this.cbGroups.setSelected(bSearchGroups);
+  }
+
+  public int getResultCountLimit() {
+	// JSpinner handles value validation itself, so there is no need to
+	// make our own validation too
+	return (Integer.parseInt(this.jsResultLimit.getValue().toString()));
+  }
+
+  public void setResultCountLimit(int iLimit) {
+	this.jsResultLimit.setValue(iLimit);
+  }
+
+  // this monitors all checkbox clicks and selects / deselects other checkboxes which are relevant
+  public void itemStateChanged(ItemEvent e) {
+	if (e.getItemSelectable().equals(this.cbSearchAllTypes)) {
+	  // "all resource types" clicked - need to select / deselect all data type checkboxes accordingly
+	  for (JCheckBox cb : this.alDataTypeCheckboxes) {
+		cb.removeItemListener(this);
+		cb.setSelected(this.cbSearchAllTypes.isSelected());
+		cb.addItemListener(this);
+	  }
+
+	  // also, enable / disable the search button
+	  this.bSearch.setEnabled(this.cbSearchAllTypes.isSelected());
+	} else if (this.alDataTypeCheckboxes.contains(e.getItemSelectable())) {
+	  // one of the checkboxes for data types was clicked (e.g. workflows, files, etc);
+	  // need to calculate how many of those are currently selected
+	  int iSelectedCnt = 0;
+	  for (JCheckBox cb : this.alDataTypeCheckboxes) {
+		if (cb.isSelected())
+		  iSelectedCnt++;
+	  }
+
+	  // if all are selected, tick "search all types" checkbox; uncheck otherwise
+	  this.cbSearchAllTypes.removeItemListener(this);
+	  this.cbSearchAllTypes.setSelected(iSelectedCnt == this.alDataTypeCheckboxes.size());
+	  this.cbSearchAllTypes.addItemListener(this);
+
+	  // enable search button if at least one data type is selected; disable otherwise
+	  this.bSearch.setEnabled(iSelectedCnt > 0);
+	}
+  }
+
+  public void keyPressed(KeyEvent e) {
+	// ENTER pressed - start search by simulating "search" button click
+	// (only do this if the "search" button was active at that moment)
+	if (e.getKeyCode() == KeyEvent.VK_ENTER
+		&& this.bSearch.isEnabled()
+		&& (Arrays.asList(new JComponent[] { this.tfSearchQuery, this.bSearch, this.cbSearchAllTypes, this.tfResultLimitTextField }).contains(e.getSource()) || this.alDataTypeCheckboxes.contains(e.getSource()))) {
+	  this.clickHandler.actionPerformed(new ActionEvent(this.bSearch, 0, ""));
+	}
+  }
+
+  public void keyReleased(KeyEvent e) {
+	// do nothing
+  }
+
+  public void keyTyped(KeyEvent e) {
+	// do nothing
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java
new file mode 100644
index 0000000..dbc3b36
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchResultsPanel.java
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * Copyright (C) 2009 The University of Manchester
+ * 
+ * Modifications to the initial code base are copyright of their respective
+ * authors, or their employers as appropriate.
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Resource;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResultsPanel extends JPanel
+{
+  // CONSTANTS
+  public static final String NO_SEARCHES_STATUS = "No searches have been done so far";
+  
+  
+  private MainComponent pluginMainComponent;
+  private MyExperimentClient myExperimentClient;
+  private Logger logger;
+  
+  // COMPONENTS
+  private JLabel lStatusLabel;
+  public JButton bRefresh;
+  public JButton bClear;
+  private JPanel jpResultsBody;
+  private ActionListener alClickHandler;
+  
+  // result data store
+  boolean bNoSearchesMadeYet;
+  String strCurrentSearchTerm;
+  HashMap<Integer, ArrayList<Resource>> hmSearchResults;
+  
+  
+  public SearchResultsPanel(ActionListener buttonClickHandler, MainComponent component, MyExperimentClient client, Logger logger)
+  {
+    super();
+    
+    // set main variables to ensure access to myExperiment, logger and the parent component
+    this.pluginMainComponent = component;
+    this.myExperimentClient = client;
+    this.logger = logger;
+    this.alClickHandler = buttonClickHandler;
+    
+    // initialise the result data collection to an empty one, just in case
+    // someone calls refresh() before setting the real result data
+    bNoSearchesMadeYet = true;
+    hmSearchResults = new HashMap<Integer, ArrayList<Resource>>();
+    
+    this.initialiseUI();
+  }
+
+  
+  
+  private void initialiseUI()
+  {
+    // label to hold the status of search (e.g. result query + count, etc)
+    this.lStatusLabel = new JLabel(NO_SEARCHES_STATUS);
+    this.lStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); // a bit of padding on the left
+    
+    // control buttons for the search results panel
+    JPanel jpButtonsPanel = new JPanel();
+    jpButtonsPanel.setLayout(new BoxLayout(jpButtonsPanel, BoxLayout.LINE_AXIS));
+    this.bClear = new JButton("Clear", WorkbenchIcons.deleteIcon);
+    this.bClear.addActionListener(this.alClickHandler);
+    this.bClear.setToolTipText("Click this button to clear the search results");
+    this.bClear.setEnabled(false);
+    jpButtonsPanel.add(this.bClear);
+    this.bRefresh = new JButton("Refresh", WorkbenchIcons.refreshIcon);
+    this.bRefresh.addActionListener(this.alClickHandler);
+    this.bRefresh.setToolTipText("Click this button to refresh the search results");
+    this.bRefresh.setEnabled(false);
+    jpButtonsPanel.add(this.bRefresh);
+    
+    // status panel containing the status label and control buttons
+    JPanel jpStatusPanel = new JPanel(new BorderLayout());
+    jpStatusPanel.setBorder(BorderFactory.createEtchedBorder());
+    jpStatusPanel.add(this.lStatusLabel, BorderLayout.CENTER);
+    jpStatusPanel.add(jpButtonsPanel, BorderLayout.EAST);
+    
+    // create empty panel for the main search result content
+    this.jpResultsBody = new JPanel();
+    jpResultsBody.setLayout(new BorderLayout());
+    
+    // PUT EVERYTHING TOGETHER
+    this.setLayout(new BorderLayout());
+    this.add(jpStatusPanel, BorderLayout.NORTH);
+    this.add(this.jpResultsBody, BorderLayout.CENTER);
+    
+    // tabbed results view is missing from this method - this is
+    // because if no results will be found in a particular category,
+    // that tab will not be displayed (hence the results view will
+    // need to be generated every time dynamically)
+  }
+  
+  
+  public void setCurrentSearchTerm(String strSearchTerm)
+  {
+    this.strCurrentSearchTerm = strSearchTerm;
+  }
+  
+  public String getCurrentSearchTerm()
+  {
+    return (this.strCurrentSearchTerm);
+  }
+  
+  
+  public void setSearchResultsData(HashMap<Integer, ArrayList<Resource>> hmResults)
+  {
+    this.bNoSearchesMadeYet = false;
+    this.hmSearchResults = hmResults;
+  }
+  
+  
+  public void setStatus(String status)
+  {
+    this.lStatusLabel.setText(status);
+  }
+  
+  public void refresh()
+  {
+    // remove all items from the main results content panel
+    this.jpResultsBody.removeAll();
+    
+    if (!this.bNoSearchesMadeYet) // this will be true if the result collection was never assigned to this result panel
+    {
+      ArrayList<Integer> alResourceTypes = new ArrayList<Integer>(this.hmSearchResults.keySet());
+      Collections.sort(alResourceTypes);  // this will ensure that the tabs are always in the correct order
+      if(alResourceTypes.isEmpty()) {
+        this.jpResultsBody.add(new JLabel("No items to display"), BorderLayout.NORTH);
+        this.repaint();
+      }
+      else {
+        JTabbedPane tpResults = new JTabbedPane();
+        
+        for(int iType : alResourceTypes)
+        {
+          // HACK: for now all item types are suitably turned into plural by simply appending "s"
+          String strTabLabel = Resource.getResourceTypeName(iType) + "s (" + this.hmSearchResults.get(iType).size() + ")";
+          
+          ResourceListPanel jpTabContents = new ResourceListPanel(pluginMainComponent, myExperimentClient, logger);
+          jpTabContents.setListItems(this.hmSearchResults.get(iType));
+          
+          JScrollPane spTabContents = new JScrollPane(jpTabContents);
+          spTabContents.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+          
+          tpResults.add(strTabLabel, spTabContents);
+        }
+        
+        this.jpResultsBody.add(tpResults, BorderLayout.CENTER);
+        this.bClear.setEnabled(true);
+        this.bRefresh.setEnabled(true);
+      }
+    }
+    
+    this.revalidate();
+  }
+  
+  
+  public void clear()
+  {
+    // remove all items from the main results content panel
+    this.jpResultsBody.removeAll();
+    this.repaint();
+    this.revalidate();
+  }
+  
+}