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

[03/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/SearchTabContentPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchTabContentPanel.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchTabContentPanel.java
new file mode 100644
index 0000000..b583491
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/SearchTabContentPanel.java
@@ -0,0 +1,450 @@
+/*******************************************************************************
+ * 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.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Base64;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine.QuerySearchInstance;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchTabContentPanel extends JPanel implements ActionListener {
+  // CONSTANTS
+  private final static int SEARCH_HISTORY_LENGTH = 50;
+  private final static int SEARCH_FAVOURITES_LENGTH = 30;
+  protected final static String SEARCH_FROM_FAVOURITES = "searchFromFavourites";
+  protected final static String SEARCH_FROM_HISTORY = "searchFromHistory";
+  protected final static String ADD_FAVOURITE_SEARCH_INSTANCE = "addFavouriteSearchInstance";
+  protected final static String REMOVE_FAVOURITE_SEARCH_INSTANCE = "removeFavouriteSearchInstance";
+
+  private final MainComponent pluginMainComponent;
+  private final MyExperimentClient myExperimentClient;
+  private final Logger logger;
+
+  // COMPONENTS
+  private JSplitPane spMainSplitPane;
+  private SearchOptionsPanel jpSearchOptions;
+  private JPanel jpFavouriteSearches;
+  private JPanel jpSearchHistory;
+  private SearchResultsPanel jpSearchResults;
+  private final ImageIcon iconFavourite = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("favourite_icon"));
+  private final ImageIcon iconRemove = new ImageIcon(MyExperimentPerspective.getLocalResourceURL("destroy_icon"));
+
+  // Data storage
+  private QuerySearchInstance siPreviousSearch;
+  private LinkedList<QuerySearchInstance> llFavouriteSearches;
+  private LinkedList<QuerySearchInstance> llSearchHistory;
+
+  // Search components 
+  private final SearchEngine searchEngine; // The search engine for executing keyword query searches 
+  private final Vector<Long> vCurrentSearchThreadID; // This will keep ID of the current search thread (there will only be one such thread)
+
+  public SearchTabContentPanel(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;
+
+	// initialise the favourite searches
+	String strFavouriteSearches = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_FAVOURITE_SEARCHES);
+	if (strFavouriteSearches != null) {
+	  Object oFavouriteSearches = Base64.decodeToObject(strFavouriteSearches);
+	  this.llFavouriteSearches = (LinkedList<QuerySearchInstance>) oFavouriteSearches;
+	} else {
+	  this.llFavouriteSearches = new LinkedList<QuerySearchInstance>();
+	}
+
+	// initialise the search history
+	String strSearchHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_SEARCH_HISTORY);
+	if (strSearchHistory != null) {
+	  Object oSearchHistory = Base64.decodeToObject(strSearchHistory);
+	  this.llSearchHistory = (LinkedList<QuerySearchInstance>) oSearchHistory;
+	} else {
+	  this.llSearchHistory = new LinkedList<QuerySearchInstance>();
+	}
+
+	this.initialiseUI();
+	this.updateFavouriteSearches();
+	this.updateSearchHistory();
+
+	// initialise the search engine
+	vCurrentSearchThreadID = new Vector<Long>(1);
+	vCurrentSearchThreadID.add(null); // this is just a placeholder, so that it's possible to update this value instead of adding new ones later
+	this.searchEngine = new SearchEngine(vCurrentSearchThreadID, false, jpSearchResults, pluginMainComponent, myExperimentClient, logger);
+
+	SwingUtilities.invokeLater(new Runnable() {
+	  public void run() {
+		// THIS MIGHT NOT BE NEEDED AS THE SEARCH OPTIONS BOX NOW
+		// SETS THE MINIMUM SIZE OF THE SIDEBAR PROPERLY
+		spMainSplitPane.setDividerLocation(390);
+		spMainSplitPane.setOneTouchExpandable(true);
+		spMainSplitPane.setDoubleBuffered(true);
+	  }
+	});
+  }
+
+  private void initialiseUI() {
+	// create search options panel
+	jpSearchOptions = new SearchOptionsPanel(this, this.pluginMainComponent, this.myExperimentClient, this.logger);
+	jpSearchOptions.setMaximumSize(new Dimension(1024, 0)); // HACK: this is to make sure that search options box won't be stretched
+
+	// create favourite searches panel
+	jpFavouriteSearches = new JPanel();
+	jpFavouriteSearches.setMaximumSize(new Dimension(1024, 0)); // HACK: this is to make sure that favourite searches box won't be stretched
+	jpFavouriteSearches.setLayout(new GridBagLayout());
+	jpFavouriteSearches.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Favourite Searches "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+
+	// create search history panel
+	jpSearchHistory = new JPanel();
+	jpSearchHistory.setMaximumSize(new Dimension(1024, 0)); // HACK: this is to make sure that search history box won't be stretched
+	jpSearchHistory.setLayout(new GridBagLayout());
+	jpSearchHistory.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " Search History "), BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+
+	// create the search sidebar
+	JPanel jpSearchSidebar = new JPanel();
+	jpSearchSidebar.setLayout(new GridBagLayout());
+
+	GridBagConstraints gbConstraints = new GridBagConstraints();
+	gbConstraints.anchor = GridBagConstraints.NORTHWEST;
+	gbConstraints.fill = GridBagConstraints.BOTH;
+	gbConstraints.weightx = 1;
+	gbConstraints.gridx = 0;
+
+	gbConstraints.gridy = 0;
+	jpSearchSidebar.add(jpSearchOptions, gbConstraints);
+
+	gbConstraints.gridy = 1;
+	jpSearchSidebar.add(jpFavouriteSearches, gbConstraints);
+
+	gbConstraints.gridy = 2;
+	jpSearchSidebar.add(jpSearchHistory, gbConstraints);
+
+	JPanel jpSidebarContainer = new JPanel();
+	jpSidebarContainer.setLayout(new BorderLayout());
+	jpSidebarContainer.add(jpSearchSidebar, BorderLayout.NORTH);
+
+	// wrap sidebar in a scroll pane
+	JScrollPane spSearchSidebar = new JScrollPane(jpSidebarContainer);
+	spSearchSidebar.getVerticalScrollBar().setUnitIncrement(ResourcePreviewBrowser.PREFERRED_SCROLL);
+	spSearchSidebar.setMinimumSize(new Dimension(jpSidebarContainer.getMinimumSize().width + 20, 0));
+
+	// create panel for search results
+	this.jpSearchResults = new SearchResultsPanel(this, pluginMainComponent, myExperimentClient, logger);
+
+	spMainSplitPane = new JSplitPane();
+	spMainSplitPane.setLeftComponent(spSearchSidebar);
+	spMainSplitPane.setRightComponent(jpSearchResults);
+
+	// PUT EVERYTHING TOGETHER
+	this.setLayout(new BorderLayout());
+	this.add(spMainSplitPane);
+  }
+
+  private void addToSearchListQueue(LinkedList<QuerySearchInstance> searchInstanceList, QuerySearchInstance searchInstanceToAdd, int queueSize) {
+	// check if such entry is already in the list
+	int iDuplicateIdx = searchInstanceList.indexOf(searchInstanceToAdd);
+
+	// only do the following if the new search instance list OR current instance is not the same
+	// as the last one in the list
+	if (searchInstanceList.size() == 0
+		|| iDuplicateIdx != searchInstanceList.size() - 1) {
+	  // if the current item is already in the list, remove it (then re-add at the end of the list)
+	  if (iDuplicateIdx >= 0)
+		searchInstanceList.remove(iDuplicateIdx);
+
+	  // we want to keep the history size constant, therefore when it reaches a certain
+	  // size, oldest element needs to be removed
+	  if (searchInstanceList.size() >= queueSize)
+		searchInstanceList.remove();
+
+	  // in either case, add the new element to the tail of the search history
+	  searchInstanceList.offer(searchInstanceToAdd);
+	}
+  }
+
+  private void addToFavouriteSearches(QuerySearchInstance searchInstance) {
+	this.addToSearchListQueue(this.llFavouriteSearches, searchInstance, SEARCH_FAVOURITES_LENGTH);
+	Collections.sort(this.llFavouriteSearches);
+  }
+
+  // the method to update search history listing
+  protected void updateFavouriteSearches() {
+	this.jpFavouriteSearches.removeAll();
+
+	if (this.llFavouriteSearches.size() == 0) {
+	  GridBagConstraints c = new GridBagConstraints();
+	  c.weightx = 1.0;
+	  c.anchor = GridBagConstraints.WEST;
+	  this.jpFavouriteSearches.add(Util.generateNoneTextLabel("No favourite searches"), c);
+	} else {
+	  for (int i = this.llFavouriteSearches.size() - 1; i >= 0; i--) {
+		addEntryToSearchListingPanel(this.llFavouriteSearches, i, SEARCH_FROM_FAVOURITES, this.jpFavouriteSearches, this.iconRemove, REMOVE_FAVOURITE_SEARCH_INSTANCE, "<html>Click to remove from your local favourite searches.<br>"
+			+ "(This will not affect your myExperiment profile settings.)</html>");
+	  }
+	}
+
+	this.jpFavouriteSearches.repaint();
+	this.jpFavouriteSearches.revalidate();
+  }
+
+  private void addToSearchHistory(QuerySearchInstance searchInstance) {
+	this.addToSearchListQueue(this.llSearchHistory, searchInstance, SEARCH_HISTORY_LENGTH);
+  }
+
+  // the method to update search history listing
+  protected void updateSearchHistory() {
+	this.jpSearchHistory.removeAll();
+
+	if (this.llSearchHistory.size() == 0) {
+	  GridBagConstraints c = new GridBagConstraints();
+	  c.weightx = 1.0;
+	  c.anchor = GridBagConstraints.WEST;
+	  this.jpSearchHistory.add(Util.generateNoneTextLabel(SearchResultsPanel.NO_SEARCHES_STATUS), c);
+	} else {
+	  for (int i = this.llSearchHistory.size() - 1; i >= 0; i--) {
+		addEntryToSearchListingPanel(this.llSearchHistory, i, SEARCH_FROM_HISTORY, this.jpSearchHistory, this.iconFavourite, ADD_FAVOURITE_SEARCH_INSTANCE, "<html>Click to add to your local favourite"
+			+ " searches - these will be available every time you use Taverna.<br>(This will not affect your"
+			+ " myExperiment profile settings.)</html>");
+	  }
+	}
+
+	this.jpSearchHistory.repaint();
+	this.jpSearchHistory.revalidate();
+
+	// also update search history in History tab
+	if (this.pluginMainComponent.getHistoryBrowser() != null) {
+	  this.pluginMainComponent.getHistoryBrowser().refreshSearchHistory();
+	}
+  }
+
+  private void addEntryToSearchListingPanel(List<QuerySearchInstance> searchInstanceList, int iIndex, String searchAction, JPanel panelToPopulate, ImageIcon entryIcon, String iconAction, String iconActionTooltip) {
+	// labels with search query and search settings
+	JClickableLabel jclCurrentEntryLabel = new JClickableLabel(searchInstanceList.get(iIndex).getSearchQuery(), searchAction
+		+ ":" + iIndex, this, WorkbenchIcons.findIcon, SwingUtilities.LEFT, searchInstanceList.get(iIndex).toString());
+	JLabel jlCurrentEntrySettings = new JLabel(searchInstanceList.get(iIndex).detailsAsString());
+	jlCurrentEntrySettings.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+
+	// grouping search details and search settings together
+	JPanel jpCurentEntryDetails = new JPanel();
+	jpCurentEntryDetails.setLayout(new GridBagLayout());
+	GridBagConstraints c = new GridBagConstraints();
+
+	c.anchor = GridBagConstraints.WEST;
+	jpCurentEntryDetails.add(jclCurrentEntryLabel, c);
+	c.weightx = 1.0;
+	jpCurentEntryDetails.add(jlCurrentEntrySettings, c);
+
+	// creating a "button" to add current item to favourites
+	JClickableLabel jclFavourite = new JClickableLabel("", iconAction + ":"
+		+ iIndex, this, entryIcon, SwingUtilities.LEFT, iconActionTooltip);
+	jclFavourite.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+
+	// putting all pieces of current item together
+	JPanel jpCurrentEntry = new JPanel();
+	jpCurrentEntry.setLayout(new GridBagLayout());
+
+	c.anchor = GridBagConstraints.WEST;
+	c.weightx = 1.0;
+	jpCurrentEntry.add(jpCurentEntryDetails, c);
+
+	c.anchor = GridBagConstraints.WEST;
+	c.weightx = 0;
+	jpCurrentEntry.add(jclFavourite, c);
+
+	// adding current item to the history list 
+	c.fill = GridBagConstraints.HORIZONTAL;
+	c.weightx = 1.0;
+	c.gridx = 0;
+	c.gridy = GridBagConstraints.RELATIVE;
+	panelToPopulate.add(jpCurrentEntry, c);
+  }
+
+  public void actionPerformed(ActionEvent e) {
+	if (e.getSource().equals(this.jpSearchOptions.bSearch)) {
+	  // "Search" button was clicked
+
+	  // if no search query is specified, display error message
+	  if (jpSearchOptions.getSearchQuery().length() == 0) {
+		javax.swing.JOptionPane.showMessageDialog(null, "Search query is empty. Please specify your search query and try again.", "Error", JOptionPane.WARNING_MESSAGE);
+		jpSearchOptions.focusSearchQueryField();
+	  } else {
+		// will ensure that if the value in the search result limit editor
+		// is invalid, it is processed properly
+		try {
+		  this.jpSearchOptions.jsResultLimit.commitEdit();
+		} catch (ParseException ex) {
+		  JOptionPane.showMessageDialog(null, "Invalid search result limit value. This should be an\n"
+			  + "integer in the range of "
+			  + SearchOptionsPanel.SEARCH_RESULT_LIMIT_MIN
+			  + ".."
+			  + SearchOptionsPanel.SEARCH_RESULT_LIMIT_MAX, "MyExperiment Plugin - Error", JOptionPane.WARNING_MESSAGE);
+		  this.jpSearchOptions.tfResultLimitTextField.selectAll();
+		  this.jpSearchOptions.tfResultLimitTextField.requestFocusInWindow();
+		  return;
+		}
+
+		// all fine, settings present - store the settings..
+		siPreviousSearch = new SearchEngine.QuerySearchInstance(jpSearchOptions.getSearchQuery(), jpSearchOptions.getResultCountLimit(), jpSearchOptions.getSearchWorkflows(), jpSearchOptions.getSearchFiles(), jpSearchOptions.getSearchPacks(), jpSearchOptions.getSearchUsers(), jpSearchOptions.getSearchGroups());
+
+		// .. and execute the query
+		this.jpSearchOptions.focusSearchQueryField();
+		this.runSearch();
+	  }
+	} else if (e.getSource() instanceof JClickableLabel) {
+	  if (e.getActionCommand().startsWith(SEARCH_FROM_HISTORY)
+		  || e.getActionCommand().startsWith(SEARCH_FROM_FAVOURITES)) {
+		// the part of the action command that is following the prefix is the ID in the search history / favourites storage;
+		// this search instance is removed from history and will be re-added at the top of it when search is launched 
+		int iEntryID = Integer.parseInt(e.getActionCommand().substring(e.getActionCommand().indexOf(":") + 1));
+		final QuerySearchInstance si = (e.getActionCommand().startsWith(SEARCH_FROM_HISTORY) ? this.llSearchHistory.remove(iEntryID) : this.llFavouriteSearches.get(iEntryID)); // in case of favourites, no need to remove the entry
+
+		// re-set search options in the settings box and re-run the search
+		SwingUtilities.invokeLater(new Runnable() {
+		  public void run() {
+			jpSearchOptions.setSearchQuery(si.getSearchQuery());
+			jpSearchOptions.setSearchAllResourceTypes(false); // reset the 'all resource types'
+			jpSearchOptions.setSearchWorkflows(si.getSearchWorkflows());
+			jpSearchOptions.setSearchFiles(si.getSearchFiles());
+			jpSearchOptions.setSearchPacks(si.getSearchPacks());
+			jpSearchOptions.setSearchUsers(si.getSearchUsers());
+			jpSearchOptions.setSearchGroups(si.getSearchGroups());
+			jpSearchOptions.setResultCountLimit(si.getResultCountLimit());
+
+			// set this as the 'latest' search
+			siPreviousSearch = si;
+
+			// run the search (and update the search history)
+			runSearch();
+		  }
+		});
+	  } else if (e.getActionCommand().startsWith(ADD_FAVOURITE_SEARCH_INSTANCE)) {
+		// get the ID of the entry in the history listing first; then fetch the instance itself
+		int iHistID = Integer.parseInt(e.getActionCommand().substring(e.getActionCommand().indexOf(":") + 1));
+		final QuerySearchInstance si = this.llSearchHistory.get(iHistID);
+
+		// add item to favourites and re-draw the panel
+		SwingUtilities.invokeLater(new Runnable() {
+		  public void run() {
+			addToFavouriteSearches(si);
+			updateFavouriteSearches();
+		  }
+		});
+	  } else if (e.getActionCommand().startsWith(REMOVE_FAVOURITE_SEARCH_INSTANCE)) {
+		// get the ID of the entry in the favourite searches listing first; then remove the instance with that ID from the list
+		int iFavouriteID = Integer.parseInt(e.getActionCommand().substring(e.getActionCommand().indexOf(":") + 1));
+		this.llFavouriteSearches.remove(iFavouriteID);
+
+		// item removed from favourites - re-draw the panel now
+		SwingUtilities.invokeLater(new Runnable() {
+		  public void run() {
+			updateFavouriteSearches();
+		  }
+		});
+	  }
+	} else if (e.getSource().equals(this.jpSearchResults.bRefresh)) {
+	  // "Refresh" button clicked; disable clearing results and re-run previous search
+	  this.jpSearchResults.bClear.setEnabled(false);
+	  this.rerunLastSearch();
+	} else if (e.getSource().equals(this.jpSearchResults.bClear)) {
+	  // "Clear" button clicked - clear last search and disable re-running it
+	  siPreviousSearch = null;
+	  vCurrentSearchThreadID.set(0, null);
+	  this.jpSearchResults.clear();
+	  this.jpSearchResults.setStatus(SearchResultsPanel.NO_SEARCHES_STATUS);
+	  this.jpSearchResults.bClear.setEnabled(false);
+	  this.jpSearchResults.bRefresh.setEnabled(false);
+	}
+  }
+
+  // search history will get updated by default
+  private void runSearch() {
+	runSearch(true);
+  }
+
+  // makes preparations and runs the current search 
+  // (has an option not to update the search history)
+  private void runSearch(boolean bUpdateHistory) {
+	if (bUpdateHistory) {
+	  // add current search to search history ..
+	  this.addToSearchHistory(siPreviousSearch);
+
+	  // .. update the search history box ..
+	  SwingUtilities.invokeLater(new Runnable() {
+		public void run() {
+		  updateSearchHistory();
+		}
+	  });
+	}
+
+	// ..and run the query
+	this.searchEngine.searchAndPopulateResults(siPreviousSearch);
+  }
+
+  // re-executes the search for the most recent query
+  // (if searches have already been done before or were not cleared)
+  public void rerunLastSearch() {
+	if (this.siPreviousSearch != null) {
+	  runSearch();
+	}
+  }
+
+  public SearchResultsPanel getSearchResultPanel() {
+	return (this.jpSearchResults);
+  }
+
+  public LinkedList<QuerySearchInstance> getSearchFavouritesList() {
+	return (this.llFavouriteSearches);
+  }
+
+  public LinkedList<QuerySearchInstance> getSearchHistory() {
+	return (this.llSearchHistory);
+  }
+
+}

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/StyledHTMLEditorKit.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/StyledHTMLEditorKit.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/StyledHTMLEditorKit.java
new file mode 100644
index 0000000..2a2302a
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/StyledHTMLEditorKit.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+public class StyledHTMLEditorKit extends HTMLEditorKit {
+
+	private final StyleSheet styleSheet;
+
+	public StyledHTMLEditorKit(StyleSheet styleSheet) {
+		this.styleSheet = styleSheet;
+	}
+	
+	@Override
+	public StyleSheet getStyleSheet() {
+		return styleSheet;
+	}
+
+}

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/TagBrowserTabContentPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagBrowserTabContentPanel.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagBrowserTabContentPanel.java
new file mode 100644
index 0000000..eca9ab0
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagBrowserTabContentPanel.java
@@ -0,0 +1,226 @@
+// Copyright (C) 2008 The University of Manchester, University of Southampton
+// and Cardiff University
+package net.sf.taverna.t2.ui.perspectives.myexperiment;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Vector;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.SwingUtilities;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Base64;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.SearchEngine;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+
+/*
+ * @author Jiten Bhagat
+ */
+public class TagBrowserTabContentPanel extends JPanel implements ActionListener {
+  // CONSTANTS
+  private static final double TAG_CLOUD_BALANCE = 0.35;
+  private static final double TAG_SIDEBAR_BALANCE = 0.4;
+  private static final int TAG_SEARCH_HISTORY_LENGTH = 50;
+
+  private MainComponent pluginMainComponent;
+  private MyExperimentClient myExperimentClient;
+  private Logger logger;
+
+  // COMPONENTS
+  private JSplitPane spMainSplitPane;
+  private JSplitPane spTagCloudSplitPane;
+  private TagCloudPanel jpMyTags;
+  private TagCloudPanel jpAllTags;
+  private SearchResultsPanel jpTagSearchResults;
+  private JButton bLoginToSeeMyTags;
+  private JPanel jpLoginToSeeMyTags;
+
+  // STORAGE
+  // last tag for which the search has been made
+  private String strCurrentTagCommand;
+  private ArrayList<Tag> lTagSearchHistory;
+
+  // Search components
+  private SearchEngine searchEngine; // The search engine for executing keyword
+  // query searches
+  private Vector<Long> vCurrentSearchThreadID; // This will keep ID of the
+
+  // current search thread (there
+  // will only be one such thread)
+
+  @SuppressWarnings("unchecked")
+  public TagBrowserTabContentPanel(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;
+
+	// no tag searches have been done yet
+	this.strCurrentTagCommand = null;
+
+	// initialise the tag search history
+	String strTagSearchHistory = (String) myExperimentClient.getSettings().get(MyExperimentClient.INI_TAG_SEARCH_HISTORY);
+	if (strTagSearchHistory != null) {
+	  Object oTagSearchHistory = Base64.decodeToObject(strTagSearchHistory);
+	  this.lTagSearchHistory = (ArrayList<Tag>) oTagSearchHistory;
+	} else {
+	  this.lTagSearchHistory = new ArrayList<Tag>();
+	}
+
+	this.initialiseUI();
+
+	// initialise the search engine
+	vCurrentSearchThreadID = new Vector<Long>(1);
+	vCurrentSearchThreadID.add(null); // this is just a placeholder, so that
+	// it's possible to update this value
+	// instead of adding new ones later
+	this.searchEngine = new SearchEngine(vCurrentSearchThreadID, true, jpTagSearchResults, pluginMainComponent, myExperimentClient, logger);
+
+	SwingUtilities.invokeLater(new Runnable() {
+	  public void run() {
+		spTagCloudSplitPane.setDividerLocation(TAG_CLOUD_BALANCE);
+		spMainSplitPane.setDividerLocation(TAG_SIDEBAR_BALANCE);
+		spMainSplitPane.setOneTouchExpandable(true);
+		spMainSplitPane.setDoubleBuffered(true);
+	  }
+	});
+  }
+
+  private void initialiseUI() {
+	// This panel will be used when the user is not logged in - i.e. when can't
+	// show "My Tags";
+	// log-in button will be shown instead
+	this.bLoginToSeeMyTags = new JButton("Login to see your tags", new ImageIcon(MyExperimentPerspective.getLocalResourceURL("login_icon")));
+	this.bLoginToSeeMyTags.addActionListener(this);
+
+	this.jpLoginToSeeMyTags = new JPanel();
+	this.jpLoginToSeeMyTags.setLayout(new GridBagLayout());
+	this.jpLoginToSeeMyTags.add(this.bLoginToSeeMyTags);
+
+	// Create the tag clouds
+	this.jpMyTags = new TagCloudPanel("My Tags", TagCloudPanel.TAGCLOUD_TYPE_USER, this, pluginMainComponent, myExperimentClient, logger);
+	this.jpAllTags = new TagCloudPanel("All Tags", TagCloudPanel.TAGCLOUD_TYPE_GENERAL, this, pluginMainComponent, myExperimentClient, logger);
+
+	// add the two tag clouds to the left-hand side sidebar
+	this.spTagCloudSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+	this.spTagCloudSplitPane.setTopComponent(this.myExperimentClient.isLoggedIn() ? jpMyTags : jpLoginToSeeMyTags);
+	this.spTagCloudSplitPane.setBottomComponent(jpAllTags);
+
+	// create panel for tag search results
+	this.jpTagSearchResults = new SearchResultsPanel(this, pluginMainComponent, myExperimentClient, logger);
+
+	this.spMainSplitPane = new JSplitPane();
+	this.spMainSplitPane.setLeftComponent(spTagCloudSplitPane);
+	this.spMainSplitPane.setRightComponent(this.jpTagSearchResults);
+
+	this.setLayout(new BorderLayout());
+	this.add(this.spMainSplitPane, BorderLayout.CENTER);
+  }
+
+  // this helper is called when the user logs in / out to swap the
+  // view accordingly; 'refresh' on "My Tags" panel should be called
+  // immediately after changing the view
+  public void setMyTagsShown(boolean bShow) {
+	if (bShow) {
+	  this.spTagCloudSplitPane.setTopComponent(this.jpMyTags);
+	} else {
+	  this.spTagCloudSplitPane.setTopComponent(this.jpLoginToSeeMyTags);
+	}
+
+	// in either case apply element balance again
+	this.spTagCloudSplitPane.setDividerLocation(TAG_CLOUD_BALANCE);
+  }
+
+  public void refresh() {
+	if (this.myExperimentClient.isLoggedIn()) {
+	  // "My Tags" are only accessible when the user has logged in
+	  this.jpMyTags.refresh();
+	}
+	this.jpAllTags.refresh();
+  }
+
+  // re-executes the search for the most recent tag
+  // (if tag searches have already been done before)
+  public void rerunLastTagSearch() {
+	if (this.strCurrentTagCommand != null) {
+	  this.actionPerformed(new ActionEvent(this.jpAllTags, 0, this.strCurrentTagCommand));
+	}
+  }
+
+  public TagCloudPanel getMyTagPanel() {
+	return (this.jpMyTags);
+  }
+
+  public TagCloudPanel getAllTagPanel() {
+	return (this.jpAllTags);
+  }
+
+  public SearchResultsPanel getTagSearchResultPanel() {
+	return (this.jpTagSearchResults);
+  }
+
+  public ArrayList<Tag> getTagSearchHistory() {
+	return (this.lTagSearchHistory);
+  }
+
+  public void actionPerformed(ActionEvent e) {
+	if (e.getSource().equals(this.jpTagSearchResults.bRefresh)) {
+	  // disable clearing results and re-run last tag search
+	  this.getTagSearchResultPanel().bClear.setEnabled(false);
+	  this.rerunLastTagSearch();
+	} else if (e.getSource().equals(this.jpTagSearchResults.bClear)) {
+	  // clear last search and disable re-running it
+	  this.strCurrentTagCommand = null;
+	  vCurrentSearchThreadID.set(0, null);
+	  this.getTagSearchResultPanel().clear();
+	  this.getTagSearchResultPanel().setStatus(SearchResultsPanel.NO_SEARCHES_STATUS);
+	  this.getTagSearchResultPanel().bClear.setEnabled(false);
+	  this.getTagSearchResultPanel().bRefresh.setEnabled(false);
+	} else if (e.getSource() instanceof JClickableLabel
+		|| e.getSource() instanceof TagCloudPanel) {
+	  // one of the tags was clicked as a hyperlink in any TagCloudPanel
+	  // (in Resource Preview Browser or in Tag Browser tab) or as a
+	  // JClickableLabel
+	  if (e.getActionCommand().startsWith("tag:")) {
+		// record the tag search command and run the search
+		this.strCurrentTagCommand = e.getActionCommand();
+		this.searchEngine.searchAndPopulateResults(strCurrentTagCommand);
+
+		// update tag search history making sure that:
+		// - there's only one occurrence of this tag in the history;
+		// - if this tag was in the history before, it is moved to the 'top'
+		// now;
+		// - predefined history size is not exceeded
+		Tag t = Tag.instantiateTagFromActionCommand(strCurrentTagCommand);
+		this.lTagSearchHistory.remove(t);
+		this.lTagSearchHistory.add(t);
+		if (this.lTagSearchHistory.size() > TAG_SEARCH_HISTORY_LENGTH)
+		  this.lTagSearchHistory.remove(0);
+
+		// now update the tag search history panel in 'History' tab
+		if (this.pluginMainComponent.getHistoryBrowser() != null) {
+		  this.pluginMainComponent.getHistoryBrowser().refreshTagSearchHistory();
+		}
+	  }
+	} else if (e.getSource().equals(this.bLoginToSeeMyTags)) {
+	  // set the return "link"
+	  this.pluginMainComponent.getMyStuffTab().cTabContentComponentToSwitchToAfterLogin = this;
+
+	  // switch to login tab
+	  this.pluginMainComponent.getMainTabs().setSelectedComponent(this.pluginMainComponent.getMyStuffTab());
+	}
+  }
+
+}

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/TagCloudPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagCloudPanel.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagCloudPanel.java
new file mode 100644
index 0000000..b03fd8f
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TagCloudPanel.java
@@ -0,0 +1,342 @@
+/*******************************************************************************
+ * 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.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTextPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.MyExperimentClient;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Tag;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.TagCloud;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ * @author Sergejs Aleksejevs, Jiten Bhagat
+ */
+public class TagCloudPanel extends JPanel implements ChangeListener, ItemListener, ActionListener, HyperlinkListener {
+  // CONSTANTS
+  private static final int TAGCLOUD_MAX_FONTSIZE = 36;
+  private static final int TAGCLOUD_MIN_FONTSIZE = 12;
+  private static final int TAGCLOUD_DEFAULT_MAX_SIZE = 350;
+  private static final int TAGCLOUD_DEFAULT_DISPLAY_SIZE = 50;
+
+  public static final int TAGCLOUD_TYPE_GENERAL = 0;
+  public static final int TAGCLOUD_TYPE_USER = 1;
+  public static final int TAGCLOUD_TYPE_RESOURCE_PREVIEW = 2;
+
+  private MainComponent pluginMainComponent;
+  private MyExperimentClient myExperimentClient;
+  private Logger logger;
+
+  // COMPONENTS
+  private String strTitle;
+  private int iType;
+  private ShadedLabel lCloudTitle;
+  private JSlider jsCloudSizeSlider;
+  private JCheckBox cbShowAllTags;
+  private JButton bRefresh;
+  private JTextPane tpTagCloudBody;
+
+  private ActionListener clickHandler;
+  private TagCloud tcData = new TagCloud();
+  private boolean bUserTagCloudSliderValueNeverSet = true;
+
+  public TagCloudPanel(String title, int iTagCloudType, ActionListener clickHandler, MainComponent component, MyExperimentClient client, Logger logger) {
+	super();
+
+	// set parameters and the main variables to ensure access to myExperiment,
+	// logger and the parent component
+	this.strTitle = title;
+	this.iType = iTagCloudType;
+	this.clickHandler = clickHandler;
+	this.pluginMainComponent = component;
+	this.myExperimentClient = client;
+	this.logger = logger;
+
+	initialiseUI();
+  }
+
+  private void initialiseUI() {
+	// set the title of the tag cloud
+	lCloudTitle = new ShadedLabel(this.strTitle, ShadedLabel.BLUE);
+
+	// create the tag cloud controls panel
+	// (all controls will be created anyway, but if that's a resource
+	// preview tag cloud, make sure that these controls are not displayed)
+	JPanel jpCloudControlsPanel = new JPanel();
+	jpCloudControlsPanel.setLayout(new BoxLayout(jpCloudControlsPanel, BoxLayout.LINE_AXIS));
+	this.jsCloudSizeSlider = new JSlider(1, TAGCLOUD_DEFAULT_MAX_SIZE, TAGCLOUD_DEFAULT_DISPLAY_SIZE);
+	this.cbShowAllTags = new JCheckBox("All tags", false);
+	this.bRefresh = new JButton("Refresh", WorkbenchIcons.refreshIcon);
+
+	if (this.iType != TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW) {
+	  this.jsCloudSizeSlider.addChangeListener(this);
+	  this.jsCloudSizeSlider.setToolTipText("Drag the slider to select how big the tag cloud should be, or check the \"All tags\" box to get the full tag cloud.");
+	  jpCloudControlsPanel.add(this.jsCloudSizeSlider);
+
+	  this.cbShowAllTags.addItemListener(this);
+	  jpCloudControlsPanel.add(this.cbShowAllTags);
+
+	  this.bRefresh.addActionListener(this);
+	  this.bRefresh.setToolTipText("Click this button to refresh the Tag Cloud");
+	  jpCloudControlsPanel.add(this.bRefresh);
+	}
+
+	// tag cloud header panel which which contains controls
+	JPanel jpCloudHeader = new JPanel(new BorderLayout());
+	jpCloudHeader.add(jpCloudControlsPanel, BorderLayout.CENTER);
+	jpCloudHeader.setBorder(BorderFactory.createEtchedBorder());
+
+	// body of the tag cloud with the actual tags
+	this.tpTagCloudBody = new JTextPane();
+	this.tpTagCloudBody.setBorder(BorderFactory.createEmptyBorder());
+	this.tpTagCloudBody.setEditable(false);
+	this.tpTagCloudBody.setContentType("text/html");
+	this.tpTagCloudBody.addHyperlinkListener(this);
+
+	JScrollPane spTagCloudBody = new JScrollPane(this.tpTagCloudBody, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+	spTagCloudBody.setBorder(BorderFactory.createEmptyBorder());
+	spTagCloudBody.setOpaque(true);
+
+	// PUT EVERYTHING TOGETHER
+	JPanel jpTagCloudContentWithControls = new JPanel();
+	jpTagCloudContentWithControls.setLayout(new BorderLayout());
+	jpTagCloudContentWithControls.add(spTagCloudBody, BorderLayout.CENTER);
+	if (this.iType != TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW) {
+	  jpTagCloudContentWithControls.add(jpCloudHeader, BorderLayout.NORTH);
+	}
+
+	this.setLayout(new BorderLayout());
+	if (this.iType != TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW) {
+	  this.add(lCloudTitle, BorderLayout.NORTH);
+	}
+	this.add(jpTagCloudContentWithControls, BorderLayout.CENTER);
+	this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2), BorderFactory.createEtchedBorder()));
+  }
+
+  public void refresh() {
+	this.lCloudTitle.setText(strTitle
+		+ " <span style='color: gray;'>(Loading...)</span>");
+
+	// Make call to myExperiment API in a different thread
+	// (then use SwingUtilities.invokeLater to update the UI when ready).
+	new Thread("Get '" + this.strTitle + "' tag cloud data") {
+	  public void run() {
+		logger.debug("Getting '" + strTitle + "' tag cloud data");
+
+		try {
+		  int size = -1;
+		  if (!cbShowAllTags.isSelected()) {
+			size = jsCloudSizeSlider.getValue();
+		  }
+
+		  // based on the type of the tag cloud, different data needs to be
+		  // fetched
+		  switch (iType) {
+			case TagCloudPanel.TAGCLOUD_TYPE_GENERAL:
+			  tcData = myExperimentClient.getGeneralTagCloud(size);
+			  break;
+
+			case TagCloudPanel.TAGCLOUD_TYPE_USER:
+			  tcData = myExperimentClient.getUserTagCloud(myExperimentClient.getCurrentUser(), size);
+			  break;
+
+			case TagCloudPanel.TAGCLOUD_TYPE_RESOURCE_PREVIEW:
+			  // fetch tag counts for tags that are already pre-set
+			  myExperimentClient.convertTagListIntoTagCloudData(tcData.getTags());
+			  break;
+
+			default:
+			  // unknown type of tag cloud; show no data
+			  tcData = new TagCloud();
+			  break;
+		  }
+
+		  SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+			  repopulate();
+			}
+		  });
+		} catch (Exception ex) {
+		  logger.error("Failed to get tag cloud data from myExperiment", ex);
+		}
+	  }
+	}.start();
+  }
+
+  public void repopulate() {
+	logger.debug("Building '" + this.strTitle + "' tag cloud...");
+
+	try {
+	  this.jsCloudSizeSlider.removeChangeListener(this);
+	  if (this.iType == TAGCLOUD_TYPE_USER) {
+		jsCloudSizeSlider.setMinimum(1);
+		jsCloudSizeSlider.setMaximum(myExperimentClient.getCurrentUser().getTags().size());
+		if (bUserTagCloudSliderValueNeverSet) {
+		  // this is the first load of the cloud, show all user tags
+		  jsCloudSizeSlider.setValue(jsCloudSizeSlider.getMaximum());
+		  bUserTagCloudSliderValueNeverSet = false;
+		} else {
+		  // not the first load, test if the position of the slider is still
+		  // within the scope
+		  // (put that back to maximum if exceeds)
+		  if (jsCloudSizeSlider.getValue() > jsCloudSizeSlider.getMaximum()
+			  || this.cbShowAllTags.isSelected())
+			jsCloudSizeSlider.setValue(jsCloudSizeSlider.getMaximum());
+		}
+	  } else {
+		// if "All tags" check box is ticked, set the slider max to the max no
+		// of tags
+		// (this will be the total number of tags available from myExperiment);
+		// if "All tags" was never checked before, max position of the slider
+		// will
+		// stay at predefined default value (because the total number of tags is
+		// not known yet)
+		if (this.cbShowAllTags.isSelected()) {
+		  int size = this.tcData.getTags().size();
+		  this.jsCloudSizeSlider.setMaximum(size);
+		  this.jsCloudSizeSlider.setValue(size);
+		}
+	  }
+	  this.jsCloudSizeSlider.addChangeListener(this);
+
+	  // For tag cloud font size calculations
+	  int iMaxCount = this.tcData.getMaxTagCount();
+
+	  StringBuffer content = new StringBuffer();
+
+	  if (this.tcData.getTags().size() > 0) {
+		content.append("<div class='outer'>");
+		content.append("<br>");
+		content.append("<div class='tag_cloud'>");
+
+		for (Tag t : this.tcData.getTags()) {
+		  // Normalise count and use it to obtain a font size value.
+		  // Also chops off based on min and max.
+		  int fontSize = (int) (((double) t.getCount() / ((double) iMaxCount / 3)) * TAGCLOUD_MAX_FONTSIZE);
+		  if (fontSize < TAGCLOUD_MIN_FONTSIZE) {
+			fontSize = TAGCLOUD_MIN_FONTSIZE;
+		  }
+		  if (fontSize > TAGCLOUD_MAX_FONTSIZE) {
+			fontSize = TAGCLOUD_MAX_FONTSIZE;
+		  }
+
+		  content.append("<a style='font-size: " + fontSize + "pt;' href='tag:"
+			  + t.getTagName() + "'>" + t.getTagName() + "</a>");
+		  content.append("&nbsp;&nbsp;&nbsp;");
+		}
+
+		content.append("<br>");
+		content.append("</div>");
+		content.append("</div>");
+	  } else {
+		content.append("<br>");
+		content.append("<span style='color: gray; font-weight: italic;'>&nbsp;&nbsp;No tags to display</span>");
+	  }
+
+	  HTMLEditorKit kit = new StyledHTMLEditorKit(pluginMainComponent.getStyleSheet());
+	  HTMLDocument doc = (HTMLDocument) (kit.createDefaultDocument());
+	  doc.insertAfterStart(doc.getRootElements()[0].getElement(0), content.toString());
+
+	  this.tpTagCloudBody.setEditorKit(kit);
+	  this.tpTagCloudBody.setDocument(doc);
+	} catch (Exception e) {
+	  logger.error("Failed to populate tag cloud", e);
+	}
+
+	SwingUtilities.invokeLater(new Runnable() {
+	  public void run() {
+		lCloudTitle.setText(strTitle
+			+ " <span style='color: gray;'>(currently showing "
+			+ tcData.getTags().size() + ")</span>");
+		revalidate();
+	  }
+	});
+  }
+
+  /**
+   * Helper method to get hold of the tag cloud data. Needed to be able to set
+   * tag cloud data when using this for resource preview (when tag fetching is
+   * not required).
+   */
+  public TagCloud getTagCloudData() {
+	return this.tcData;
+  }
+
+  public void stateChanged(ChangeEvent e) {
+	if (e.getSource().equals(this.jsCloudSizeSlider)) {
+	  // cloud size slider was dragged to a new place and the drag event
+	  // has finished; refresh the tag cloud with the newly selected tag count
+	  if (!this.jsCloudSizeSlider.getValueIsAdjusting()) {
+		this.cbShowAllTags.removeItemListener(this);
+		this.cbShowAllTags.setSelected(false);
+		this.cbShowAllTags.addItemListener(this);
+
+		this.refresh();
+	  }
+	}
+  }
+
+  public void itemStateChanged(ItemEvent e) {
+	if (e.getItemSelectable().equals(this.cbShowAllTags)) {
+	  // "Show all" clicked - need to refresh with all tags being displayed
+	  this.refresh();
+	}
+  }
+
+  public void actionPerformed(ActionEvent e) {
+	if (e.getSource().equals(this.bRefresh)) {
+	  // refresh button clicked on the cloud controls panel -
+	  // simply refresh the cloud with the same parameters
+	  this.refresh();
+	}
+  }
+
+  public void hyperlinkUpdate(HyperlinkEvent e) {
+	if (e.getSource().equals(this.tpTagCloudBody)
+		&& e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+	  // one of the tags was clicked, but click processing is off-loaded to
+	  // 'clickHandler'
+	  this.clickHandler.actionPerformed(new ActionEvent(this, (this.getClass().getName() + e.getDescription()).hashCode(), e.getDescription()));
+	}
+  }
+}

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/TestJFrameForLocalLaunch.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TestJFrameForLocalLaunch.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TestJFrameForLocalLaunch.java
new file mode 100644
index 0000000..15dd3a4
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/TestJFrameForLocalLaunch.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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 javax.swing.JFrame;
+
+public class TestJFrameForLocalLaunch {
+
+	/**
+	 * This is a simple test class for launching myExperiment perspective
+	 * from outside Taverna. At some point it will be not usable anymore,
+	 * when proper integration of myExperiment plugin is made.
+	 *
+	 * @author Sergejs Aleksejevs
+	 */
+	public static void main(String[] args)
+	{
+	  JFrame frame = new JFrame("myExperiment Perspective Test");
+	  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+	  frame.setMinimumSize(new Dimension(1000, 700));
+	  frame.setLocation(300, 150);
+	  frame.getContentPane().add(new net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent(null, null));
+
+	  frame.pack();
+	  frame.setVisible(true);
+	}
+
+}

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/UploadWorkflowDialog.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/UploadWorkflowDialog.java b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/UploadWorkflowDialog.java
new file mode 100644
index 0000000..ed47183
--- /dev/null
+++ b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/UploadWorkflowDialog.java
@@ -0,0 +1,849 @@
+/*******************************************************************************
+ * 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.Component;
+import java.awt.Container;
+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.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.License;
+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.ui.perspectives.myexperiment.model.ServerResponse;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Util;
+import net.sf.taverna.t2.ui.perspectives.myexperiment.model.Workflow;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAsAction;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Emmanuel Tagarira, Sergejs Aleksejevs
+ */
+public class UploadWorkflowDialog extends HelpEnabledDialog implements ActionListener,
+		CaretListener, ComponentListener, KeyListener, FocusListener {
+	// components for accessing application's main elements
+	private final MainComponent pluginMainComponent = MainComponent.MAIN_COMPONENT;
+	private final MyExperimentClient myExperimentClient = MainComponent.MY_EXPERIMENT_CLIENT;
+	private final Logger logger = MainComponent.LOGGER;
+
+	// COMPONENTS
+	private JTextArea taDescription;
+	private JTextField tfTitle;
+	private JButton bUpload;
+	private JButton bCancel;
+	private JLabel lStatusMessage;
+	private JComboBox jcbLicences;
+	private JComboBox jcbSharingPermissions;
+	private String licence;
+	private String sharing;
+
+	// STORAGE
+	private File localWorkflowFile; // the local workflow file to be uploaded
+	private Resource updateResource; // the workflow resource that is to be
+	// updated
+	private File uploadFile; // THE UPLOAD FILE
+
+	private String strDescription = null;
+	private String strTitle = null;
+	private boolean bUploadingSuccessful = false;
+
+	// misc.
+	private int gridYPositionForStatusLabel;
+	private JRadioButton rbSelectLocalFile;
+	private JRadioButton rbSelectOpenWorkflow;
+	private JButton bSelectFile;
+	private JComboBox jcbOpenWorkflows;
+	private final JLabel selectedFileLabel = new JLabel("no wokflow file selected");
+	private boolean uploadWorkflowFromLocalFile;
+	JFileChooser jfsSelectFile = new JFileChooser();
+
+	private boolean userRequestedWorkflowUpload;
+	private final FileManager fileManager;
+
+	public UploadWorkflowDialog(JFrame parent, boolean doUpload, FileManager fileManager) {
+		super(parent, "Upload workflow to myExperiment", true);
+		this.fileManager = fileManager;
+		initVarsAndUI(doUpload, null);
+	}
+
+	public UploadWorkflowDialog(JFrame parent, boolean doUpload, Resource resource,
+			FileManager fileManager) {
+		super(parent, (doUpload ? "Upload new workflow version" : "Update workflow information"),
+				true);
+		this.fileManager = fileManager;
+		initVarsAndUI(doUpload, resource);
+	}
+
+	private void initVarsAndUI(boolean doUpload, Resource resource) {
+		// set the resource for which the comment is being added
+		this.updateResource = resource;
+		this.userRequestedWorkflowUpload = doUpload;
+
+		// set options of the 'add comment' dialog box
+		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+		initialiseUI();
+		this.setMinimumSize(new Dimension(525, 375));
+	}
+
+	private JPanel createSelectSource() {
+		// create radio buttons
+		ButtonGroup radioButtons = new ButtonGroup();
+		rbSelectOpenWorkflow = new JRadioButton("Already Opened Workflow");
+		rbSelectOpenWorkflow.addFocusListener(this);
+		rbSelectLocalFile = new JRadioButton("Select Local File");
+		rbSelectLocalFile.addFocusListener(this);
+		radioButtons.add(rbSelectOpenWorkflow);
+		rbSelectOpenWorkflow.setSelected(true);
+		radioButtons.add(rbSelectLocalFile);
+
+		// create the source panel and add items
+		JPanel source = new JPanel(new GridBagLayout());
+		source.setBorder(BorderFactory.createTitledBorder("Workflow source"));
+
+		GridBagConstraints c = new GridBagConstraints();
+		c.anchor = GridBagConstraints.NORTHWEST;
+		c.gridy = 0;
+		c.gridx = 0;
+		c.gridwidth = 1;
+		c.weightx = 1;
+		c.insets = new Insets(3, 0, 3, 0);
+		c.fill = GridBagConstraints.BOTH;
+
+		// add info label
+		// JLabel info = new
+		// JLabel("Upload a workflow you would like to upload:");
+		// source.add(info, c);
+
+		// add open workflow radio button
+		c.gridy++;
+		source.add(rbSelectOpenWorkflow, c);
+		c.gridx = 1;
+		c.gridwidth = 2;
+		createDropdown();
+		source.add(jcbOpenWorkflows, c);
+
+		// add local file radio button
+		c.gridwidth = 1;
+		c.gridy++;
+		c.gridx = 0;
+		source.add(rbSelectLocalFile, c);
+		c.gridx = 1;
+		source.add(selectedFileLabel, c);
+		bSelectFile = new JButton(WorkbenchIcons.openIcon);
+		bSelectFile.addActionListener(this);
+		bSelectFile.setToolTipText("Select the file you would like to upload to myExperiment");
+		c.gridx = 2;
+		source.add(bSelectFile, c);
+
+		return source;
+	}
+
+	private void createDropdown() {
+		List<DataflowSelection> openDataflows = new ArrayList<DataflowSelection>();
+
+		int currentlyOpenedIndex = 0;
+		boolean foundIndex = false;
+
+		for (Dataflow df : fileManager.getOpenDataflows()) {
+			Object source = fileManager.getDataflowSource(df);
+
+			String name = "";
+			boolean getLocalName = source instanceof InputStream;
+			if (source != null)
+				name = (getLocalName ? df.getLocalName() : source.toString());
+
+			if (df.equals(fileManager.getCurrentDataflow())) {
+				name = "<html><body>" + name + " - " + " <i>(current)</i></body></html>";
+				foundIndex = true;
+			}
+
+			openDataflows.add(new DataflowSelection(df, name));
+			if (!foundIndex)
+				currentlyOpenedIndex++;
+		}
+
+		jcbOpenWorkflows = new JComboBox(openDataflows.toArray());
+		jcbOpenWorkflows.setSelectedIndex(currentlyOpenedIndex);
+	}
+
+	private JPanel createMetadataPanel() {
+		Insets fieldInset = new Insets(0, 5, 4, 5);
+		Insets labelInset = new Insets(3, 5, 4, 5);
+
+		GridBagConstraints c = new GridBagConstraints();
+		c.gridx = 0;
+		c.weightx = 1;
+		c.gridy = 0;
+		c.anchor = GridBagConstraints.WEST;
+		c.gridwidth = 2;
+		c.fill = GridBagConstraints.HORIZONTAL;
+
+		JPanel metaPanel = new JPanel(new GridBagLayout());
+		metaPanel.setBorder(BorderFactory.createTitledBorder("Workflow information"));
+
+		// title
+		JLabel lTitle = new JLabel("Workflow title:");
+		c.insets = labelInset;
+		c.gridy++;
+		metaPanel.add(lTitle, c);
+
+		this.tfTitle = new JTextField();
+		if (this.updateResource != null)
+			this.tfTitle.setText(this.updateResource.getTitle());
+		c.gridy++;
+		c.insets = fieldInset;
+		metaPanel.add(this.tfTitle, c);
+
+		// description
+		JLabel lDescription = new JLabel("Workflow description:");
+		c.gridy++;
+		c.insets = labelInset;
+		metaPanel.add(lDescription, c);
+
+		this.taDescription = new JTextArea(5, 35);
+		this.taDescription.setLineWrap(true);
+		this.taDescription.setWrapStyleWord(true);
+		if (this.updateResource != null)
+			this.taDescription.setText(this.updateResource.getDescription());
+
+		JScrollPane spDescription = new JScrollPane(this.taDescription);
+		c.gridy++;
+		c.insets = fieldInset;
+		metaPanel.add(spDescription, c);
+
+		// licences
+		String[] licenseText = new String[License.SUPPORTED_TYPES.length];
+		for (int x = 0; x < License.SUPPORTED_TYPES.length; x++)
+			licenseText[x] = License.getInstance(License.SUPPORTED_TYPES[x]).getText();
+
+		jcbLicences = new JComboBox(licenseText);
+    String defaultLicenseText = License.getInstance(License.DEFAULT_LICENSE)
+    .getText();
+    jcbLicences.setSelectedItem(defaultLicenseText);
+
+		if (this.updateResource != null) { // adding a new workflow
+			Workflow wf = (Workflow) this.updateResource;
+			String wfText = wf.getLicense().getText();
+			for (int x = 0; x < licenseText.length; x++)
+				if (wfText.equals(licenseText[x])) {
+					jcbLicences.setSelectedIndex(x);
+					break;
+				}
+		}
+
+		jcbLicences.addActionListener(this);
+		jcbLicences.setEditable(false);
+
+		JLabel lLicense = new JLabel("Please select the licence to apply:");
+		c.gridy++;
+		c.insets = labelInset;
+		metaPanel.add(lLicense, c);
+
+		c.gridy++;
+		c.insets = fieldInset;
+		metaPanel.add(jcbLicences, c);
+
+		// sharing - options: private / view / download
+		String[] permissions = { "This workflow is private.",
+				"Anyone can view, but noone can download.",
+				"Anyone can view, and anyone can download" };
+
+		this.jcbSharingPermissions = new JComboBox(permissions);
+
+		jcbSharingPermissions.addActionListener(this);
+		jcbSharingPermissions.setEditable(false);
+
+		JLabel jSharing = new JLabel("Please select your sharing permissions:");
+		c.gridy++;
+		c.insets = labelInset;
+		metaPanel.add(jSharing, c);
+
+		c.gridy++;
+		c.insets = fieldInset;
+		metaPanel.add(jcbSharingPermissions, c);
+
+		return metaPanel;
+	}
+
+	private void initialiseUI() {
+		// get content pane
+		Container contentPane = this.getContentPane();
+
+		Insets fieldInset = new Insets(3, 5, 3, 5);
+
+		// set up layout
+		contentPane.setLayout(new GridBagLayout());
+		GridBagConstraints c = new GridBagConstraints();
+		c.gridx = 0;
+		c.gridy = 0;
+		c.anchor = GridBagConstraints.NORTHWEST;
+		c.gridwidth = 2;
+		c.fill = GridBagConstraints.HORIZONTAL;
+
+		// ADD ALL COMPONENTS
+		// source for workflow to upload
+		if (userRequestedWorkflowUpload) {
+			c.insets = fieldInset;
+			contentPane.add(createSelectSource(), c);
+			c.gridy++;
+		}
+
+		// create metadata panel
+		contentPane.add(createMetadataPanel(), c);
+
+		// buttons
+		this.bUpload = new JButton(userRequestedWorkflowUpload ? "Upload Workflow"
+				: "Update Workflow");
+		this.bUpload.setDefaultCapable(true);
+		this.getRootPane().setDefaultButton(this.bUpload);
+		this.bUpload.addActionListener(this);
+		this.bUpload.addKeyListener(this);
+
+		c.gridy++;
+		c.anchor = GridBagConstraints.EAST;
+		c.gridwidth = 1;
+		c.fill = GridBagConstraints.NONE;
+		c.weightx = 0.5;
+		c.insets = new Insets(10, 5, 10, 5);
+		contentPane.add(bUpload, c);
+
+		this.bCancel = new JButton("Cancel");
+		this.bCancel.setPreferredSize(this.bUpload.getPreferredSize());
+		this.bCancel.addActionListener(this);
+		c.gridx = 1;
+		c.anchor = GridBagConstraints.WEST;
+		c.weightx = 0.5;
+		contentPane.add(bCancel, c);
+
+		this.pack();
+		this.addComponentListener(this);
+
+		gridYPositionForStatusLabel = c.gridy;
+	}
+
+	/**
+	 * Opens up a modal dialog where the user can enter the comment text. Window
+	 * is simply closed if 'Cancel' button is pressed; on pressing 'Post
+	 * Comment' button the window will turn into 'waiting' state, post the
+	 * comment and return the resulting XML document (which would contain the
+	 * newly added comment) back to the caller.
+	 *
+	 * @return String value of the non-empty comment text to be sent to
+	 *         myExperiment or null if action was cancelled.
+	 */
+	public boolean launchUploadDialogAndPostIfRequired() {
+		// makes the 'add comment' dialog visible, then waits until it is
+		// closed;
+		// control returns to this method when the dialog window is disposed
+		this.setVisible(true);
+		return (bUploadingSuccessful);
+	}
+
+	private File performSourceCheck() {
+		if (!rbSelectLocalFile.isSelected() && !rbSelectOpenWorkflow.isSelected()) { // it
+			// is
+			// logicall
+			// impossible
+			// to
+			// get
+			// this
+			// message,
+			// have
+			// it
+			// JUST
+			// IN
+			// CASE
+			JOptionPane.showConfirmDialog(this,
+					"You have not selected a source for you workflow.\n"
+							+ "Please select a source and try again.", "Select Workflow Source",
+					JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
+			return null;
+		}
+
+		if (rbSelectOpenWorkflow.isSelected()) { // user requested to use a flow
+			// currently open in t2
+			Dataflow dataflowToUpload = ((DataflowSelection) jcbOpenWorkflows.getSelectedItem())
+					.getDataflow();
+			SaveWorkflowAsAction saveAction = new SaveWorkflowAsAction(fileManager);
+
+			boolean skipPrompt = false;
+			if (fileManager.isDataflowChanged(dataflowToUpload)) { // if flow
+																	// has
+				// changed, prompt
+				// user to save
+				JOptionPane.showConfirmDialog(this, "The workflow you are trying to upload has\n"
+						+ "changed since the last time it was saved.\n\n"
+						+ "Please save your file to proceed...", "Save Workflow",
+						JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE);
+				saveAction.saveDataflow(this, dataflowToUpload);
+				skipPrompt = true;
+			}
+
+			File dataflowFile = (File) fileManager.getDataflowSource(dataflowToUpload);
+			if (dataflowFile == null && !skipPrompt) {
+				JOptionPane.showConfirmDialog(this, "You cannot upload an empty workflow.\n"
+						+ "Please select a different workflow before\n"
+						+ "you attempt the upload again.", "Upload Error",
+						JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
+				return null;
+			} else
+				return dataflowFile;
+
+		} else { // user wants to use local file
+			if (localWorkflowFile == null) {
+				JOptionPane.showConfirmDialog(this, "You have not selected a file to upload.\n"
+						+ "Please select a file and try again.", "Select Workflow Source",
+						JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
+				return null;
+			}
+
+			return localWorkflowFile;
+		}
+	}
+
+	private void getMetadata() {
+		// get sharing permission
+		switch (this.jcbSharingPermissions.getSelectedIndex()) {
+		case 0:
+			this.sharing = "private";
+			break;
+		case 1:
+			this.sharing = "view";
+			break;
+		case 2:
+			this.sharing = "download";
+			break;
+		}
+
+		// get licence
+		this.licence = License.SUPPORTED_TYPES[this.jcbLicences.getSelectedIndex()];
+
+		// get title
+		this.strTitle = this.tfTitle.getText();
+		this.strTitle = this.strTitle.trim();
+
+		// get description
+		this.strDescription = this.taDescription.getText();
+		this.strDescription = this.strDescription.trim();
+	}
+
+	public void actionPerformed(ActionEvent e) {
+		if (e.getSource().equals(this.bUpload)) { // * *** *** *** * UPLOAD
+													// BUTTON *
+			// *** *** *** *
+			// perform source check returns a file if attaining the source was
+			// successful
+			if (userRequestedWorkflowUpload) {
+				uploadFile = performSourceCheck();
+				if (uploadFile == null)
+					return;
+			}
+
+			// collect and put the metadata values in their respectable vars
+			getMetadata();
+
+			// if the description or the title are empty, prompt the user to
+			// confirm
+			// the upload
+			boolean proceedWithUpload = false;
+			if ((this.strDescription.length() == 0) && (this.strTitle.length() == 0)) {
+				String strInfo = "The workflow 'title' field and the 'description' field\n"
+						+ "(or both) are empty.  Any metadata found within the\n"
+						+ "workflow will be used instead.  Do you wish to proceed?";
+				int confirm = JOptionPane.showConfirmDialog(this, strInfo, "Empty fields",
+						JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
+				if (confirm == JOptionPane.YES_OPTION)
+					proceedWithUpload = true;
+			} else
+				proceedWithUpload = true;
+
+			if (proceedWithUpload) {
+				// the window will stay visible, but should turn into 'waiting'
+				// state
+				final JRootPane rootPane = this.getRootPane();
+				final Container contentPane = this.getContentPane();
+				contentPane.remove(this.bUpload);
+				contentPane.remove(this.bCancel);
+				if (this.lStatusMessage != null)
+					contentPane.remove(this.lStatusMessage);
+				this.taDescription.setEditable(false);
+
+				final GridBagConstraints c = new GridBagConstraints();
+				c.gridx = 0;
+				c.gridy = gridYPositionForStatusLabel;
+				c.gridwidth = 2;
+				c.anchor = GridBagConstraints.CENTER;
+				c.fill = GridBagConstraints.NONE;
+				c.insets = new Insets(10, 5, 10, 5);
+				lStatusMessage = new JLabel((updateResource == null ? "Uploading" : "Updating")
+						+ " your workflow...", new ImageIcon(
+						MyExperimentPerspective.getLocalResourceURL("spinner")),
+						SwingConstants.CENTER);
+				contentPane.add(lStatusMessage, c);
+
+				// disable the (X) button (ideally, would need to remove it, but
+				// there's
+				// no way to do this)
+				this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+
+				// revalidate the window
+				this.pack();
+				this.validate();
+				this.repaint();
+
+				new Thread("Posting workflow") {
+					boolean formatRecognized = false;
+
+					@Override
+					public void run() {
+						String workflowFileContent = "";
+						if (userRequestedWorkflowUpload) {
+							if (uploadFile != null) {
+								try {
+									BufferedReader reader = new BufferedReader(new FileReader(
+											uploadFile));
+									String line;
+
+									String scuflSchemaDef = "xmlns:s=\"http://org.embl.ebi.escience/xscufl/0.1alpha\"";
+									String t2flowSchemaDef = "xmlns=\"http://taverna.sf.net/2008/xml/t2flow\"";
+
+									while ((line = reader.readLine()) != null) {
+										if (!formatRecognized
+												&& (line.contains(scuflSchemaDef) || line
+														.contains(t2flowSchemaDef)))
+											formatRecognized = true;
+
+										workflowFileContent += line + "\n";
+									}
+								} catch (Exception e) {
+									lStatusMessage = new JLabel("Error occurred:" + e.getMessage(),
+											new ImageIcon(MyExperimentPerspective
+													.getLocalResourceURL("failure_icon")),
+											SwingConstants.LEFT);
+									logger.error(e.getCause() + "\n" + e.getMessage());
+								}
+							}
+						}
+
+						// *** POST THE WORKFLOW ***
+						final ServerResponse response;
+
+						if ((userRequestedWorkflowUpload && formatRecognized)
+								|| !userRequestedWorkflowUpload) {
+							if (updateResource == null) // upload a new workflow
+								response = myExperimentClient.postWorkflow(workflowFileContent,
+										Util.stripAllHTML(strTitle),
+										Util.stripAllHTML(strDescription), licence, sharing);
+							else
+								// edit existing workflow
+								response = myExperimentClient.updateWorkflowVersionOrMetadata(
+										updateResource, workflowFileContent,
+										Util.stripAllHTML(strTitle),
+										Util.stripAllHTML(strDescription), licence, sharing);
+
+							bUploadingSuccessful = (response.getResponseCode() == HttpURLConnection.HTTP_OK);
+						} else {
+							bUploadingSuccessful = false;
+							response = null;
+						}
+
+						SwingUtilities.invokeLater(new Runnable() {
+							public void run() {
+								// *** REACT TO POSTING RESULT ***
+								if (bUploadingSuccessful) {
+									// workflow uploaded successfully
+									setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+
+									// disable all fields in dialog
+									tfTitle.setEnabled(false);
+									taDescription.setEnabled(false);
+									jcbLicences.setEnabled(false);
+									jcbSharingPermissions.setEnabled(false);
+									if (userRequestedWorkflowUpload) {
+										rbSelectOpenWorkflow.setEnabled(false);
+										rbSelectLocalFile.setEnabled(false);
+										selectedFileLabel.setEnabled(false);
+										bSelectFile.setEnabled(false);
+										jcbOpenWorkflows.setEnabled(false);
+									}
+									contentPane.remove(lStatusMessage);
+
+									c.insets = new Insets(10, 5, 5, 5);
+									lStatusMessage = new JLabel("Your workflow was successfully "
+											+ (updateResource == null ? "uploaded." : "updated."),
+											new ImageIcon(MyExperimentPerspective
+													.getLocalResourceURL("success_icon")),
+											SwingConstants.LEFT);
+									contentPane.add(lStatusMessage, c);
+
+									bCancel.setText("OK");
+									bCancel.setDefaultCapable(true);
+									rootPane.setDefaultButton(bCancel);
+									c.insets = new Insets(5, 5, 10, 5);
+									c.gridy++;
+									contentPane.add(bCancel, c);
+
+									pack();
+									bCancel.requestFocusInWindow();
+
+									// update uploaded items history making sure
+									// that:
+									// - there's only one occurrence of this
+									// item in the history;
+									// - if this item was in the history before,
+									// it is moved to
+									// the 'top' now;
+									// - predefined history size is not exceeded
+									MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+											.getUploadedItemsHistoryList().remove(updateResource);
+									MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+											.getUploadedItemsHistoryList().add(updateResource);
+									if (MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+											.getUploadedItemsHistoryList().size() > HistoryBrowserTabContentPanel.UPLOADED_ITEMS_HISTORY_LENGTH) {
+										MainComponent.MAIN_COMPONENT.getHistoryBrowser()
+												.getUploadedItemsHistoryList().remove(0);
+									}
+
+									// now update the uploaded items history
+									// panel in 'History'
+									// tab
+									if (MainComponent.MAIN_COMPONENT.getHistoryBrowser() != null) {
+										MainComponent.MAIN_COMPONENT
+												.getHistoryBrowser()
+												.refreshHistoryBox(
+														HistoryBrowserTabContentPanel.UPLOADED_ITEMS_HISTORY);
+									}
+
+								} else {
+									// posting wasn't successful, notify the
+									// user
+									// and provide an option to close window or
+									// start again
+									setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+									taDescription.setEditable(true);
+									contentPane.remove(lStatusMessage);
+
+									c.insets = new Insets(10, 5, 5, 5);
+
+									String msg;
+									if (!formatRecognized)
+										msg = "Error occured: Invalid Taverna workflow.";
+									else
+										msg = "An error occured while processing your request.";
+
+									lStatusMessage = new JLabel(msg, new ImageIcon(
+											MyExperimentPerspective
+													.getLocalResourceURL("failure_icon")),
+											SwingConstants.LEFT);
+									contentPane.add(lStatusMessage, c);
+
+									bUpload.setText("Try again");
+									bUpload.setToolTipText("Please review your workflow or myExperiment base URL");
+									c.anchor = GridBagConstraints.EAST;
+									c.insets = new Insets(5, 5, 10, 5);
+									c.gridwidth = 1;
+									c.weightx = 0.5;
+									c.gridx = 0;
+									c.gridy++;
+									contentPane.add(bUpload, c);
+									rootPane.setDefaultButton(bUpload);
+
+									c.anchor = GridBagConstraints.WEST;
+									c.gridx = 1;
+									bCancel.setPreferredSize(bUpload.getPreferredSize());
+									contentPane.add(bCancel, c);
+
+									pack();
+									validate();
+									repaint();
+								}
+							}
+						});
+					}
+				}.start();
+			} // if proceedWithUpload
+		} else if (e.getSource().equals(this.bCancel)) { // * CANCEL BUTTON *
+			// cleanup the input fields if it wasn't posted successfully +
+			// simply
+			// close and destroy the window
+			if (!this.bUploadingSuccessful) {
+				this.strDescription = null;
+				this.tfTitle = null;
+			}
+			this.dispose();
+		} else if (e.getSource().equals(bSelectFile)) {// * SELECT FILE BUTTON *
+			// *
+			if (jfsSelectFile.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+				localWorkflowFile = jfsSelectFile.getSelectedFile();
+
+				if (localWorkflowFile != null) {
+					selectedFileLabel.setText(localWorkflowFile.getAbsolutePath());
+					selectedFileLabel.setEnabled(true);
+				}
+				pack();
+			}
+		}
+	}
+
+	public void keyPressed(KeyEvent e) {
+		// if TAB was pressed in the text field (title), need to move keyboard
+		// focus
+		if (e.getSource().equals(this.tfTitle) || e.getSource().equals(this.taDescription)) {
+			if (e.getKeyCode() == KeyEvent.VK_TAB) {
+				if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) == KeyEvent.SHIFT_DOWN_MASK) {
+					// SHIFT + TAB move focus backwards
+					((Component) e.getSource()).transferFocusBackward();
+				} else {
+					// TAB moves focus forward
+					((Component) e.getSource()).transferFocus();
+				}
+				e.consume();
+			}
+		}
+	}
+
+	public void focusGained(FocusEvent e) {
+		if (e.getSource().equals(rbSelectLocalFile)) {
+			uploadWorkflowFromLocalFile = true;
+			bSelectFile.setEnabled(uploadWorkflowFromLocalFile);
+			jcbOpenWorkflows.setEnabled(!uploadWorkflowFromLocalFile);
+
+			if (localWorkflowFile != null) {
+				selectedFileLabel.setEnabled(uploadWorkflowFromLocalFile);
+				selectedFileLabel.setText(localWorkflowFile.getAbsolutePath());
+				pack();
+			} else
+				selectedFileLabel.setEnabled(!uploadWorkflowFromLocalFile);
+
+		} else if (e.getSource().equals(rbSelectOpenWorkflow)) {
+			uploadWorkflowFromLocalFile = false;
+			selectedFileLabel.setEnabled(uploadWorkflowFromLocalFile);
+			bSelectFile.setEnabled(uploadWorkflowFromLocalFile);
+			jcbOpenWorkflows.setEnabled(!uploadWorkflowFromLocalFile);
+		}
+	}
+
+	public void componentShown(ComponentEvent e) {
+		// center this dialog box within the preview browser window
+		if (updateResource == null) // upload has been pressed from the MAIN
+			// perspective window
+			Util.centerComponentWithinAnother(this.pluginMainComponent, this);
+		else
+			// upload pressed from resource preview window
+			Util.centerComponentWithinAnother(this.pluginMainComponent.getPreviewBrowser(), this);
+	}
+
+	public void focusLost(FocusEvent e) {
+		// not in use
+	}
+
+	public void caretUpdate(CaretEvent e) {
+		// not in use
+	}
+
+	public void componentHidden(ComponentEvent e) {
+		// not in use
+	}
+
+	public void componentMoved(ComponentEvent e) {
+		// not in use
+	}
+
+	public void keyReleased(KeyEvent e) {
+		// not in use
+	}
+
+	public void keyTyped(KeyEvent e) {
+		// not in use
+	}
+
+	public void componentResized(ComponentEvent e) {
+		// not in use
+	}
+
+	private class DataflowSelection {
+		private final Dataflow dataflow;
+		private final String name;
+
+		public DataflowSelection(Dataflow dataflow, String name) {
+			this.dataflow = dataflow;
+			this.name = name;
+		}
+
+		public Dataflow getDataflow() {
+			return dataflow;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		@Override
+		public String toString() {
+			return name;
+		}
+	}
+
+}

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/model/Base64$InputStream.class
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$InputStream.class b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$InputStream.class
new file mode 100644
index 0000000..a1a5f82
Binary files /dev/null and b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$InputStream.class differ

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/model/Base64$OutputStream.class
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$OutputStream.class b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$OutputStream.class
new file mode 100644
index 0000000..e754656
Binary files /dev/null and b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64$OutputStream.class differ

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/model/Base64.class
----------------------------------------------------------------------
diff --git a/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.class b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.class
new file mode 100644
index 0000000..84fcee9
Binary files /dev/null and b/taverna-perspective-myexperiment/src/main/java/net/sf/taverna/t2/ui/perspectives/myexperiment/model/Base64.class differ