From https://github.com/taverna/taverna-ui-impl master
diff --git a/taverna-workbench-activity-palette-impl/pom.xml b/taverna-workbench-activity-palette-impl/pom.xml
new file mode 100644
index 0000000..4926a94
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>activity-palette-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Activity Palette Impl</name>
+ <description>Activity Palette Implementation</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>activity-palette-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-api</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ </dependency>
+
+ <!-- TODO Remove non-test impl dependency -->
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-impl</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-impl</artifactId>
+ <version>${taverna.configuration.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-impl</artifactId>
+ <version>${taverna.configuration.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java
new file mode 100644
index 0000000..c5221be
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionConstants.java
@@ -0,0 +1,10 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+public interface ServiceDescriptionConstants {
+ String SERVICE_PANEL_CONFIGURATION = "servicePanelConfiguration";
+ String PROVIDERS = "providers";
+ String IGNORED = "ignored";
+ String PROVIDER_ID = "providerID";
+ String CONFIGURATION = "configuration";
+ String TYPE = "type";
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java
new file mode 100644
index 0000000..0a40bda
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionDeserializer.java
@@ -0,0 +1,167 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.IGNORED;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDERS;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDER_ID;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.TYPE;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import uk.org.taverna.scufl2.api.configurations.Configuration;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class ServiceDescriptionDeserializer {
+ private List<ServiceDescriptionProvider> serviceDescriptionProviders;
+
+ ServiceDescriptionDeserializer(
+ List<ServiceDescriptionProvider> serviceDescriptionProviders) {
+ this.serviceDescriptionProviders = serviceDescriptionProviders;
+ }
+
+ public void deserialize(ServiceDescriptionRegistry registry,
+ File serviceDescriptionsFile) throws DeserializationException {
+ try (FileInputStream serviceDescriptionFileStream = new FileInputStream(
+ serviceDescriptionsFile)) {
+ deserialize(registry, serviceDescriptionFileStream);
+ } catch (FileNotFoundException ex) {
+ throw new DeserializationException("Could not locate file "
+ + serviceDescriptionsFile.getAbsolutePath()
+ + " containing service descriptions.");
+ } catch (IOException ex) {
+ throw new DeserializationException(
+ "Could not read stream containing service descriptions from "
+ + serviceDescriptionsFile.getAbsolutePath(), ex);
+ }
+ }
+
+ public void deserialize(ServiceDescriptionRegistry registry,
+ URL serviceDescriptionsURL) throws DeserializationException {
+ try (InputStream serviceDescriptionInputStream = serviceDescriptionsURL
+ .openStream()) {
+ deserialize(registry, serviceDescriptionInputStream);
+ } catch (FileNotFoundException ex) {
+ throw new DeserializationException("Could not open URL "
+ + serviceDescriptionsURL
+ + " containing service descriptions.");
+ } catch (IOException ex) {
+ throw new DeserializationException(
+ "Could not read stream containing service descriptions from "
+ + serviceDescriptionsURL, ex);
+ }
+ }
+
+ private static final JsonFactory factory = new JsonFactory();
+
+ private void deserialize(ServiceDescriptionRegistry registry,
+ InputStream serviceDescriptionsInputStream) throws IOException,
+ DeserializationException {
+ ObjectNode node = (ObjectNode) new ObjectMapper(factory)
+ .readTree(serviceDescriptionsInputStream);
+ List<ServiceDescriptionProvider> providers = deserializeProviders(node,
+ true);
+ for (ServiceDescriptionProvider provider : providers)
+ registry.addServiceDescriptionProvider(provider);
+ }
+
+ public Collection<? extends ServiceDescriptionProvider> deserializeDefaults(
+ ServiceDescriptionRegistry registry,
+ File defaultConfigurableServiceProvidersFile)
+ throws DeserializationException {
+ ObjectNode node;
+ try (FileInputStream serviceDescriptionStream = new FileInputStream(
+ defaultConfigurableServiceProvidersFile)) {
+ node = (ObjectNode) new ObjectMapper(factory)
+ .readTree(serviceDescriptionStream);
+ } catch (IOException e) {
+ throw new DeserializationException("Can't read "
+ + defaultConfigurableServiceProvidersFile);
+ }
+ return deserializeProviders(node, false);
+ }
+
+ private List<ServiceDescriptionProvider> deserializeProviders(
+ ObjectNode rootNode, boolean obeyIgnored)
+ throws DeserializationException {
+ List<ServiceDescriptionProvider> providers = new ArrayList<>();
+
+ ArrayNode providersNode = (ArrayNode) rootNode.get(PROVIDERS);
+ if (providersNode != null)
+ for (JsonNode provider : providersNode)
+ providers.add(deserializeProvider((ObjectNode) provider));
+
+ if (obeyIgnored) {
+ ArrayNode ignoredNode = (ArrayNode) rootNode.get(IGNORED);
+ if (ignoredNode != null)
+ for (JsonNode provider : ignoredNode)
+ providers
+ .remove(deserializeProvider((ObjectNode) provider));
+ }
+
+ return providers;
+ }
+
+ private ServiceDescriptionProvider deserializeProvider(
+ ObjectNode providerNode) throws DeserializationException {
+ String providerId = providerNode.get(PROVIDER_ID).asText().trim();
+ ServiceDescriptionProvider provider = null;
+ for (ServiceDescriptionProvider serviceProvider : serviceDescriptionProviders)
+ if (serviceProvider.getId().equals(providerId)) {
+ provider = serviceProvider;
+ break;
+ }
+ if (provider == null)
+ throw new DeserializationException(
+ "Could not find provider with id " + providerId);
+
+ /*
+ * So we know the service provider now, but we need a separate instance
+ * of that provider for each providerElem. E.g. we can have 2 or more
+ * WSDL provider elements and need to return a separate provider
+ * instance for each as they will have different configurations.
+ */
+ ServiceDescriptionProvider instance = provider.newInstance();
+
+ if (instance instanceof ConfigurableServiceProvider)
+ try {
+ Configuration config = new Configuration();
+ config.setType(URI.create(providerNode.get(TYPE).textValue()));
+ config.setJson(providerNode.get(CONFIGURATION));
+ if (config != null)
+ ((ConfigurableServiceProvider) instance).configure(config);
+ } catch (Exception e) {
+ throw new DeserializationException(
+ "Could not configure provider " + providerId
+ + " using bean " + providerNode, e);
+ }
+ return instance;
+ }
+
+ @SuppressWarnings("serial")
+ static class DeserializationException extends Exception {
+ public DeserializationException(String string) {
+ super(string);
+ }
+
+ public DeserializationException(String string, Exception ex) {
+ super(string, ex);
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java
new file mode 100644
index 0000000..9dc3f00
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionRegistryImpl.java
@@ -0,0 +1,652 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static java.lang.System.currentTimeMillis;
+import static java.lang.Thread.MIN_PRIORITY;
+import static java.lang.Thread.currentThread;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescription;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider.FindServiceDescriptionsCallBack;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.servicedescriptions.events.AddedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.PartialServiceDescriptionsNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderErrorNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderStatusNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderUpdatingNotification;
+import net.sf.taverna.t2.servicedescriptions.events.ProviderWarningNotification;
+import net.sf.taverna.t2.servicedescriptions.events.RemovedProviderEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionProvidedEvent;
+import net.sf.taverna.t2.servicedescriptions.events.ServiceDescriptionRegistryEvent;
+import net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionDeserializer.DeserializationException;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class ServiceDescriptionRegistryImpl implements ServiceDescriptionRegistry {
+ /**
+ * If a writable property of this name on a provider exists (ie. the provider has a method
+ * setServiceDescriptionRegistry(ServiceDescriptionRegistry registry) - then this property will
+ * be set to the current registry.
+ */
+ public static final String SERVICE_DESCRIPTION_REGISTRY = "serviceDescriptionRegistry";
+ public static Logger logger = Logger.getLogger(ServiceDescriptionRegistryImpl.class);
+ public static final ThreadGroup threadGroup = new ThreadGroup("Service description providers");
+ /**
+ * Total maximum timeout while waiting for description threads to finish
+ */
+ private static final long DESCRIPTION_THREAD_TIMEOUT_MS = 3000;
+ protected static final String CONF_DIR = "conf";
+ protected static final String SERVICE_PROVIDERS_FILENAME = "service_providers.xml";
+ private static final String DEFAULT_CONFIGURABLE_SERVICE_PROVIDERS_FILENAME = "default_service_providers.xml";
+
+ private ServiceDescriptionsConfiguration serviceDescriptionsConfig;
+ private ApplicationConfiguration applicationConfiguration;
+ /**
+ * <code>false</code> until first call to {@link #loadServiceProviders()} - which is done by
+ * first call to {@link #getServiceDescriptionProviders()}.
+ */
+ private boolean hasLoadedProviders = false;
+ /**
+ * <code>true</code> while {@link #loadServiceProviders(File)},
+ * {@link #loadServiceProviders(URL)} or {@link #loadServiceProviders()} is in progress, avoids
+ * triggering {@link #saveServiceDescriptions()} on
+ * {@link #addServiceDescriptionProvider(ServiceDescriptionProvider)} calls.
+ */
+ private boolean loading = false;
+ private MultiCaster<ServiceDescriptionRegistryEvent> observers = new MultiCaster<>(this);
+ private List<ServiceDescriptionProvider> serviceDescriptionProviders;
+ private Set<ServiceDescriptionProvider> allServiceProviders;
+ private Map<ServiceDescriptionProvider, Set<ServiceDescription>> providerDescriptions = new HashMap<>();
+ private Map<ServiceDescriptionProvider, Thread> serviceDescriptionThreads = new HashMap<>();
+ /**
+ * Service providers added by the user, should be saved
+ */
+ private Set<ServiceDescriptionProvider> userAddedProviders = new HashSet<>();
+ private Set<ServiceDescriptionProvider> userRemovedProviders = new HashSet<>();
+ private Set<ServiceDescriptionProvider> defaultServiceDescriptionProviders;
+ /**
+ * File containing a list of configured ConfigurableServiceProviders which is used to get the
+ * default set of service descriptions together with those provided by AbstractTemplateServiceS.
+ * This file is located in the conf directory of the Taverna startup directory.
+ */
+ private File defaultConfigurableServiceProvidersFile;
+ private boolean defaultSystemConfigurableProvidersLoaded = false;
+
+ static {
+ threadGroup.setMaxPriority(MIN_PRIORITY);
+ }
+
+ public ServiceDescriptionRegistryImpl(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ defaultConfigurableServiceProvidersFile = new File(
+ getTavernaStartupConfigurationDirectory(),
+ DEFAULT_CONFIGURABLE_SERVICE_PROVIDERS_FILENAME);
+ }
+
+ /**
+ * Get the Taverna distribution (startup) configuration directory.
+ */
+ private File getTavernaStartupConfigurationDirectory() {
+ File distroHome = null;
+ File configDirectory = null;
+ distroHome = applicationConfiguration.getStartupDir();
+ configDirectory = new File(distroHome, "conf");
+ if (!configDirectory.exists())
+ configDirectory.mkdir();
+ return configDirectory;
+ }
+
+ private static void joinThreads(Collection<? extends Thread> threads,
+ long descriptionThreadTimeoutMs) {
+ long finishJoinBy = currentTimeMillis() + descriptionThreadTimeoutMs;
+ for (Thread thread : threads) {
+ // No shorter timeout than 1 ms (thread.join(0) waits forever!)
+ long timeout = Math.max(1, finishJoinBy - currentTimeMillis());
+ try {
+ thread.join(timeout);
+ } catch (InterruptedException e) {
+ currentThread().interrupt();
+ return;
+ }
+ if (thread.isAlive())
+ logger.debug("Thread did not finish " + thread);
+ }
+ }
+
+
+ @Override
+ public void addObserver(Observer<ServiceDescriptionRegistryEvent> observer) {
+ observers.addObserver(observer);
+ }
+
+ @Override
+ public void addServiceDescriptionProvider(ServiceDescriptionProvider provider) {
+ synchronized (this) {
+ userRemovedProviders.remove(provider);
+ if (!getDefaultServiceDescriptionProviders().contains(provider))
+ userAddedProviders.add(provider);
+ allServiceProviders.add(provider);
+ }
+
+ // Spring-like auto-config
+ try {
+ // BeanUtils should ignore this if provider does not have that property
+ BeanUtils.setProperty(provider, SERVICE_DESCRIPTION_REGISTRY, this);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ logger.warn("Could not set serviceDescriptionRegistry on "
+ + provider, e);
+ }
+
+ if (!loading)
+ saveServiceDescriptions();
+ observers.notify(new AddedProviderEvent(provider));
+ updateServiceDescriptions(false, false);
+ }
+
+ private File findServiceDescriptionsFile() {
+ File confDir = new File(
+ applicationConfiguration.getApplicationHomeDir(), CONF_DIR);
+ confDir.mkdirs();
+ if (!confDir.isDirectory())
+ throw new RuntimeException("Invalid directory: " + confDir);
+ File serviceDescriptionsFile = new File(confDir,
+ SERVICE_PROVIDERS_FILENAME);
+ return serviceDescriptionsFile;
+ }
+
+ @Override
+ public List<Observer<ServiceDescriptionRegistryEvent>> getObservers() {
+ return observers.getObservers();
+ }
+
+ // Fallback to this method that uses hardcoded default services if you cannot read them from
+ // the file.
+// @SuppressWarnings("unchecked")
+// public synchronized Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProvidersFallback() {
+// /*if (defaultServiceDescriptionProviders != null) {
+// return defaultServiceDescriptionProviders;
+// }
+// defaultServiceDescriptionProviders = new HashSet<ServiceDescriptionProvider>();
+// */
+// for (ServiceDescriptionProvider provider : serviceDescriptionProviders) {
+//
+// /* We do not need these - already loaded them from getDefaultServiceDescriptionProviders()
+// if (!(provider instanceof ConfigurableServiceProvider)) {
+// defaultServiceDescriptionProviders.add(provider);
+// continue;
+// }*/
+//
+// // Just load the hard coded default configurable service providers
+// if (provider instanceof ConfigurableServiceProvider){
+// ConfigurableServiceProvider<Object> template = ((ConfigurableServiceProvider<Object>)
+// provider);
+// // Get configurations
+// List<Object> configurables = template.getDefaultConfigurations();
+// for (Object config : configurables) {
+// // Make a copy that we can configure
+// ConfigurableServiceProvider<Object> configurableProvider = template.clone();
+// try {
+// configurableProvider.configure(config);
+// } catch (ConfigurationException e) {
+// logger.warn("Can't configure provider "
+// + configurableProvider + " with " + config);
+// continue;
+// }
+// defaultServiceDescriptionProviders.add(configurableProvider);
+// }
+// }
+// }
+// return defaultServiceDescriptionProviders;
+// }
+
+ // Get the default services.
+ @Override
+ public synchronized Set<ServiceDescriptionProvider> getDefaultServiceDescriptionProviders() {
+ if (defaultServiceDescriptionProviders != null)
+ return defaultServiceDescriptionProviders;
+ defaultServiceDescriptionProviders = new HashSet<>();
+
+ /*
+ * Add default configurable service description providers from the
+ * default_service_providers.xml file
+ */
+ if (defaultConfigurableServiceProvidersFile.exists()) {
+ try {
+ ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+ serviceDescriptionProviders);
+ defaultServiceDescriptionProviders.addAll(deserializer
+ .deserializeDefaults(this,
+ defaultConfigurableServiceProvidersFile));
+ /*
+ * We have successfully loaded the defaults for system
+ * configurable providers. Note that there are still defaults
+ * for third party configurable providers, which will be loaded
+ * below using getDefaultConfigurations().
+ */
+ defaultSystemConfigurableProvidersLoaded = true;
+ } catch (Exception e) {
+ logger.error("Could not load default service providers from "
+ + defaultConfigurableServiceProvidersFile.getAbsolutePath(), e);
+
+ /*
+ * Fallback on the old hardcoded method of loading default
+ * system configurable service providers using
+ * getDefaultConfigurations().
+ */
+ defaultSystemConfigurableProvidersLoaded = false;
+ }
+ } else {
+ logger.warn("Could not find the file "
+ + defaultConfigurableServiceProvidersFile.getAbsolutePath()
+ + " containing default system service providers. "
+ + "Using the hardcoded list of default system providers.");
+
+ /*
+ * Fallback on the old hardcoded method of loading default system
+ * configurable service providers using getDefaultConfigurations().
+ */
+ defaultSystemConfigurableProvidersLoaded = false;
+ }
+
+ /*
+ * Load other default service description providers - template, local
+ * workers and third party configurable service providers
+ */
+ for (ServiceDescriptionProvider provider : serviceDescriptionProviders) {
+ /*
+ * Template service providers (beanshell, string constant, etc. )
+ * and providers of local workers.
+ */
+ if (!(provider instanceof ConfigurableServiceProvider)) {
+ defaultServiceDescriptionProviders.add(provider);
+ continue;
+ }
+
+ /*
+ * Default system or third party configurable service description
+ * provider. System ones are read from the
+ * default_service_providers.xml file so getDefaultConfigurations()
+ * on them will not have much effect here unless
+ * defaultSystemConfigurableProvidersLoaded is set to false.
+ */
+ //FIXME needs to be designed to work using Configuration instances
+ //FIXME needs to get configurations via OSGi discovery
+ /*
+ ConfigurableServiceProvider template = (ConfigurableServiceProvider) provider;
+ // Get configurations
+ for (ObjectNode config : template.getDefaultConfigurations()) {
+ // Make a copy that we can configure
+ ConfigurableServiceProvider configurableProvider = template.clone();
+ try {
+ configurableProvider.configure(config);
+ } catch (ConfigurationException e) {
+ logger.warn("Can't configure provider "
+ + configurableProvider + " with " + config);
+ continue;
+ }
+ defaultServiceDescriptionProviders.add(configurableProvider);
+ }
+ */
+ }
+
+ return defaultServiceDescriptionProviders;
+ }
+
+ @Override
+ public synchronized Set<ServiceDescriptionProvider> getServiceDescriptionProviders() {
+ if (allServiceProviders != null)
+ return new HashSet<>(allServiceProviders);
+ allServiceProviders = new HashSet<>(userAddedProviders);
+ synchronized (this) {
+ if (!hasLoadedProviders)
+ try {
+ loadServiceProviders();
+ } catch (Exception e) {
+ logger.error("Could not load service providers", e);
+ } finally {
+ hasLoadedProviders = true;
+ }
+ }
+ for (ServiceDescriptionProvider provider : getDefaultServiceDescriptionProviders()) {
+ if (userRemovedProviders.contains(provider))
+ continue;
+ if (provider instanceof ConfigurableServiceProvider
+ && !serviceDescriptionsConfig.isIncludeDefaults())
+ // We'll skip the default configurable service provders
+ continue;
+ allServiceProviders.add(provider);
+ }
+ return new HashSet<>(allServiceProviders);
+ }
+
+ @Override
+ public Set<ServiceDescriptionProvider> getServiceDescriptionProviders(
+ ServiceDescription sd) {
+ Set<ServiceDescriptionProvider> result = new HashSet<>();
+ for (ServiceDescriptionProvider sdp : providerDescriptions.keySet())
+ if (providerDescriptions.get(sdp).contains(sd))
+ result.add(sdp);
+ return result;
+ }
+
+ @Override
+ public Set<ServiceDescription> getServiceDescriptions() {
+ updateServiceDescriptions(false, true);
+ Set<ServiceDescription> serviceDescriptions = new HashSet<>();
+ synchronized (providerDescriptions) {
+ for (Set<ServiceDescription> providerDesc : providerDescriptions
+ .values())
+ serviceDescriptions.addAll(providerDesc);
+ }
+ return serviceDescriptions;
+ }
+
+ @Override
+ public ServiceDescription getServiceDescription(URI serviceType) {
+ for (ServiceDescription serviceDescription : getServiceDescriptions())
+ if (serviceDescription.getActivityType().equals(serviceType))
+ return serviceDescription;
+ return null;
+ }
+
+ @Override
+ public List<ConfigurableServiceProvider> getUnconfiguredServiceProviders() {
+ List<ConfigurableServiceProvider> providers = new ArrayList<>();
+ for (ServiceDescriptionProvider provider : serviceDescriptionProviders)
+ if (provider instanceof ConfigurableServiceProvider)
+ providers.add((ConfigurableServiceProvider) provider);
+ return providers;
+ }
+
+ @Override
+ public Set<ServiceDescriptionProvider> getUserAddedServiceProviders() {
+ return new HashSet<>(userAddedProviders);
+ }
+
+ @Override
+ public Set<ServiceDescriptionProvider> getUserRemovedServiceProviders() {
+ return new HashSet<>(userRemovedProviders);
+ }
+
+ @Override
+ public void loadServiceProviders() {
+ File serviceProviderFile = findServiceDescriptionsFile();
+ if (serviceProviderFile.isFile())
+ loadServiceProviders(serviceProviderFile);
+ hasLoadedProviders = true;
+ }
+
+ @Override
+ public void loadServiceProviders(File serviceProvidersFile) {
+ ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+ serviceDescriptionProviders);
+ loading = true;
+ try {
+ deserializer.deserialize(this, serviceProvidersFile);
+ } catch (DeserializationException e) {
+ logger.error("failed to deserialize configuration", e);
+ }
+ loading = false;
+ }
+
+ @Override
+ public void loadServiceProviders(URL serviceProvidersURL) {
+ ServiceDescriptionDeserializer deserializer = new ServiceDescriptionDeserializer(
+ serviceDescriptionProviders);
+ loading = true;
+ try {
+ deserializer.deserialize(this, serviceProvidersURL);
+ } catch (DeserializationException e) {
+ logger.error("failed to deserialize configuration", e);
+ }
+ loading = false;
+ }
+
+ @Override
+ public void refresh() {
+ updateServiceDescriptions(true, false);
+ }
+
+ @Override
+ public void removeObserver(Observer<ServiceDescriptionRegistryEvent> observer) {
+ observers.removeObserver(observer);
+ }
+
+ @Override
+ public synchronized void removeServiceDescriptionProvider(
+ ServiceDescriptionProvider provider) {
+ if (!userAddedProviders.remove(provider))
+ // Not previously added - must be a default one.. but should we remove it?
+ if (loading || serviceDescriptionsConfig.isRemovePermanently()
+ && serviceDescriptionsConfig.isIncludeDefaults())
+ userRemovedProviders.add(provider);
+ if (allServiceProviders.remove(provider)) {
+ synchronized (providerDescriptions) {
+ Thread thread = serviceDescriptionThreads.remove(provider);
+ if (thread != null)
+ thread.interrupt();
+ providerDescriptions.remove(provider);
+ }
+ observers.notify(new RemovedProviderEvent(provider));
+ }
+ if (!loading)
+ saveServiceDescriptions();
+ }
+
+ @Override
+ public void saveServiceDescriptions() {
+ File serviceDescriptionsFile = findServiceDescriptionsFile();
+ saveServiceDescriptions(serviceDescriptionsFile);
+ }
+
+ @Override
+ public void saveServiceDescriptions(File serviceDescriptionsFile) {
+ ServiceDescriptionSerializer serializer = new ServiceDescriptionSerializer();
+ try {
+ serializer.serializeRegistry(this, serviceDescriptionsFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Can't save service descriptions to "
+ + serviceDescriptionsFile);
+ }
+ }
+
+ /**
+ * Exports all configurable service providers (that give service
+ * descriptions) currently found in the Service Registry (apart from service
+ * templates and local services) regardless of who added them (user or
+ * default system providers).
+ * <p>
+ * Unlike {@link #saveServiceDescriptions}, this export does not have the
+ * "ignored providers" section as this is just a plain export of everything
+ * in the Service Registry.
+ *
+ * @param serviceDescriptionsFile
+ */
+ @Override
+ public void exportCurrentServiceDescriptions(File serviceDescriptionsFile) {
+ ServiceDescriptionSerializer serializer = new ServiceDescriptionSerializer();
+ try {
+ serializer.serializeFullRegistry(this, serviceDescriptionsFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Could not save service descriptions to "
+ + serviceDescriptionsFile);
+ }
+ }
+
+ public void setServiceDescriptionProvidersList(
+ List<ServiceDescriptionProvider> serviceDescriptionProviders) {
+ this.serviceDescriptionProviders = serviceDescriptionProviders;
+ }
+
+ private void updateServiceDescriptions(boolean refreshAll, boolean waitFor) {
+ List<Thread> threads = new ArrayList<>();
+ for (ServiceDescriptionProvider provider : getServiceDescriptionProviders()) {
+ synchronized (providerDescriptions) {
+ if (providerDescriptions.containsKey(provider) && !refreshAll)
+ // We'll used the cached values
+ continue;
+ Thread oldThread = serviceDescriptionThreads.get(provider);
+ if (oldThread != null && oldThread.isAlive()) {
+ if (refreshAll)
+ // New thread will override the old thread
+ oldThread.interrupt();
+ else {
+ // observers.notify(new ProviderStatusNotification(provider, "Waiting for provider"));
+ continue;
+ }
+ }
+ // Not run yet - we'll start a new tread
+ Thread thread = new FindServiceDescriptionsThread(provider);
+ threads.add(thread);
+ serviceDescriptionThreads.put(provider, thread);
+ thread.start();
+ }
+ }
+ if (waitFor)
+ joinThreads(threads, DESCRIPTION_THREAD_TIMEOUT_MS);
+ }
+
+ @Override
+ public boolean isDefaultSystemConfigurableProvidersLoaded() {
+ return defaultSystemConfigurableProvidersLoaded;
+ }
+
+ /**
+ * Sets the serviceDescriptionsConfig.
+ *
+ * @param serviceDescriptionsConfig
+ * the new value of serviceDescriptionsConfig
+ */
+ public void setServiceDescriptionsConfig(
+ ServiceDescriptionsConfiguration serviceDescriptionsConfig) {
+ this.serviceDescriptionsConfig = serviceDescriptionsConfig;
+ }
+
+ class FindServiceDescriptionsThread extends Thread implements
+ UncaughtExceptionHandler, FindServiceDescriptionsCallBack {
+ private final ServiceDescriptionProvider provider;
+ private boolean aborting = false;
+ private final Set<ServiceDescription> providerDescs = new HashSet<>();
+
+ FindServiceDescriptionsThread(ServiceDescriptionProvider provider) {
+ super(threadGroup, "Find service descriptions from " + provider);
+ this.provider = provider;
+ setUncaughtExceptionHandler(this);
+ setDaemon(true);
+ }
+
+ @Override
+ public void fail(String message, Throwable ex) {
+ logger.warn("Provider " + getProvider() + ": " + message, ex);
+ if (aborting)
+ return;
+ observers.notify(new ProviderErrorNotification(getProvider(),
+ message, ex));
+ }
+
+ @Override
+ public void finished() {
+ if (aborting)
+ return;
+ synchronized (providerDescriptions) {
+ providerDescriptions.put(getProvider(), providerDescs);
+ }
+ observers.notify(new ServiceDescriptionProvidedEvent(getProvider(),
+ providerDescs));
+ }
+
+ @Override
+ public void partialResults(
+ Collection<? extends ServiceDescription> serviceDescriptions) {
+ if (aborting)
+ return;
+ providerDescs.addAll(serviceDescriptions);
+ synchronized (providerDescriptions) {
+ providerDescriptions.put(getProvider(), providerDescs);
+ }
+ observers.notify(new PartialServiceDescriptionsNotification(
+ getProvider(), serviceDescriptions));
+ }
+
+ @Override
+ public void status(String message) {
+ logger.debug("Provider " + getProvider() + ": " + message);
+ if (aborting)
+ return;
+ observers.notify(new ProviderStatusNotification(getProvider(),
+ message));
+ }
+
+ @Override
+ public void warning(String message) {
+ logger.warn("Provider " + getProvider() + ": " + message);
+ if (aborting)
+ return;
+ observers.notify(new ProviderWarningNotification(getProvider(),
+ message));
+ }
+
+ public ServiceDescriptionProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public void interrupt() {
+ aborting = true;
+ super.interrupt();
+ }
+
+ @Override
+ public void run() {
+ observers.notify(new ProviderUpdatingNotification(provider));
+ getProvider().findServiceDescriptionsAsync(this);
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable ex) {
+ logger.error("Uncaught exception in " + t, ex);
+ fail("Uncaught exception", ex);
+ }
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java
new file mode 100644
index 0000000..8a047a3
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionSerializer.java
@@ -0,0 +1,102 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.IGNORED;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDERS;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.PROVIDER_ID;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.SERVICE_PANEL_CONFIGURATION;
+import static net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionConstants.TYPE;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Set;
+
+import net.sf.taverna.t2.servicedescriptions.ConfigurableServiceProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+
+import org.apache.log4j.Logger;
+import org.jdom.JDOMException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class ServiceDescriptionSerializer {
+ private static Logger logger = Logger
+ .getLogger(ServiceDescriptionSerializer.class);
+
+ public void serializeRegistry(ServiceDescriptionRegistry registry, File file)
+ throws IOException {
+ Set<ServiceDescriptionProvider> ignoreProviders = registry
+ .getUserRemovedServiceProviders();
+ JsonNode registryElement = serializeRegistry(registry, ignoreProviders);
+ try (BufferedOutputStream bufferedOutStream = new BufferedOutputStream(
+ new FileOutputStream(file))) {
+ bufferedOutStream.write(registryElement.toString()
+ .getBytes("UTF-8"));
+ }
+ }
+
+ /**
+ * Export the whole service registry to an xml file, regardless of who added
+ * the service provider (user or system default). In this case there will be
+ * no "ignored providers" in the saved file.
+ */
+ public void serializeFullRegistry(ServiceDescriptionRegistry registry,
+ File file) throws IOException {
+ JsonNode registryElement = serializeRegistry(registry, ALL_PROVIDERS);
+ try (BufferedOutputStream bufferedOutStream = new BufferedOutputStream(
+ new FileOutputStream(file))) {
+ bufferedOutStream.write(registryElement.toString()
+ .getBytes("UTF-8"));
+ }
+ }
+
+ private static final JsonNodeFactory factory = JsonNodeFactory.instance;
+ private static final Set<ServiceDescriptionProvider> ALL_PROVIDERS = null;
+
+ private JsonNode serializeRegistry(ServiceDescriptionRegistry registry,
+ Set<ServiceDescriptionProvider> ignoreProviders) {
+ ObjectNode overallConfiguration = factory.objectNode();
+ overallConfiguration.put(SERVICE_PANEL_CONFIGURATION,
+ ignoreProviders != ALL_PROVIDERS ? "full" : "defaults only");
+ ArrayNode providers = overallConfiguration.putArray(PROVIDERS);
+
+ for (ServiceDescriptionProvider provider : registry
+ .getUserAddedServiceProviders())
+ try {
+ providers.add(serializeProvider(provider));
+ } catch (JDOMException | IOException e) {
+ logger.warn("Could not serialize " + provider, e);
+ }
+
+ if (ignoreProviders != ALL_PROVIDERS) {
+ ArrayNode ignored = overallConfiguration.putArray(IGNORED);
+ for (ServiceDescriptionProvider provider : ignoreProviders)
+ try {
+ ignored.add(serializeProvider(provider));
+ } catch (JDOMException | IOException e) {
+ logger.warn("Could not serialize " + provider, e);
+ }
+ }
+
+ return overallConfiguration;
+ }
+
+ private JsonNode serializeProvider(ServiceDescriptionProvider provider)
+ throws JDOMException, IOException {
+ ObjectNode node = factory.objectNode();
+ node.put(PROVIDER_ID, provider.getId());
+
+ if (provider instanceof ConfigurableServiceProvider) {
+ ConfigurableServiceProvider configurable = (ConfigurableServiceProvider) provider;
+ node.put(TYPE, configurable.getConfiguration().getType().toString());
+ node.put(CONFIGURATION, configurable.getConfiguration().getJson());
+ }
+ return node;
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java
new file mode 100644
index 0000000..ee180a7
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionXMLConstants.java
@@ -0,0 +1,15 @@
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import org.jdom.Namespace;
+
+public interface ServiceDescriptionXMLConstants {
+
+ public static final Namespace SERVICE_DESCRIPTION_NS = Namespace
+ .getNamespace("http://taverna.sf.net/2009/xml/servicedescription");
+ public static final String PROVIDER = "provider";
+ public static final String PROVIDERS = "providers";
+ public static final String SERVICE_DESCRIPTIONS = "serviceDescriptions";
+ public static final String IGNORED_PROVIDERS = "ignoredProviders";
+ public static final String PROVIDER_IDENTIFIER = "providerId";
+
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java
new file mode 100644
index 0000000..ef0295c
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/servicedescriptions/impl/ServiceDescriptionsConfigurationImpl.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.servicedescriptions.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class ServiceDescriptionsConfigurationImpl extends AbstractConfigurable
+ implements ServiceDescriptionsConfiguration {
+ private static final String INCLUDE_DEFAULTS = "includeDefaults";
+ private static final String SERVICE_PALETTE = "Service providers";
+ private static final String SERVICE_PALETTE_PREFIX = "ServiceProviders";
+ private static final String CATEGORY = "Services";
+ private static final String UUID = "f0d1ef24-9337-412f-b2c3-220a01e2efd0";
+ private static final String REMOVE_PERMANENTLY = "removePermanently";
+
+ public ServiceDescriptionsConfigurationImpl(
+ ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return CATEGORY;
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ Map<String, String> defaults = new HashMap<String, String>();
+ defaults.put(INCLUDE_DEFAULTS, "true");
+ defaults.put(REMOVE_PERMANENTLY, "true");
+ return defaults;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return SERVICE_PALETTE;
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return SERVICE_PALETTE_PREFIX;
+ }
+
+ @Override
+ public String getUUID() {
+ return UUID;
+ }
+
+ @Override
+ public boolean isIncludeDefaults() {
+ return Boolean.parseBoolean(getProperty(INCLUDE_DEFAULTS));
+ }
+
+ @Override
+ public void setIncludeDefaults(boolean includeDefaults) {
+ setProperty(INCLUDE_DEFAULTS, Boolean.toString(includeDefaults));
+ }
+
+ @Override
+ public boolean isRemovePermanently() {
+ return Boolean.parseBoolean(getProperty(REMOVE_PERMANENTLY));
+ }
+
+ @Override
+ public void setRemovePermanently(boolean removePermanently) {
+ setProperty(REMOVE_PERMANENTLY, Boolean.toString(removePermanently));
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java
new file mode 100644
index 0000000..46f82c4
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfiguration.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class ActivityPaletteConfiguration extends AbstractConfigurable {
+ private Map<String,String> defaultPropertyMap;
+
+ public ActivityPaletteConfiguration(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return "Services";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<>();
+
+ // //wsdl
+ //defaultPropertyMap.put("taverna.defaultwsdl", "http://www.ebi.ac.uk/xembl/XEMBL.wsdl,"+
+ // "http://soap.genome.jp/KEGG.wsdl,"+
+ // "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/soap/eutils.wsdl,"+
+ // "http://soap.bind.ca/wsdl/bind.wsdl,"+
+ // "http://www.ebi.ac.uk/ws/services/urn:Dbfetch?wsdl");
+
+ // //soaplab
+ //defaultPropertyMap.put("taverna.defaultsoaplab", "http://www.ebi.ac.uk/soaplab/services/");
+
+ // //biomart
+ //defaultPropertyMap.put("taverna.defaultmartregistry","http://www.biomart.org/biomart");
+
+ //add property names
+ //defaultPropertyMap.put("name.taverna.defaultwsdl", "WSDL");
+ //defaultPropertyMap.put("name.taverna.defaultsoaplab","Soaplab");
+ //defaultPropertyMap.put("name.taverna.defaultmartregistry", "Biomart");
+ }
+ return defaultPropertyMap;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Activity Palette";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "ActivityPalette";
+ }
+
+ @Override
+ public String getUUID() {
+ return "ad9f3a60-5967-11dd-ae16-0800200c9a66";
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java
new file mode 100644
index 0000000..6166e60
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationPanel.java
@@ -0,0 +1,284 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.LEFT;
+import static java.awt.FlowLayout.RIGHT;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.border.BevelBorder.LOWERED;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BoxLayout;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.border.BevelBorder;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ActivityPaletteConfigurationPanel extends JPanel {
+ private static Logger logger = Logger
+ .getLogger(ActivityPaletteConfigurationPanel.class);
+
+ private Map<String,List<String>> values = new HashMap<>();
+ private Map<String,String> names = new HashMap<>();
+ private DefaultComboBoxModel<String> model;
+ private DefaultListModel<String> listModel;
+ private JList<String> propertyListItems;
+ private String selectedKey;
+ private JButton deleteTypeButton;
+ private final ActivityPaletteConfiguration config;
+
+ public ActivityPaletteConfigurationPanel(ActivityPaletteConfiguration config) {
+ super(new BorderLayout());
+ this.config = config;
+
+ model = new DefaultComboBoxModel<>();
+ for (String key : config.getInternalPropertyMap().keySet()) {
+ if (key.startsWith("taverna.")
+ && config.getPropertyStringList(key) != null) {
+ model.addElement(key);
+ values.put(key,
+ new ArrayList<>(config.getPropertyStringList(key)));
+ }
+ if (key.startsWith("name.taverna."))
+ names.put(key, config.getProperty(key).toString());
+ }
+ deleteTypeButton = new JButton("Delete");
+
+ final JButton addTypeButton = new JButton("Add");
+ final JComboBox<String> comboBox = new JComboBox<>(model);
+ comboBox.setRenderer(new DefaultListCellRenderer() {
+ @Override
+ public Component getListCellRendererComponent(JList<?> list,
+ Object value, int index, boolean isSelected,
+ boolean cellHasFocus) {
+ if (value != null && value instanceof String) {
+ String name = names.get("name." + value);
+ if (name != null)
+ value = name;
+ }
+ return super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ }
+ });
+
+ deleteTypeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String displayText = names.get("name." + selectedKey);
+ if (displayText == null)
+ displayText = selectedKey;
+ if (confirm("Confirm removal",
+ "Are you sure you wish to remove the type "
+ + displayText + "?")) {
+ names.remove("name." + selectedKey);
+ values.remove(selectedKey);
+ model.removeElement(selectedKey);
+ comboBox.setSelectedIndex(0);
+ }
+ }
+ });
+
+ addTypeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String key = input("New key", "Provide the new key.");
+ if (key == null)
+ return;
+ String name = input("Name for the key",
+ "Provide the name for the key: " + key);
+ if (name == null)
+ return;
+
+ values.put(key, new ArrayList<String>());
+ names.put("name." + key, name);
+ model.addElement(key);
+ comboBox.setSelectedItem(key);
+ }
+ });
+
+ comboBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (comboBox.getSelectedItem() != null
+ && comboBox.getSelectedItem() instanceof String) {
+ selectedKey = (String) comboBox.getSelectedItem();
+ List<String> selectedList = values.get(selectedKey);
+ populateList(selectedList);
+ deleteTypeButton.setEnabled(selectedList.size() == 0);
+ }
+ }
+ });
+
+ JPanel propertySelectionPanel = new JPanel(new FlowLayout(LEFT));
+ propertySelectionPanel.add(new JLabel("Activity type:"));
+ propertySelectionPanel.add(comboBox);
+ propertySelectionPanel.add(addTypeButton);
+ propertySelectionPanel.add(deleteTypeButton);
+ add(propertySelectionPanel, NORTH);
+
+ JPanel listPanel = new JPanel(new BorderLayout());
+ listModel = new DefaultListModel<>();
+ propertyListItems = new JList<>(listModel);
+ propertyListItems.setBorder(new BevelBorder(LOWERED));
+
+ listPanel.add(propertyListItems, CENTER);
+ listPanel.add(listButtons(), EAST);
+
+ add(listPanel, CENTER);
+
+ add(applyButtonPanel(), SOUTH);
+
+ if (model.getSize() > 0)
+ comboBox.setSelectedItem(model.getElementAt(0));
+ }
+
+ private void populateList(List<String> selectedList) {
+ listModel.removeAllElements();
+ for (String item : selectedList)
+ listModel.addElement(item);
+ }
+
+ private JPanel applyButtonPanel() {
+ JPanel applyPanel = new JPanel(new FlowLayout(RIGHT));
+ JButton applyButton = new JButton("Apply");
+
+ applyButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ config.getInternalPropertyMap().clear();
+ for (String key : values.keySet()) {
+ List<String> properties = values.get(key);
+ config.setPropertyStringList(key, new ArrayList<>(
+ properties));
+ }
+ for (String key : names.keySet())
+ config.setProperty(key, names.get(key));
+ store();
+ }
+ });
+
+ applyPanel.add(applyButton);
+ return applyPanel;
+ }
+
+ private void store() {
+ try {
+ //FIXME
+ //ConfigurationManager.getInstance().store(config);
+ } catch (Exception e1) {
+ logger.error("There was an error storing the configuration:"
+ + config.getFilePrefix() + " (UUID=" + config.getUUID()
+ + ")", e1);
+ }
+ }
+
+ private JPanel listButtons() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, Y_AXIS));
+ JButton addButton = new JButton("+");
+ addButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String value = input("New property", "Provide new value for: "
+ + selectedKey);
+ if (value != null) {
+ listModel.addElement(value);
+ values.get(selectedKey).add(value);
+ deleteTypeButton.setEnabled(false);
+ }
+ }
+ });
+
+ JButton deleteButton = new JButton("-");
+ deleteButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Object value = propertyListItems.getSelectedValue();
+ if (confirm("Confirm removal",
+ "Are you sure you wish to remove " + value + "?")) {
+ listModel.removeElement(value);
+ values.get(selectedKey).remove(value);
+ if (values.get(selectedKey).size() == 0)
+ deleteTypeButton.setEnabled(true);
+ }
+ }
+ });
+
+ panel.add(addButton);
+ panel.add(deleteButton);
+
+ return panel;
+ }
+
+ private boolean confirm(String title, String message) {
+ return showConfirmDialog(this, message, title, YES_NO_OPTION,
+ WARNING_MESSAGE) == YES_OPTION;
+ }
+
+ private String input(String title, String message) {
+ return showInputDialog(this, message, title, INFORMATION_MESSAGE);
+ }
+
+/* private JButton getAddTypeButton() {
+ JButton result = new JButton("Add");
+ result.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String val = input("New property value","New property value");
+ if (val!=null) {
+ if (values.get(val) == null) {
+ model.addElement(val);
+ values.put(val, new ArrayList<String>());
+ } else
+ showMessageDialog(ActivityPaletteConfigurationPanel.this, "This property already exists");
+ }
+ }
+ });
+ return result;
+ }
+*/
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java
new file mode 100644
index 0000000..39c4a5a
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationUIFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+public class ActivityPaletteConfigurationUIFactory implements
+ ConfigurationUIFactory {
+ private ActivityPaletteConfiguration activityPaletteConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid != null && uuid.equals(getConfigurable().getUUID());
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return activityPaletteConfiguration;
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new ActivityPaletteConfigurationPanel(
+ activityPaletteConfiguration);
+ }
+
+ public void setActivityPaletteConfiguration(
+ ActivityPaletteConfiguration activityPaletteConfiguration) {
+ this.activityPaletteConfiguration = activityPaletteConfiguration;
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..6c9fed9
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+#net.sf.taverna.t2.workbench.ui.activitypalette.ActivityPaletteConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml
new file mode 100644
index 0000000..34921f5
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ServiceDescriptionRegistryImpl" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry"/>
+ <service ref="ServiceDescriptionsConfigurationImpl" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionsConfiguration"/>
+
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+ <list id="serviceDescriptionProviders" interface="net.sf.taverna.t2.servicedescriptions.ServiceDescriptionProvider" cardinality="0..N" greedy-proxying="true"/>
+
+</beans:beans>
diff --git a/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml
new file mode 100644
index 0000000..9f7110f
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/main/resources/META-INF/spring/activity-palette-impl-context.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean name="ServiceDescriptionRegistryImpl" class="net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionRegistryImpl">
+ <constructor-arg name="applicationConfiguration" ref="applicationConfiguration" />
+ <property name="serviceDescriptionProvidersList" ref="serviceDescriptionProviders" />
+ <property name="serviceDescriptionsConfig">
+ <ref local="ServiceDescriptionsConfigurationImpl"/>
+ </property>
+ </bean>
+
+ <bean id="ServiceDescriptionsConfigurationImpl" class="net.sf.taverna.t2.servicedescriptions.impl.ServiceDescriptionsConfigurationImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+
+ <!-- Don't think ActivityPalette is still used -->
+ <!-- <bean id="ActivityPaletteConfiguration" class="net.sf.taverna.t2.workbench.ui.activitypalette.ActivityPaletteConfiguration">
+ <constructor-arg ref="configurationManager"/>
+ </bean> -->
+
+</beans>
diff --git a/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java b/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java
new file mode 100644
index 0000000..081a9af
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/java/net/sf/taverna/t2/workbench/ui/activitypalette/ActivityPaletteConfigurationTest.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.activitypalette;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ActivityPaletteConfigurationTest {
+
+ private ActivityPaletteConfiguration conf;
+ private ConfigurationManagerImpl manager;
+
+ @Before
+ public void setup() {
+ File f = new File(System.getProperty("java.io.tmpdir"));
+ final File d = new File(f,UUID.randomUUID().toString());
+ d.mkdir();
+ manager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl() {
+ @Override
+ public File getApplicationHomeDir() {
+ return d;
+ }
+ });
+ conf=new ActivityPaletteConfiguration(manager);
+ conf.restoreDefaults();
+ }
+
+ @Test
+ public void testEmptyList() throws Exception {
+ conf.setPropertyStringList("list", new ArrayList<String>());
+ assertTrue("Result was not a list but was:"+conf.getProperty("list"),conf.getPropertyStringList("list") instanceof List);
+ assertTrue("Result was not a list but was:"+conf.getPropertyStringList("list"),conf.getPropertyStringList("list") instanceof List);
+ List<String> list = conf.getPropertyStringList("list");
+ assertEquals("There should be 0 elements",0,list.size());
+ }
+
+ @Test
+ public void testSingleItem() throws Exception {
+ List<String> list = new ArrayList<>();
+ list.add("fred");
+ conf.setPropertyStringList("single", list);
+
+ assertTrue("should be an ArrayList",conf.getPropertyStringList("single") instanceof List);
+ List<String> l = conf.getPropertyStringList("single");
+ assertEquals("There should be 1 element",1,l.size());
+ assertEquals("Its value should be fred","fred",l.get(0));
+ }
+
+ @Test
+ public void testList() throws Exception {
+ List<String> list = new ArrayList<>();
+ list.add("fred");
+ list.add("bloggs");
+ conf.setPropertyStringList("list", list);
+
+ assertTrue("should be an ArrayList",conf.getPropertyStringList("list") instanceof List);
+ List<String> l = conf.getPropertyStringList("list");
+ assertEquals("There should be 1 element",2,l.size());
+ assertEquals("Its value should be fred","fred",l.get(0));
+ assertEquals("Its value should be bloggs","bloggs",l.get(1));
+ }
+
+ @Test
+ public void testNull() throws Exception {
+ assertNull("Should return null",conf.getProperty("blah blah blah"));
+ }
+}
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI
new file mode 100644
index 0000000..9f3c02d
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PartitionAlgorithmSetSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.partition.DummyPartitionAlgorithmSet
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI
new file mode 100644
index 0000000..f5e7226
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.PropertyExtractorSPI
@@ -0,0 +1,3 @@
+net.sf.taverna.t2.partition.DummyExtractor1
+net.sf.taverna.t2.partition.DummyExtractor2
+net.sf.taverna.t2.partition.DummyExtractor3
\ No newline at end of file
diff --git a/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory
new file mode 100644
index 0000000..2d26d31a
--- /dev/null
+++ b/taverna-workbench-activity-palette-impl/src/test/resources/META-INF/services/net.sf.taverna.t2.partition.QueryFactory
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.partition.DummyActivityQueryFactory
+net.sf.taverna.t2.partition.DummyQueryFactory
\ No newline at end of file
diff --git a/taverna-workbench-configuration-impl/pom.xml b/taverna-workbench-configuration-impl/pom.xml
new file mode 100644
index 0000000..19356bb
--- /dev/null
+++ b/taverna-workbench-configuration-impl/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>configuration-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Configuration Management Implementations</name>
+ <description>General configuration management</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-impl</artifactId>
+ <version>0.1.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-impl</artifactId>
+ <version>0.1.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java
new file mode 100644
index 0000000..0e63a4a
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationImpl.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * An implementation of Configurable for general Workbench configuration
+ * properties
+ *
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchConfigurationImpl extends AbstractConfigurable implements
+ WorkbenchConfiguration {
+ private static Logger logger = Logger
+ .getLogger(WorkbenchConfiguration.class);
+ private static final int DEFAULT_MAX_MENU_ITEMS = 20;
+ public static final String TAVERNA_DOTLOCATION = "taverna.dotlocation";
+ public static final String MAX_MENU_ITEMS = "taverna.maxmenuitems";
+ public static final String WARN_INTERNAL_ERRORS = "taverna.warninternal";
+ public static final String CAPTURE_CONSOLE = "taverna.captureconsole";
+ private static final String BIN = "bin";
+ private static final String BUNDLE_CONTENTS = "Contents";
+ private static final String BUNDLE_MAC_OS = "MacOS";
+ private static final String DOT_EXE = "dot.exe";
+ private static final String DOT_FALLBACK = "dot";
+ public static String uuid = "c14856f0-5967-11dd-ae16-0800200c9a66";
+ private static final String MAC_OS_X = "Mac OS X";
+ private static final String WIN32I386 = "win32i386";
+ private static final String WINDOWS = "Windows";
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ /**
+ * Constructs a new <code>WorkbenchConfigurationImpl</code>.
+ *
+ * @param configurationManager
+ */
+ public WorkbenchConfigurationImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ Map<String, String> defaultWorkbenchProperties = null;
+ Map<String, String> workbenchProperties = new HashMap<String, String>();
+
+ @Override
+ public String getCategory() {
+ return "general";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultWorkbenchProperties == null) {
+ defaultWorkbenchProperties = new HashMap<>();
+ String dotLocation = System.getProperty(TAVERNA_DOTLOCATION) != null ? System
+ .getProperty(TAVERNA_DOTLOCATION) : getDefaultDotLocation();
+ if (dotLocation != null)
+ defaultWorkbenchProperties
+ .put(TAVERNA_DOTLOCATION, dotLocation);
+ defaultWorkbenchProperties.put(MAX_MENU_ITEMS,
+ Integer.toString(DEFAULT_MAX_MENU_ITEMS));
+ defaultWorkbenchProperties.put(WARN_INTERNAL_ERRORS,
+ Boolean.FALSE.toString());
+ defaultWorkbenchProperties.put(CAPTURE_CONSOLE,
+ Boolean.TRUE.toString());
+ }
+ return defaultWorkbenchProperties;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Workbench";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "Workbench";
+ }
+
+ @Override
+ public String getUUID() {
+ return uuid;
+ }
+
+ @Override
+ public boolean getWarnInternalErrors() {
+ String property = getProperty(WARN_INTERNAL_ERRORS);
+ return Boolean.parseBoolean(property);
+ }
+
+ @Override
+ public boolean getCaptureConsole() {
+ String property = getProperty(CAPTURE_CONSOLE);
+ return Boolean.parseBoolean(property);
+ }
+
+ @Override
+ public void setWarnInternalErrors(boolean warnInternalErrors) {
+ setProperty(WARN_INTERNAL_ERRORS, Boolean.toString(warnInternalErrors));
+ }
+
+ @Override
+ public void setCaptureConsole(boolean captureConsole) {
+ setProperty(CAPTURE_CONSOLE, Boolean.toString(captureConsole));
+ }
+
+ @Override
+ public void setMaxMenuItems(int maxMenuItems) {
+ if (maxMenuItems < 2)
+ throw new IllegalArgumentException(
+ "Maximum menu items must be at least 2");
+ setProperty(MAX_MENU_ITEMS, Integer.toString(maxMenuItems));
+ }
+
+ @Override
+ public int getMaxMenuItems() {
+ String property = getProperty(MAX_MENU_ITEMS);
+ try {
+ int maxMenuItems = Integer.parseInt(property);
+ if (maxMenuItems >= 2)
+ return maxMenuItems;
+ logger.warn(MAX_MENU_ITEMS + " can't be less than 2");
+ } catch (NumberFormatException ex) {
+ logger.warn("Invalid number for " + MAX_MENU_ITEMS + ": "
+ + property);
+ }
+ // We'll return the default instead
+ return DEFAULT_MAX_MENU_ITEMS;
+ }
+
+ @Override
+ public String getDotLocation() {
+ return getProperty(TAVERNA_DOTLOCATION);
+ }
+
+ @Override
+ public void setDotLocation(String dotLocation) {
+ setProperty(TAVERNA_DOTLOCATION, dotLocation);
+ }
+
+ private String getDefaultDotLocation() {
+ if (applicationConfiguration == null)
+ return null;
+ File startupDir = applicationConfiguration.getStartupDir();
+ if (startupDir == null)
+ return DOT_FALLBACK;
+
+ String os = System.getProperty("os.name");
+ if (os.equals(MAC_OS_X))
+ if (startupDir.getParentFile() != null) {
+ File contentsDir = startupDir.getParentFile().getParentFile();
+ if (contentsDir != null
+ && contentsDir.getName().equalsIgnoreCase(
+ BUNDLE_CONTENTS)) {
+ File dot = new File(new File(contentsDir, BUNDLE_MAC_OS),
+ DOT_FALLBACK);
+ if (dot.exists())
+ return dot.getAbsolutePath();
+ }
+ } else if (os.startsWith(WINDOWS)) {
+ File binWin386Dir = new File(new File(startupDir, BIN),
+ WIN32I386);
+ File dot = new File(binWin386Dir, DOT_EXE);
+ if (dot.exists())
+ return dot.getAbsolutePath();
+ }
+ return DOT_FALLBACK;
+ }
+
+ /**
+ * Sets the applicationConfiguration.
+ *
+ * @param applicationConfiguration
+ * the new value of applicationConfiguration
+ */
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ defaultWorkbenchProperties = null;
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java
new file mode 100644
index 0000000..ecddc35
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationPanel.java
@@ -0,0 +1,266 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import static java.awt.GridBagConstraints.*;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openIcon;
+
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkbenchConfigurationPanel extends JPanel {
+ private static final String RESTART_MSG = "For the new configuration to be fully applied, it is advised to restart Taverna.";
+ private static final String DOT_PATH_MSG = "Path to Graphviz executable <code>dot</code>:";
+ private static final String CONTEXT_MENU_SIZE_MSG = "Maximum number of services/ports in right-click menu:";
+ private static Logger logger = Logger
+ .getLogger(WorkbenchConfigurationUIFactory.class);
+
+ private JTextField dotLocation = new JTextField(25);
+ private JTextField menuItems = new JTextField(10);
+ private JCheckBox warnInternal = new JCheckBox("Warn on internal errors");
+ private JCheckBox captureConsole = new JCheckBox(
+ "Capture output on stdout/stderr to log file");
+
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ public WorkbenchConfigurationPanel(
+ WorkbenchConfiguration workbenchConfiguration) {
+ super();
+ this.workbenchConfiguration = workbenchConfiguration;
+ initComponents();
+ }
+
+ private static JLabel htmlLabel(String html) {
+ return new JLabel("<html><body>" + html + "</body></html>");
+ }
+
+ private void initComponents() {
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea(
+ "General Workbench configuration");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ gbc.anchor = WEST;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.fill = HORIZONTAL;
+ this.add(descriptionText, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(10, 5, 0, 0);
+ gbc.fill = NONE;
+ this.add(htmlLabel(DOT_PATH_MSG), gbc);
+
+ dotLocation.setText(workbenchConfiguration.getDotLocation());
+ gbc.gridy++;
+ gbc.gridwidth = 1;
+ gbc.weightx = 1.0;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ gbc.fill = HORIZONTAL;
+ this.add(dotLocation, gbc);
+
+ JButton browseButton = new JButton();
+ gbc.gridx = 1;
+ gbc.weightx = 0.0;
+ gbc.fill = NONE;
+ this.add(browseButton, gbc);
+ browseButton.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ System.setProperty("com.apple.macos.use-file-dialog-packages",
+ "false");
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.putClientProperty(
+ "JFileChooser.appBundleIsTraversable", "always");
+ fileChooser.putClientProperty(
+ "JFileChooser.packageIsTraversable", "always");
+
+ fileChooser.setDialogTitle("Browse for dot");
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+
+ fileChooser.setMultiSelectionEnabled(false);
+
+ int returnVal = fileChooser
+ .showOpenDialog(WorkbenchConfigurationPanel.this);
+ if (returnVal == APPROVE_OPTION)
+ dotLocation.setText(fileChooser.getSelectedFile()
+ .getAbsolutePath());
+ }
+ });
+ browseButton.setIcon(openIcon);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.insets = new Insets(10, 5, 0, 0);
+ gbc.fill = HORIZONTAL;
+ this.add(htmlLabel(CONTEXT_MENU_SIZE_MSG), gbc);
+
+ menuItems.setText(Integer.toString(workbenchConfiguration
+ .getMaxMenuItems()));
+ gbc.gridy++;
+ gbc.weightx = 1.0;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ gbc.fill = HORIZONTAL;
+ this.add(menuItems, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.fill = HORIZONTAL;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ warnInternal
+ .setSelected(workbenchConfiguration.getWarnInternalErrors());
+ this.add(warnInternal, gbc);
+
+ gbc.gridy++;
+ gbc.insets = new Insets(0, 0, 10, 0);
+ captureConsole.setSelected(workbenchConfiguration.getCaptureConsole());
+ this.add(captureConsole, gbc);
+
+ // Add the buttons panel
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.gridwidth = 3;
+ gbc.weightx = 1.0;
+ gbc.weighty = 1.0;
+ gbc.fill = BOTH;
+ gbc.anchor = SOUTH;
+ this.add(getButtonsPanel(), gbc);
+ }
+
+ private Component getButtonsPanel() {
+ final JPanel panel = new JPanel();
+ panel.setLayout(new FlowLayout(FlowLayout.CENTER));
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ resetFields();
+ }
+ });
+ panel.add(resetButton);
+
+ JButton applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ String menus = menuItems.getText();
+ try {
+ workbenchConfiguration.setMaxMenuItems(Integer
+ .valueOf(menus));
+ } catch (IllegalArgumentException e) {
+ String message = "Invalid menu items number " + menus
+ + ":\n" + e.getLocalizedMessage();
+ showMessageDialog(panel, message, "Invalid menu items",
+ WARNING_MESSAGE);
+ return;
+ }
+
+ workbenchConfiguration.setCaptureConsole(captureConsole
+ .isSelected());
+ workbenchConfiguration.setWarnInternalErrors(warnInternal
+ .isSelected());
+ workbenchConfiguration.setDotLocation(dotLocation.getText());
+ try {
+ showMessageDialog(panel, RESTART_MSG, "Restart adviced",
+ INFORMATION_MESSAGE);
+ } catch (Exception e) {
+ logger.error("Error storing updated configuration", e);
+ }
+ }
+ });
+ panel.add(applyButton);
+ return panel;
+ }
+
+ /**
+ * Resets the shown field values to those currently set (last saved) in the
+ * configuration.
+ *
+ * @param configurable
+ */
+ private void resetFields() {
+ menuItems.setText(Integer.toString(workbenchConfiguration
+ .getMaxMenuItems()));
+ dotLocation.setText(workbenchConfiguration.getDotLocation());
+ warnInternal
+ .setSelected(workbenchConfiguration.getWarnInternalErrors());
+ captureConsole.setSelected(workbenchConfiguration.getCaptureConsole());
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java
new file mode 100644
index 0000000..263233f
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/WorkbenchConfigurationUIFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+public class WorkbenchConfigurationUIFactory implements ConfigurationUIFactory {
+ private WorkbenchConfiguration workbenchConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(workbenchConfiguration.getUUID());
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new WorkbenchConfigurationPanel(workbenchConfiguration);
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return workbenchConfiguration;
+ }
+
+ public void setWorkbenchConfiguration(
+ WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java
new file mode 100644
index 0000000..4c03dbe
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerImpl.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.colour;
+
+import static java.awt.Color.WHITE;
+import static java.awt.Color.decode;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+/**
+ * A factory class that determines the colour that a Colourable UI component
+ * should be displayed as, according to a schema configured by the user.
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @see Colourable
+ */
+public class ColourManagerImpl extends AbstractConfigurable implements
+ ColourManager {
+ // Names of things that may be coloured
+ private static final String WORKFLOW_PORT_OBJECT = "uk.org.taverna.scufl2.api.port.WorkflowPort";
+ private static final String PROCESSOR_PORT_OBJECT = "uk.org.taverna.scufl2.api.port.ProcessorPort";
+ private static final String PROCESSOR_OBJECT = "uk.org.taverna.scufl2.api.core.Processor";
+ private static final String MERGE_OBJECT = "net.sf.taverna.t2.workflowmodel.Merge";
+ private static final String NONEXECUTABLE_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/nonExecutable";
+ private static final String XML_SPLITTER_OUT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/xml-splitter/out";
+ private static final String XML_SPLITTER_IN_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/xml-splitter/in";
+ private static final String LOCALWORKER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/localworker";
+ private static final String WSDL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/wsdl";
+ private static final String CONSTANT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/constant";
+ private static final String SOAPLAB_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/soaplab";
+ private static final String RSHELL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/rshell";
+ private static final String NESTED_WORKFLOW = "http://ns.taverna.org.uk/2010/activity/nested-workflow";
+ private static final String MOBY_PARSER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/parser";
+ private static final String MOBY_OBJECT_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/object";
+ private static final String MOBY_SERVICE_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomoby/service";
+ private static final String BIOMART_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/biomart";
+ private static final String BEANSHELL_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/beanshell";
+ private static final String APICONSUMER_ACTIVITY = "http://ns.taverna.org.uk/2010/activity/apiconsumer";
+
+ // Names of colours used
+ private static final String burlywood2 = "#deb887";
+ private static final String darkgoldenrod1 = "#ffb90f";
+ private static final String darkolivegreen3 = "#a2cd5a";
+ private static final String gold = "#ffd700";
+ private static final String grey = "#777777";
+ private static final String lightcyan2 = "#d1eeee";
+ private static final String lightgoldenrodyellow = "#fafad2";
+ // light purple non standard
+ private static final String lightpurple = "#ab92ea";
+ private static final String lightsteelblue = "#b0c4de";
+ private static final String mediumorchid2 = "#d15fee";
+ private static final String palegreen = "#98fb98";
+ private static final String pink = "#ffc0cb";
+ private static final String purplish = "#8070ff";
+ // ShadedLabel.Orange
+ private static final String shadedorange = "#eece8f";
+ // ShadedLabel.Green
+ private static final String shadedgreen = "#a1c69d";
+ // slightly lighter than the real steelblue4
+ private static final String steelblue4 = "#648faa";
+ private static final String turquoise = "#77aadd";
+ private static final String white = "#ffffff";
+
+ private Map<String, String> defaultPropertyMap;
+ private Map<Object, Color> cachedColours;
+
+ public ColourManagerImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ initialiseDefaults();
+ }
+
+ @Override
+ public String getCategory() {
+ return "colour";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ if (defaultPropertyMap == null)
+ initialiseDefaults();
+ return defaultPropertyMap;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Colour Management";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "ColourManagement";
+ }
+
+ /**
+ * Unique identifier for this ColourManager
+ */
+ @Override
+ public String getUUID() {
+ return "a2148420-5967-11dd-ae16-0800200c9a66";
+ }
+
+ private void initialiseDefaults() {
+ defaultPropertyMap = new HashMap<>();
+ cachedColours = new HashMap<>();
+
+ defaultPropertyMap.put(APICONSUMER_ACTIVITY, palegreen);
+ defaultPropertyMap.put(BEANSHELL_ACTIVITY, burlywood2);
+ defaultPropertyMap.put(BIOMART_ACTIVITY, lightcyan2);
+ defaultPropertyMap.put(CONSTANT_ACTIVITY, lightsteelblue);
+ defaultPropertyMap.put(LOCALWORKER_ACTIVITY, mediumorchid2);
+ defaultPropertyMap.put(MOBY_SERVICE_ACTIVITY, darkgoldenrod1);
+ defaultPropertyMap.put(MOBY_OBJECT_ACTIVITY, gold);
+ defaultPropertyMap.put(MOBY_PARSER_ACTIVITY, white);
+ defaultPropertyMap.put(NESTED_WORKFLOW, pink);
+ defaultPropertyMap.put(RSHELL_ACTIVITY, steelblue4);
+ defaultPropertyMap.put(SOAPLAB_ACTIVITY, lightgoldenrodyellow);
+ defaultPropertyMap.put(WSDL_ACTIVITY, darkolivegreen3);
+ defaultPropertyMap.put(XML_SPLITTER_IN_ACTIVITY, lightpurple);
+ defaultPropertyMap.put(XML_SPLITTER_OUT_ACTIVITY, lightpurple);
+
+ defaultPropertyMap.put(NONEXECUTABLE_ACTIVITY, grey);
+
+ defaultPropertyMap.put(MERGE_OBJECT, turquoise);
+ defaultPropertyMap.put(PROCESSOR_OBJECT, shadedgreen);
+ defaultPropertyMap.put(PROCESSOR_PORT_OBJECT, purplish);
+ defaultPropertyMap.put(WORKFLOW_PORT_OBJECT, shadedorange);
+ }
+
+ @Override
+ public Color getPreferredColour(String itemKey) {
+ Color colour = cachedColours.get(itemKey);
+ if (colour == null) {
+ String colourString = (String) getProperty(itemKey);
+ colour = colourString == null ? WHITE : decode(colourString);
+ cachedColours.put(itemKey, colour);
+ }
+ return colour;
+ }
+
+ @Override
+ public void setPreferredColour(String itemKey, Color colour) {
+ cachedColours.put(itemKey, colour);
+ }
+
+ @Override
+ public void restoreDefaults() {
+ super.restoreDefaults();
+ if (cachedColours == null)
+ cachedColours = new HashMap<>();
+ else
+ cachedColours.clear();
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java
new file mode 100644
index 0000000..0ff6c65
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/mimetype/MimeTypeManagerImpl.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.mimetype;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.workbench.configuration.mimetype.MimeTypeManager;
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+public class MimeTypeManagerImpl extends AbstractConfigurable implements
+ MimeTypeManager {
+ /**
+ * Constructs a new <code>MimeTypeManagerImpl</code>.
+ *
+ * @param configurationManager
+ */
+ public MimeTypeManagerImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ @Override
+ public String getCategory() {
+ return "Mime Type";
+ }
+
+ @Override
+ public Map<String, String> getDefaultPropertyMap() {
+ HashMap<String, String> map = new HashMap<>();
+ map.put("text/plain", "Plain Text");
+ map.put("text/xml", "XML Text");
+ map.put("text/html", "HTML Text");
+ map.put("text/rtf", "Rich Text Format");
+ map.put("text/x-graphviz", "Graphviz Dot File");
+ map.put("image/png", "PNG Image");
+ map.put("image/jpeg", "JPEG Image");
+ map.put("image/gif", "GIF Image");
+ map.put("application/octet-stream", "Binary Data");
+ map.put("application/zip", "Zip File");
+ map.put("chemical/x-swissprot", "SWISSPROT Flat File");
+ map.put("chemical/x-embl-dl-nucleotide", "EMBL Flat File");
+ map.put("chemical/x-ppd", "PPD File");
+ map.put("chemical/seq-aa-genpept", "Genpept Protein");
+ map.put("chemical/seq-na-genbank", "Genbank Nucleotide");
+ map.put("chemical/x-pdb", "PDB 3D Structure File");
+ return map;
+ }
+
+ @Override
+ public String getUUID() {
+ return "b9277fa0-5967-11dd-ae16-0800200c9a66";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Mime Type Manager";
+ }
+
+ @Override
+ public String getFilePrefix() {
+ return "MimeTypeManagerImpl";
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java
new file mode 100644
index 0000000..4910f78
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/T2ConfigurationFrameImpl.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.ui;
+
+import static javax.swing.JSplitPane.HORIZONTAL_SPLIT;
+import static net.sf.taverna.t2.workbench.helper.HelpCollator.registerComponent;
+import static net.sf.taverna.t2.workbench.helper.Helper.setKeyCatcher;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.ListModel;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+
+public class T2ConfigurationFrameImpl implements T2ConfigurationFrame {
+ private static Logger logger = Logger.getLogger(T2ConfigurationFrameImpl.class);
+ private static final int FRAME_WIDTH = 700;
+ private static final int FRAME_HEIGHT = 450;
+
+ private List<ConfigurationUIFactory> configurationUIFactories = new ArrayList<>();
+
+ private JFrame frame;
+ private JSplitPane splitPane;
+ private JList<ConfigurableItem> list;
+
+ public T2ConfigurationFrameImpl() {
+ }
+
+ @Override
+ public void showFrame() {
+ getFrame().setVisible(true);
+ }
+
+ @Override
+ public void showConfiguration(String name) {
+ showFrame();
+ ListModel<ConfigurableItem> lm = list.getModel();
+ for (int i = 0; i < lm.getSize(); i++)
+ if (lm.getElementAt(i).toString().equals(name)) {
+ list.setSelectedIndex(i);
+ break;
+ }
+ }
+
+ private JFrame getFrame() {
+ if (frame != null)
+ return frame;
+
+ frame = new JFrame();
+ setKeyCatcher(frame);
+ registerComponent(frame);
+ frame.setLayout(new BorderLayout());
+
+ /*
+ * Split pane to hold list of properties (on the left) and their
+ * configurable options (on the right)
+ */
+ splitPane = new JSplitPane(HORIZONTAL_SPLIT);
+ splitPane.setBorder(null);
+
+ list = getConfigurationList();
+ JScrollPane jspList = new JScrollPane(list);
+ jspList.setBorder(new EmptyBorder(5, 5, 5, 5));
+ jspList.setMinimumSize(new Dimension(150,
+ jspList.getPreferredSize().height));
+
+ splitPane.setLeftComponent(jspList);
+ splitPane.setRightComponent(new JPanel());
+ splitPane.setDividerSize(1);
+
+ // select first item if one exists
+ if (list.getModel().getSize() > 0)
+ list.setSelectedValue(list.getModel().getElementAt(0), true);
+
+ frame.add(splitPane);
+ frame.setSize(new Dimension(FRAME_WIDTH, FRAME_HEIGHT));
+ return frame;
+ }
+
+ private JList<ConfigurableItem> getConfigurationList() {
+ if (list != null)
+ return list;
+
+ list = new JList<>();
+ list.addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (list.getSelectedValue() instanceof ConfigurableItem) {
+ ConfigurableItem item = (ConfigurableItem) list
+ .getSelectedValue();
+ setMainPanel(item.getPanel());
+ }
+
+ /*
+ * Keep the split pane's divider at its current position - but
+ * looks ugly. The problem with divider moving from its current
+ * position after selecting an item from the list on the left is
+ * that the right hand side panels are loaded dynamically and it
+ * seems there is nothing we can do about it - it's just the
+ * JSplitPane's behaviour
+ */
+ // splitPane.setDividerLocation(splitPane.getLastDividerLocation());
+ }
+ });
+ list.setListData(getListItems());
+ return list;
+ }
+
+ private void setMainPanel(JPanel panel) {
+ panel.setBorder(new EmptyBorder(15, 15, 15, 15));
+ splitPane.setRightComponent(panel);
+ }
+
+ public void setConfigurationUIFactories(
+ List<ConfigurationUIFactory> configurationUIFactories) {
+ this.configurationUIFactories = configurationUIFactories;
+ }
+
+ private ConfigurableItem[] getListItems() {
+ List<ConfigurableItem> arrayList = new ArrayList<>();
+ for (ConfigurationUIFactory fac : configurationUIFactories) {
+ String name = fac.getConfigurable().getDisplayName();
+ if (name != null) {
+ logger.info("Adding configurable for name: " + name);
+ arrayList.add(new ConfigurableItem(fac));
+ } else {
+ logger.warn("The configurable " + fac.getConfigurable().getClass()
+ + " has a null name");
+ }
+ }
+ // Sort the list alphabetically
+ ConfigurableItem[] array = arrayList.toArray(new ConfigurableItem[0]);
+ Arrays.sort(array, new Comparator<ConfigurableItem>() {
+ @Override
+ public int compare(ConfigurableItem item1, ConfigurableItem item2) {
+ return item1.toString().compareToIgnoreCase(item2.toString());
+ }
+ });
+ return array;
+ }
+
+ public void update(Object service, Map<?, ?> properties) {
+ getConfigurationList().setListData(getListItems());
+ if (frame != null) {
+ frame.revalidate();
+ frame.repaint();
+ // select first item if one exists
+ if (list.getModel().getSize() > 0)
+ list.setSelectedValue(list.getModel().getElementAt(0), true);
+ }
+ }
+
+ class ConfigurableItem {
+ private final ConfigurationUIFactory factory;
+
+ public ConfigurableItem(ConfigurationUIFactory factory) {
+ this.factory = factory;
+ }
+
+ public JPanel getPanel() {
+ return factory.getConfigurationPanel();
+ }
+
+ @Override
+ public String toString() {
+ return factory.getConfigurable().getDisplayName();
+ }
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java
new file mode 100644
index 0000000..453f0c0
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchConfigurationMenu.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.ui;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+
+public class WorkbenchConfigurationMenu extends AbstractMenuAction {
+ private static final String MAC_OS_X = "Mac OS X";
+
+ private T2ConfigurationFrame t2ConfigurationFrame;
+
+ public WorkbenchConfigurationMenu() {
+ super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#preferences"),
+ 100);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Preferences") {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ t2ConfigurationFrame.showFrame();
+ }
+ };
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return !MAC_OS_X.equalsIgnoreCase(System.getProperty("os.name"));
+ }
+
+ public void setT2ConfigurationFrame(T2ConfigurationFrame t2ConfigurationFrame) {
+ this.t2ConfigurationFrame = t2ConfigurationFrame;
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java
new file mode 100644
index 0000000..d131ac3
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ui/WorkbenchPreferencesSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.ui;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class WorkbenchPreferencesSection extends AbstractMenuSection {
+ private static final URI FILE_MENU = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ private static final URI PREFERENCES_MENU_ITEM = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#preferences");
+
+ public WorkbenchPreferencesSection() {
+ super(FILE_MENU, 100, PREFERENCES_MENU_ITEM);
+ }
+}
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..3b51dd4
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchPreferencesSection
+net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchConfigurationMenu
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..4af55ec
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml
new file mode 100644
index 0000000..29aea44
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context-osgi.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="t2ConfigurationFrame" interface="net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame" />
+
+ <service ref="WorkbenchConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <service ref="WorkbenchPreferencesSection" auto-export="interfaces" />
+ <service ref="WorkbenchConfigurationMenu" auto-export="interfaces" />
+
+ <service ref="ColourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+ <service ref="WorkbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <service ref="MimeTypeManager" interface="net.sf.taverna.t2.workbench.configuration.mimetype.MimeTypeManager" />
+
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+
+ <list id="configurationUIFactories" interface="uk.org.taverna.configuration.ConfigurationUIFactory" cardinality="0..N">
+ <listener ref="t2ConfigurationFrame" bind-method="update" unbind-method="update"/>
+ </list>
+
+</beans:beans>
diff --git a/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml
new file mode 100644
index 0000000..40da7fd
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/main/resources/META-INF/spring/configuration-impl-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="t2ConfigurationFrame" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.T2ConfigurationFrameImpl">
+ <property name="configurationUIFactories" ref="configurationUIFactories" />
+ </bean>
+
+ <bean id="WorkbenchConfigurationUIFactory" class="net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationUIFactory">
+ <property name="workbenchConfiguration">
+ <ref local="WorkbenchConfiguration"/>
+ </property>
+ </bean>
+
+ <bean id="WorkbenchPreferencesSection" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchPreferencesSection" />
+ <bean id="WorkbenchConfigurationMenu" class="net.sf.taverna.t2.workbench.ui.impl.configuration.ui.WorkbenchConfigurationMenu">
+ <property name="t2ConfigurationFrame">
+ <ref local="t2ConfigurationFrame"/>
+ </property>
+ </bean>
+
+ <bean id="ColourManager" class="net.sf.taverna.t2.workbench.ui.impl.configuration.colour.ColourManagerImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+ <bean id="WorkbenchConfiguration" class="net.sf.taverna.t2.workbench.ui.impl.configuration.WorkbenchConfigurationImpl">
+ <constructor-arg ref="configurationManager"/>
+ <property name="applicationConfiguration" ref="applicationConfiguration" />
+ </bean>
+ <bean id="MimeTypeManager" class="net.sf.taverna.t2.workbench.ui.impl.configuration.mimetype.MimeTypeManagerImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java
new file mode 100644
index 0000000..1202c11
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/ConfigurationManagerTest.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.UUID;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.ui.impl.configuration.colour.ColourManagerImpl;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ConfigurationManagerTest {
+
+ ConfigurationManagerImpl configurationManager;
+
+ @Before
+ public void setup() {
+ configurationManager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl());
+ }
+
+ @Test
+ public void createConfigManager() {
+ assertNotNull("Config Manager should not be null", configurationManager);
+ }
+
+ @Ignore("Hardcoded /Users/Ian") //FIXME: update test to work using File.createTempFile(...)
+ @Test
+ public void populateConfigOfColourmanager() {
+ ColourManager manager= new ColourManagerImpl(null);
+
+ manager.setProperty("colour.first", "25");
+ manager.setProperty("colour.second", "223");
+
+ configurationManager.setBaseConfigLocation(new File("/Users/Ian/scratch"));
+ try {
+ configurationManager.store(manager);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+
+ ColourManager manager2 = new ColourManagerImpl(configurationManager);
+
+ try {
+ configurationManager.populate(manager2);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ assertEquals("Properties do not match", manager2.getProperty("colour.first"), manager.getProperty("colour.first"));
+ assertEquals("Properties do not match", manager2.getProperty("colour.second"), manager.getProperty("colour.second"));
+
+
+ }
+
+ @Test
+ public void saveColoursForDummyColourable() {
+ String dummy = "";
+ ColourManager manager=new ColourManagerImpl(configurationManager);
+ manager.setProperty(dummy.getClass().getCanonicalName(), "#000000");
+
+ File f = new File(System.getProperty("java.io.tmpdir"));
+ File d = new File(f, UUID.randomUUID().toString());
+ d.mkdir();
+ configurationManager.setBaseConfigLocation(d);
+ try {
+ configurationManager.store(manager);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+
+ try {
+ configurationManager.populate(manager);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+
+ }
+
+}
diff --git a/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java
new file mode 100644
index 0000000..0239ea8
--- /dev/null
+++ b/taverna-workbench-configuration-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/configuration/colour/ColourManagerTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.configuration.colour;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.UUID;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.app.impl.ApplicationConfigurationImpl;
+import uk.org.taverna.configuration.impl.ConfigurationManagerImpl;
+
+public class ColourManagerTest {
+
+ private ConfigurationManagerImpl configurationManager;
+
+ @Before
+ public void setup() {
+ configurationManager = new ConfigurationManagerImpl(new ApplicationConfigurationImpl());
+
+ File f = new File(System.getProperty("java.io.tmpdir"));
+ File d = new File(f, UUID.randomUUID().toString());
+ d.mkdir();
+ configurationManager.setBaseConfigLocation(d);
+ }
+
+ @Test
+ public void testGetPreferredColourEqualsWhite() throws Exception {
+ String dummy = new String();
+
+ Color c = new ColourManagerImpl(configurationManager).getPreferredColour(dummy);
+ assertEquals("The default colour should be WHITE", Color.WHITE, c);
+ }
+
+ @Test
+ public void testConfigurableness() throws Exception {
+ ColourManager manager = new ColourManagerImpl(configurationManager);
+ assertTrue(manager instanceof Configurable);
+
+ assertEquals("wrong category", "colour", manager.getCategory());
+ assertEquals("wrong name", "Colour Management", manager.getDisplayName());
+ assertEquals("wrong UUID", "a2148420-5967-11dd-ae16-0800200c9a66",
+ manager.getUUID());
+ assertNotNull("there is no default property map", manager
+ .getDefaultPropertyMap());
+ }
+
+ @Test
+ public void saveAsWrongArrayType() throws Exception {
+ String dummy = "";
+ ColourManager manager = new ColourManagerImpl(configurationManager);
+ manager.setProperty(dummy.getClass().getCanonicalName(), "#ffffff");
+
+ File baseLoc = File.createTempFile("test", "scratch");
+ baseLoc.delete();
+ assertTrue("Could not make directory " + baseLoc, baseLoc.mkdir());
+ configurationManager.setBaseConfigLocation(baseLoc);
+ configurationManager.store(manager);
+ configurationManager.populate(manager);
+ manager.getPreferredColour(dummy);
+ }
+
+}
diff --git a/taverna-workbench-contextual-views-impl/pom.xml b/taverna-workbench-contextual-views-impl/pom.xml
new file mode 100644
index 0000000..1cafa80
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>contextual-views-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Contextual Views Implementation</name>
+ <description>Contextual views for the activities</description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>contextual-views-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java
new file mode 100644
index 0000000..8bac354
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/activity/impl/ContextualViewFactoryRegistryImpl.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+
+/**
+ * @author Alan R Williams
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+
+/**
+ * An SPI registry for discovering ActivityViewFactories for a given object,
+ * like an {@link net.sf.taverna.t2.workflowmodel.processor.activity.Activity}.
+ * <p>
+ * For {@link ContextualViewFactory factories} to be found, its full qualified
+ * name needs to be defined as a resource file
+ * <code>/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewFactory</code>
+ *
+ * @author Stuart Owen
+ * @author Ian Dunlop
+ * @author Stian Soiland-Reyes
+ *
+ * @see ContextualViewFactory
+ */
+public class ContextualViewFactoryRegistryImpl implements
+ ContextualViewFactoryRegistry {
+ private List<ContextualViewFactory<?>> contextualViewFactories;
+
+ /**
+ * Discover and return the ContextualViewFactory associated to the provided
+ * object. This is accomplished by returning the discovered
+ * {@link ContextualViewFactory#canHandle(Object)} that returns true for
+ * that Object.
+ *
+ * @param object
+ * @return
+ * @see ContextualViewFactory#canHandle(Object)
+ */
+ @Override
+ public List<ContextualViewFactory<?>> getViewFactoriesForObject(
+ Object object) {
+ List<ContextualViewFactory<?>> result = new ArrayList<>();
+ for (ContextualViewFactory<?> factory : contextualViewFactories)
+ if (factory.canHandle(object))
+ result.add(factory);
+ return result;
+ }
+
+ public void setContextualViewFactories(
+ List<ContextualViewFactory<?>> contextualViewFactories) {
+ this.contextualViewFactories = contextualViewFactories;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java
new file mode 100644
index 0000000..018a121
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualView.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated;
+
+import static javax.swing.BoxLayout.Y_AXIS;
+
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.regex.Pattern;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * This is a ContextualView that should be able to display and allow editing of
+ * Annotation information for any Annotated. At the moment it is only used for
+ * Dataflow.
+ *
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+class AnnotatedContextualView extends ContextualView {
+ private static final int WORKFLOW_NAME_LENGTH = 20;
+ public static final String VIEW_TITLE = "Annotations";
+ private final static String MISSING_VALUE = "Type here to give details";
+ private final static int DEFAULT_AREA_WIDTH = 60;
+ private final static int DEFAULT_AREA_ROWS = 8;
+
+ private static Logger logger = Logger
+ .getLogger(AnnotatedContextualView.class);
+ private static PropertyResourceBundle prb = (PropertyResourceBundle) ResourceBundle
+ .getBundle("annotatedcontextualview");
+
+ // TODO convert to scufl2
+ // private static AnnotationTools annotationTools = new AnnotationTools();
+
+ /**
+ * The object to which the Annotations apply
+ */
+ private Annotated<?> annotated;
+ private SelectionManager selectionManager;
+ private EditManager editManager;
+ private boolean isStandalone = false;
+ private JPanel panel;
+ @SuppressWarnings("unused")
+ private final List<AnnotationBeanSPI> annotationBeans;
+
+ public AnnotatedContextualView(Annotated<?> annotated,
+ EditManager editManager, SelectionManager selectionManager,
+ List<AnnotationBeanSPI> annotationBeans) {
+ super();
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ this.annotationBeans = annotationBeans;
+ this.annotated = annotated;
+
+ initialise();
+ initView();
+ }
+
+ @Override
+ public void refreshView() {
+ initialise();
+ }
+
+ private void initialise() {
+ if (panel == null) {
+ panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, Y_AXIS));
+ } else
+ panel.removeAll();
+ populatePanel();
+ revalidate();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ return panel;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return VIEW_TITLE;
+ }
+
+ private Map<String,String> getAnnotations() {
+ // TODO convert to scufl2
+ Map<String, String> result = new HashMap<>();
+ //for (Class<?> c : annotationTools.getAnnotatingClasses(annotated)) {
+ // String name = "";
+ // try {
+ // name = prb.getString(c.getCanonicalName());
+ // } catch (MissingResourceException e) {
+ // name = c.getCanonicalName();
+ // }
+ // String value = annotationTools.getAnnotationString(annotated, c,
+ // MISSING_VALUE);
+ // result.put(name,value);
+ //}
+ return result;
+ }
+ public void populatePanel() {
+ JPanel scrollPanel = new JPanel();
+ scrollPanel.setLayout(new BoxLayout(scrollPanel, Y_AXIS));
+ panel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ Map<String,String>annotations = getAnnotations();
+ for (String name : annotations.keySet()) {
+ JPanel subPanel = new JPanel();
+ subPanel.setBorder(new TitledBorder(name));
+ subPanel.add(createTextArea(String.class, annotations.get(name)));
+ scrollPanel.add(subPanel);
+ }
+ JScrollPane scrollPane = new JScrollPane(scrollPanel);
+ panel.add(scrollPane);
+ }
+
+ private JScrollPane createTextArea(Class<?> c, String value) {
+ DialogTextArea area = new DialogTextArea(value);
+ area.setFocusable(true);
+ area.addFocusListener(new TextAreaFocusListener(area, c));
+ area.setColumns(DEFAULT_AREA_WIDTH);
+ area.setRows(DEFAULT_AREA_ROWS);
+ area.setLineWrap(true);
+ area.setWrapStyleWord(true);
+
+ return new JScrollPane(area);
+ }
+
+ private class TextAreaFocusListener implements FocusListener {
+ String oldValue = null;
+ Class<?> annotationClass;
+ DialogTextArea area = null;
+
+ public TextAreaFocusListener(DialogTextArea area, Class<?> c) {
+ annotationClass = c;
+ oldValue = area.getText();
+ this.area = area;
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ if (area.getText().equals(MISSING_VALUE))
+ area.setText("");
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ String currentValue = area.getText();
+ if (currentValue.isEmpty() || currentValue.equals(MISSING_VALUE)) {
+ currentValue = MISSING_VALUE;
+ area.setText(currentValue);
+ }
+ if (!currentValue.equals(oldValue)) {
+ if (currentValue == MISSING_VALUE)
+ currentValue = "";
+ try {
+ WorkflowBundle currentDataflow = selectionManager
+ .getSelectedWorkflowBundle();
+ List<Edit<?>> editList = new ArrayList<>();
+ addWorkflowNameEdits(currentValue, currentDataflow,
+ editList);
+ if (!isStandalone)
+ ContextualViewComponent.selfGenerated = true;
+ editManager.doDataflowEdit(currentDataflow,
+ new CompoundEdit(editList));
+ ContextualViewComponent.selfGenerated = false;
+ } catch (EditException e1) {
+ logger.warn("Can't set annotation", e1);
+ }
+ oldValue = area.getText();
+ }
+ }
+
+ private boolean isTitleAnnotation() {
+ // TODO convert to scufl2
+ return prb.getString(annotationClass.getCanonicalName()).equals(
+ "Title");
+ }
+
+ // TODO convert to scufl2
+ private void addWorkflowNameEdits(String currentValue,
+ WorkflowBundle currentDataflow, List<Edit<?>> editList) {
+ //editList.add(annotationTools.setAnnotationString(annotated,
+ // annotationClass, currentValue, edits));
+ if (annotated == currentDataflow && isTitleAnnotation()
+ && !currentValue.isEmpty()) {
+ @SuppressWarnings("unused")
+ String sanitised = sanitiseName(currentValue);
+ //editList.add(edits.getUpdateDataflowNameEdit(currentDataflow,
+ // sanitised));
+ }
+ }
+ }
+
+ /**
+ * Checks that the name does not have any characters that are invalid for a
+ * processor name.
+ * <p>
+ * The resulting name must contain only the chars [A-Za-z_0-9].
+ *
+ * @param name
+ * the original name
+ * @return the sanitised name
+ */
+ private static String sanitiseName(String name) {
+ if (name.length() > WORKFLOW_NAME_LENGTH)
+ name = name.substring(0, WORKFLOW_NAME_LENGTH);
+ if (Pattern.matches("\\w++", name))
+ return name;
+ StringBuilder temp = new StringBuilder();
+ for (char c : name.toCharArray())
+ temp.append(Character.isLetterOrDigit(c) || c == '_' ? c : '_');
+ return temp.toString();
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 500;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java
new file mode 100644
index 0000000..eb18803
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/annotated/AnnotatedContextualViewFactory.java
@@ -0,0 +1,43 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated;
+
+import static java.util.Collections.singletonList;
+
+import java.util.List;
+
+import net.sf.taverna.t2.annotation.Annotated;
+import net.sf.taverna.t2.annotation.AnnotationBeanSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.Activity;
+
+public class AnnotatedContextualViewFactory implements
+ ContextualViewFactory<Annotated<?>> {
+ private EditManager editManager;
+ private List<AnnotationBeanSPI> annotationBeans;
+ private SelectionManager selectionManager;
+
+ @Override
+ public boolean canHandle(Object selection) {
+ return ((selection instanceof Annotated) && !(selection instanceof Activity));
+ }
+
+ @Override
+ public List<ContextualView> getViews(Annotated<?> selection) {
+ return singletonList((ContextualView) new AnnotatedContextualView(
+ selection, editManager, selectionManager, annotationBeans));
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setAnnotationBeans(List<AnnotationBeanSPI> annotationBeans) {
+ this.annotationBeans = annotationBeans;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java
new file mode 100644
index 0000000..f9308b5
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualView.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.condition;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+
+/**
+ * Contextual view for dataflow's control (condition) links.
+ *
+ * @author David Withers
+ */
+class ConditionContextualView extends ContextualView {
+ private static final long serialVersionUID = -894521200616176439L;
+
+ private final BlockingControlLink condition;
+ private JPanel contitionView;
+
+ public ConditionContextualView(BlockingControlLink condition) {
+ this.condition = condition;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return contitionView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Control link: " + condition.getBlock().getName()
+ + " runs after " + condition.getUntilFinished().getName();
+ }
+
+ @Override
+ public void refreshView() {
+ contitionView = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ contitionView.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel label = new JLabel(
+ "<html><body><i>No details available.</i></body><html>");
+ contitionView.add(label);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java
new file mode 100644
index 0000000..ea69f1a
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/condition/ConditionContextualViewFactory.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.condition;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Condition;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+
+/**
+ * A factory of contextual views for dataflow's condition links.
+ *
+ * @author David Withers
+ *
+ */
+public class ConditionContextualViewFactory implements
+ ContextualViewFactory<BlockingControlLink> {
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof Condition;
+ }
+
+ @Override
+ public List<ContextualView> getViews(BlockingControlLink condition) {
+ return asList(new ContextualView[] { new ConditionContextualView(
+ condition) });
+ }
+
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java
new file mode 100644
index 0000000..4a63868
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualView.java
@@ -0,0 +1,108 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow;
+
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.panelForHtml;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+class DataflowContextualView extends ContextualView {
+ private static int MAX_LENGTH = 50;
+ private static final String ELLIPSIS = "...";
+
+ private Workflow dataflow;
+ private JEditorPane editorPane;
+ private final FileManager fileManager;
+ private final ColourManager colourManager;
+
+ public DataflowContextualView(Workflow dataflow, FileManager fileManager,
+ ColourManager colourManager) {
+ this.dataflow = dataflow;
+ this.fileManager = fileManager;
+ this.colourManager = colourManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ editorPane = createEditorPane(buildHtml());
+ return panelForHtml(editorPane);
+ }
+
+ private String buildHtml() {
+ StringBuilder html = new StringBuilder(getHtmlHead(getBackgroundColour()));
+ html.append(buildTableOpeningTag());
+
+ html.append("<tr><td colspan=\"2\" align=\"center\"><b>Source</b></td></tr>");
+ String source = "Newly created";
+ if (fileManager.getDataflowSource(dataflow.getParent()) != null)
+ source = fileManager.getDataflowName(dataflow.getParent());
+
+ html.append("<tr><td colspan=\"2\" align=\"center\">").append(source)
+ .append("</td></tr>");
+ if (!dataflow.getInputPorts().isEmpty()) {
+ html.append("<tr><th>Input Port Name</th><th>Depth</th></tr>");
+ for (InputWorkflowPort dip : dataflow.getInputPorts())
+ html.append("<tr><td>")
+ .append(dip.getName())
+ .append("</td><td>")
+ .append(dip.getDepth() < 0 ? "invalid/unpredicted"
+ : dip.getDepth()).append("</td></tr>");
+ }
+ if (!dataflow.getOutputPorts().isEmpty()) {
+ html.append("<tr><th>Output Port Name</th><th>Depth</th></tr>");
+ for (OutputWorkflowPort dop : dataflow.getOutputPorts())
+ html.append("<tr><td>")
+ .append(dop.getName())
+ .append("</td><td>")
+ .append(/*(dop.getDepth() < 0 ?*/ "invalid/unpredicted" /*: dop.getDepth())*/)
+ .append("</td>" + "</tr>");
+ }
+
+ return html.append("</table>").append("</body></html>").toString();
+ }
+
+ public String getBackgroundColour() {
+ return colourManager.getDefaultPropertyMap().get(
+ Dataflow.class.toString());
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+
+ private String limitName(String fullName) {
+ if (fullName.length() <= MAX_LENGTH)
+ return fullName;
+ return fullName.substring(0, MAX_LENGTH - ELLIPSIS.length()) + ELLIPSIS;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Workflow " + limitName(dataflow.getName());
+ }
+
+ @Override
+ public void refreshView() {
+ editorPane.setText(buildHtml());
+ repaint();
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java
new file mode 100644
index 0000000..0d7f3c0
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflow/DataflowContextualViewFactory.java
@@ -0,0 +1,41 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowContextualViewFactory implements
+ ContextualViewFactory<Workflow> {
+ private FileManager fileManager;
+ private ColourManager colourManager;
+
+ @Override
+ public boolean canHandle(Object selection) {
+ return selection instanceof Workflow;
+ }
+
+ @Override
+ public List<ContextualView> getViews(Workflow selection) {
+ return Arrays.asList(new ContextualView[] {
+ new DataflowContextualView(selection, fileManager, colourManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java
new file mode 100644
index 0000000..3f17a65
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualView.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's input ports.
+ *
+ * @author Alex Nenadic
+ */
+class DataflowInputPortContextualView extends ContextualView{
+ private static final long serialVersionUID = -8746856072335775933L;
+
+ private InputWorkflowPort dataflowInputPort;
+ private JPanel dataflowInputPortView;
+ @SuppressWarnings("unused")
+ private FileManager fileManager;
+
+ public DataflowInputPortContextualView(InputWorkflowPort inputport,
+ FileManager fileManager) {
+ this.dataflowInputPort = inputport;
+ this.fileManager = fileManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return dataflowInputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Workflow input port: " + dataflowInputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ dataflowInputPortView = new JPanel(new FlowLayout(LEFT));
+ dataflowInputPortView.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel label = new JLabel(getTextFromDepth("port",
+ dataflowInputPort.getDepth()));
+ dataflowInputPortView.add(label);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ public Action getConfigureAction(Frame owner) {
+ return new AbstractAction("Update prediction") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // fileManager.getCurrentDataflow().checkValidity();
+ refreshView();
+ }
+ };
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java
new file mode 100644
index 0000000..5dc5434
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowinputport/DataflowInputPortContextualViewFactory.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+
+/**
+ * A factory of contextual views for dataflow's input ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowInputPortContextualViewFactory implements
+ ContextualViewFactory<InputWorkflowPort> {
+ private FileManager fileManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof InputWorkflowPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(InputWorkflowPort inputport) {
+ return Arrays.asList(new ContextualView[] {
+ new DataflowInputPortContextualView(inputport, fileManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java
new file mode 100644
index 0000000..9ba55fe
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowOutputPortContextualView extends ContextualView {
+ private static final long serialVersionUID = 5496014085110553051L;
+
+ private OutputWorkflowPort dataflowOutputPort;
+ private JPanel dataflowOutputPortView;
+ @SuppressWarnings("unused")
+ private FileManager fileManager;
+
+ public DataflowOutputPortContextualView(OutputWorkflowPort outputport,
+ FileManager fileManager) {
+ this.dataflowOutputPort = outputport;
+ this.fileManager = fileManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return dataflowOutputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Workflow output port: " + dataflowOutputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ dataflowOutputPortView = new JPanel(new FlowLayout(LEFT));
+ dataflowOutputPortView.setBorder(new EmptyBorder(5,5,5,5));
+ JLabel label = new JLabel(getTextForLabel());
+ dataflowOutputPortView.add(label);
+ }
+
+ private String getTextForLabel() {
+ //FIXME
+ //return getTextFromDepth("port", dataflowOutputPort.getDepth());
+ return "Fix depth for OutputWorkflowPort";
+ }
+
+ private void updatePrediction() {
+ //FIXME
+ // fileManager.getCurrentDataflow().checkValidity();
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Action getConfigureAction(Frame owner) {
+ return new AbstractAction("Update prediction") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updatePrediction();
+ refreshView();
+ }
+ };
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java
new file mode 100644
index 0000000..20ac960
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/dataflowoutputport/DataflowOutputPortContextualViewFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+
+/**
+ * A factory of contextual views for dataflow's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class DataflowOutputPortContextualViewFactory implements
+ ContextualViewFactory<OutputWorkflowPort> {
+ private FileManager fileManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof DataflowOutputPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(OutputWorkflowPort outputport) {
+ return Arrays.asList(new ContextualView[] {
+ new DataflowOutputPortContextualView(outputport, fileManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java
new file mode 100644
index 0000000..daa3414
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.core.DataLink;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow's datalinks.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+class DatalinkContextualView extends ContextualView {
+ private static final long serialVersionUID = -5031256519235454876L;
+
+ private DataLink datalink;
+ private JPanel datalinkView;
+ @SuppressWarnings("unused")
+ private final FileManager fileManager;
+
+ public DatalinkContextualView(DataLink datalink, FileManager fileManager) {
+ this.datalink = datalink;
+ this.fileManager = fileManager;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return datalinkView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Data link: " + datalink.getReceivesFrom().getName() + " -> " + datalink.getSendsTo().getName();
+ }
+
+ @Override
+ public void refreshView() {
+ datalinkView = new JPanel(new FlowLayout(LEFT));
+ datalinkView.setBorder(new EmptyBorder(5,5,5,5));
+ JLabel label = new JLabel (getTextForLabel());
+ datalinkView.add(label);
+ }
+
+ private String getTextForLabel() {
+ //FIXME
+ // return getTextFromDepth("link", datalink.getResolvedDepth());
+ return "Fix DataLink resolved depth";
+ }
+
+ private void updatePrediction() {
+ //FIXME
+ // fileManager.getCurrentDataflow().checkValidity();
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Action getConfigureAction(Frame owner) {
+ return new AbstractAction("Update prediction") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updatePrediction();
+ refreshView();
+ }
+ };
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java
new file mode 100644
index 0000000..fa8bf96
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/datalink/DatalinkContextualViewFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.Datalink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * A factory of contextual views for dataflow's datalinks.
+ *
+ * @author Alex Nenadic
+ */
+public class DatalinkContextualViewFactory implements
+ ContextualViewFactory<DataLink> {
+ private FileManager fileManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof Datalink;
+ }
+
+ @Override
+ public List<ContextualView> getViews(DataLink datalink) {
+ return Arrays.asList(new ContextualView[] {
+ new DatalinkContextualView(datalink, fileManager)});
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java
new file mode 100644
index 0000000..11306d0
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponent.java
@@ -0,0 +1,389 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.BLUE;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.ORANGE;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.minusIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.plusIcon;
+
+import java.awt.Color;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.Utils;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class ContextualViewComponent extends JScrollPane implements UIComponentSPI {
+ /** delay before contextual view is redrawn */
+ private static final int DELAY = 250;
+ private static final Color[] colors = new Color[] { BLUE, GREEN, ORANGE };
+ // HACK ALERT!
+ public static boolean selfGenerated = false;
+
+ private Observer<DataflowSelectionMessage> dataflowSelectionListener = new DataflowSelectionListener();
+ private SelectionManager selectionManager;
+ private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+ GridBagConstraints gbc;
+ protected Map<JPanel, SectionLabel> panelToLabelMap = new HashMap<>();
+ private String lastOpenedSectionName = "";
+ private JPanel mainPanel;
+ private List<JPanel> shownComponents = null;
+ int colorIndex = 0;
+ private Timer updateSelectionTimer = null;
+ private Object lastSelectedObject = null;
+
+ private static final Comparator<ContextualView> viewComparator = new Comparator<ContextualView>() {
+ @Override
+ public int compare(ContextualView o1, ContextualView o2) {
+ return o1.getPreferredPosition() - o2.getPreferredPosition();
+ }
+ };
+
+ public ContextualViewComponent(EditManager editManager,
+ SelectionManager selectionManager,
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+ this.selectionManager = selectionManager;
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ updateSelectionTimer = new Timer(DELAY, updateSelectionListener);
+ updateSelectionTimer.setRepeats(false);
+
+ initialise();
+
+ editManager.addObserver(new EditManagerObserver());
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Details";
+ }
+
+ private void initialise() {
+ mainPanel = new JPanel(new GridBagLayout());
+ this.setViewportView(mainPanel);
+ }
+
+ @Override
+ public void onDisplay() {
+ }
+
+ @Override
+ public void onDispose() {
+ updateSelectionTimer.stop();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void updateContextualView(List<ContextualViewFactory<?>> viewFactories,
+ Object selection) {
+ if (selection == lastSelectedObject)
+ return;
+ lastSelectedObject = selection;
+ mainPanel = new JPanel(new GridBagLayout());
+ panelToLabelMap.clear();
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.weightx = 0.1;
+ gbc.fill = HORIZONTAL;
+
+ gbc.gridy = 0;
+ shownComponents = new ArrayList<>();
+ List<ContextualView> views = new ArrayList<>();
+ for (ContextualViewFactory<?> cvf : viewFactories)
+ views.addAll(((ContextualViewFactory<Object>) cvf)
+ .getViews(selection));
+ Collections.sort(views, viewComparator);
+ colorIndex = 0;
+ if (views.isEmpty())
+ mainPanel.add(new JLabel("No details available"));
+ else
+ populateContextualView(viewFactories, gbc, views);
+ gbc.weighty = 0.1;
+ gbc.fill = BOTH;
+ mainPanel.add(new JPanel(), gbc);
+ // mainPanel.revalidate();
+ // mainPanel.repaint();
+ this.setViewportView(mainPanel);
+ // this.revalidate();
+ // this.repaint();
+ }
+
+ private void populateContextualView(
+ List<ContextualViewFactory<?>> viewFactories,
+ GridBagConstraints gbc, List<ContextualView> views) {
+ JPanel firstPanel = null;
+ JPanel lastOpenedSection = null;
+ for (ContextualView view : views) {
+ SectionLabel label = new SectionLabel(view.getViewTitle(), nextColor());
+ mainPanel.add(label, gbc);
+ gbc.gridy++;
+ JPanel subPanel = new JPanel();
+ if (view.getViewTitle().equals(lastOpenedSectionName))
+ lastOpenedSection = subPanel;
+ subPanel.setLayout(new GridBagLayout());
+
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.weightx = 0.1;
+ constraints.weighty = 0;
+ constraints.anchor = CENTER;
+ constraints.fill = HORIZONTAL;
+
+ subPanel.add(view, constraints);
+ Frame frame = Utils.getParentFrame(this);
+ Action configureAction = view.getConfigureAction(frame);
+ if (configureAction != null) {
+ JButton configButton = new JButton(configureAction);
+ if (configButton.getText() == null
+ || configButton.getText().isEmpty())
+ configButton.setText("Configure");
+ constraints.gridy++;
+ constraints.fill = NONE;
+ constraints.anchor = LINE_START;
+ subPanel.add(configButton, constraints);
+ }
+ if (firstPanel == null)
+ firstPanel = subPanel;
+ mainPanel.add(subPanel, gbc);
+ shownComponents.add(subPanel);
+ gbc.gridy++;
+ if (viewFactories.size() != 1)
+ makeCloseable(subPanel, label);
+ else {
+ lastOpenedSectionName = label.getText();
+ lastOpenedSection = subPanel;
+ panelToLabelMap.put(subPanel, label);
+ subPanel.setVisible(false);
+ }
+ }
+ if (lastOpenedSection != null)
+ openSection(lastOpenedSection);
+ else if (firstPanel != null)
+ openSection(firstPanel);
+ }
+
+ private void clearContextualView() {
+ lastSelectedObject = null;
+ mainPanel = new JPanel(new GridBagLayout());
+ mainPanel.add(new JLabel("No details available"));
+ this.setViewportView(mainPanel);
+ this.revalidate();
+ }
+
+ public void updateSelection(Object selectedItem) {
+ findContextualView(selectedItem);
+ }
+
+ private Runnable updateSelectionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Object selection = getSelection();
+ if (selection == null)
+ clearContextualView();
+ else
+ updateSelection(selection);
+ }
+ };
+
+ private ActionListener updateSelectionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(updateSelectionRunnable);
+ }
+ };
+
+ public void updateSelection() {
+ updateSelectionTimer.restart();
+ }
+
+ private Object getSelection() {
+ WorkflowBundle workflowBundle = selectionManager.getSelectedWorkflowBundle();
+
+ /*
+ * If there is no currently opened dataflow, clear the contextual view
+ * panel
+ */
+ if (workflowBundle == null) {
+ return null;
+ }
+ DataflowSelectionModel selectionModel = selectionManager
+ .getDataflowSelectionModel(workflowBundle);
+ Set<Object> selection = selectionModel.getSelection();
+
+ /*
+ * If the dataflow is opened but no component of the dataflow is
+ * selected, clear the contextual view panel
+ */
+ if (selection.isEmpty())
+ return null;
+ return selection.iterator().next();
+ }
+
+ private void findContextualView(Object selection) {
+ List<ContextualViewFactory<?>> viewFactoriesForBeanType = contextualViewFactoryRegistry
+ .getViewFactoriesForObject(selection);
+ updateContextualView(viewFactoriesForBeanType, selection);
+ }
+
+ private final class SelectionManagerObserver extends SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender, SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent)
+ bundleSelected((WorkflowBundleSelectionEvent) message);
+ }
+
+ private void bundleSelected(WorkflowBundleSelectionEvent event) {
+ WorkflowBundle oldBundle = event
+ .getPreviouslySelectedWorkflowBundle();
+ WorkflowBundle newBundle = event.getSelectedWorkflowBundle();
+
+ if (oldBundle != null)
+ selectionManager.getDataflowSelectionModel(oldBundle)
+ .removeObserver(dataflowSelectionListener);
+ if (newBundle != null)
+ selectionManager.getDataflowSelectionModel(newBundle)
+ .addObserver(dataflowSelectionListener);
+ lastSelectedObject = null;
+ updateSelection();
+ }
+ }
+
+ private final class DataflowSelectionListener extends SwingAwareObserver<DataflowSelectionMessage> {
+ @Override
+ public void notifySwing(Observable<DataflowSelectionMessage> sender,
+ DataflowSelectionMessage message) {
+ updateSelection();
+ }
+ }
+
+ private final class EditManagerObserver extends SwingAwareObserver<EditManagerEvent> {
+ @Override
+ public void notifySwing(Observable<EditManagerEvent> sender, EditManagerEvent message) {
+ Object selection = getSelection();
+ if ((selection != lastSelectedObject) && !selfGenerated) {
+ lastSelectedObject = null;
+ refreshView();
+ }
+ }
+ }
+
+ public void refreshView() {
+ if (mainPanel != null)
+ updateSelection();
+ }
+
+ private final class SectionLabel extends ShadedLabel {
+ private JLabel expand;
+
+ private SectionLabel(String text, Color colour) {
+ super(text, colour);
+ expand = new JLabel(minusIcon);
+ add(expand, 0);
+ setExpanded(true);
+ }
+
+ public void setExpanded(boolean expanded) {
+ if (expanded)
+ expand.setIcon(minusIcon);
+ else
+ expand.setIcon(plusIcon);
+ }
+ }
+
+ private void makeCloseable(JPanel panel, SectionLabel label) {
+ panel.setVisible(false);
+ if (panelToLabelMap.get(panel) != label) {
+ panelToLabelMap.put(panel, label);
+ // Only add mouse listener once
+ label.addMouseListener(new SectionOpener(panel));
+ }
+ }
+
+ protected class SectionOpener extends MouseAdapter {
+ private final JPanel sectionToOpen;
+
+ public SectionOpener(JPanel sectionToOpen) {
+ this.sectionToOpen = sectionToOpen;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ openSection(sectionToOpen);
+ }
+ }
+
+ public synchronized void openSection(JPanel sectionToOpen) {
+ lastOpenedSectionName = "";
+ for (Entry<JPanel, SectionLabel> entry : panelToLabelMap.entrySet()) {
+ JPanel section = entry.getKey();
+ SectionLabel sectionLabel = entry.getValue();
+
+ if (section != sectionToOpen)
+ section.setVisible(false);
+ else {
+ section.setVisible(!section.isVisible());
+ if (section.isVisible())
+ lastOpenedSectionName = sectionLabel.getText();
+ }
+ sectionLabel.setExpanded(section.isVisible());
+ }
+ this.revalidate();
+ this.repaint();
+ }
+
+ private Color nextColor() {
+ if (colorIndex >= colors.length)
+ colorIndex = 0;
+ return colors[colorIndex++];
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java
new file mode 100644
index 0000000..db43a0d
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/impl/ContextualViewComponentFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2008 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.workbench.ui.views.contextualviews.impl;
+
+import javax.swing.ImageIcon;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI;
+import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
+
+public class ContextualViewComponentFactory implements UIComponentFactorySPI {
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private ContextualViewFactoryRegistry contextualViewFactoryRegistry;
+
+ @Override
+ public UIComponentSPI getComponent() {
+ return new ContextualViewComponent(editManager, selectionManager,
+ contextualViewFactoryRegistry);
+ }
+
+ @Override
+ public ImageIcon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Details";
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setContextualViewFactoryRegistry(
+ ContextualViewFactoryRegistry contextualViewFactoryRegistry) {
+ this.contextualViewFactoryRegistry = contextualViewFactoryRegistry;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java
new file mode 100644
index 0000000..c1b3d06
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualView.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * Contextual view for dataflow procerssor's input ports.
+ *
+ * @author Alex Nenadic
+ */
+class InputPortContextualView extends ContextualView {
+ private static final String NO_DETAILS_AVAILABLE_HTML = "<html><body>"
+ + "<i>No details available.</i>" + "</body><html>";
+ private static final long serialVersionUID = -7743029534480678624L;
+
+ private InputActivityPort inputPort;
+ private JPanel inputPortView;
+
+ public InputPortContextualView(InputActivityPort inputport) {
+ this.inputPort = inputport;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return inputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service input port: " + inputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ inputPortView = new JPanel(new FlowLayout(LEFT));
+ inputPortView.setBorder(new EmptyBorder(5, 5, 5, 5));
+ JLabel label = new JLabel(NO_DETAILS_AVAILABLE_HTML);
+ inputPortView.add(label);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java
new file mode 100644
index 0000000..490e5b7
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/inputport/InputPortContextualViewFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+
+/**
+ * A factory of contextual views for dataflow proessor's (i.e. its associated
+ * activity's) input ports.
+ *
+ * @author Alex Nenadic
+ */
+public class InputPortContextualViewFactory implements
+ ContextualViewFactory<InputActivityPort> {
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof InputActivityPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(InputActivityPort inputport) {
+ return Arrays.asList(new ContextualView[] {
+ new InputPortContextualView(inputport)});
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java
new file mode 100644
index 0000000..567cc4b
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationAction.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workflow.edits.ReorderMergePositionsEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * Configuration action for a Merge. This action changes the order of
+ * merge's incoming ports.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+class MergeConfigurationAction extends AbstractAction {
+ private static Logger logger = Logger
+ .getLogger(MergeConfigurationAction.class);
+
+ private final List<DataLink> reorderedDataLinksList;
+ private final List<DataLink> datalinks;
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ MergeConfigurationAction(List<DataLink> datalinks,
+ List<DataLink> reorderedDataLinksList, EditManager editManager,
+ SelectionManager selectionManager) {
+ this.datalinks = datalinks;
+ this.reorderedDataLinksList = reorderedDataLinksList;
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ReorderMergePositionsEdit edit = new ReorderMergePositionsEdit(
+ datalinks, reorderedDataLinksList);
+
+ WorkflowBundle bundle = selectionManager.getSelectedWorkflowBundle();
+
+ try {
+ editManager.doDataflowEdit(bundle, edit);
+ } catch (IllegalStateException ex1) {
+ logger.error("Could not configure merge", ex1);
+ } catch (EditException ex2) {
+ logger.error("Could not configure merge", ex2);
+ }
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java
new file mode 100644
index 0000000..66eeb3e
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeConfigurationView.java
@@ -0,0 +1,233 @@
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.lang.Math.max;
+import static javax.swing.BoxLayout.Y_AXIS;
+import static javax.swing.ListSelectionModel.SINGLE_SELECTION;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
+import static javax.swing.SwingConstants.CENTER;
+import static javax.swing.SwingConstants.LEFT;
+import static javax.swing.SwingConstants.RIGHT;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.downArrowIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.upArrowIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+@SuppressWarnings("serial")
+public class MergeConfigurationView extends HelpEnabledDialog {
+ private static final String TITLE = "<html><body><b>Order of incoming links</b></body></html>";
+
+ private List<DataLink> dataLinks;
+ private List<DataLink> reorderedDataLinks;
+ /** Ordered list of labels for dataLinks to be displayed to the user */
+ private DefaultListModel<String> labelListModel;
+ /** JList that displays the labelListModel */
+ JList<String> list;
+ /** Button to push the dataLink up the list */
+ private JButton upButton;
+ /** Button to push the dataLink down the list */
+ private JButton downButton;
+ private final EditManager editManager;
+ private final SelectionManager selectionManager;
+
+ public MergeConfigurationView(List<DataLink> dataLinks, EditManager editManager,
+ SelectionManager selectionManager) {
+ super((Frame)null, "Merge Configuration", true);
+
+ this.dataLinks = new ArrayList<>(dataLinks);
+ reorderedDataLinks = new ArrayList<>(dataLinks);
+ this.editManager = editManager;
+ this.selectionManager = selectionManager;
+ labelListModel = new DefaultListModel<>();
+ for (DataLink dataLink : dataLinks)
+ labelListModel.addElement(dataLink.toString());
+
+ initComponents();
+ }
+
+ private void initComponents() {
+ getContentPane().setLayout(new BorderLayout());
+
+ JPanel listPanel = new JPanel();
+ listPanel.setLayout(new BorderLayout());
+ listPanel.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10, 10),
+ new EtchedBorder()));
+
+ JLabel title = new JLabel(TITLE);
+ title.setBorder(new EmptyBorder(5, 5, 5, 5));
+ listPanel.add(title, NORTH);
+
+ list = new JList<>(labelListModel);
+ list.setSelectionMode(SINGLE_SELECTION);
+ list.setVisibleRowCount(-1);
+ list.addListSelectionListener(new ListSelectionListener() {
+ /**
+ * Enable and disable up and down buttons based on which item in the
+ * list is selected
+ */
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ int index = list.getSelectedIndex();
+ if ((index == -1) || (index == 0 && labelListModel.size() == 0)) {
+ // nothing selected or only one item in the list
+ upButton.setEnabled(false);
+ downButton.setEnabled(false);
+ } else {
+ upButton.setEnabled(index > 0);
+ downButton.setEnabled(index < labelListModel.size() - 1);
+ }
+ }
+ });
+
+ final JScrollPane listScroller = new JScrollPane(list);
+ listScroller.setBorder(new EmptyBorder(5, 5, 5, 5));
+ listScroller.setBackground(listPanel.getBackground());
+ listScroller.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_ALWAYS);
+ listScroller.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
+ // Set the size of scroll pane to make all list items visible
+ FontMetrics fm = listScroller.getFontMetrics(this.getFont());
+ int listScrollerHeight = fm.getHeight() * labelListModel.size() + 75; //+75 just in case
+ listScroller.setPreferredSize(new Dimension(listScroller
+ .getPreferredSize().width, max(listScrollerHeight,
+ listScroller.getPreferredSize().height)));
+ listPanel.add(listScroller, BorderLayout.CENTER);
+
+ JPanel upDownButtonPanel = new JPanel();
+ upDownButtonPanel.setLayout(new BoxLayout(upDownButtonPanel, Y_AXIS));
+ upDownButtonPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+
+ upButton = new JButton(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int index = list.getSelectedIndex();
+ if (index != -1) {
+ // Swap the labels
+ String label = (String) labelListModel.elementAt(index);
+ labelListModel.set(index, labelListModel.get(index - 1));
+ labelListModel.set(index - 1, label);
+ // Swap the dataLinks
+ DataLink dataLink = reorderedDataLinks.get(index);
+ reorderedDataLinks.set(index,
+ reorderedDataLinks.get(index - 1));
+ reorderedDataLinks.set(index - 1, dataLink);
+ // Make the pushed item selected
+ list.setSelectedIndex(index - 1);
+ // Refresh the list
+ listScroller.repaint();
+ listScroller.revalidate();
+ }
+ }
+ });
+ upButton.setIcon(upArrowIcon);
+ upButton.setText("Up");
+ // Place text to the right of icon, vertically centered
+ upButton.setVerticalTextPosition(CENTER);
+ upButton.setHorizontalTextPosition(RIGHT);
+ // Set the horizontal alignment of the icon and text
+ upButton.setHorizontalAlignment(LEFT);
+ upButton.setEnabled(false);
+ upDownButtonPanel.add(upButton);
+
+ downButton = new JButton(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int index = list.getSelectedIndex();
+ if (index != -1) {
+ // Swap the labels
+ String label = (String) labelListModel.elementAt(index);
+ labelListModel.set(index, labelListModel.get(index + 1));
+ labelListModel.set(index + 1, label);
+ // Swap the dataLinks
+ DataLink dataLink = reorderedDataLinks.get(index);
+ reorderedDataLinks.set(index,
+ reorderedDataLinks.get(index + 1));
+ reorderedDataLinks.set(index + 1, dataLink);
+ // Make the pushed item selected
+ list.setSelectedIndex(index + 1);
+ // Refresh the list
+ list.repaint();
+ listScroller.revalidate();
+ }
+ }
+ });
+ downButton.setIcon(downArrowIcon);
+ downButton.setText("Down");
+ // Place text to the right of icon, vertically centered
+ downButton.setVerticalTextPosition(CENTER);
+ downButton.setHorizontalTextPosition(RIGHT);
+ // Set the horizontal alignment of the icon and text
+ downButton.setHorizontalAlignment(LEFT);
+ downButton.setEnabled(false);
+ // set the up button to be of the same size as down button
+ upButton.setPreferredSize(downButton.getPreferredSize());
+ upButton.setMaximumSize(downButton.getPreferredSize());
+ upButton.setMinimumSize(downButton.getPreferredSize());
+ upDownButtonPanel.add(downButton);
+
+ listPanel.add(upDownButtonPanel, EAST);
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+
+ JButton jbOK = new JButton("OK");
+ jbOK.addActionListener(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ new MergeConfigurationAction(dataLinks, reorderedDataLinks,
+ editManager, selectionManager).actionPerformed(e);
+ closeDialog();
+ }
+ });
+
+ JButton jbCancel = new JButton("Cancel");
+ jbCancel.addActionListener(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ closeDialog();
+ }
+ });
+
+ buttonPanel.add(jbOK);
+ buttonPanel.add(jbCancel);
+
+ getContentPane().add(listPanel, BorderLayout.CENTER);
+ getContentPane().add(buttonPanel, SOUTH);
+ pack();
+ }
+
+ /**
+ * Close the dialog.
+ */
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java
new file mode 100644
index 0000000..deb09fb
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualView.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static java.awt.FlowLayout.LEFT;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.buildTableOpeningTag;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.createEditorPane;
+import static net.sf.taverna.t2.lang.ui.HtmlUtils.getHtmlHead;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JPanel;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.Merge;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * Contextual view for a {@link Merge}.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+class MergeContextualView extends ContextualView {
+ @SuppressWarnings("unused")
+ private DataLink dataLink;
+ private List<DataLink> datalinks;
+ @SuppressWarnings("unused")
+ private WorkflowBundle workflow;
+ private JEditorPane editorPane;
+ private final EditManager editManager;
+ private final ColourManager colourManager;
+ private final SelectionManager selectionManager;
+
+ // TODO inject from Spring via factory?
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+ public MergeContextualView(DataLink dataLink, EditManager editManager,
+ SelectionManager selectionManager, ColourManager colourManager) {
+ this.dataLink = dataLink;
+ this.selectionManager = selectionManager;
+ datalinks = scufl2Tools.datalinksTo(dataLink.getSendsTo());
+ this.editManager = editManager;
+ this.colourManager = colourManager;
+ workflow = selectionManager.getSelectedWorkflowBundle();
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ editorPane = createEditorPane(buildHtml());
+ return panelForHtml(editorPane);
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Merge Position";
+ }
+
+ /**
+ * Update the view with the latest information from the configuration bean.
+ */
+ @Override
+ public void refreshView() {
+ editorPane.setText(buildHtml());
+ repaint();
+ }
+
+ private String buildHtml() {
+ StringBuilder html = new StringBuilder(
+ getHtmlHead(getBackgroundColour()));
+ html.append(buildTableOpeningTag())
+ .append("<tr><td colspan=\"2\"><b>")
+ .append(getViewTitle())
+ .append("</b></td></tr>")
+ .append("<tr><td colspan=\"2\"><b>Ordered incoming links</b></td></tr>");
+
+ int counter = 1;
+ for (DataLink datalink : datalinks)
+ html.append("<tr><td>").append(counter++).append(".</td><td>")
+ .append(datalink).append("</td></tr>");
+
+ return html.append("</table>").append("</body></html>").toString();
+ }
+
+ protected JPanel panelForHtml(JEditorPane editorPane) {
+ final JPanel panel = new JPanel();
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(LEFT));
+
+ JButton configureButton = new JButton(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ MergeConfigurationView mergeConfigurationView = new MergeConfigurationView(
+ datalinks, editManager, selectionManager);
+ mergeConfigurationView.setLocationRelativeTo(panel);
+ mergeConfigurationView.setVisible(true);
+ }
+ });
+ configureButton.setText("Configure");
+ buttonPanel.add(configureButton);
+
+ panel.setLayout(new BorderLayout());
+ panel.add(editorPane, CENTER);
+ panel.add(buttonPanel, SOUTH);
+ return panel;
+ }
+
+ public String getBackgroundColour() {
+ return colourManager.getDefaultPropertyMap().get(
+ "net.sf.taverna.t2.workflowmodel.Merge");
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java
new file mode 100644
index 0000000..712b183
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/merge/MergeContextualViewFactory.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.merge;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import uk.org.taverna.scufl2.api.core.DataLink;
+
+/**
+ * A factory of contextual views for dataflow's merges.
+ *
+ * @author Alex Nenadic
+ */
+public class MergeContextualViewFactory implements ContextualViewFactory<DataLink> {
+ private EditManager editManager;
+ private SelectionManager selectionManager;
+ private ColourManager colourManager;
+
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof DataLink
+ && ((DataLink) object).getMergePosition() != null;
+ }
+
+ @Override
+ public List<ContextualView> getViews(DataLink merge) {
+ return Arrays.asList(new ContextualView[] {
+ new MergeContextualView(merge, editManager, selectionManager, colourManager)});
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void setColourManager(ColourManager colourManager) {
+ this.colourManager = colourManager;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java
new file mode 100644
index 0000000..f2c7861
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualView.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport;
+
+import static java.awt.FlowLayout.LEFT;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+/**
+ * Contextual view for dataflow procerssor's output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class OutputPortContextualView extends ContextualView {
+ private static final String NO_DETAILS_AVAILABLE_HTML = "<html><body>"
+ + "<i>No details available.</i>" + "</body><html>";
+ private static final long serialVersionUID = -7743029534480678624L;
+
+ private ActivityOutputPort outputPort;
+ private JPanel outputPortView;
+
+ public OutputPortContextualView(ActivityOutputPort outputport) {
+ this.outputPort = outputport;
+ initView();
+ }
+
+ @Override
+ public JComponent getMainFrame() {
+ refreshView();
+ return outputPortView;
+ }
+
+ @Override
+ public String getViewTitle() {
+ return "Service output port: " + outputPort.getName();
+ }
+
+ @Override
+ public void refreshView() {
+ outputPortView = new JPanel(new FlowLayout(LEFT));
+ outputPortView.setBorder(new EmptyBorder(5,5,5,5));
+ JLabel label = new JLabel(NO_DETAILS_AVAILABLE_HTML);
+ outputPortView.add(label);
+ }
+
+ @Override
+ public int getPreferredPosition() {
+ return 100;
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java
new file mode 100644
index 0000000..71e2cd4
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/java/net/sf/taverna/t2/workbench/ui/views/contextualviews/outputport/OutputPortContextualViewFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityOutputPort;
+
+/**
+ * A factory of contextual views for dataflow proessor's (i.e. its associated
+ * activity's) output ports.
+ *
+ * @author Alex Nenadic
+ */
+public class OutputPortContextualViewFactory implements
+ ContextualViewFactory<ActivityOutputPort> {
+ @Override
+ public boolean canHandle(Object object) {
+ return object instanceof ActivityOutputPort;
+ }
+
+ @Override
+ public List<ContextualView> getViews(ActivityOutputPort outputport) {
+ return Arrays.asList(new ContextualView[] {
+ new OutputPortContextualView(outputport)});
+ }
+}
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
new file mode 100644
index 0000000..7744cb3
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory
@@ -0,0 +1,9 @@
+net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport.OutputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport.InputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport.DataflowOutputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport.DataflowInputPortContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink.DatalinkContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.condition.ConditionContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualViewFactory
+net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow.DataflowContextualViewFactory
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
new file mode 100644
index 0000000..a564691
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualViewComponentFactory
\ No newline at end of file
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml
new file mode 100644
index 0000000..767943d
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context-osgi.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="ContextualViewComponentFactory" interface="net.sf.taverna.t2.workbench.ui.zaria.UIComponentFactorySPI" />
+
+ <service ref="OutputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="InputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DataflowOutputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DataflowInputPortContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DatalinkContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="ConditionContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="MergeContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="AnnotatedContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+ <service ref="DataflowContextualViewFactory" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" />
+
+ <service ref="ContextualViewFactoryRegistry" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactoryRegistry" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="colourManager" interface="net.sf.taverna.t2.workbench.configuration.colour.ColourManager" />
+
+ <list id="annotationBeans" interface="net.sf.taverna.t2.annotation.AnnotationBeanSPI" />
+ <list id="contextualViewFactories" interface="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ContextualViewFactory" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml
new file mode 100644
index 0000000..18bbd36
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/META-INF/spring/contextual-views-impl-context.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="ContextualViewComponentFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.impl.ContextualViewComponentFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="contextualViewFactoryRegistry" ref="ContextualViewFactoryRegistry"/>
+ </bean>
+
+ <bean id="OutputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.outputport.OutputPortContextualViewFactory" />
+ <bean id="InputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.inputport.InputPortContextualViewFactory" />
+ <bean id="DataflowOutputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowoutputport.DataflowOutputPortContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="DataflowInputPortContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflowinputport.DataflowInputPortContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="DatalinkContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.datalink.DatalinkContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ </bean>
+ <bean id="ConditionContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.condition.ConditionContextualViewFactory" />
+ <bean id="MergeContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.merge.MergeContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+ <bean id="AnnotatedContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualViewFactory">
+ <property name="editManager" ref="editManager" />
+ <property name="selectionManager" ref="selectionManager" />
+ <property name="annotationBeans" ref ="annotationBeans"/>
+ </bean>
+ <bean id="DataflowContextualViewFactory" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.dataflow.DataflowContextualViewFactory">
+ <property name="fileManager" ref="fileManager" />
+ <property name="colourManager" ref="colourManager" />
+ </bean>
+
+ <bean id="ContextualViewFactoryRegistry" class="net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.impl.ContextualViewFactoryRegistryImpl">
+ <property name="contextualViewFactories" ref="contextualViewFactories" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties b/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties
new file mode 100644
index 0000000..e3196f6
--- /dev/null
+++ b/taverna-workbench-contextual-views-impl/src/main/resources/annotatedcontextualview.properties
@@ -0,0 +1,4 @@
+net.sf.taverna.t2.annotation.annotationbeans.FreeTextDescription: Description
+net.sf.taverna.t2.annotation.annotationbeans.Author: Author
+net.sf.taverna.t2.annotation.annotationbeans.DescriptiveTitle: Title
+net.sf.taverna.t2.annotation.annotationbeans.ExampleValue: Example
\ No newline at end of file
diff --git a/taverna-workbench-edits-impl/pom.xml b/taverna-workbench-edits-impl/pom.xml
new file mode 100644
index 0000000..b5b725a
--- /dev/null
+++ b/taverna-workbench-edits-impl/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>edits-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Edits implementation</name>
+ <description>
+ Implementation for doing workflow edits and undo.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
new file mode 100644
index 0000000..f97d36c
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Implementation of {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class EditManagerImpl implements EditManager {
+ private static Logger logger = Logger.getLogger(EditManagerImpl.class);
+
+ private MultiCaster<EditManagerEvent> multiCaster = new MultiCaster<>(this);
+ private Map<WorkflowBundle, DataflowEdits> editsForDataflow = new HashMap<>();
+
+ @Override
+ public void addObserver(Observer<EditManagerEvent> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ @Override
+ public boolean canRedoDataflowEdit(WorkflowBundle dataflow) {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ return edits.canRedo();
+ }
+
+ @Override
+ public boolean canUndoDataflowEdit(WorkflowBundle dataflow) {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ return edits.canUndo();
+ }
+
+ @Override
+ public void doDataflowEdit(WorkflowBundle dataflow, Edit<?> edit)
+ throws EditException {
+ // We do the edit before we notify the observers
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ synchronized (edits) {
+ // Make sure the edits are in the order they were performed
+ edit.doEdit();
+ edits.addEdit(edit);
+ }
+ multiCaster.notify(new DataflowEditEvent(dataflow, edit));
+ }
+
+ @Override
+ public List<Observer<EditManagerEvent>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ @Override
+ public void redoDataflowEdit(WorkflowBundle dataflow) throws EditException {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ Edit<?> edit;
+ synchronized (edits) {
+ if (!edits.canRedo())
+ return;
+ edit = edits.getLastUndo();
+ edit.doEdit();
+ edits.addRedo(edit);
+ }
+ multiCaster.notify(new DataFlowRedoEvent(dataflow, edit));
+ }
+
+ @Override
+ public void removeObserver(Observer<EditManagerEvent> observer) {
+ multiCaster.removeObserver(observer);
+ }
+
+ @Override
+ public void undoDataflowEdit(WorkflowBundle dataflow) {
+ DataflowEdits edits = getEditsForDataflow(dataflow);
+ Edit<?> edit;
+ synchronized (edits) {
+ if (!edits.canUndo())
+ return;
+ edit = edits.getLastEdit();
+ edit.undo();
+ edits.addUndo(edit);
+ }
+ logger.info("Undoing an edit");
+ multiCaster.notify(new DataFlowUndoEvent(dataflow, edit));
+ }
+
+ /**
+ * Get the set of edits for a given dataflow, creating if neccessary.
+ *
+ * @param dataflow
+ * Dataflow the edits relate to
+ * @return A {@link DataflowEdits} instance to keep edits for the given
+ * dataflow
+ */
+ protected synchronized DataflowEdits getEditsForDataflow(WorkflowBundle dataflow) {
+ DataflowEdits edits = editsForDataflow.get(dataflow);
+ if (edits == null) {
+ edits = new DataflowEdits();
+ editsForDataflow.put(dataflow, edits);
+ }
+ return edits;
+ }
+
+ /**
+ * A set of edits and undoes for a {@link Dataflow}
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ public class DataflowEdits {
+ /**
+ * List of edits that have been performed and can be undone.
+ */
+ private List<Edit<?>> edits = new ArrayList<>();
+ /**
+ * List of edits that have been undone and can be redone
+ */
+ private List<Edit<?>> undoes = new ArrayList<>();
+
+ /**
+ * Add an {@link Edit} that has been done by the EditManager.
+ * <p>
+ * This can later be retrieved using {@link #getLastEdit()}. After
+ * calling this {@link #canRedo()} will be false.
+ *
+ * @param edit
+ * {@link Edit} that has been undone
+ */
+ public synchronized void addEdit(Edit<?> edit) {
+ addEditOrRedo(edit, false);
+ }
+
+ /**
+ * Add an {@link Edit} that has been redone by the EditManager.
+ * <p>
+ * The {@link Edit} must be the same as the last undo returned through
+ * {@link #getLastUndo()}.
+ * <p>
+ * This method works like {@link #addEdit(Edit)} except that instead of
+ * removing all possible redoes, only the given {@link Edit} is removed.
+ *
+ * @param edit
+ * {@link Edit} that has been redone
+ */
+ public synchronized void addRedo(Edit<?> edit) {
+ addEditOrRedo(edit, true);
+ }
+
+ /**
+ * Add an {@link Edit} that has been undone by the EditManager.
+ * <p>
+ * After calling this method {@link #canRedo()} will be true, and the
+ * edit can be retrieved using {@link #getLastUndo()}.
+ * </p>
+ * <p>
+ * The {@link Edit} must be the last edit returned from
+ * {@link #getLastEdit()}, after calling this method
+ * {@link #getLastEdit()} will return the previous edit or
+ * {@link #canUndo()} will be false if there are no more edits.
+ *
+ * @param edit
+ * {@link Edit} that has been undone
+ */
+ public synchronized void addUndo(Edit<?> edit) {
+ int lastIndex = edits.size() - 1;
+ if (lastIndex < 0 || !edits.get(lastIndex).equals(edit))
+ throw new IllegalArgumentException("Can't undo unknown edit "
+ + edit);
+ undoes.add(edit);
+ edits.remove(lastIndex);
+ }
+
+ /**
+ * True if there are undone events that can be redone.
+ *
+ * @return <code>true</code> if there are undone events
+ */
+ public boolean canRedo() {
+ return !undoes.isEmpty();
+ }
+
+ /**
+ * True if there are edits that can be undone and later added with
+ * {@link #addUndo(Edit)}.
+ *
+ * @return <code>true</code> if there are edits that can be undone
+ */
+ public boolean canUndo() {
+ return !edits.isEmpty();
+ }
+
+ /**
+ * Get the last edit that can be undone. This edit was the last one to
+ * be added with {@link #addEdit(Edit)} or {@link #addRedo(Edit)}.
+ *
+ * @return The last added {@link Edit}
+ * @throws IllegalStateException
+ * If there are no more edits (Check with {@link #canUndo()}
+ * first)
+ *
+ */
+ public synchronized Edit<?> getLastEdit() throws IllegalStateException {
+ if (edits.isEmpty())
+ throw new IllegalStateException("No more edits");
+ int lastEdit = edits.size() - 1;
+ return edits.get(lastEdit);
+ }
+
+ /**
+ * Get the last edit that can be redone. This edit was the last one to
+ * be added with {@link #addUndo(Edit)}.
+ *
+ * @return The last undone {@link Edit}
+ * @throws IllegalStateException
+ * If there are no more edits (Check with {@link #canRedo()}
+ * first)
+ *
+ */
+ public synchronized Edit<?> getLastUndo() throws IllegalStateException {
+ if (undoes.isEmpty())
+ throw new IllegalStateException("No more undoes");
+ int lastUndo = undoes.size() - 1;
+ return undoes.get(lastUndo);
+ }
+
+ /**
+ * Add an edit or redo. Common functionallity called by
+ * {@link #addEdit(Edit)} and {@link #addRedo(Edit)}.
+ *
+ * @see #addEdit(Edit)
+ * @see #addRedo(Edit)
+ * @param edit
+ * The {@link Edit} to add
+ * @param isRedo
+ * True if this is a redo
+ */
+ protected void addEditOrRedo(Edit<?> edit, boolean isRedo) {
+ edits.add(edit);
+ if (undoes.isEmpty())
+ return;
+ if (isRedo) {
+ // It's a redo, remove only the last one
+ int lastUndoIndex = undoes.size() - 1;
+ Edit<?> lastUndo = undoes.get(lastUndoIndex);
+ if (!edit.equals(lastUndo))
+ throw new IllegalArgumentException(
+ "Can only redo last undo");
+ undoes.remove(lastUndoIndex);
+ } else
+ // It's a new edit, remove all redos
+ undoes.clear();
+ }
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
new file mode 100644
index 0000000..97d14a6
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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.workbench.edits.impl.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_Y;
+import static java.awt.event.KeyEvent.VK_Z;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.redoIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.undoIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractUndoAction extends AbstractAction {
+ protected EditManager editManager;
+ private SelectionManager selectionManager;
+
+ public AbstractUndoAction(String label, EditManager editManager) {
+ super(label);
+ this.editManager = editManager;
+ if (label.equals("Undo")) {
+ this.putValue(SMALL_ICON, undoIcon);
+ this.putValue(SHORT_DESCRIPTION, "Undo an action");
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_Z, getDefaultToolkit()
+ .getMenuShortcutKeyMask()));
+ } else if (label.equals("Redo")) {
+ this.putValue(SMALL_ICON, redoIcon);
+ this.putValue(SHORT_DESCRIPTION, "Redo an action");
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_Y, getDefaultToolkit()
+ .getMenuShortcutKeyMask()));
+ }
+ editManager.addObserver(new EditManagerObserver());
+ updateStatus();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ WorkflowBundle workflowBundle = getCurrentDataflow();
+ if (workflowBundle != null)
+ performUndoOrRedo(workflowBundle);
+ }
+
+ /**
+ * Check if action should be enabled or disabled and update its status.
+ */
+ public void updateStatus() {
+ WorkflowBundle workflowBundle = getCurrentDataflow();
+ if (workflowBundle == null)
+ setEnabled(false);
+ setEnabled(isActive(workflowBundle));
+ }
+
+ /**
+ * Retrieve the current dataflow from the {@link ModelMap}, or
+ * <code>null</code> if no workflow is active.
+ *
+ * @return The current {@link Dataflow}
+ */
+ protected WorkflowBundle getCurrentDataflow() {
+ if (selectionManager == null)
+ return null;
+ return selectionManager.getSelectedWorkflowBundle();
+ }
+
+ /**
+ * Return <code>true</code> if the action should be enabled when the given
+ * {@link Dataflow} is the current, ie. if it's undoable or redoable.
+ *
+ * @param dataflow
+ * Current {@link Dataflow}
+ * @return <code>true</code> if the action should be enabled.
+ */
+ protected abstract boolean isActive(WorkflowBundle workflowBundle);
+
+ /**
+ * Called by {@link #actionPerformed(ActionEvent)} when the current dataflow
+ * is not <code>null</code>.
+ *
+ * @param dataflow
+ * {@link Dataflow} on which to undo or redo
+ */
+ protected abstract void performUndoOrRedo(WorkflowBundle workflowBundle);
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ if (selectionManager != null)
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ /**
+ * Update the status if there's been an edit done on the current workflow.
+ *
+ */
+ protected class EditManagerObserver implements Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (!(message instanceof AbstractDataflowEditEvent))
+ return;
+ AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+ if (dataflowEdit.getDataFlow().equals(dataflowEdit.getDataFlow()))
+ // It's an edit that could effect our undoability
+ updateStatus();
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof WorkflowBundleSelectionEvent)
+ updateStatus();
+ else if (message instanceof PerspectiveSelectionEvent) {
+ PerspectiveSelectionEvent perspectiveSelectionEvent = (PerspectiveSelectionEvent) message;
+ if (DESIGN_PERSPECTIVE_ID.equals(perspectiveSelectionEvent
+ .getSelectedPerspective().getID()))
+ updateStatus();
+ else
+ setEnabled(false);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
new file mode 100644
index 0000000..2abc139
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Redo the previous {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class RedoMenuAction extends AbstractMenuAction {
+ private static Logger logger = Logger.getLogger(RedoMenuAction.class);
+ private final EditManager editManager;
+ private SelectionManager selectionManager;
+ private AbstractUndoAction undoAction;
+
+ public RedoMenuAction(EditManager editManager) {
+ super(UNDO_SECTION_URI, 20);
+ this.editManager = editManager;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ undoAction = new AbstractUndoAction("Redo", editManager) {
+ @Override
+ protected boolean isActive(WorkflowBundle workflowBundle) {
+ return editManager.canRedoDataflowEdit(workflowBundle);
+ }
+
+ @Override
+ protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+ try {
+ editManager.redoDataflowEdit(workflowBundle);
+ } catch (EditException | RuntimeException e) {
+ logger.warn("Could not redo for " + workflowBundle, e);
+ showMessageDialog(null, "Could not redo for workflow "
+ + workflowBundle + ":\n" + e, "Could not redo",
+ ERROR_MESSAGE);
+ }
+ }
+ };
+ undoAction.setSelectionManager(selectionManager);
+ return undoAction;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ if (undoAction != null)
+ undoAction.setSelectionManager(selectionManager);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
new file mode 100644
index 0000000..e1242b3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Undo the last {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class UndoMenuAction extends AbstractMenuAction {
+ private static Logger logger = Logger.getLogger(UndoMenuAction.class);
+ private final EditManager editManager;
+ private SelectionManager selectionManager;
+ private AbstractUndoAction undoAction;
+
+ public UndoMenuAction(EditManager editManager) {
+ super(UNDO_SECTION_URI, 10);
+ this.editManager = editManager;
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ undoAction = new AbstractUndoAction("Undo", editManager) {
+ @Override
+ protected boolean isActive(WorkflowBundle workflowBundle) {
+ return editManager.canUndoDataflowEdit(workflowBundle);
+ }
+
+ @Override
+ protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+ try {
+ editManager.undoDataflowEdit(workflowBundle);
+ } catch (RuntimeException e) {
+ logger.warn("Could not undo for " + workflowBundle, e);
+ showMessageDialog(null, "Could not undo for workflow "
+ + workflowBundle + ":\n" + e, "Could not undo",
+ ERROR_MESSAGE);
+ }
+ }
+ };
+ undoAction.setSelectionManager(selectionManager);
+ return undoAction;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ if (undoAction != null)
+ undoAction.setSelectionManager(selectionManager);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
new file mode 100644
index 0000000..b83a650
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * A section of the Edit menu that contains {@link UndoMenuSection undo} and
+ * {@link RedoMenuAction redo}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class UndoMenuSection extends AbstractMenuSection {
+ public static final URI UNDO_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/edits#undoSection");
+ public static final URI EDIT_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#edit");
+
+ public UndoMenuSection() {
+ super(EDIT_MENU_URI, 10, UNDO_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
new file mode 100644
index 0000000..9eea85a
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class EditToolbarSection extends AbstractMenuSection {
+ public static final URI EDIT_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+
+ public EditToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 60, EDIT_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
new file mode 100644
index 0000000..09c0058
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction;
+
+public class RedoToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_REDO_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarRedo");
+ private final RedoMenuAction redoMenuAction;
+
+ public RedoToolbarAction(RedoMenuAction redoMenuAction) {
+ super(EDIT_TOOLBAR_SECTION, 20, EDIT_TOOLBAR_REDO_URI);
+ this.redoMenuAction = redoMenuAction;
+ }
+
+ @Override
+ protected Action createAction() {
+ return redoMenuAction.getAction();
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
new file mode 100644
index 0000000..8e31ed3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction;
+
+public class UndoToolbarAction extends AbstractMenuAction {
+ private static final URI EDIT_TOOLBAR_UNDO_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarUndo");
+ private final UndoMenuAction undoMenuAction;
+
+ public UndoToolbarAction(UndoMenuAction undoMenuAction) {
+ super(EDIT_TOOLBAR_SECTION, 10, EDIT_TOOLBAR_UNDO_URI);
+ this.undoMenuAction = undoMenuAction;
+ }
+
+ @Override
+ protected Action createAction() {
+ return undoMenuAction.getAction();
+ }
+}
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6938308
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,6 @@
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection
+net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
new file mode 100644
index 0000000..92ee088
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
new file mode 100644
index 0000000..8eb7041
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UndoMenuSection" auto-export="interfaces" />
+ <service ref="UndoMenuAction" auto-export="interfaces" />
+ <service ref="RedoMenuAction" auto-export="interfaces" />
+ <service ref="EditToolbarSection" auto-export="interfaces" />
+ <service ref="UndoToolbarAction" auto-export="interfaces" />
+ <service ref="RedoToolbarAction" auto-export="interfaces" />
+
+ <service ref="EditManagerImpl" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" cardinality="0..1" />
+
+</beans:beans>
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
new file mode 100644
index 0000000..33f0b7b
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UndoMenuSection" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection" />
+ <bean id="UndoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction">
+ <constructor-arg name="editManager">
+ <ref local="EditManagerImpl" />
+ </constructor-arg>
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="RedoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction">
+ <constructor-arg name="editManager">
+ <ref local="EditManagerImpl" />
+ </constructor-arg>
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+ <bean id="EditToolbarSection" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection" />
+ <bean id="UndoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction">
+ <constructor-arg>
+ <ref local="UndoMenuAction" />
+ </constructor-arg>
+ </bean>
+ <bean id="RedoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction">
+ <constructor-arg>
+ <ref local="RedoMenuAction" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="EditManagerImpl" class="net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl" />
+
+</beans>
diff --git a/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
new file mode 100644
index 0000000..9123671
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class TestEditManagerImpl {
+
+ private Workflow dataflow;
+
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+
+ private Processor processor;
+
+ @Test
+ public void addProcessor() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+ assertFalse("Edit was already applied", edit.isApplied());
+ assertTrue("Did already add processor", dataflow.getProcessors()
+ .isEmpty());
+
+ editManager.doDataflowEdit(dataflow.getParent(), edit);
+ assertTrue("Edit was not applied", edit.isApplied());
+ assertEquals("Did not add processor", processor, dataflow.getProcessors().first());
+
+ // Should have received the edit event
+ assertEquals("Incorrect number of events", 1,
+ editManagerObserver.events.size());
+ EditManagerEvent event = editManagerObserver.events.get(0);
+ assertTrue("Event was not a DataflowEditEvent",
+ event instanceof DataflowEditEvent);
+ DataflowEditEvent dataEditEvent = (DataflowEditEvent) event;
+ assertEquals("Event did not have correct workflow", dataflow,
+ dataEditEvent.getDataFlow().getWorkflows().first());
+ assertEquals("Event did not have correct edit", edit, dataEditEvent
+ .getEdit());
+
+ }
+
+ @Test
+ public void undoAddProcessor() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+ editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+ assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+ editManager.undoDataflowEdit(dataflow.getParent());
+ assertTrue("Did not undo add processor", dataflow.getProcessors()
+ .isEmpty());
+
+ // Should have received the undo event
+ assertEquals("Incorrect number of events", 2,
+ editManagerObserver.events.size());
+ EditManagerEvent event = editManagerObserver.events.get(1);
+ assertTrue("Event was not a DataflowEditEvent",
+ event instanceof DataFlowUndoEvent);
+ DataFlowUndoEvent dataEditEvent = (DataFlowUndoEvent) event;
+ assertEquals("Event did not have correct workflow", dataflow,
+ dataEditEvent.getDataFlow().getWorkflows().first());
+ assertEquals("Event did not have correct edit", edit, dataEditEvent
+ .getEdit());
+ assertFalse("Edit was still applied", edit.isApplied());
+ }
+
+ @Test
+ public void multipleUndoesRedoes() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Workflow dataflowA = createDataflow();
+ Workflow dataflowB = createDataflow();
+ Workflow dataflowC = createDataflow();
+
+ Processor processorA1 = createProcessor();
+ Processor processorA2 = createProcessor();
+ Processor processorA3 = createProcessor();
+ Processor processorB1 = createProcessor();
+ Processor processorC1 = createProcessor();
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflowA, processorA1);
+ editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowB, processorB1);
+ editManager.doDataflowEdit(dataflowB.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowA, processorA2);
+ editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowC, processorC1);
+ editManager.doDataflowEdit(dataflowC.getParent(), edit);
+
+ edit = new AddProcessorEdit(dataflowA, processorA3);
+ editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+
+
+ assertFalse("Did not add processors", dataflowA.getProcessors().isEmpty());
+ assertEquals(3, dataflowA.getProcessors().size());
+ editManager.undoDataflowEdit(dataflowA.getParent());
+ assertEquals(2, dataflowA.getProcessors().size());
+ editManager.undoDataflowEdit(dataflowA.getParent());
+ assertEquals(1, dataflowA.getProcessors().size());
+ editManager.undoDataflowEdit(dataflowA.getParent());
+ assertEquals(0, dataflowA.getProcessors().size());
+
+ assertEquals(1, dataflowB.getProcessors().size());
+ assertEquals(1, dataflowC.getProcessors().size());
+
+ assertTrue(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+ editManager.undoDataflowEdit(dataflowC.getParent());
+ assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+ editManager.undoDataflowEdit(dataflowC.getParent()); // extra one
+ assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+
+
+ assertEquals(1, dataflowB.getProcessors().size());
+ assertEquals(0, dataflowC.getProcessors().size());
+
+ editManager.undoDataflowEdit(dataflowB.getParent());
+ assertEquals(0, dataflowA.getProcessors().size());
+ assertEquals(0, dataflowB.getProcessors().size());
+ assertEquals(0, dataflowC.getProcessors().size());
+
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(1, dataflowA.getProcessors().size());
+
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(2, dataflowA.getProcessors().size());
+
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(3, dataflowA.getProcessors().size());
+
+ // does not affect it
+ editManager.redoDataflowEdit(dataflowA.getParent());
+ assertEquals(3, dataflowA.getProcessors().size());
+ assertEquals(0, dataflowB.getProcessors().size());
+ assertEquals(0, dataflowC.getProcessors().size());
+ }
+
+ @Test
+ public void emptyUndoDoesNotFail() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+ editManager.undoDataflowEdit(dataflow.getParent());
+ }
+
+ @Test
+ public void extraUndoesDoesNotFail() throws Exception {
+ EditManager editManager = new EditManagerImpl();
+ editManager.addObserver(editManagerObserver);
+
+ Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+ editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+ assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+ editManager.undoDataflowEdit(dataflow.getParent());
+ assertTrue("Did not undo add processor", dataflow.getProcessors()
+ .isEmpty());
+ editManager.undoDataflowEdit(dataflow.getParent());
+ }
+
+ @Before
+ public void makeDataflow() {
+ dataflow = createDataflow();
+ }
+
+ protected Workflow createDataflow() {
+ WorkflowBundle workflowBundle = new WorkflowBundle();
+ Workflow workflow = new Workflow();
+ workflow.setParent(workflowBundle);
+ return workflow;
+ }
+
+ protected Processor createProcessor() {
+ Processor processor = new Processor();
+ processor.setName("proc-" + UUID.randomUUID());
+ return processor;
+ }
+
+ @Before
+ public void makeProcessor() {
+ processor = createProcessor();
+ }
+
+ private class EditManagerObserver implements Observer<EditManagerEvent> {
+
+ public List<EditManagerEvent> events = new ArrayList<>();
+
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ events.add(message);
+ if (message instanceof DataflowEditEvent) {
+ DataflowEditEvent dataflowEdit = (DataflowEditEvent) message;
+ assertTrue("Edit was not applied on edit event", dataflowEdit
+ .getEdit().isApplied());
+ } else if (message instanceof DataFlowUndoEvent) {
+ DataFlowUndoEvent dataflowUndo = (DataFlowUndoEvent) message;
+ assertFalse("Edit was applied on undo event", dataflowUndo
+ .getEdit().isApplied());
+ } else if (message instanceof DataFlowRedoEvent) {
+ DataFlowRedoEvent dataflowEdit = (DataFlowRedoEvent) message;
+ assertTrue("Edit was not applied on edit event", dataflowEdit
+ .getEdit().isApplied());
+ } else {
+ fail("Unknown event: " + message);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-file-impl/pom.xml b/taverna-workbench-file-impl/pom.xml
new file mode 100644
index 0000000..98dcce7
--- /dev/null
+++ b/taverna-workbench-file-impl/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>file-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>File opening implementation</name>
+ <description>
+ Implementation for doing file (i.e. workflow) open/save in the
+ workbench.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>com.springsource.org.apache.commons.lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>edits-impl</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-t2flow</artifactId>
+ <version>${scufl2.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-rdfxml</artifactId>
+ <version>${scufl2.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
new file mode 100644
index 0000000..86bc091
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowFromDataflowPersistenceHandler extends
+ AbstractDataflowPersistenceHandler implements
+ DataflowPersistenceHandler {
+ private static final WorkflowBundleFileType WORKFLOW_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (!getOpenFileTypes().contains(fileType))
+ throw new IllegalArgumentException("Unsupported file type "
+ + fileType);
+
+ WorkflowBundle workflowBundle = (WorkflowBundle) source;
+ Date lastModified = null;
+ Object canonicalSource = null;
+ return new DataflowInfo(WORKFLOW_BUNDLE_FILE_TYPE, canonicalSource,
+ workflowBundle, lastModified);
+ }
+
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Arrays.<FileType> asList(WORKFLOW_BUNDLE_FILE_TYPE);
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Arrays.<Class<?>> asList(Workflow.class);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
new file mode 100644
index 0000000..39117e9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static org.apache.commons.collections.map.LazyMap.decorate;
+import static org.apache.commons.lang.ClassUtils.getAllInterfaces;
+import static org.apache.commons.lang.ClassUtils.getAllSuperclasses;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.commons.collections.Factory;
+
+// TODO: Cache lookups / build one massive structure
+public class DataflowPersistenceHandlerRegistry {
+ private static final MapFactory MAP_FACTORY = new MapFactory();
+ private static final SetFactory SET_FACTORY = new SetFactory();
+
+ @SuppressWarnings("unchecked")
+ protected static List<Class<?>> findAllParentClasses(
+ final Class<?> sourceClass) {
+ List<Class<?>> superClasses = new ArrayList<>();
+ superClasses.add(sourceClass);
+ superClasses.addAll(getAllSuperclasses(sourceClass));
+ superClasses.addAll(getAllInterfaces(sourceClass));
+ return superClasses;
+ }
+
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> openClassToHandlers;
+ private Map<Class<?>, Set<FileType>> openClassToTypes;
+ private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> openFileClassToHandler;
+ private Map<FileType, Set<DataflowPersistenceHandler>> openFileToHandler;
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> saveClassToHandlers;
+ private Map<Class<?>, Set<FileType>> saveClassToTypes;
+ private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> saveFileClassToHandler;
+ private Map<FileType, Set<DataflowPersistenceHandler>> saveFileToHandler;
+
+ private List<DataflowPersistenceHandler> dataflowPersistenceHandlers;
+
+ public DataflowPersistenceHandlerRegistry() {
+ }
+
+ public Set<FileType> getOpenFileTypes() {
+ return getOpenFileClassToHandler().keySet();
+ }
+
+ public Set<FileType> getOpenFileTypesFor(Class<?> sourceClass) {
+ Set<FileType> fileTypes = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ fileTypes.addAll(getOpenClassToTypes().get(candidateClass));
+ return fileTypes;
+ }
+
+ public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+ Class<? extends Object> sourceClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ handlers.addAll(getOpenClassToHandlers().get(candidateClass));
+ return handlers;
+ }
+
+ public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+ FileType fileType, Class<? extends Object> sourceClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+ candidateClass));
+ return handlers;
+ }
+
+ public Set<DataflowPersistenceHandler> getOpenHandlersForType(
+ FileType fileType) {
+ return getOpenFileToHandler().get(fileType);
+ }
+
+ public synchronized Set<DataflowPersistenceHandler> getOpenHandlersForType(
+ FileType fileType, Class<?> sourceClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+ handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+ candidateClass));
+ return handlers;
+ }
+
+ public Set<FileType> getSaveFileTypes() {
+ return getSaveFileClassToHandler().keySet();
+ }
+
+ public Set<FileType> getSaveFileTypesFor(Class<?> destinationClass) {
+ Set<FileType> fileTypes = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+ fileTypes.addAll(getSaveClassToTypes().get(candidateClass));
+ return fileTypes;
+ }
+
+ public Set<DataflowPersistenceHandler> getSaveHandlersFor(
+ Class<? extends Object> destinationClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+ handlers.addAll(getSaveClassToHandlers().get(candidateClass));
+ return handlers;
+ }
+
+ public Set<DataflowPersistenceHandler> getSaveHandlersForType(
+ FileType fileType, Class<?> destinationClass) {
+ Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+ for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+ handlers.addAll(getSaveFileClassToHandler().get(fileType).get(
+ candidateClass));
+ return handlers;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private synchronized void createCollections() {
+ openFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+ openFileToHandler = decorate(new HashMap(), SET_FACTORY);
+ openClassToTypes = decorate(new HashMap(), SET_FACTORY);
+ openClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+
+ saveFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+ saveFileToHandler = decorate(new HashMap(), SET_FACTORY);
+ saveClassToTypes = decorate(new HashMap(), SET_FACTORY);
+ saveClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+ }
+
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> getOpenClassToHandlers() {
+ return openClassToHandlers;
+ }
+
+ private synchronized Map<Class<?>, Set<FileType>> getOpenClassToTypes() {
+ return openClassToTypes;
+ }
+
+ private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getOpenFileClassToHandler() {
+ return openFileClassToHandler;
+ }
+
+ private Map<FileType, Set<DataflowPersistenceHandler>> getOpenFileToHandler() {
+ return openFileToHandler;
+ }
+
+ private Map<Class<?>, Set<DataflowPersistenceHandler>> getSaveClassToHandlers() {
+ return saveClassToHandlers;
+ }
+
+ private synchronized Map<Class<?>, Set<FileType>> getSaveClassToTypes() {
+ return saveClassToTypes;
+ }
+
+ private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getSaveFileClassToHandler() {
+ return saveFileClassToHandler;
+ }
+
+ /**
+ * Bind method for SpringDM.
+ *
+ * @param service
+ * @param properties
+ */
+ public void update(Object service, Map<?, ?> properties) {
+ if (dataflowPersistenceHandlers != null)
+ updateColletions();
+ }
+
+ public synchronized void updateColletions() {
+ createCollections();
+ for (DataflowPersistenceHandler handler : dataflowPersistenceHandlers) {
+ for (FileType openFileType : handler.getOpenFileTypes()) {
+ Set<DataflowPersistenceHandler> set = openFileToHandler
+ .get(openFileType);
+ set.add(handler);
+ for (Class<?> openClass : handler.getOpenSourceTypes()) {
+ openFileClassToHandler.get(openFileType).get(openClass)
+ .add(handler);
+ openClassToTypes.get(openClass).add(openFileType);
+ }
+ }
+ for (Class<?> openClass : handler.getOpenSourceTypes())
+ openClassToHandlers.get(openClass).add(handler);
+
+ for (FileType saveFileType : handler.getSaveFileTypes()) {
+ saveFileToHandler.get(saveFileType).add(handler);
+ for (Class<?> saveClass : handler.getSaveDestinationTypes()) {
+ saveFileClassToHandler.get(saveFileType).get(saveClass)
+ .add(handler);
+ saveClassToTypes.get(saveClass).add(saveFileType);
+ }
+ }
+ for (Class<?> openClass : handler.getSaveDestinationTypes())
+ saveClassToHandlers.get(openClass).add(handler);
+ }
+ }
+
+ public void setDataflowPersistenceHandlers(
+ List<DataflowPersistenceHandler> dataflowPersistenceHandlers) {
+ this.dataflowPersistenceHandlers = dataflowPersistenceHandlers;
+ }
+
+ private static class MapFactory implements Factory {
+ @Override
+ @SuppressWarnings("rawtypes")
+ public Object create() {
+ return decorate(new HashMap(), SET_FACTORY);
+ }
+ }
+
+ private static class SetFactory implements Factory {
+ @Override
+ public Object create() {
+ return new LinkedHashSet<Object>();
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
new file mode 100644
index 0000000..89ae39c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Information about an open dataflow that was opened from or saved to a
+ * {@link File}.
+ *
+ * @see DataflowInfo
+ * @see FileManager
+ * @author Stian Soiland-Reyes
+ */
+public class FileDataflowInfo extends DataflowInfo {
+ private static Logger logger = Logger.getLogger(FileDataflowInfo.class);
+
+ public FileDataflowInfo(FileType fileType, File source,
+ WorkflowBundle workflowBundle) {
+ super(fileType, canonicalFile(source), workflowBundle,
+ lastModifiedFile(source));
+ }
+
+ protected static Date lastModifiedFile(File file) {
+ long lastModifiedLong = file.lastModified();
+ if (lastModifiedLong == 0)
+ return null;
+ return new Date(lastModifiedLong);
+ }
+
+ public static File canonicalFile(File file) {
+ try {
+ return file.getCanonicalFile();
+ } catch (IOException e) {
+ logger.warn("Could not find canonical file for " + file);
+ return file;
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
new file mode 100644
index 0000000..aadb3f1
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileManagerImpl.java
@@ -0,0 +1,601 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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.workbench.file.impl;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+import static java.util.Collections.singleton;
+import static javax.swing.SwingUtilities.invokeAndWait;
+import static javax.swing.SwingUtilities.isEventDispatchThread;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.ClosingDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Implementation of {@link FileManager}
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class FileManagerImpl implements FileManager {
+ private static Logger logger = Logger.getLogger(FileManagerImpl.class);
+ private static int nameIndex = 1;
+
+ /**
+ * The last blank workflowBundle created using #newDataflow() until it has
+ * been changed - when this variable will be set to null again. Used to
+ * automatically close unmodified blank workflowBundles on open.
+ */
+ private WorkflowBundle blankWorkflowBundle = null;
+ @SuppressWarnings("unused")
+ private EditManager editManager;
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ protected MultiCaster<FileManagerEvent> observers = new MultiCaster<>(this);
+ /**
+ * Ordered list of open WorkflowBundle
+ */
+ private LinkedHashMap<WorkflowBundle, OpenDataflowInfo> openDataflowInfos = new LinkedHashMap<>();
+ private DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry;
+ private Scufl2Tools scufl2Tools = new Scufl2Tools();
+ private WorkflowBundle currentWorkflowBundle;
+
+ public DataflowPersistenceHandlerRegistry getPersistanceHandlerRegistry() {
+ return dataflowPersistenceHandlerRegistry;
+ }
+
+ public FileManagerImpl(EditManager editManager) {
+ this.editManager = editManager;
+ editManager.addObserver(editManagerObserver);
+ }
+
+ /**
+ * Add an observer to be notified of {@link FileManagerEvent}s, such as
+ * {@link OpenedDataflowEvent} and {@link SavedDataflowEvent}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void addObserver(Observer<FileManagerEvent> observer) {
+ observers.addObserver(observer);
+ }
+
+ @Override
+ public boolean canSaveWithoutDestination(WorkflowBundle workflowBundle) {
+ OpenDataflowInfo dataflowInfo = getOpenDataflowInfo(workflowBundle);
+ if (dataflowInfo.getSource() == null)
+ return false;
+ Set<?> handlers = getPersistanceHandlerRegistry()
+ .getSaveHandlersForType(
+ dataflowInfo.getFileType(),
+ dataflowInfo.getDataflowInfo().getCanonicalSource()
+ .getClass());
+ return !handlers.isEmpty();
+ }
+
+ @Override
+ public boolean closeDataflow(WorkflowBundle workflowBundle,
+ boolean failOnUnsaved) throws UnsavedException {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ ClosingDataflowEvent message = new ClosingDataflowEvent(workflowBundle);
+ observers.notify(message);
+ if (message.isAbortClose())
+ return false;
+ if ((failOnUnsaved && getOpenDataflowInfo(workflowBundle).isChanged()))
+ throw new UnsavedException(workflowBundle);
+ if (workflowBundle.equals(getCurrentDataflow())) {
+ // We'll need to change current workflowBundle
+ // Find best candidate to the left or right
+ List<WorkflowBundle> workflowBundles = getOpenDataflows();
+ int openIndex = workflowBundles.indexOf(workflowBundle);
+ if (openIndex == -1)
+ throw new IllegalArgumentException("Workflow was not opened "
+ + workflowBundle);
+
+ if (openIndex > 0)
+ setCurrentDataflow(workflowBundles.get(openIndex - 1));
+ else if (openIndex == 0 && workflowBundles.size() > 1)
+ setCurrentDataflow(workflowBundles.get(1));
+ else
+ // If it was the last one, start a new, empty workflowBundle
+ newDataflow();
+ }
+ if (workflowBundle == blankWorkflowBundle)
+ blankWorkflowBundle = null;
+ openDataflowInfos.remove(workflowBundle);
+ observers.notify(new ClosedDataflowEvent(workflowBundle));
+ return true;
+ }
+
+ @Override
+ public WorkflowBundle getCurrentDataflow() {
+ return currentWorkflowBundle;
+ }
+
+ @Override
+ public WorkflowBundle getDataflowBySource(Object source) {
+ for (Entry<WorkflowBundle, OpenDataflowInfo> infoEntry : openDataflowInfos
+ .entrySet()) {
+ OpenDataflowInfo info = infoEntry.getValue();
+ if (source.equals(info.getSource()))
+ return infoEntry.getKey();
+ }
+ // Not found
+ return null;
+ }
+
+ @Override
+ public String getDataflowName(WorkflowBundle workflowBundle) {
+ Object source = null;
+ if (isDataflowOpen(workflowBundle))
+ source = getDataflowSource(workflowBundle);
+ // Fallback
+ String name;
+ Workflow workflow = workflowBundle.getMainWorkflow();
+ if (workflow != null)
+ name = workflow.getName();
+ else
+ name = workflowBundle.getName();
+ if (source == null)
+ return name;
+ if (source instanceof File)
+ return ((File) source).getAbsolutePath();
+ else if (source instanceof URL)
+ return source.toString();
+
+ // Check if it has implemented a toString() method
+ Method toStringMethod = null;
+ Method toStringMethodFromObject = null;
+ try {
+ toStringMethod = source.getClass().getMethod("toString");
+ toStringMethodFromObject = Object.class.getMethod("toString");
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Source did not implement Object.toString() " + source);
+ }
+ if (!toStringMethod.equals(toStringMethodFromObject))
+ return source.toString();
+ return name;
+ }
+
+ @Override
+ public String getDefaultWorkflowName() {
+ return "Workflow" + (nameIndex++);
+ }
+
+ @Override
+ public Object getDataflowSource(WorkflowBundle workflowBundle) {
+ return getOpenDataflowInfo(workflowBundle).getSource();
+ }
+
+ @Override
+ public FileType getDataflowType(WorkflowBundle workflowBundle) {
+ return getOpenDataflowInfo(workflowBundle).getFileType();
+ }
+
+ @Override
+ public List<Observer<FileManagerEvent>> getObservers() {
+ return observers.getObservers();
+ }
+
+ /**
+ * Get the {@link OpenDataflowInfo} for the given WorkflowBundle
+ *
+ * @throws NullPointerException
+ * if the WorkflowBundle was <code>null</code>
+ * @throws IllegalArgumentException
+ * if the WorkflowBundle was not open.
+ * @param workflowBundle
+ * WorkflowBundle which information is to be found
+ * @return The {@link OpenDataflowInfo} describing the WorkflowBundle
+ */
+ protected synchronized OpenDataflowInfo getOpenDataflowInfo(
+ WorkflowBundle workflowBundle) {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ OpenDataflowInfo info = openDataflowInfos.get(workflowBundle);
+ if (info == null)
+ throw new IllegalArgumentException("Workflow was not opened "
+ + workflowBundle);
+ return info;
+ }
+
+ @Override
+ public List<WorkflowBundle> getOpenDataflows() {
+ return new ArrayList<>(openDataflowInfos.keySet());
+ }
+
+ @Override
+ public List<FileFilter> getOpenFileFilters() {
+ List<FileFilter> fileFilters = new ArrayList<>();
+
+ Set<FileType> fileTypes = getPersistanceHandlerRegistry()
+ .getOpenFileTypes();
+ if (!fileTypes.isEmpty())
+ fileFilters.add(new MultipleFileTypes(fileTypes,
+ "All supported workflows"));
+ for (FileType fileType : fileTypes)
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public List<FileFilter> getOpenFileFilters(Class<?> sourceClass) {
+ List<FileFilter> fileFilters = new ArrayList<>();
+ for (FileType fileType : getPersistanceHandlerRegistry()
+ .getOpenFileTypesFor(sourceClass))
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public List<FileFilter> getSaveFileFilters() {
+ List<FileFilter> fileFilters = new ArrayList<>();
+ for (FileType fileType : getPersistanceHandlerRegistry()
+ .getSaveFileTypes())
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public List<FileFilter> getSaveFileFilters(Class<?> destinationClass) {
+ List<FileFilter> fileFilters = new ArrayList<>();
+ for (FileType fileType : getPersistanceHandlerRegistry()
+ .getSaveFileTypesFor(destinationClass))
+ fileFilters.add(new FileTypeFileFilter(fileType));
+ return fileFilters;
+ }
+
+ @Override
+ public boolean isDataflowChanged(WorkflowBundle workflowBundle) {
+ return getOpenDataflowInfo(workflowBundle).isChanged();
+ }
+
+ @Override
+ public boolean isDataflowOpen(WorkflowBundle workflowBundle) {
+ return openDataflowInfos.containsKey(workflowBundle);
+ }
+
+ @Override
+ public WorkflowBundle newDataflow() {
+ WorkflowBundle workflowBundle = new WorkflowBundle();
+ workflowBundle.setMainWorkflow(new Workflow());
+ workflowBundle.getMainWorkflow().setName(getDefaultWorkflowName());
+ workflowBundle.setMainProfile(new Profile());
+ scufl2Tools.setParents(workflowBundle);
+ blankWorkflowBundle = null;
+ openDataflowInternal(workflowBundle);
+ blankWorkflowBundle = workflowBundle;
+ observers.notify(new OpenedDataflowEvent(workflowBundle));
+ return workflowBundle;
+ }
+
+ @Override
+ public void openDataflow(WorkflowBundle workflowBundle) {
+ openDataflowInternal(workflowBundle);
+ observers.notify(new OpenedDataflowEvent(workflowBundle));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WorkflowBundle openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (isHeadless())
+ return performOpenDataflow(fileType, source);
+
+ OpenDataflowRunnable r = new OpenDataflowRunnable(this, fileType,
+ source);
+ if (isEventDispatchThread()) {
+ r.run();
+ } else
+ try {
+ invokeAndWait(r);
+ } catch (InterruptedException | InvocationTargetException e) {
+ throw new OpenException("Opening was interrupted", e);
+ }
+ OpenException thrownException = r.getException();
+ if (thrownException != null)
+ throw thrownException;
+ return r.getDataflow();
+ }
+
+ public WorkflowBundle performOpenDataflow(FileType fileType, Object source)
+ throws OpenException {
+ DataflowInfo dataflowInfo;
+ WorkflowBundle workflowBundle;
+ dataflowInfo = openDataflowSilently(fileType, source);
+ workflowBundle = dataflowInfo.getDataflow();
+ openDataflowInternal(workflowBundle);
+ getOpenDataflowInfo(workflowBundle).setOpenedFrom(dataflowInfo);
+ observers.notify(new OpenedDataflowEvent(workflowBundle));
+ return workflowBundle;
+ }
+
+ @Override
+ public DataflowInfo openDataflowSilently(FileType fileType, Object source)
+ throws OpenException {
+ Set<DataflowPersistenceHandler> handlers;
+ Class<? extends Object> sourceClass = source.getClass();
+
+ boolean unknownFileType = (fileType == null);
+ if (unknownFileType)
+ handlers = getPersistanceHandlerRegistry().getOpenHandlersFor(
+ sourceClass);
+ else
+ handlers = getPersistanceHandlerRegistry().getOpenHandlersFor(
+ fileType, sourceClass);
+ if (handlers.isEmpty())
+ throw new OpenException("Unsupported file type or class "
+ + fileType + " " + sourceClass);
+
+ Throwable lastException = null;
+ for (DataflowPersistenceHandler handler : handlers) {
+ Collection<FileType> fileTypes;
+ if (unknownFileType)
+ fileTypes = handler.getOpenFileTypes();
+ else
+ fileTypes = singleton(fileType);
+ for (FileType candidateFileType : fileTypes) {
+ if (unknownFileType && (source instanceof File))
+ /*
+ * If source is file but fileType was not explicitly set
+ * from the open workflow dialog - check the file extension
+ * and decide which handler to use based on that (so that we
+ * do not loop though all handlers)
+ */
+ if (!((File) source).getPath().endsWith(
+ candidateFileType.getExtension()))
+ continue;
+
+ try {
+ DataflowInfo openDataflow = handler.openDataflow(
+ candidateFileType, source);
+ WorkflowBundle workflowBundle = openDataflow.getDataflow();
+ logger.info("Loaded workflow: " + workflowBundle.getName()
+ + " " + workflowBundle.getGlobalBaseURI()
+ + " from " + source + " using " + handler);
+ return openDataflow;
+ } catch (OpenException ex) {
+ logger.warn("Could not open workflow " + source + " using "
+ + handler + " of type " + candidateFileType);
+ lastException = ex;
+ }
+ }
+ }
+ throw new OpenException("Could not open workflow " + source + "\n",
+ lastException);
+ }
+
+ /**
+ * Mark the WorkflowBundle as opened, and close the blank WorkflowBundle if
+ * needed.
+ *
+ * @param workflowBundle
+ * WorkflowBundle that has been opened
+ */
+ protected void openDataflowInternal(WorkflowBundle workflowBundle) {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ if (isDataflowOpen(workflowBundle))
+ throw new IllegalArgumentException("Workflow is already open: "
+ + workflowBundle);
+
+ openDataflowInfos.put(workflowBundle, new OpenDataflowInfo());
+ setCurrentDataflow(workflowBundle);
+ if (openDataflowInfos.size() == 2 && blankWorkflowBundle != null)
+ /*
+ * Behave like a word processor and close the blank WorkflowBundle
+ * when another workflow has been opened
+ */
+ try {
+ closeDataflow(blankWorkflowBundle, true);
+ } catch (UnsavedException e) {
+ logger.error("Blank workflow was modified "
+ + "and could not be closed");
+ }
+ }
+
+ @Override
+ public void removeObserver(Observer<FileManagerEvent> observer) {
+ observers.removeObserver(observer);
+ }
+
+ @Override
+ public void saveDataflow(WorkflowBundle workflowBundle,
+ boolean failOnOverwrite) throws SaveException {
+ if (workflowBundle == null)
+ throw new NullPointerException("Dataflow can't be null");
+ OpenDataflowInfo lastSave = getOpenDataflowInfo(workflowBundle);
+ if (lastSave.getSource() == null)
+ throw new SaveException("Can't save without source "
+ + workflowBundle);
+ saveDataflow(workflowBundle, lastSave.getFileType(),
+ lastSave.getSource(), failOnOverwrite);
+ }
+
+ @Override
+ public void saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination, boolean failOnOverwrite) throws SaveException {
+ DataflowInfo savedDataflow = saveDataflowSilently(workflowBundle,
+ fileType, destination, failOnOverwrite);
+ getOpenDataflowInfo(workflowBundle).setSavedTo(savedDataflow);
+ observers.notify(new SavedDataflowEvent(workflowBundle));
+ }
+
+ @Override
+ public DataflowInfo saveDataflowSilently(WorkflowBundle workflowBundle,
+ FileType fileType, Object destination, boolean failOnOverwrite)
+ throws SaveException, OverwriteException {
+ Set<DataflowPersistenceHandler> handlers;
+
+ Class<? extends Object> destinationClass = destination.getClass();
+ if (fileType != null)
+ handlers = getPersistanceHandlerRegistry().getSaveHandlersForType(
+ fileType, destinationClass);
+ else
+ handlers = getPersistanceHandlerRegistry().getSaveHandlersFor(
+ destinationClass);
+
+ SaveException lastException = null;
+ for (DataflowPersistenceHandler handler : handlers) {
+ if (failOnOverwrite) {
+ OpenDataflowInfo openDataflowInfo = getOpenDataflowInfo(workflowBundle);
+ if (handler.wouldOverwriteDataflow(workflowBundle, fileType,
+ destination, openDataflowInfo.getDataflowInfo()))
+ throw new OverwriteException(destination);
+ }
+ try {
+ DataflowInfo savedDataflow = handler.saveDataflow(
+ workflowBundle, fileType, destination);
+ savedDataflow.getDataflow();
+ logger.info("Saved workflow: " + workflowBundle.getName() + " "
+ + workflowBundle.getGlobalBaseURI() + " to "
+ + savedDataflow.getCanonicalSource() + " using "
+ + handler);
+ return savedDataflow;
+ } catch (SaveException ex) {
+ logger.warn("Could not save to " + destination + " using "
+ + handler);
+ lastException = ex;
+ }
+ }
+
+ if (lastException == null)
+ throw new SaveException("Unsupported file type or class "
+ + fileType + " " + destinationClass);
+ throw new SaveException("Could not save to " + destination + ":\n"
+ + lastException.getLocalizedMessage(), lastException);
+ }
+
+ @Override
+ public void setCurrentDataflow(WorkflowBundle workflowBundle) {
+ setCurrentDataflow(workflowBundle, false);
+ }
+
+ @Override
+ public void setCurrentDataflow(WorkflowBundle workflowBundle,
+ boolean openIfNeeded) {
+ currentWorkflowBundle = workflowBundle;
+ if (!isDataflowOpen(workflowBundle)) {
+ if (!openIfNeeded)
+ throw new IllegalArgumentException("Workflow is not open: "
+ + workflowBundle);
+ openDataflow(workflowBundle);
+ return;
+ }
+ observers.notify(new SetCurrentDataflowEvent(workflowBundle));
+ }
+
+ @Override
+ public void setDataflowChanged(WorkflowBundle workflowBundle,
+ boolean isChanged) {
+ getOpenDataflowInfo(workflowBundle).setIsChanged(isChanged);
+ if (blankWorkflowBundle == workflowBundle)
+ blankWorkflowBundle = null;
+ }
+
+ @Override
+ public Object getCanonical(Object source) throws IllegalArgumentException,
+ URISyntaxException, IOException {
+ Object canonicalSource = source;
+
+ if (source instanceof URL) {
+ URL url = ((URL) source);
+ if (url.getProtocol().equalsIgnoreCase("file"))
+ canonicalSource = new File(url.toURI());
+ }
+
+ if (canonicalSource instanceof File)
+ canonicalSource = ((File) canonicalSource).getCanonicalFile();
+ return canonicalSource;
+ }
+
+ public void setDataflowPersistenceHandlerRegistry(
+ DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry) {
+ this.dataflowPersistenceHandlerRegistry = dataflowPersistenceHandlerRegistry;
+ }
+
+ /**
+ * Observe the {@link EditManager} for changes to open workflowBundles. A
+ * change of an open workflow would set it as changed using
+ * {@link FileManagerImpl#setDataflowChanged(Dataflow, boolean)}.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ */
+ private final class EditManagerObserver implements
+ Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent) {
+ AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+ WorkflowBundle workflowBundle = dataflowEdit.getDataFlow();
+ /**
+ * TODO: on undo/redo - keep last event or similar to determine
+ * if workflow was saved before. See
+ * FileManagerTest#isChangedWithUndo().
+ */
+ setDataflowChanged(workflowBundle, true);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
new file mode 100644
index 0000000..6416163
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileTypeFileFilter.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class FileTypeFileFilter extends FileFilter {
+ private final FileType fileType;
+
+ public FileTypeFileFilter(FileType fileType) {
+ this.fileType = fileType;
+ }
+
+ @Override
+ public String getDescription() {
+ return fileType.getDescription();
+ }
+
+ @Override
+ public boolean accept(File file) {
+ if (file.isDirectory())
+ // Don't grey out directories
+ return true;
+ if (fileType.getExtension() == null)
+ return false;
+ return file.getName().toLowerCase()
+ .endsWith("." + fileType.getExtension());
+ }
+
+ public FileType getFileType() {
+ return fileType;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
new file mode 100644
index 0000000..c398805
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/MultipleFileTypes.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.util.Set;
+
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class MultipleFileTypes extends FileFilter {
+ private String description;
+ private final Set<FileType> fileTypes;
+
+ public MultipleFileTypes(Set<FileType> fileTypes, String description) {
+ this.fileTypes = fileTypes;
+ this.description = description;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public boolean accept(File file) {
+ if (file.isDirectory())
+ return true;
+
+ String lowerFileName = file.getName().toLowerCase();
+ for (FileType fileType : fileTypes) {
+ if (fileType.getExtension() == null)
+ continue;
+ if (lowerFileName.endsWith(fileType.getExtension()))
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
new file mode 100644
index 0000000..dc08cff
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInProgressDialog.java
@@ -0,0 +1,88 @@
+
+/*******************************************************************************
+ * 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.workbench.file.impl;
+
+import static java.awt.BorderLayout.CENTER;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.workingIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * Dialog that is popped up while we are opening a workflow.
+ *
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class OpenDataflowInProgressDialog extends HelpEnabledDialog {
+ private boolean userCancelled = false;
+
+ public OpenDataflowInProgressDialog() {
+ super(getMainWindow(), "Opening workflow", true);
+ setResizable(false);
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(new EmptyBorder(10,10,10,10));
+
+ JPanel textPanel = new JPanel();
+ JLabel text = new JLabel(workingIcon);
+ text.setText("Opening workflow...");
+ text.setBorder(new EmptyBorder(10,0,10,0));
+ textPanel.add(text);
+ panel.add(textPanel, CENTER);
+
+/*
+ * Cancellation does not work when opening
+
+ // Cancel button
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ userCancelled = true;
+ setVisible(false);
+ dispose();
+ }
+ });
+ JPanel cancelButtonPanel = new JPanel();
+ cancelButtonPanel.add(cancelButton);
+ panel.add(cancelButtonPanel, BorderLayout.SOUTH);
+*/
+ setContentPane(panel);
+ setPreferredSize(new Dimension(300, 100));
+
+ pack();
+ }
+
+ public boolean hasUserCancelled() {
+ return userCancelled;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
new file mode 100644
index 0000000..4a4a1e3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowInfo.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+/**
+ * Information about an open dataflow.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class OpenDataflowInfo {
+ private DataflowInfo dataflowInfo;
+ private boolean isChanged;
+ private Date openedAt;
+
+ public OpenDataflowInfo() {
+ }
+
+ public FileType getFileType() {
+ if (dataflowInfo == null)
+ return null;
+ return dataflowInfo.getFileType();
+ }
+
+ public Date getLastModified() {
+ if (dataflowInfo == null)
+ return null;
+ return dataflowInfo.getLastModified();
+ }
+
+ public Date getOpenedAtDate() {
+ return openedAt;
+ }
+
+ public Object getSource() {
+ if (dataflowInfo == null)
+ return null;
+ return dataflowInfo.getCanonicalSource();
+ }
+
+ public boolean isChanged() {
+ return isChanged;
+ }
+
+ public void setIsChanged(boolean isChanged) {
+ this.isChanged = isChanged;
+ }
+
+ public synchronized void setOpenedFrom(DataflowInfo dataflowInfo) {
+ setDataflowInfo(dataflowInfo);
+ setOpenedAt(new Date());
+ setIsChanged(false);
+ }
+
+ public synchronized void setSavedTo(DataflowInfo dataflowInfo) {
+ setDataflowInfo(dataflowInfo);
+ setIsChanged(false);
+ }
+
+ private void setDataflowInfo(DataflowInfo dataflowInfo) {
+ this.dataflowInfo = dataflowInfo;
+ }
+
+ private void setOpenedAt(Date openedAt) {
+ this.openedAt = openedAt;
+ }
+
+ public DataflowInfo getDataflowInfo() {
+ return dataflowInfo;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
new file mode 100644
index 0000000..9d687b8
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowRunnable.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static java.lang.Thread.sleep;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ */
+public class OpenDataflowRunnable implements Runnable {
+ private final FileManagerImpl fileManager;
+ private final FileType fileType;
+ private final Object source;
+ private WorkflowBundle dataflow;
+ private OpenException e;
+
+ public OpenDataflowRunnable(FileManagerImpl fileManager, FileType fileType,
+ Object source) {
+ this.fileManager = fileManager;
+ this.fileType = fileType;
+ this.source = source;
+ }
+
+ @Override
+ public void run() {
+ OpenDataflowSwingWorker openDataflowSwingWorker = new OpenDataflowSwingWorker(
+ fileType, source, fileManager);
+ OpenDataflowInProgressDialog dialog = new OpenDataflowInProgressDialog();
+ openDataflowSwingWorker
+ .addPropertyChangeListener(new SwingWorkerCompletionWaiter(
+ dialog));
+ openDataflowSwingWorker.execute();
+
+ /*
+ * Give a chance to the SwingWorker to finish so we do not have to
+ * display the dialog
+ */
+ try {
+ sleep(500);
+ } catch (InterruptedException e) {
+ this.e = new OpenException("Opening was interrupted");
+ }
+ if (!openDataflowSwingWorker.isDone())
+ dialog.setVisible(true); // this will block the GUI
+ boolean userCancelled = dialog.hasUserCancelled(); // see if user cancelled the dialog
+
+ if (userCancelled) {
+ // Stop the OpenDataflowSwingWorker if it is still working
+ openDataflowSwingWorker.cancel(true);
+ dataflow = null;
+ this.e = new OpenException("Opening was cancelled");
+ // exit
+ return;
+ }
+ dataflow = openDataflowSwingWorker.getDataflow();
+ this.e = openDataflowSwingWorker.getException();
+ }
+
+ public WorkflowBundle getDataflow() {
+ return dataflow;
+ }
+
+ public OpenException getException() {
+ return this.e;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
new file mode 100644
index 0000000..4cbd2f8
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/OpenDataflowSwingWorker.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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.workbench.file.impl;
+
+import javax.swing.SwingWorker;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class OpenDataflowSwingWorker extends
+ SwingWorker<WorkflowBundle, Object> {
+ @SuppressWarnings("unused")
+ private Logger logger = Logger.getLogger(OpenDataflowSwingWorker.class);
+ private FileType fileType;
+ private Object source;
+ private FileManagerImpl fileManagerImpl;
+ private WorkflowBundle workflowBundle;
+ private OpenException e = null;
+
+ public OpenDataflowSwingWorker(FileType fileType, Object source,
+ FileManagerImpl fileManagerImpl) {
+ this.fileType = fileType;
+ this.source = source;
+ this.fileManagerImpl = fileManagerImpl;
+ }
+
+ @Override
+ protected WorkflowBundle doInBackground() throws Exception {
+ try {
+ workflowBundle = fileManagerImpl.performOpenDataflow(fileType,
+ source);
+ } catch (OpenException e) {
+ this.e = e;
+ }
+ return workflowBundle;
+ }
+
+ public WorkflowBundle getDataflow() {
+ return workflowBundle;
+ }
+
+ public OpenException getException() {
+ return e;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
new file mode 100644
index 0000000..bf37faf
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2DataflowOpener.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class T2DataflowOpener extends AbstractDataflowPersistenceHandler
+ implements DataflowPersistenceHandler {
+ private static final T2FlowFileType T2_FLOW_FILE_TYPE = new T2FlowFileType();
+ private static Logger logger = Logger.getLogger(T2DataflowOpener.class);
+
+ private WorkflowBundleIO workflowBundleIO;
+
+ @SuppressWarnings("resource")
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (!getOpenFileTypes().contains(fileType))
+ throw new OpenException("Unsupported file type "
+ + fileType);
+ InputStream inputStream;
+ Date lastModified = null;
+ Object canonicalSource = source;
+ if (source instanceof InputStream)
+ inputStream = (InputStream) source;
+ else if (source instanceof File)
+ try {
+ inputStream = new FileInputStream((File) source);
+ } catch (FileNotFoundException e) {
+ throw new OpenException("Could not open file " + source + ":\n" + e.getLocalizedMessage(), e);
+ }
+ else if (source instanceof URL) {
+ URL url = ((URL) source);
+ try {
+ URLConnection connection = url.openConnection();
+ connection.setRequestProperty("Accept", "text/xml");
+ inputStream = connection.getInputStream();
+ if (connection.getLastModified() != 0)
+ lastModified = new Date(connection.getLastModified());
+ } catch (IOException e) {
+ throw new OpenException("Could not open connection to URL "
+ + source+ ":\n" + e.getLocalizedMessage(), e);
+ }
+ try {
+ if (url.getProtocol().equalsIgnoreCase("file"))
+ canonicalSource = new File(url.toURI());
+ } catch (URISyntaxException e) {
+ logger.warn("Invalid file URI created from " + url);
+ }
+ } else {
+ throw new OpenException("Unsupported source type "
+ + source.getClass());
+ }
+
+ final WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = openDataflowStream(inputStream);
+ } finally {
+ try {
+ if (!(source instanceof InputStream))
+ // We created the stream, we'll close it
+ inputStream.close();
+ } catch (IOException ex) {
+ logger.warn("Could not close inputstream " + inputStream, ex);
+ }
+ }
+ if (canonicalSource instanceof File)
+ return new FileDataflowInfo(T2_FLOW_FILE_TYPE,
+ (File) canonicalSource, workflowBundle);
+ return new DataflowInfo(T2_FLOW_FILE_TYPE, canonicalSource,
+ workflowBundle, lastModified);
+ }
+
+ protected WorkflowBundle openDataflowStream(InputStream workflowXMLstream)
+ throws OpenException {
+ WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = workflowBundleIO.readBundle(workflowXMLstream, null);
+ } catch (ReaderException e) {
+ throw new OpenException("Could not read the workflow", e);
+ } catch (IOException e) {
+ throw new OpenException("Could not open the workflow file for parsing", e);
+ } catch (Exception e) {
+ throw new OpenException("Error while opening workflow", e);
+ }
+
+ return workflowBundle;
+ }
+
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Arrays.<FileType> asList(new T2FlowFileType());
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Arrays.<Class<?>> asList(InputStream.class, URL.class,
+ File.class);
+ }
+
+ public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+ this.workflowBundleIO = workflowBundleIO;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
new file mode 100644
index 0000000..62b5892
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FileFilter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public class T2FileFilter extends FileFilter {
+ @Override
+ public boolean accept(final File file) {
+ return file.getName().toLowerCase().endsWith(".t2flow");
+ }
+
+ @Override
+ public String getDescription() {
+ return "Taverna 2 workflows";
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
new file mode 100644
index 0000000..a2774e4
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/T2FlowFileType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class T2FlowFileType extends FileType {
+ public static final String APPLICATION_VND_TAVERNA_T2FLOW_XML = "application/vnd.taverna.t2flow+xml";
+
+ @Override
+ public String getDescription() {
+ return "Taverna 2 workflow";
+ }
+
+ @Override
+ public String getExtension() {
+ return "t2flow";
+ }
+
+ @Override
+ public String getMimeType() {
+ return APPLICATION_VND_TAVERNA_T2FLOW_XML;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
new file mode 100644
index 0000000..d272b41
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileFilter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public class WorkflowBundleFileFilter extends FileFilter {
+ @Override
+ public boolean accept(final File file) {
+ return file.getName().toLowerCase().endsWith(".wfbundle");
+ }
+
+ @Override
+ public String getDescription() {
+ return "Taverna 3 workflows";
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
new file mode 100644
index 0000000..09b30b0
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleFileType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import net.sf.taverna.t2.workbench.file.FileType;
+
+public class WorkflowBundleFileType extends FileType {
+ public static final String APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE = "application/vnd.taverna.scufl2.workflow-bundle";
+
+ @Override
+ public String getDescription() {
+ return "Taverna 3 workflow";
+ }
+
+ @Override
+ public String getExtension() {
+ return "wfbundle";
+ }
+
+ @Override
+ public String getMimeType() {
+ return APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
new file mode 100644
index 0000000..7c54f7e
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleOpener.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.ReaderException;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class WorkflowBundleOpener extends AbstractDataflowPersistenceHandler
+ implements DataflowPersistenceHandler {
+ private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+ private static Logger logger = Logger.getLogger(WorkflowBundleOpener.class);
+ private WorkflowBundleIO workflowBundleIO;
+
+ @SuppressWarnings("resource")
+ @Override
+ public DataflowInfo openDataflow(FileType fileType, Object source)
+ throws OpenException {
+ if (!getOpenFileTypes().contains(fileType))
+ throw new OpenException("Unsupported file type " + fileType);
+ InputStream inputStream;
+ Date lastModified = null;
+ Object canonicalSource = source;
+ if (source instanceof InputStream) {
+ inputStream = (InputStream) source;
+ } else if (source instanceof File) {
+ try {
+ inputStream = new FileInputStream((File) source);
+ } catch (FileNotFoundException e) {
+ throw new OpenException("Could not open file " + source + ":\n"
+ + e.getLocalizedMessage(), e);
+ }
+ } else if (source instanceof URL) {
+ URL url = ((URL) source);
+ try {
+ URLConnection connection = url.openConnection();
+ connection.setRequestProperty("Accept", "application/zip");
+ inputStream = connection.getInputStream();
+ if (connection.getLastModified() != 0)
+ lastModified = new Date(connection.getLastModified());
+ } catch (IOException e) {
+ throw new OpenException("Could not open connection to URL "
+ + source + ":\n" + e.getLocalizedMessage(), e);
+ }
+ try {
+ if (url.getProtocol().equalsIgnoreCase("file"))
+ canonicalSource = new File(url.toURI());
+ } catch (URISyntaxException e) {
+ logger.warn("Invalid file URI created from " + url);
+ }
+ } else
+ throw new OpenException("Unsupported source type "
+ + source.getClass());
+
+ final WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = openDataflowStream(inputStream);
+ } finally {
+ // We created the stream, we'll close it
+ try {
+ if (!(source instanceof InputStream))
+ inputStream.close();
+ } catch (IOException ex) {
+ logger.warn("Could not close inputstream " + inputStream, ex);
+ }
+ }
+ if (canonicalSource instanceof File)
+ return new FileDataflowInfo(WF_BUNDLE_FILE_TYPE,
+ (File) canonicalSource, workflowBundle);
+ return new DataflowInfo(WF_BUNDLE_FILE_TYPE, canonicalSource,
+ workflowBundle, lastModified);
+ }
+
+ protected WorkflowBundle openDataflowStream(InputStream inputStream)
+ throws OpenException {
+ WorkflowBundle workflowBundle;
+ try {
+ workflowBundle = workflowBundleIO.readBundle(inputStream, null);
+ } catch (ReaderException e) {
+ throw new OpenException("Could not read the workflow", e);
+ } catch (IOException e) {
+ throw new OpenException("Could not open the workflow for parsing",
+ e);
+ } catch (Exception e) {
+ throw new OpenException("Error while opening workflow", e);
+ }
+
+ return workflowBundle;
+ }
+
+ @Override
+ public List<FileType> getOpenFileTypes() {
+ return Arrays.<FileType> asList(WF_BUNDLE_FILE_TYPE);
+ }
+
+ @Override
+ public List<Class<?>> getOpenSourceTypes() {
+ return Arrays.<Class<?>> asList(InputStream.class, URL.class,
+ File.class);
+ }
+
+ public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+ this.workflowBundleIO = workflowBundleIO;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
new file mode 100644
index 0000000..f6b7108
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/WorkflowBundleSaver.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static net.sf.taverna.t2.workbench.file.impl.WorkflowBundleFileType.APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+
+public class WorkflowBundleSaver extends AbstractDataflowPersistenceHandler
+ implements DataflowPersistenceHandler {
+ private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+ private static Logger logger = Logger.getLogger(WorkflowBundleSaver.class);
+ private WorkflowBundleIO workflowBundleIO;
+
+ @Override
+ public DataflowInfo saveDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination) throws SaveException {
+ if (!getSaveFileTypes().contains(fileType))
+ throw new IllegalArgumentException("Unsupported file type "
+ + fileType);
+ OutputStream outStream;
+ if (destination instanceof File)
+ try {
+ outStream = new FileOutputStream((File) destination);
+ } catch (FileNotFoundException e) {
+ throw new SaveException("Can't create workflow file "
+ + destination + ":\n" + e.getLocalizedMessage(), e);
+ }
+ else if (destination instanceof OutputStream)
+ outStream = (OutputStream) destination;
+ else
+ throw new SaveException("Unsupported destination type "
+ + destination.getClass());
+
+ try {
+ saveDataflowToStream(workflowBundle, outStream);
+ } finally {
+ try {
+ // Only close if we opened the stream
+ if (!(destination instanceof OutputStream))
+ outStream.close();
+ } catch (IOException e) {
+ logger.warn("Could not close stream", e);
+ }
+ }
+
+ if (destination instanceof File)
+ return new FileDataflowInfo(WF_BUNDLE_FILE_TYPE, (File) destination,
+ workflowBundle);
+ return new DataflowInfo(WF_BUNDLE_FILE_TYPE, destination, workflowBundle);
+ }
+
+ protected void saveDataflowToStream(WorkflowBundle workflowBundle,
+ OutputStream fileOutStream) throws SaveException {
+ try {
+ workflowBundleIO.writeBundle(workflowBundle, fileOutStream,
+ APPLICATION_VND_TAVERNA_SCUFL2_WORKFLOW_BUNDLE);
+ } catch (Exception e) {
+ throw new SaveException("Can't write workflow:\n"
+ + e.getLocalizedMessage(), e);
+ }
+ }
+
+ @Override
+ public List<FileType> getSaveFileTypes() {
+ return Arrays.<FileType> asList(WF_BUNDLE_FILE_TYPE);
+ }
+
+ @Override
+ public List<Class<?>> getSaveDestinationTypes() {
+ return Arrays.<Class<?>> asList(File.class, OutputStream.class);
+ }
+
+ @Override
+ public boolean wouldOverwriteDataflow(WorkflowBundle workflowBundle, FileType fileType,
+ Object destination, DataflowInfo lastDataflowInfo) {
+ if (!getSaveFileTypes().contains(fileType))
+ throw new IllegalArgumentException("Unsupported file type "
+ + fileType);
+ if (!(destination instanceof File))
+ return false;
+
+ File file;
+ try {
+ file = ((File) destination).getCanonicalFile();
+ } catch (IOException e) {
+ return false;
+ }
+ if (!file.exists())
+ return false;
+ if (lastDataflowInfo == null)
+ return true;
+ Object lastDestination = lastDataflowInfo.getCanonicalSource();
+ if (!(lastDestination instanceof File))
+ return true;
+ File lastFile = (File) lastDestination;
+ if (!lastFile.getAbsoluteFile().equals(file))
+ return true;
+
+ Date lastModified = new Date(file.lastModified());
+ if (lastModified.equals(lastDataflowInfo.getLastModified()))
+ return false;
+ return true;
+ }
+
+ public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
+ this.workflowBundleIO = workflowBundleIO;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java
new file mode 100644
index 0000000..2309b9c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseAllWorkflowsAction.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_L;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeAllIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class CloseAllWorkflowsAction extends AbstractAction {
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(CloseWorkflowAction.class);
+ private static final String CLOSE_ALL_WORKFLOWS = "Close all workflows";
+ private FileManager fileManager;
+ private CloseWorkflowAction closeWorkflowAction;
+
+ public CloseAllWorkflowsAction(EditManager editManager, FileManager fileManager) {
+ super(CLOSE_ALL_WORKFLOWS, closeAllIcon);
+ this.fileManager = fileManager;
+ closeWorkflowAction = new CloseWorkflowAction(editManager, fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_W, getDefaultToolkit().getMenuShortcutKeyMask()
+ | SHIFT_DOWN_MASK));
+ putValue(MNEMONIC_KEY, VK_L);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ Component parentComponent = null;
+ if (event.getSource() instanceof Component)
+ parentComponent = (Component) event.getSource();
+ closeAllWorkflows(parentComponent);
+ }
+
+ public boolean closeAllWorkflows(Component parentComponent) {
+ // Close in reverse so we can save nested workflows first
+ List<WorkflowBundle> workflowBundles = fileManager.getOpenDataflows();
+
+ Collections.reverse(workflowBundles);
+
+ for (WorkflowBundle workflowBundle : workflowBundles) {
+ boolean success = closeWorkflowAction.closeWorkflow(
+ parentComponent, workflowBundle);
+ if (!success)
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java
new file mode 100644
index 0000000..03c90a6
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/CloseWorkflowAction.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_C;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.UnsavedException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class CloseWorkflowAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(CloseWorkflowAction.class);
+ private static final String CLOSE_WORKFLOW = "Close workflow";
+ private final SaveWorkflowAction saveWorkflowAction;
+ private FileManager fileManager;
+
+ public CloseWorkflowAction(EditManager editManager, FileManager fileManager) {
+ super(CLOSE_WORKFLOW, closeIcon);
+ this.fileManager = fileManager;
+ saveWorkflowAction = new SaveWorkflowAction(editManager, fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_W, getDefaultToolkit().getMenuShortcutKeyMask()));
+ putValue(MNEMONIC_KEY, VK_C);
+
+ }
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Component parentComponent = null;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ closeWorkflow(parentComponent, fileManager.getCurrentDataflow());
+ }
+
+ public boolean closeWorkflow(Component parentComponent, WorkflowBundle workflowBundle) {
+ if (workflowBundle == null) {
+ logger.warn("Attempted to close a null workflow");
+ return false;
+ }
+
+ try {
+ return fileManager.closeDataflow(workflowBundle, true);
+ } catch (UnsavedException e1) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ String msg = "Do you want to save changes before closing the workflow "
+ + fileManager.getDataflowName(workflowBundle) + "?";
+ switch (showConfirmDialog(parentComponent, msg, "Save workflow?",
+ YES_NO_CANCEL_OPTION)) {
+ case NO_OPTION:
+ try {
+ fileManager.closeDataflow(workflowBundle, false);
+ return true;
+ } catch (UnsavedException e2) {
+ logger.error("Unexpected UnsavedException while "
+ + "closing workflow", e2);
+ return false;
+ }
+ case YES_OPTION:
+ boolean saved = saveWorkflowAction.saveDataflow(
+ parentComponent, workflowBundle);
+ if (!saved)
+ return false;
+ return closeWorkflow(parentComponent, workflowBundle);
+ case CANCEL_OPTION:
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java
new file mode 100644
index 0000000..3b9256a
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/NewWorkflowAction.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_N;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.newIcon;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class NewWorkflowAction extends AbstractAction {
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger.getLogger(NewWorkflowAction.class);
+ private static final String NEW_WORKFLOW = "New workflow";
+ private FileManager fileManager;
+
+ public NewWorkflowAction(FileManager fileManager) {
+ super(NEW_WORKFLOW, newIcon);
+ this.fileManager = fileManager;
+ putValue(SHORT_DESCRIPTION, NEW_WORKFLOW);
+ putValue(MNEMONIC_KEY, KeyEvent.VK_N);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_N, getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fileManager.newDataflow();
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java
new file mode 100644
index 0000000..08030c7
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenNestedWorkflowAction.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import java.awt.Component;
+import java.io.File;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a nested workflow from a file.
+ *
+ * @author Alex Nenadic
+ */
+public class OpenNestedWorkflowAction extends OpenWorkflowAction {
+ private static final long serialVersionUID = -5398423684000142379L;
+ private static Logger logger = Logger
+ .getLogger(OpenNestedWorkflowAction.class);
+
+ public OpenNestedWorkflowAction(FileManager fileManager) {
+ super(fileManager);
+ }
+
+ /**
+ * Opens a nested workflow from a file (should be one file even though the
+ * method takes a list of files - this is because it overrides the
+ * {@link OpenWorkflowAction#openWorkflows(Component, File[], FileType, OpenCallback)
+ * openWorkflows(...)} method).
+ */
+ @Override
+ public void openWorkflows(final Component parentComponent, File[] files,
+ FileType fileType, OpenCallback openCallback) {
+ ErrorLoggingOpenCallbackWrapper callback = new ErrorLoggingOpenCallbackWrapper(
+ openCallback);
+ for (File file : files)
+ try {
+ callback.aboutToOpenDataflow(file);
+ WorkflowBundle workflowBundle = fileManager.openDataflow(
+ fileType, file);
+ callback.openedDataflow(file, workflowBundle);
+ } catch (final RuntimeException ex) {
+ logger.warn("Could not open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ } catch (final OpenException ex) {
+ logger.warn("Could not open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ return;
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java
new file mode 100644
index 0000000..e2ecbd7
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowAction.java
@@ -0,0 +1,395 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_O;
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a workflow from a file. All file types exposed by the
+ * {@link FileManager} as compatible with the {@link File} type are supported.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class OpenWorkflowAction extends AbstractAction {
+ private static final long serialVersionUID = 103237694130052153L;
+ private static Logger logger = Logger.getLogger(OpenWorkflowAction.class);
+ private static final String OPEN_WORKFLOW = "Open workflow...";
+
+ public final OpenCallback DUMMY_OPEN_CALLBACK = new OpenCallbackAdapter();
+ protected FileManager fileManager;
+
+ public OpenWorkflowAction(FileManager fileManager) {
+ super(OPEN_WORKFLOW, openIcon);
+ this.fileManager = fileManager;
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_O, getDefaultToolkit().getMenuShortcutKeyMask()));
+ putValue(MNEMONIC_KEY, VK_O);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ final Component parentComponent;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ else
+ parentComponent = null;
+ openWorkflows(parentComponent);
+ }
+
+ /**
+ * Pop up an Open-dialogue to select one or more workflow files to open.
+ * <p>
+ * Note that the file opening occurs in a separate thread. If you want to
+ * check if the file was opened or not, which workflow was opened, etc, use
+ * {@link #openWorkflows(Component, OpenCallback)} instead.
+ *
+ * @see #openWorkflows(Component, OpenCallback)
+ * @param parentComponent
+ * The UI parent component to use for pop up dialogues
+ *
+ * @return <code>false</code> if no files were selected or the dialogue was
+ * cancelled, or <code>true</code> if the process of opening one or
+ * more files has been started.
+ */
+ public void openWorkflows(Component parentComponent) {
+ openWorkflows(parentComponent, DUMMY_OPEN_CALLBACK);
+ }
+
+ /**
+ * Open an array of workflow files.
+ *
+ * @param parentComponent
+ * Parent component for UI dialogues
+ * @param files
+ * Array of files to be opened
+ * @param fileType
+ * {@link FileType} of the files that are to be opened, for
+ * instance
+ * {@link net.sf.taverna.t2.workbench.file.impl.T2FlowFileType},
+ * or <code>null</code> to guess.
+ * @param openCallback
+ * An {@link OpenCallback} to be invoked during and after opening
+ * the file. Use {@link OpenWorkflowAction#DUMMY_OPEN_CALLBACK}
+ * if no callback is needed.
+ */
+ public void openWorkflows(final Component parentComponent, File[] files,
+ FileType fileType, OpenCallback openCallback) {
+ ErrorLoggingOpenCallbackWrapper callback = new ErrorLoggingOpenCallbackWrapper(
+ openCallback);
+ for (File file : files)
+ try {
+ Object canonicalSource = fileManager.getCanonical(file);
+ WorkflowBundle alreadyOpen = fileManager.getDataflowBySource(canonicalSource);
+ if (alreadyOpen != null) {
+ /*
+ * The workflow from the same source is already opened - ask
+ * the user if they want to switch to it or open another
+ * copy...
+ */
+
+ Object[] options = { "Switch to opened", "Open new copy",
+ "Cancel" };
+ switch (showOptionDialog(
+ null,
+ "The workflow from the same location is already opened.\n"
+ + "Do you want to switch to it or open a new copy?",
+ "File Manager Alert", YES_NO_CANCEL_OPTION,
+ QUESTION_MESSAGE, null, options, // the titles of buttons
+ options[0])) { // default button title
+ case YES_OPTION:
+ fileManager.setCurrentDataflow(alreadyOpen);
+ return;
+ case CANCEL_OPTION:
+ // do nothing
+ return;
+ }
+ // else open the workflow as usual
+ }
+
+ callback.aboutToOpenDataflow(file);
+ WorkflowBundle workflowBundle = fileManager.openDataflow(fileType, file);
+ callback.openedDataflow(file, workflowBundle);
+ } catch (RuntimeException ex) {
+ logger.warn("Failed to open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ } catch (Exception ex) {
+ logger.warn("Failed to open workflow from " + file, ex);
+ if (!callback.couldNotOpenDataflow(file, ex))
+ showErrorMessage(parentComponent, file, ex);
+ return;
+ }
+ }
+
+ /**
+ * Pop up an Open-dialogue to select one or more workflow files to open.
+ *
+ * @param parentComponent
+ * The UI parent component to use for pop up dialogues
+ * @param openCallback
+ * An {@link OpenCallback} to be called during the file opening.
+ * The callback will be invoked for each file that has been
+ * opened, as file opening happens in a separate thread that
+ * might execute after the return of this method.
+ * @return <code>false</code> if no files were selected or the dialogue was
+ * cancelled, or <code>true</code> if the process of opening one or
+ * more files has been started.
+ */
+ public boolean openWorkflows(final Component parentComponent,
+ OpenCallback openCallback) {
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = userNodeForPackage(getClass());
+ String curDir = prefs
+ .get("currentDir", System.getProperty("user.home"));
+ fileChooser.setDialogTitle(OPEN_WORKFLOW);
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ List<FileFilter> fileFilters = fileManager.getOpenFileFilters();
+ if (fileFilters.isEmpty()) {
+ logger.warn("No file types found for opening workflow");
+ showMessageDialog(parentComponent,
+ "No file types found for opening workflow.", "Error",
+ ERROR_MESSAGE);
+ return false;
+ }
+ for (FileFilter fileFilter : fileFilters)
+ fileChooser.addChoosableFileFilter(fileFilter);
+ fileChooser.setFileFilter(fileFilters.get(0));
+ fileChooser.setCurrentDirectory(new File(curDir));
+ fileChooser.setMultiSelectionEnabled(true);
+
+ int returnVal = fileChooser.showOpenDialog(parentComponent);
+ if (returnVal == APPROVE_OPTION) {
+ prefs.put("currentDir", fileChooser.getCurrentDirectory()
+ .toString());
+ final File[] selectedFiles = fileChooser.getSelectedFiles();
+ if (selectedFiles.length == 0) {
+ logger.warn("No files selected");
+ return false;
+ }
+ FileFilter fileFilter = fileChooser.getFileFilter();
+ FileType fileType;
+ if (fileFilter instanceof FileTypeFileFilter)
+ fileType = ((FileTypeFileFilter) fileChooser.getFileFilter())
+ .getFileType();
+ else
+ // Unknown filetype, try all of them
+ fileType = null;
+ new FileOpenerThread(parentComponent, selectedFiles, fileType,
+ openCallback).start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Show an error message if a file could not be opened
+ *
+ * @param parentComponent
+ * @param file
+ * @param throwable
+ */
+ protected void showErrorMessage(final Component parentComponent,
+ final File file, final Throwable throwable) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ Throwable cause = throwable;
+ while (cause.getCause() != null)
+ cause = cause.getCause();
+ showMessageDialog(
+ parentComponent,
+ "Failed to open workflow from " + file + ": \n"
+ + cause.getMessage(), "Warning",
+ WARNING_MESSAGE);
+ }
+ });
+
+ }
+
+ /**
+ * Callback interface for openWorkflows().
+ * <p>
+ * The callback will be invoked during the invocation of
+ * {@link OpenWorkflowAction#openWorkflows(Component, OpenCallback)} and
+ * {@link OpenWorkflowAction#openWorkflows(Component, File[], FileType, OpenCallback)}
+ * as file opening happens in a separate thread.
+ *
+ * @author Stian Soiland-Reyes
+ */
+ public interface OpenCallback {
+ /**
+ * Called before a workflowBundle is to be opened from the given file
+ *
+ * @param file
+ * File which workflowBundle is to be opened
+ */
+ void aboutToOpenDataflow(File file);
+
+ /**
+ * Called if an exception happened while attempting to open the
+ * workflowBundle.
+ *
+ * @param file
+ * File which was attempted to be opened
+ * @param ex
+ * An {@link OpenException} or a {@link RuntimeException}.
+ * @return <code>true</code> if the error has been handled, or
+ * <code>false</code>3 if a UI warning dialogue is to be opened.
+ */
+ boolean couldNotOpenDataflow(File file, Exception ex);
+
+ /**
+ * Called when a workflowBundle has been successfully opened. The workflowBundle
+ * will be registered in {@link FileManager#getOpenDataflows()}.
+ *
+ * @param file
+ * File from which workflowBundle was opened
+ * @param workflowBundle
+ * WorkflowBundle that was opened
+ */
+ void openedDataflow(File file, WorkflowBundle workflowBundle);
+ }
+
+ /**
+ * Adapter for {@link OpenCallback}
+ *
+ * @author Stian Soiland-Reyes
+ */
+ public static class OpenCallbackAdapter implements OpenCallback {
+ @Override
+ public void aboutToOpenDataflow(File file) {
+ }
+
+ @Override
+ public boolean couldNotOpenDataflow(File file, Exception ex) {
+ return false;
+ }
+
+ @Override
+ public void openedDataflow(File file, WorkflowBundle workflowBundle) {
+ }
+ }
+
+ private final class FileOpenerThread extends Thread {
+ private final File[] files;
+ private final FileType fileType;
+ private final OpenCallback openCallback;
+ private final Component parentComponent;
+
+ private FileOpenerThread(Component parentComponent,
+ File[] selectedFiles, FileType fileType,
+ OpenCallback openCallback) {
+ super("Opening workflows(s) " + Arrays.asList(selectedFiles));
+ this.parentComponent = parentComponent;
+ this.files = selectedFiles;
+ this.fileType = fileType;
+ this.openCallback = openCallback;
+ }
+
+ @Override
+ public void run() {
+ openWorkflows(parentComponent, files, fileType, openCallback);
+ }
+ }
+
+ /**
+ * A wrapper for {@link OpenCallback} implementations that logs exceptions
+ * thrown without disrupting the caller of the callback.
+ *
+ * @author Stian Soiland-Reyes
+ */
+ protected class ErrorLoggingOpenCallbackWrapper implements OpenCallback {
+ private final OpenCallback wrapped;
+
+ public ErrorLoggingOpenCallbackWrapper(OpenCallback wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public void aboutToOpenDataflow(File file) {
+ try {
+ wrapped.aboutToOpenDataflow(file);
+ } catch (RuntimeException wrapperEx) {
+ logger.warn("Failed OpenCallback " + wrapped
+ + ".aboutToOpenDataflow(File)", wrapperEx);
+ }
+ }
+
+ @Override
+ public boolean couldNotOpenDataflow(File file, Exception ex) {
+ try {
+ return wrapped.couldNotOpenDataflow(file, ex);
+ } catch (RuntimeException wrapperEx) {
+ logger.warn("Failed OpenCallback " + wrapped
+ + ".couldNotOpenDataflow(File, Exception)", wrapperEx);
+ return false;
+ }
+ }
+
+ @Override
+ public void openedDataflow(File file, WorkflowBundle workflowBundle) {
+ try {
+ wrapped.openedDataflow(file, workflowBundle);
+ } catch (RuntimeException wrapperEx) {
+ logger.warn("Failed OpenCallback " + wrapped
+ + ".openedDataflow(File, Dataflow)", wrapperEx);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
new file mode 100644
index 0000000..e98a8f2
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/OpenWorkflowFromURLAction.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (C) 2008 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.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_L;
+import static javax.swing.JOptionPane.CANCEL_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.openurlIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URL;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * An action for opening a workflow from a url.
+ *
+ * @author David Withers
+ */
+public class OpenWorkflowFromURLAction extends AbstractAction {
+ private static final long serialVersionUID = 1474356457949961974L;
+ private static Logger logger = Logger
+ .getLogger(OpenWorkflowFromURLAction.class);
+ private static Preferences prefs = Preferences
+ .userNodeForPackage(OpenWorkflowFromURLAction.class);
+ private static final String PREF_CURRENT_URL = "currentUrl";
+ private static final String ACTION_NAME = "Open workflow location...";
+ private static final String ACTION_DESCRIPTION = "Open a workflow from the web into a new workflow";
+
+ private Component component;
+ private FileManager fileManager;
+
+ public OpenWorkflowFromURLAction(final Component component,
+ FileManager fileManager) {
+ this.component = component;
+ this.fileManager = fileManager;
+ putValue(SMALL_ICON, openurlIcon);
+ putValue(NAME, ACTION_NAME);
+ putValue(SHORT_DESCRIPTION, ACTION_DESCRIPTION);
+ putValue(MNEMONIC_KEY, VK_L);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_L, getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String currentUrl = prefs.get(PREF_CURRENT_URL, "http://");
+
+ final String url = (String) showInputDialog(component,
+ "Enter the URL of a workflow definition to load",
+ "Workflow URL", QUESTION_MESSAGE, null, null, currentUrl);
+ if (url != null)
+ new Thread("OpenWorkflowFromURLAction") {
+ @Override
+ public void run() {
+ openFromURL(url);
+ }
+ }.start();
+ }
+
+ private void openFromURL(String urlString) {
+ try {
+ URL url = new URL(urlString);
+
+ Object canonicalSource = fileManager.getCanonical(url);
+ WorkflowBundle alreadyOpen = fileManager
+ .getDataflowBySource(canonicalSource);
+ if (alreadyOpen != null) {
+ /*
+ * The workflow from the same source is already opened - ask the
+ * user if they want to switch to it or open another copy.
+ */
+
+ Object[] options = { "Switch to opened", "Open new copy",
+ "Cancel" };
+ int iSelected = showOptionDialog(
+ null,
+ "The workflow from the same location is already opened.\n"
+ + "Do you want to switch to it or open a new copy?",
+ "File Manager Alert", YES_NO_CANCEL_OPTION,
+ QUESTION_MESSAGE, null, options, // the titles of buttons
+ options[0]); // default button title
+
+ if (iSelected == YES_OPTION) {
+ fileManager.setCurrentDataflow(alreadyOpen);
+ return;
+ } else if (iSelected == CANCEL_OPTION) {
+ // do nothing
+ return;
+ }
+ // else open the workflow as usual
+ }
+
+ fileManager.openDataflow(null, url);
+ prefs.put(PREF_CURRENT_URL, urlString);
+ } catch (Exception ex) {
+ logger.warn("Failed to open the workflow from url " + urlString
+ + " \n", ex);
+ showMessageDialog(component,
+ "Failed to open the workflow from url " + urlString + " \n"
+ + ex.getMessage(), "Error!", ERROR_MESSAGE);
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java
new file mode 100644
index 0000000..401a232
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/PasswordInput.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.EventQueue.invokeLater;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+
+/**
+ * Simple dialogue to handle username/password input for workflow URL requiring
+ * http authentication.
+ *
+ * @author Stuart Owen
+ * @author Stian Soiland-Reyes
+ * @author Alan R Williams
+ */
+@SuppressWarnings("serial")
+public class PasswordInput extends HelpEnabledDialog {
+ private static Logger logger = Logger.getLogger(PasswordInput.class);
+
+ private String password = null;
+ private String username = null;
+ private URL url = null;
+ private int tryCount = 0;
+ private final static int MAX_TRIES = 3;
+
+ private JButton cancelButton;
+ private JLabel jLabel1;
+ private JLabel jLabel2;
+ private JLabel messageLabel;
+ private JButton okButton;
+ private JPasswordField passwordTextField;
+ private JLabel urlLabel;
+ private JTextField usernameTextField;
+
+ public void setUrl(URL url) {
+ this.url = url;
+ urlLabel.setText(url.toExternalForm());
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public PasswordInput(JFrame parent) {
+ super(parent, "Authorization", true, null);
+ initComponents();
+ }
+
+ /** Creates new form PasswordInput */
+ public PasswordInput() {
+ super((JFrame) null, "Authorization", true, null);
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ private void initComponents() {
+ usernameTextField = new javax.swing.JTextField();
+ cancelButton = new javax.swing.JButton();
+ okButton = new javax.swing.JButton();
+ passwordTextField = new javax.swing.JPasswordField();
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ messageLabel = new javax.swing.JLabel();
+ urlLabel = new javax.swing.JLabel();
+
+ getContentPane().setLayout(null);
+
+ setModal(true);
+ // setResizable(false);
+ getContentPane().add(usernameTextField);
+ usernameTextField.setBounds(20, 80, 280, 22);
+
+ cancelButton.setText("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ cancelButtonActionPerformed(evt);
+ }
+ });
+
+ getContentPane().add(cancelButton);
+ cancelButton.setBounds(230, 160, 75, 29);
+
+ okButton.setText("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+
+ getContentPane().add(okButton);
+ okButton.setBounds(150, 160, 75, 29);
+
+ getContentPane().add(passwordTextField);
+ passwordTextField.setBounds(20, 130, 280, 22);
+
+ jLabel1.setText("Username");
+ getContentPane().add(jLabel1);
+ jLabel1.setBounds(20, 60, 70, 16);
+
+ jLabel2.setText("Password");
+ getContentPane().add(jLabel2);
+ jLabel2.setBounds(20, 110, 70, 16);
+
+ messageLabel.setText("A username and password is required for:");
+ getContentPane().add(messageLabel);
+ messageLabel.setBounds(20, 10, 270, 20);
+
+ urlLabel.setText("service");
+ getContentPane().add(urlLabel);
+ urlLabel.setBounds(20, 30, 270, 16);
+
+ pack();
+ }
+
+ private void okButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ String password = String.valueOf(passwordTextField.getPassword());
+ String username = usernameTextField.getText();
+ HttpURLConnection connection;
+ try {
+ connection = (HttpURLConnection) url.openConnection();
+ String userPassword = username + ":" + password;
+ /*
+ * Note: non-latin1 support for basic auth is fragile/unsupported
+ * and must be MIME-encoded (RFC2047) according to
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=41489
+ */
+ byte[] encoded = Base64.encodeBase64(userPassword
+ .getBytes("latin1"));
+ connection.setRequestProperty("Authorization", "Basic "
+ + new String(encoded, "ascii"));
+ connection.setRequestProperty("Accept", "text/xml");
+ int code = connection.getResponseCode();
+
+ /*
+ * NB: myExperiment gives a 500 response for an invalid
+ * username/password
+ */
+ if (code == 401 || code == 500) {
+ tryCount++;
+ showMessageDialog(this, "The username and password failed",
+ "Invalid username or password", ERROR_MESSAGE);
+ if (tryCount >= MAX_TRIES) { // close after 3 attempts.
+ this.password = null;
+ this.username = null;
+ this.setVisible(false);
+ }
+ } else {
+ this.username = username;
+ this.password = password;
+ this.setVisible(false);
+ }
+ } catch (IOException ex) {
+ logger.error("Could not get password", ex);
+ }
+ }
+
+ private void cancelButtonActionPerformed(ActionEvent evt) {
+ this.password = null;
+ this.username = null;
+ this.setVisible(false);
+ }
+
+ /**
+ * @param args
+ * the command line arguments
+ */
+ public static void main(String args[]) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ new PasswordInput().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java
new file mode 100644
index 0000000..6b011d3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveAllWorkflowsAction.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import static java.awt.event.KeyEvent.VK_A;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAllIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class SaveAllWorkflowsAction extends AbstractAction {
+ private final class FileManagerObserver implements
+ Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ updateEnabled();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static Logger logger = Logger
+ .getLogger(SaveAllWorkflowsAction.class);
+ private static final String SAVE_ALL_WORKFLOWS = "Save all workflows";
+
+ private final SaveWorkflowAction saveWorkflowAction;
+ private FileManager fileManager;
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+ public SaveAllWorkflowsAction(EditManager editManager,
+ FileManager fileManager) {
+ super(SAVE_ALL_WORKFLOWS, saveAllIcon);
+ this.fileManager = fileManager;
+ saveWorkflowAction = new SaveWorkflowAction(editManager, fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_S, getDefaultToolkit().getMenuShortcutKeyMask()
+ | SHIFT_DOWN_MASK));
+ putValue(MNEMONIC_KEY, VK_A);
+
+ fileManager.addObserver(fileManagerObserver);
+ updateEnabled();
+ }
+
+ public void updateEnabled() {
+ setEnabled(!(fileManager.getOpenDataflows().isEmpty()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ Component parentComponent = null;
+ if (ev.getSource() instanceof Component)
+ parentComponent = (Component) ev.getSource();
+ saveAllDataflows(parentComponent);
+ }
+
+ public void saveAllDataflows(Component parentComponent) {
+ // Save in reverse so we save nested workflows first
+ List<WorkflowBundle> workflowBundles = fileManager.getOpenDataflows();
+ Collections.reverse(workflowBundles);
+
+ for (WorkflowBundle workflowBundle : workflowBundles)
+ if (!saveWorkflowAction.saveDataflow(parentComponent,
+ workflowBundle))
+ break;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java
new file mode 100644
index 0000000..9776550
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAction.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+@SuppressWarnings("serial")
+public class SaveWorkflowAction extends AbstractAction {
+ private static Logger logger = Logger.getLogger(SaveWorkflowAction.class);
+ private static final String SAVE_WORKFLOW = "Save workflow";
+
+ private final SaveWorkflowAsAction saveWorkflowAsAction;
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ private FileManager fileManager;
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+ public SaveWorkflowAction(EditManager editManager, FileManager fileManager) {
+ super(SAVE_WORKFLOW, saveIcon);
+ this.fileManager = fileManager;
+ saveWorkflowAsAction = new SaveWorkflowAsAction(fileManager);
+ putValue(
+ ACCELERATOR_KEY,
+ getKeyStroke(VK_S, getDefaultToolkit().getMenuShortcutKeyMask()));
+ putValue(MNEMONIC_KEY, VK_S);
+ editManager.addObserver(editManagerObserver);
+ fileManager.addObserver(fileManagerObserver);
+ updateEnabledStatus(fileManager.getCurrentDataflow());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ Component parentComponent = null;
+ if (ev.getSource() instanceof Component)
+ parentComponent = (Component) ev.getSource();
+ saveCurrentDataflow(parentComponent);
+ }
+
+ public boolean saveCurrentDataflow(Component parentComponent) {
+ WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+ return saveDataflow(parentComponent, workflowBundle);
+ }
+
+ public boolean saveDataflow(Component parentComponent,
+ WorkflowBundle workflowBundle) {
+ if (!fileManager.canSaveWithoutDestination(workflowBundle))
+ return saveWorkflowAsAction.saveDataflow(parentComponent,
+ workflowBundle);
+
+ try {
+ try {
+ fileManager.saveDataflow(workflowBundle, true);
+ Object workflowBundleSource = fileManager
+ .getDataflowSource(workflowBundle);
+ logger.info("Saved workflow " + workflowBundle + " to "
+ + workflowBundleSource);
+ return true;
+ } catch (OverwriteException ex) {
+ Object workflowBundleSource = fileManager
+ .getDataflowSource(workflowBundle);
+ logger.info("Workflow was changed on source: "
+ + workflowBundleSource);
+ fileManager.setCurrentDataflow(workflowBundle);
+ String msg = "Workflow destination " + workflowBundleSource
+ + " has been changed from elsewhere, "
+ + "are you sure you want to overwrite?";
+ int ret = showConfirmDialog(parentComponent, msg,
+ "Workflow changed", YES_NO_CANCEL_OPTION);
+ if (ret == YES_OPTION) {
+ fileManager.saveDataflow(workflowBundle, false);
+ logger.info("Saved workflow " + workflowBundle
+ + " by overwriting " + workflowBundleSource);
+ return true;
+ } else if (ret == NO_OPTION) {
+ // Pop up Save As instead to choose another name
+ return saveWorkflowAsAction.saveDataflow(parentComponent,
+ workflowBundle);
+ } else {
+ logger.info("Aborted overwrite of " + workflowBundleSource);
+ return false;
+ }
+ }
+ } catch (SaveException ex) {
+ logger.warn("Could not save workflow " + workflowBundle, ex);
+ showMessageDialog(parentComponent, "Could not save workflow: \n\n"
+ + ex.getMessage(), "Warning", WARNING_MESSAGE);
+ return false;
+ } catch (RuntimeException ex) {
+ logger.warn("Could not save workflow " + workflowBundle, ex);
+ showMessageDialog(parentComponent, "Could not save workflow: \n\n"
+ + ex.getMessage(), "Warning", WARNING_MESSAGE);
+ return false;
+ }
+ }
+
+ protected void updateEnabledStatus(WorkflowBundle workflowBundle) {
+ setEnabled(workflowBundle != null
+ && fileManager.isDataflowChanged(workflowBundle));
+ }
+
+ private final class EditManagerObserver implements
+ Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent) {
+ WorkflowBundle workflowBundle = ((AbstractDataflowEditEvent) message)
+ .getDataFlow();
+ if (workflowBundle == fileManager.getCurrentDataflow())
+ updateEnabledStatus(workflowBundle);
+ }
+ }
+ }
+
+ private final class FileManagerObserver implements
+ Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof SavedDataflowEvent)
+ updateEnabledStatus(((SavedDataflowEvent) message)
+ .getDataflow());
+ else if (message instanceof SetCurrentDataflowEvent)
+ updateEnabledStatus(((SetCurrentDataflowEvent) message)
+ .getDataflow());
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java
new file mode 100644
index 0000000..1872d5d
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/actions/SaveWorkflowAsAction.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.actions;
+
+import static java.awt.event.KeyEvent.VK_F6;
+import static java.awt.event.KeyEvent.VK_S;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAsIcon;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+import net.sf.taverna.t2.workbench.file.impl.FileTypeFileFilter;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+@SuppressWarnings("serial")
+public class SaveWorkflowAsAction extends AbstractAction {
+ private static final String SAVE_WORKFLOW_AS = "Save workflow as...";
+ private static final String PREF_CURRENT_DIR = "currentDir";
+ private static Logger logger = Logger.getLogger(SaveWorkflowAsAction.class);
+ private FileManager fileManager;
+
+ public SaveWorkflowAsAction(FileManager fileManager) {
+ super(SAVE_WORKFLOW_AS, saveAsIcon);
+ this.fileManager = fileManager;
+ fileManager.addObserver(new FileManagerObserver());
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F6, 0));
+ putValue(MNEMONIC_KEY, VK_S);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Component parentComponent = null;
+ if (e.getSource() instanceof Component)
+ parentComponent = (Component) e.getSource();
+ WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+ if (workflowBundle == null) {
+ showMessageDialog(parentComponent, "No workflow open yet",
+ "No workflow to save", ERROR_MESSAGE);
+ return;
+ }
+ saveCurrentDataflow(parentComponent);
+ }
+
+ public boolean saveCurrentDataflow(Component parentComponent) {
+ WorkflowBundle workflowBundle = fileManager.getCurrentDataflow();
+ return saveDataflow(parentComponent, workflowBundle);
+ }
+
+ private String determineFileName(final WorkflowBundle workflowBundle) {
+ String result;
+ Object source = fileManager.getDataflowSource(workflowBundle);
+ String fileName = null;
+ if (source instanceof File)
+ fileName = ((File) source).getName();
+ else if (source instanceof URL)
+ fileName = ((URL) source).getPath();
+
+ if (fileName != null) {
+ int lastIndex = fileName.lastIndexOf(".");
+ if (lastIndex > 0)
+ fileName = fileName.substring(0, fileName.lastIndexOf("."));
+ result = fileName;
+ } else {
+ Workflow mainWorkflow = workflowBundle.getMainWorkflow();
+ if (mainWorkflow != null)
+ result = mainWorkflow.getName();
+ else
+ result = workflowBundle.getName();
+ }
+ return result;
+ }
+
+ public boolean saveDataflow(Component parentComponent, WorkflowBundle workflowBundle) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ JFileChooser fileChooser = new JFileChooser();
+ Preferences prefs = Preferences.userNodeForPackage(getClass());
+ String curDir = prefs
+ .get(PREF_CURRENT_DIR, System.getProperty("user.home"));
+ fileChooser.setDialogTitle(SAVE_WORKFLOW_AS);
+
+ fileChooser.resetChoosableFileFilters();
+ fileChooser.setAcceptAllFileFilterUsed(false);
+
+ List<FileFilter> fileFilters = fileManager
+ .getSaveFileFilters(File.class);
+ if (fileFilters.isEmpty()) {
+ logger.warn("No file types found for saving workflow "
+ + workflowBundle);
+ showMessageDialog(parentComponent,
+ "No file types found for saving workflow.", "Error",
+ ERROR_MESSAGE);
+ return false;
+ }
+ for (FileFilter fileFilter : fileFilters)
+ fileChooser.addChoosableFileFilter(fileFilter);
+ fileChooser.setFileFilter(fileFilters.get(0));
+ fileChooser.setCurrentDirectory(new File(curDir));
+
+ File possibleName = new File(determineFileName(workflowBundle));
+ boolean tryAgain = true;
+ while (tryAgain) {
+ tryAgain = false;
+ fileChooser.setSelectedFile(possibleName);
+ int returnVal = fileChooser.showSaveDialog(parentComponent);
+ if (returnVal == APPROVE_OPTION) {
+ prefs.put(PREF_CURRENT_DIR, fileChooser.getCurrentDirectory()
+ .toString());
+ File file = fileChooser.getSelectedFile();
+ FileTypeFileFilter fileFilter = (FileTypeFileFilter) fileChooser
+ .getFileFilter();
+ FileType fileType = fileFilter.getFileType();
+ String extension = "." + fileType.getExtension();
+ if (!file.getName().toLowerCase().endsWith(extension)) {
+ String newName = file.getName() + extension;
+ file = new File(file.getParentFile(), newName);
+ }
+
+ // TODO: Open in separate thread to avoid hanging UI
+ try {
+ try {
+ fileManager.saveDataflow(workflowBundle, fileType,
+ file, true);
+ logger.info("Saved workflow " + workflowBundle + " to "
+ + file);
+ return true;
+ } catch (OverwriteException ex) {
+ logger.info("File already exists: " + file);
+ String msg = "Are you sure you want to overwrite existing file "
+ + file + "?";
+ int ret = showConfirmDialog(parentComponent, msg,
+ "File already exists", YES_NO_CANCEL_OPTION);
+ if (ret == YES_OPTION) {
+ fileManager.saveDataflow(workflowBundle, fileType,
+ file, false);
+ logger.info("Saved workflow " + workflowBundle
+ + " by overwriting " + file);
+ return true;
+ } else if (ret == NO_OPTION) {
+ tryAgain = true;
+ continue;
+ } else {
+ logger.info("Aborted overwrite of " + file);
+ return false;
+ }
+ }
+ } catch (SaveException ex) {
+ logger.warn("Could not save workflow to " + file, ex);
+ showMessageDialog(parentComponent,
+ "Could not save workflow to " + file + ": \n\n"
+ + ex.getMessage(), "Warning",
+ WARNING_MESSAGE);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected void updateEnabledStatus(WorkflowBundle workflowBundle) {
+ setEnabled(workflowBundle != null);
+ }
+
+ private final class FileManagerObserver implements Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof SetCurrentDataflowEvent)
+ updateEnabledStatus(((SetCurrentDataflowEvent) message)
+ .getDataflow());
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
new file mode 100644
index 0000000..6c0be19
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/hooks/CloseWorkflowsOnShutdown.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2010 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.workbench.file.impl.hooks;
+
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseAllWorkflowsAction;
+
+/**
+ * Close open workflows (and ask the user if she wants to save changes) on
+ * shutdown.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class CloseWorkflowsOnShutdown implements ShutdownSPI {
+ private CloseAllWorkflowsAction closeAllWorkflowsAction;
+
+ public CloseWorkflowsOnShutdown(EditManager editManager,
+ FileManager fileManager) {
+ closeAllWorkflowsAction = new CloseAllWorkflowsAction(editManager,
+ fileManager);
+ }
+
+ @Override
+ public int positionHint() {
+ /*
+ * Quite early, we don't want to do various clean-up in case the user
+ * clicks Cancel
+ */
+ return 50;
+ }
+
+ @Override
+ public boolean shutdown() {
+ return closeAllWorkflowsAction.closeAllWorkflows(null);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java
new file mode 100644
index 0000000..e8e5252
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseAllMenuAction.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseAllWorkflowsAction;
+
+public class FileCloseAllMenuAction extends AbstractMenuAction {
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileCloseAll");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileCloseAllMenuAction(EditManager editManager,
+ FileManager fileManager) {
+ super(FILE_URI, 39, FILE_CLOSE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CloseAllWorkflowsAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java
new file mode 100644
index 0000000..a97219f
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileCloseMenuAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseWorkflowAction;
+
+public class FileCloseMenuAction extends AbstractMenuAction {
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileClose");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileCloseMenuAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_URI, 30, FILE_CLOSE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CloseWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java
new file mode 100644
index 0000000..3a48e0d
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileNewMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.NewWorkflowAction;
+
+public class FileNewMenuAction extends AbstractMenuAction {
+ private static final URI FILE_NEW_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileNew");
+ private final FileManager fileManager;
+
+ public FileNewMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 10, FILE_NEW_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new NewWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java
new file mode 100644
index 0000000..9af1d6b
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenFromURLMenuAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowFromURLAction;
+
+public class FileOpenFromURLMenuAction extends AbstractMenuAction {
+
+ private static final URI FILE_OPEN_FROM_URL_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenURL");
+ private final FileManager fileManager;
+
+ public FileOpenFromURLMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 30, FILE_OPEN_FROM_URL_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowFromURLAction(null, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java
new file mode 100644
index 0000000..4ee4e39
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowAction;
+
+public class FileOpenMenuAction extends AbstractMenuAction {
+ private static final URI FILE_OPEN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpen");
+ private final FileManager fileManager;
+
+ public FileOpenMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 20, FILE_OPEN_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java
new file mode 100644
index 0000000..46ef476
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileOpenMenuSection extends AbstractMenuSection {
+ public static final URI FILE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ public static final URI FILE_OPEN_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenSection");
+
+ public FileOpenMenuSection() {
+ super(FILE_URI, 20, FILE_OPEN_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java
new file mode 100644
index 0000000..76ef759
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileOpenRecentMenuAction.java
@@ -0,0 +1,418 @@
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_0;
+import static java.awt.event.KeyEvent.VK_R;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static javax.swing.Action.NAME;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection.FILE_OPEN_SECTION_URI;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.events.AbstractDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SavedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class FileOpenRecentMenuAction extends AbstractMenuCustom implements
+ Observer<FileManagerEvent> {
+ public static final URI RECENT_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileOpenRecent");
+ private static final String CONF = "conf";
+ private static Logger logger = Logger
+ .getLogger(FileOpenRecentMenuAction.class);
+ private static final String RECENT_WORKFLOWS_XML = "recentWorkflows.xml";
+ private static final int MAX_ITEMS = 10;
+
+ private FileManager fileManager;
+ private ApplicationConfiguration applicationConfiguration;
+ private JMenu menu;
+ private List<Recent> recents = new ArrayList<>();
+ private Thread loadRecentThread;
+
+ public FileOpenRecentMenuAction(FileManager fileManager) {
+ super(FILE_OPEN_SECTION_URI, 30, RECENT_URI);
+ this.fileManager = fileManager;
+ fileManager.addObserver(this);
+ }
+
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ FileManager fileManager = (FileManager) sender;
+ if (message instanceof OpenedDataflowEvent
+ || message instanceof SavedDataflowEvent) {
+ AbstractDataflowEvent dataflowEvent = (AbstractDataflowEvent) message;
+ WorkflowBundle dataflow = dataflowEvent.getDataflow();
+ Object dataflowSource = fileManager.getDataflowSource(dataflow);
+ FileType dataflowType = fileManager.getDataflowType(dataflow);
+ addRecent(dataflowSource, dataflowType);
+ }
+ if (message instanceof ClosedDataflowEvent)
+ // Make sure enabled/disabled status is correct
+ updateRecentMenu();
+ }
+
+ public void updateRecentMenu() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ updateRecentMenuGUI();
+ }
+ });
+ saveRecent();
+ }
+
+ protected void addRecent(Object dataflowSource, FileType dataflowType) {
+ if (dataflowSource == null)
+ return;
+ if (!(dataflowSource instanceof Serializable)) {
+ logger.warn("Can't serialize workflow source for 'Recent workflows': "
+ + dataflowSource);
+ return;
+ }
+ synchronized (recents) {
+ Recent recent = new Recent((Serializable) dataflowSource, dataflowType);
+ if (recents.contains(recent))
+ recents.remove(recent);
+ recents.add(0, recent); // Add to front
+ }
+ updateRecentMenu();
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ action = new DummyAction("Recent workflows");
+ action.putValue(MNEMONIC_KEY, VK_R);
+ menu = new JMenu(action);
+ // Disabled until we have loaded the recent workflows
+ menu.setEnabled(false);
+ loadRecentThread = new Thread("Loading recent workflow menu") {
+ // Avoid hanging GUI initialization while deserialising
+ @Override
+ public void run() {
+ loadRecent();
+ updateRecentMenu();
+ }
+ };
+ loadRecentThread.start();
+ return menu;
+ }
+
+ protected synchronized void loadRecent() {
+ File confDir = new File(applicationConfiguration.getApplicationHomeDir(), CONF);
+ confDir.mkdir();
+ File recentFile = new File(confDir, RECENT_WORKFLOWS_XML);
+ if (!recentFile.isFile())
+ return;
+ try {
+ loadRecent(recentFile);
+ } catch (JDOMException|IOException e) {
+ logger.warn("Could not read recent workflows from file "
+ + recentFile, e);
+ }
+ }
+
+ private void loadRecent(File recentFile) throws FileNotFoundException,
+ IOException, JDOMException {
+ SAXBuilder builder = new SAXBuilder();
+ @SuppressWarnings("unused")
+ Document document;
+ try (InputStream fileInputStream = new BufferedInputStream(
+ new FileInputStream(recentFile))) {
+ document = builder.build(fileInputStream);
+ }
+ synchronized (recents) {
+ recents.clear();
+ //RecentDeserializer deserialiser = new RecentDeserializer();
+ try {
+ // recents.addAll(deserialiser.deserializeRecent(document
+ // .getRootElement()));
+ } catch (Exception e) {
+ logger.warn("Could not read recent workflows from file "
+ + recentFile, e);
+ }
+ }
+ }
+
+ protected synchronized void saveRecent() {
+ File confDir = new File(applicationConfiguration.getApplicationHomeDir(), CONF);
+ confDir.mkdir();
+ File recentFile = new File(confDir, RECENT_WORKFLOWS_XML);
+
+ try {
+ saveRecent(recentFile);
+// } catch (JDOMException e) {
+// logger.warn("Could not generate XML for recent workflows to file "
+// + recentFile, e);
+ } catch (IOException e) {
+ logger.warn("Could not write recent workflows to file "
+ + recentFile, e);
+ }
+ }
+
+ private void saveRecent(File recentFile) throws FileNotFoundException,
+ IOException {
+ // RecentSerializer serializer = new RecentSerializer();
+ // XMLOutputter outputter = new XMLOutputter();
+
+ // Element serializedRecent;
+ synchronized (recents) {
+ if (recents.size() > MAX_ITEMS)
+ // Remove excess entries
+ recents.subList(MAX_ITEMS, recents.size()).clear();
+ // serializedRecent = serializer.serializeRecent(recents);
+ }
+ try (OutputStream outputStream = new BufferedOutputStream(
+ new FileOutputStream(recentFile))) {
+ // outputter.output(serializedRecent, outputStream);
+ }
+ }
+
+ protected void updateRecentMenuGUI() {
+ int items = 0;
+ menu.removeAll();
+ synchronized (recents) {
+ for (Recent recent : recents) {
+ if (++items >= MAX_ITEMS)
+ break;
+ OpenRecentAction openRecentAction = new OpenRecentAction(
+ recent, fileManager);
+ if (fileManager.getDataflowBySource(recent.getDataflowSource()) != null)
+ openRecentAction.setEnabled(false);
+ // else setEnabled(true)
+ JMenuItem menuItem = new JMenuItem(openRecentAction);
+ if (items < 10) {
+ openRecentAction.putValue(NAME, items + " "
+ + openRecentAction.getValue(NAME));
+ menuItem.setMnemonic(VK_0 + items);
+ }
+ menu.add(menuItem);
+ }
+ }
+ menu.setEnabled(items > 0);
+ menu.revalidate();
+ }
+
+ @SuppressWarnings("serial")
+ public static class OpenRecentAction extends AbstractAction implements
+ Runnable {
+ private final Recent recent;
+ private Component component = null;
+ private final FileManager fileManager;
+
+ public OpenRecentAction(Recent recent, FileManager fileManager) {
+ this.recent = recent;
+ this.fileManager = fileManager;
+ Serializable source = recent.getDataflowSource();
+ String name;
+ if (source instanceof File)
+ name = ((File) source).getAbsolutePath();
+ else
+ name = source.toString();
+ this.putValue(NAME, name);
+ this.putValue(SHORT_DESCRIPTION, "Open the workflow " + name);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ component = null;
+ if (e.getSource() instanceof Component)
+ component = (Component) e.getSource();
+ setEnabled(false);
+ new Thread(this, "Opening workflow from "
+ + recent.getDataflowSource()).start();
+ }
+
+ /**
+ * Opening workflow in separate thread
+ */
+ @Override
+ public void run() {
+ final Serializable source = recent.getDataflowSource();
+ try {
+ fileManager.openDataflow(recent.makefileType(), source);
+ } catch (OpenException ex) {
+ logger.warn("Failed to open the workflow from " + source
+ + " \n", ex);
+ showMessageDialog(component,
+ "Failed to open the workflow from url " + source
+ + " \n" + ex.getMessage(), "Error!",
+ ERROR_MESSAGE);
+ } finally {
+ setEnabled(true);
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class Recent implements Serializable {
+ private final class RecentFileType extends FileType {
+ @Override
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ @Override
+ public String getExtension() {
+ return extension;
+ }
+
+ @Override
+ public String getDescription() {
+ return "File type " + extension + " " + mimeType;
+ }
+ }
+
+ private Serializable dataflowSource;
+ private String mimeType;
+ private String extension;
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public void setExtension(String extension) {
+ this.extension = extension;
+ }
+
+ public Recent() {
+ }
+
+ public FileType makefileType() {
+ if (mimeType == null && extension == null)
+ return null;
+ return new RecentFileType();
+ }
+
+ public Recent(Serializable dataflowSource, FileType dataflowType) {
+ setDataflowSource(dataflowSource);
+ if (dataflowType != null) {
+ setMimeType(dataflowType.getMimeType());
+ setExtension(dataflowType.getExtension());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime
+ * result
+ + ((dataflowSource == null) ? 0 : dataflowSource.hashCode());
+ result = prime * result
+ + ((extension == null) ? 0 : extension.hashCode());
+ result = prime * result
+ + ((mimeType == null) ? 0 : mimeType.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof Recent))
+ return false;
+ Recent other = (Recent) obj;
+
+ if (dataflowSource == null) {
+ if (other.dataflowSource != null)
+ return false;
+ } else if (!dataflowSource.equals(other.dataflowSource))
+ return false;
+
+ if (extension == null) {
+ if (other.extension != null)
+ return false;
+ } else if (!extension.equals(other.extension))
+ return false;
+
+ if (mimeType == null) {
+ if (other.mimeType != null)
+ return false;
+ } else if (!mimeType.equals(other.mimeType))
+ return false;
+
+ return true;
+ }
+
+ public Serializable getDataflowSource() {
+ return dataflowSource;
+ }
+
+ public void setDataflowSource(Serializable dataflowSource) {
+ this.dataflowSource = dataflowSource;
+ }
+
+ @Override
+ public String toString() {
+ return getDataflowSource() + "";
+ }
+ }
+
+ // TODO find new serialization
+// protected static class RecentDeserializer extends AbstractXMLDeserializer {
+// public Collection<Recent> deserializeRecent(Element el) {
+// return (Collection<Recent>) super.createBean(el, getClass()
+// .getClassLoader());
+// }
+// }
+//
+// protected static class RecentSerializer extends AbstractXMLSerializer {
+// public Element serializeRecent(List<Recent> x) throws JDOMException,
+// IOException {
+// Element beanAsElement = super.beanAsElement(x);
+// return beanAsElement;
+// }
+// }
+
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java
new file mode 100644
index 0000000..86edacb
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAllMenuAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveAllWorkflowsAction;
+
+public class FileSaveAllMenuAction extends AbstractMenuAction {
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileSaveAllMenuAction(EditManager editManager,
+ FileManager fileManager) {
+ super(FILE_SAVE_SECTION_URI, 30);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveAllWorkflowsAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java
new file mode 100644
index 0000000..77917c9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveAsMenuAction.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAsAction;
+
+public class FileSaveAsMenuAction extends AbstractMenuAction {
+ private final FileManager fileManager;
+
+ public FileSaveAsMenuAction(FileManager fileManager) {
+ super(FILE_SAVE_SECTION_URI, 20);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveWorkflowAsAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java
new file mode 100644
index 0000000..eeaecb3
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection.FILE_SAVE_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAction;
+
+public class FileSaveMenuAction extends AbstractMenuAction {
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public FileSaveMenuAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_SAVE_SECTION_URI, 10);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java
new file mode 100644
index 0000000..a75a855
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/FileSaveMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileSaveMenuSection extends AbstractMenuSection {
+ public static final URI FILE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file");
+ public static final URI FILE_SAVE_SECTION_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileSaveSection");
+
+ public FileSaveMenuSection() {
+ super(FILE_URI, 40, FILE_SAVE_SECTION_URI);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java
new file mode 100644
index 0000000..e056572
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/menu/WorkflowsMenu.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_0;
+import static java.awt.event.KeyEvent.VK_W;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static javax.swing.SwingUtilities.invokeLater;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.AbstractDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class WorkflowsMenu extends AbstractMenuCustom {
+ private EditManagerObserver editManagerObserver = new EditManagerObserver();
+ private FileManager fileManager;
+ private FileManagerObserver fileManagerObserver = new FileManagerObserver();
+
+ private JMenu workflowsMenu;
+
+ public WorkflowsMenu(EditManager editManager, FileManager fileManager) {
+ super(DEFAULT_MENU_BAR, 900);
+ this.fileManager = fileManager;
+ fileManager.addObserver(fileManagerObserver);
+ editManager.addObserver(editManagerObserver);
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ DummyAction action = new DummyAction("Workflows");
+ action.putValue(MNEMONIC_KEY, VK_W);
+
+ workflowsMenu = new JMenu(action);
+
+ updateWorkflowsMenu();
+ return workflowsMenu;
+ }
+
+ public void updateWorkflowsMenu() {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ updateWorkflowsMenuUI();
+ }
+ });
+ }
+
+ protected void updateWorkflowsMenuUI() {
+ workflowsMenu.setEnabled(false);
+ workflowsMenu.removeAll();
+ ButtonGroup workflowsGroup = new ButtonGroup();
+
+ int i = 0;
+ WorkflowBundle currentDataflow = fileManager.getCurrentDataflow();
+ for (WorkflowBundle workflowBundle : fileManager.getOpenDataflows()) {
+ String name = fileManager.getDataflowName(workflowBundle);
+ if (fileManager.isDataflowChanged(workflowBundle))
+ name = "*" + name;
+ // A counter
+ name = ++i + " " + name;
+
+ SwitchWorkflowAction switchWorkflowAction = new SwitchWorkflowAction(
+ name, workflowBundle);
+ if (i < 10)
+ switchWorkflowAction.putValue(MNEMONIC_KEY, new Integer(VK_0
+ + i));
+
+ JRadioButtonMenuItem switchWorkflowMenuItem = new JRadioButtonMenuItem(
+ switchWorkflowAction);
+ workflowsGroup.add(switchWorkflowMenuItem);
+ if (workflowBundle.equals(currentDataflow))
+ switchWorkflowMenuItem.setSelected(true);
+ workflowsMenu.add(switchWorkflowMenuItem);
+ }
+ if (i == 0)
+ workflowsMenu.add(new NoWorkflowsOpen());
+ workflowsMenu.setEnabled(true);
+ workflowsMenu.revalidate();
+ }
+
+ private final class EditManagerObserver implements
+ Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender,
+ EditManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEditEvent)
+ updateWorkflowsMenu();
+ }
+ }
+
+ private final class FileManagerObserver implements
+ Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof AbstractDataflowEvent)
+ updateWorkflowsMenu();
+ // TODO: Don't rebuild whole menu
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private final class NoWorkflowsOpen extends AbstractAction {
+ private NoWorkflowsOpen() {
+ super("No workflows open");
+ setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // No-op
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private final class SwitchWorkflowAction extends AbstractAction {
+ private final WorkflowBundle workflowBundle;
+
+ private SwitchWorkflowAction(String name, WorkflowBundle workflowBundle) {
+ super(name);
+ this.workflowBundle = workflowBundle;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ }
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java
new file mode 100644
index 0000000..68ef3f9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/CloseToolbarAction.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.CloseWorkflowAction;
+
+/**
+ * Action to close the current workflow.
+ *
+ * @author Alex Nenadic
+ */
+public class CloseToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_CLOSE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarClose");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public CloseToolbarAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 30, FILE_CLOSE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new CloseWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java
new file mode 100644
index 0000000..257d590
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/FileToolbarMenuSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class FileToolbarMenuSection extends AbstractMenuSection {
+ public static final URI FILE_TOOLBAR_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarSection");
+
+ public FileToolbarMenuSection() {
+ super(DEFAULT_TOOL_BAR, 10, FILE_TOOLBAR_SECTION);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java
new file mode 100644
index 0000000..2c8e922
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/NewToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.NewWorkflowAction;
+
+public class NewToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_NEW_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarNew");
+ private final FileManager fileManager;
+
+ public NewToolbarAction(FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 10, FILE_NEW_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new NewWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java
new file mode 100644
index 0000000..ae99509
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowAction;
+
+public class OpenToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_OPEN_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarOpen");
+ private final FileManager fileManager;
+
+ public OpenToolbarAction(FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 20, FILE_OPEN_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowAction(fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java
new file mode 100644
index 0000000..2554063
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/OpenWorkflowFromURLToolbarAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.OpenWorkflowFromURLAction;
+
+public class OpenWorkflowFromURLToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_OPEN_FROM_URL_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarOpenFromURL");
+ private final FileManager fileManager;
+
+ public OpenWorkflowFromURLToolbarAction(FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 25, FILE_OPEN_FROM_URL_URI);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OpenWorkflowFromURLAction(null, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java
new file mode 100644
index 0000000..53ba720
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/toolbar/SaveToolbarAction.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection.FILE_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.impl.actions.SaveWorkflowAction;
+
+public class SaveToolbarAction extends AbstractMenuAction {
+ private static final URI FILE_SAVE_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#fileToolbarSave");
+ private final EditManager editManager;
+ private final FileManager fileManager;
+
+ public SaveToolbarAction(EditManager editManager, FileManager fileManager) {
+ super(FILE_TOOLBAR_SECTION, 40, FILE_SAVE_URI);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Action createAction() {
+ return new SaveWorkflowAction(editManager, fileManager);
+ }
+}
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..100915c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,20 @@
+net.sf.taverna.t2.workbench.file.impl.menu.FileCloseMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileNewMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenFromURLMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection
+net.sf.taverna.t2.workbench.file.impl.menu.FileOpenRecentMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAllMenuAction
+net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAsMenuAction
+
+net.sf.taverna.t2.workbench.file.impl.menu.WorkflowsMenu
+net.sf.taverna.t2.workbench.file.impl.menu.FileCloseAllMenuAction
+
+net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection
+net.sf.taverna.t2.workbench.file.impl.toolbar.NewToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.OpenToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.OpenWorkflowFromURLToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.SaveToolbarAction
+net.sf.taverna.t2.workbench.file.impl.toolbar.CloseToolbarAction
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..cc53d36
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.file.impl.hooks.CloseWorkflowsOnShutdown
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
new file mode 100644
index 0000000..cfd1c7a
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener
+net.sf.taverna.t2.workbench.file.impl.DataflowFromDataflowPersistenceHandler
\ No newline at end of file
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager
new file mode 100644
index 0000000..656feeb
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.file.FileManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.file.impl.FileManagerImpl
\ No newline at end of file
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml
new file mode 100644
index 0000000..7c6e290
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context-osgi.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="FileCloseMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.close" />
+ </service-properties>
+ </service>
+ <service ref="FileNewMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.new" />
+ </service-properties>
+ </service>
+ <service ref="FileOpenMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.open" />
+ </service-properties>
+ </service>
+ <service ref="FileOpenFromURLMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.open.url" />
+ </service-properties>
+ </service>
+ <service ref="FileOpenMenuSection" auto-export="interfaces" />
+ <service ref="FileOpenRecentMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.open.recent" />
+ </service-properties>
+ </service>
+ <service ref="FileSaveMenuSection" auto-export="interfaces" />
+ <service ref="FileSaveMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.save" />
+ </service-properties>
+ </service>
+ <service ref="FileSaveAllMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.save.all" />
+ </service-properties>
+ </service>
+ <service ref="FileSaveAsMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.save.as" />
+ </service-properties>
+ </service>
+ <service ref="WorkflowsMenu" auto-export="interfaces" />
+ <service ref="FileCloseAllMenuAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.close.all" />
+ </service-properties>
+ </service>
+ <service ref="FileToolbarMenuSection" auto-export="interfaces" />
+ <service ref="NewToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.new" />
+ </service-properties>
+ </service>
+ <service ref="OpenToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.open" />
+ </service-properties>
+ </service>
+ <service ref="OpenWorkflowFromURLToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.open.url" />
+ </service-properties>
+ </service>
+ <service ref="SaveToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.save" />
+ </service-properties>
+ </service>
+ <service ref="CloseToolbarAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="toolbar.close" />
+ </service-properties>
+ </service>
+
+ <service ref="T2DataflowOpener" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+
+ <service ref="WorkflowBundleOpener" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+ <service ref="WorkflowBundleSaver" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" />
+
+ <service ref="CloseWorkflowsOnShutdown" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+ <service ref="FileManagerImpl" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+ <reference id="workflowBundleIO" interface="uk.org.taverna.scufl2.api.io.WorkflowBundleIO" />
+
+ <list id="dataflowPersistenceHandlers" interface="net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler" cardinality="0..N">
+ <listener ref="DataflowPersistenceHandlerRegistry" bind-method="update" unbind-method="update" />
+ </list>
+</beans:beans>
diff --git a/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml
new file mode 100644
index 0000000..493df5f
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/resources/META-INF/spring/file-impl-context.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="FileCloseMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileCloseMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileNewMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileNewMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileOpenMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileOpenFromURLMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenFromURLMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileOpenMenuSection" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenMenuSection" />
+ <bean id="FileOpenRecentMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileOpenRecentMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ </bean>
+ <bean id="FileSaveMenuSection" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuSection" />
+ <bean id="FileSaveMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileSaveAllMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAllMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileSaveAsMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileSaveAsMenuAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="WorkflowsMenu" class="net.sf.taverna.t2.workbench.file.impl.menu.WorkflowsMenu">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileCloseAllMenuAction" class="net.sf.taverna.t2.workbench.file.impl.menu.FileCloseAllMenuAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="FileToolbarMenuSection" class="net.sf.taverna.t2.workbench.file.impl.toolbar.FileToolbarMenuSection" />
+ <bean id="NewToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.NewToolbarAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="OpenToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.OpenToolbarAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="OpenWorkflowFromURLToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.OpenWorkflowFromURLToolbarAction">
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="SaveToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.SaveToolbarAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+ <bean id="CloseToolbarAction" class="net.sf.taverna.t2.workbench.file.impl.toolbar.CloseToolbarAction">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="T2DataflowOpener" class="net.sf.taverna.t2.workbench.file.impl.T2DataflowOpener">
+ <property name="workflowBundleIO" ref="workflowBundleIO"/>
+ </bean>
+
+ <bean id="WorkflowBundleOpener" class="net.sf.taverna.t2.workbench.file.impl.WorkflowBundleOpener">
+ <property name="workflowBundleIO" ref="workflowBundleIO"/>
+ </bean>
+ <bean id="WorkflowBundleSaver" class="net.sf.taverna.t2.workbench.file.impl.WorkflowBundleSaver">
+ <property name="workflowBundleIO" ref="workflowBundleIO"/>
+ </bean>
+
+ <bean id="CloseWorkflowsOnShutdown" class="net.sf.taverna.t2.workbench.file.impl.hooks.CloseWorkflowsOnShutdown">
+ <constructor-arg ref="editManager" />
+ <constructor-arg>
+ <ref local="FileManagerImpl" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="FileManagerImpl" class="net.sf.taverna.t2.workbench.file.impl.FileManagerImpl">
+ <constructor-arg name="editManager" ref="editManager" />
+ <property name="dataflowPersistenceHandlerRegistry">
+ <ref local="DataflowPersistenceHandlerRegistry"/>
+ </property>
+ </bean>
+
+ <bean id="DataflowPersistenceHandlerRegistry" class="net.sf.taverna.t2.workbench.file.impl.DataflowPersistenceHandlerRegistry">
+ <property name="dataflowPersistenceHandlers" ref="dataflowPersistenceHandlers" />
+ </bean>
+
+
+</beans>
diff --git a/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java b/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java
new file mode 100644
index 0000000..691b278
--- /dev/null
+++ b/taverna-workbench-file-impl/src/test/java/net/sf/taverna/t2/workbench/file/impl/FileManagerTest.java
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.file.exceptions.OverwriteException;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+import net.sf.taverna.t2.workflow.edits.RenameEdit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleReader;
+import uk.org.taverna.scufl2.api.io.WorkflowBundleWriter;
+import uk.org.taverna.scufl2.rdfxml.RDFXMLReader;
+import uk.org.taverna.scufl2.rdfxml.RDFXMLWriter;
+import uk.org.taverna.scufl2.translator.t2flow.T2FlowReader;
+
+public class FileManagerTest {
+
+ private static final WorkflowBundleFileType WF_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+ private static final T2FlowFileType T2_FLOW_FILE_TYPE = new T2FlowFileType();
+
+ private static final String DUMMY_WORKFLOW_T2FLOW = "dummy-workflow.t2flow";
+
+ private FileManagerImpl fileManager;
+ private EditManager editManager;
+
+ private FileManagerObserver fileManagerObserver= new FileManagerObserver();;
+
+ @Test
+ public void close() throws Exception {
+ assertTrue("Non-empty set of open dataflows", fileManager
+ .getOpenDataflows().isEmpty());
+ WorkflowBundle dataflow = openDataflow();
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(dataflow), fileManager.getOpenDataflows());
+ fileManager.closeDataflow(dataflow, true);
+ assertNotSame(dataflow, fileManager.getOpenDataflows().get(0));
+ assertTrue("Did not insert empty dataflow after close", fileManager
+ .getOpenDataflows().get(0).getMainWorkflow().getProcessors().isEmpty());
+ }
+
+ @Test
+ public void openRemovesEmptyDataflow() throws Exception {
+ WorkflowBundle newDataflow = fileManager.newDataflow();
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(newDataflow), fileManager.getOpenDataflows());
+ WorkflowBundle dataflow = openDataflow();
+ // Should have removed newDataflow
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(dataflow), fileManager.getOpenDataflows());
+ }
+
+ @Test
+ public void isChanged() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ assertFalse("Dataflow should not have changed", fileManager
+ .isDataflowChanged(dataflow));
+
+ // Do a change
+ Processor emptyProcessor = new Processor();
+ Edit<Workflow> addProcessorEdit = new AddProcessorEdit(dataflow.getMainWorkflow(),
+ emptyProcessor);
+ editManager.doDataflowEdit(dataflow, addProcessorEdit);
+ assertTrue("Dataflow should have changed", fileManager
+ .isDataflowChanged(dataflow));
+
+ // Save it with the change
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertFalse("Dataflow should no longer be marked as changed",
+ fileManager.isDataflowChanged(dataflow));
+ }
+
+ @Ignore("Undo support for ischanged not yet implemented")
+ @Test
+ public void isChangedWithUndo() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ // Do a change
+ Processor emptyProcessor = new Processor();
+ Edit<Workflow> addProcessorEdit = new AddProcessorEdit(dataflow.getMainWorkflow(),
+ emptyProcessor);
+ editManager.doDataflowEdit(dataflow, addProcessorEdit);
+ assertTrue("Dataflow should have changed", fileManager
+ .isDataflowChanged(dataflow));
+ editManager.undoDataflowEdit(dataflow);
+ assertFalse(
+ "Dataflow should no longer be marked as changed after undo",
+ fileManager.isDataflowChanged(dataflow));
+ editManager.redoDataflowEdit(dataflow);
+ assertTrue("Dataflow should have changed after redo before save",
+ fileManager.isDataflowChanged(dataflow));
+
+ // Save it with the change
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertFalse("Dataflow should no longer be marked as changed",
+ fileManager.isDataflowChanged(dataflow));
+
+ editManager.undoDataflowEdit(dataflow);
+ assertTrue("Dataflow should have changed after undo", fileManager
+ .isDataflowChanged(dataflow));
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ editManager.redoDataflowEdit(dataflow);
+ assertTrue("Dataflow should have changed after redo after save",
+ fileManager.isDataflowChanged(dataflow));
+ }
+
+ @Test
+ public void isListed() throws Exception {
+ assertTrue("Non-empty set of open data flows", fileManager
+ .getOpenDataflows().isEmpty());
+ WorkflowBundle dataflow = openDataflow();
+ assertEquals("Unexpected list of open dataflows", Arrays
+ .asList(dataflow), fileManager.getOpenDataflows());
+ }
+
+ /**
+ * Always uses a <strong>new</strong> file manager instead of the instance
+ * one from {@link FileManager#getInstance()}.
+ *
+ * @see #getFileManagerInstance()
+ *
+ */
+ @Before
+ public void makeFileManager() {
+ System.setProperty("java.awt.headless", "true");
+ editManager = new EditManagerImpl();
+ fileManager = new FileManagerImpl(editManager);
+ fileManagerObserver = new FileManagerObserver();
+ fileManager.addObserver(fileManagerObserver);
+ WorkflowBundleIO workflowBundleIO = new WorkflowBundleIO();
+ workflowBundleIO.setReaders(Arrays.<WorkflowBundleReader>asList(new RDFXMLReader(), new T2FlowReader()));
+ workflowBundleIO.setWriters(Arrays.<WorkflowBundleWriter>asList(new RDFXMLWriter()));
+ T2DataflowOpener t2DataflowOpener = new T2DataflowOpener();
+ t2DataflowOpener.setWorkflowBundleIO(workflowBundleIO);
+ WorkflowBundleOpener workflowBundleOpener = new WorkflowBundleOpener();
+ workflowBundleOpener.setWorkflowBundleIO(workflowBundleIO);
+ WorkflowBundleSaver workflowBundleSaver = new WorkflowBundleSaver();
+ workflowBundleSaver.setWorkflowBundleIO(workflowBundleIO);
+ DataflowPersistenceHandlerRegistry dataflowPersistenceHandlerRegistry = new DataflowPersistenceHandlerRegistry();
+ dataflowPersistenceHandlerRegistry.setDataflowPersistenceHandlers(Arrays.asList(
+ new DataflowPersistenceHandler[] {t2DataflowOpener, workflowBundleOpener, workflowBundleSaver}));
+ dataflowPersistenceHandlerRegistry.updateColletions();
+ fileManager.setDataflowPersistenceHandlerRegistry(dataflowPersistenceHandlerRegistry);
+ }
+
+ @Test
+ public void open() throws Exception {
+ assertTrue("ModelMapObserver already contained messages",
+ fileManagerObserver.messages.isEmpty());
+ WorkflowBundle dataflow = openDataflow();
+ assertNotNull("Dataflow was not loaded", dataflow);
+ assertEquals("Loaded dataflow was not set as current dataflow",
+ dataflow, fileManager.getCurrentDataflow());
+ assertFalse("ModelMapObserver did not contain message",
+ fileManagerObserver.messages.isEmpty());
+ assertEquals("ModelMapObserver contained unexpected messages", 2,
+ fileManagerObserver.messages.size());
+ FileManagerEvent event = fileManagerObserver.messages.get(0);
+ assertTrue(event instanceof SetCurrentDataflowEvent);
+ assertEquals(dataflow, ((SetCurrentDataflowEvent) event).getDataflow());
+ }
+
+ @Test
+ public void openSilently() throws Exception {
+ assertTrue("ModelMapObserver already contained messages",
+ fileManagerObserver.messages.isEmpty());
+ URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+ DataflowInfo info = fileManager.openDataflowSilently(T2_FLOW_FILE_TYPE, url);
+
+ WorkflowBundle dataflow = info.getDataflow();
+ assertNotNull("Dataflow was not loaded", dataflow);
+
+ assertNotSame("Loaded dataflow was set as current dataflow",
+ dataflow, fileManager.getCurrentDataflow());
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+ }
+
+ @Test
+ public void canSaveDataflow() throws Exception {
+ WorkflowBundle savedDataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ fileManager.saveDataflow(savedDataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ assertTrue(fileManager.canSaveWithoutDestination(savedDataflow));
+ fileManager.saveDataflow(savedDataflow, true);
+ fileManager.closeDataflow(savedDataflow, true);
+
+ WorkflowBundle otherFlow = fileManager.openDataflow(WF_BUNDLE_FILE_TYPE, dataflowFile.toURI()
+ .toURL());
+ assertTrue(fileManager.canSaveWithoutDestination(otherFlow));
+ }
+
+ @Test
+ public void save() throws Exception {
+ WorkflowBundle savedDataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ assertFalse("File should not exist", dataflowFile.isFile());
+ fileManager.saveDataflow(savedDataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertTrue("File should exist", dataflowFile.isFile());
+ WorkflowBundle loadedDataflow = fileManager.openDataflow(WF_BUNDLE_FILE_TYPE, dataflowFile.toURI()
+ .toURL());
+ assertNotSame("Dataflow was not reopened", savedDataflow,
+ loadedDataflow);
+ assertEquals("Unexpected number of processors in saved dataflow", 1,
+ savedDataflow.getMainWorkflow().getProcessors().size());
+ assertEquals("Unexpected number of processors in loaded dataflow", 1,
+ loadedDataflow.getMainWorkflow().getProcessors().size());
+
+ Processor savedProcessor = savedDataflow.getMainWorkflow().getProcessors().first();
+ Processor loadedProcessor = loadedDataflow.getMainWorkflow().getProcessors().first();
+ assertEquals("Loaded processor had wrong name", savedProcessor
+ .getName(), loadedProcessor.getName());
+
+ // TODO convert to scufl2
+// BeanshellActivity savedActivity = (BeanshellActivity) savedProcessor
+// .getActivityList().get(0);
+// BeanshellActivity loadedActivity = (BeanshellActivity) loadedProcessor
+// .getActivityList().get(0);
+// String savedScript = savedActivity.getConfiguration().getScript();
+// String loadedScript = loadedActivity.getConfiguration().getScript();
+// assertEquals("Unexpected saved script",
+// "String output = input + \"XXX\";", savedScript);
+// assertEquals("Loaded script did not matched saved script", savedScript,
+// loadedScript);
+ }
+
+ @Test
+ public void saveSilent() throws Exception {
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+
+ URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+ DataflowInfo info = fileManager.openDataflowSilently(T2_FLOW_FILE_TYPE, url);
+ WorkflowBundle dataflow = info.getDataflow();
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ dataflowFile.delete();
+ assertFalse("File should not exist", dataflowFile.isFile());
+
+ fileManager.saveDataflowSilently(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, false);
+ assertTrue("File should exist", dataflowFile.isFile());
+
+ assertTrue("ModelMapObserver contained unexpected messages",
+ fileManagerObserver.messages.isEmpty());
+
+ }
+
+ @Test
+ public void saveOverwriteAgain() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.delete();
+ dataflowFile.deleteOnExit();
+ // File did NOT exist, should not fail
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+
+ Processor processor = dataflow.getMainWorkflow().getProcessors().first();
+ Edit<Processor> renameEdit = new RenameEdit<Processor>(processor,
+ processor.getName() + "-changed");
+ editManager.doDataflowEdit(dataflow, renameEdit);
+
+ // Last save was OURs, so should *not* fail - even if we now use
+ // the specific saveDataflow() method
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+
+ //Thread.sleep(1500);
+ WorkflowBundle otherFlow = openDataflow();
+ // Saving another flow to same file should still fail
+ try {
+ fileManager.saveDataflow(otherFlow,WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ fail("Should have thrown OverwriteException");
+ } catch (OverwriteException ex) {
+ // Expected
+ }
+ }
+
+ @Test(expected = OverwriteException.class)
+ public void saveOverwriteWarningFails() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.deleteOnExit();
+ // Should fail as file already exists
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ }
+
+ @Test
+ public void saveOverwriteWarningWorks() throws Exception {
+ WorkflowBundle dataflow = openDataflow();
+ File dataflowFile = File.createTempFile("test", ".t2flow");
+ dataflowFile.delete();
+ dataflowFile.deleteOnExit();
+ // File did NOT exist, should not fail
+ fileManager.saveDataflow(dataflow, WF_BUNDLE_FILE_TYPE, dataflowFile, true);
+ }
+
+ @After
+ public void stopListeningToModelMap() {
+ fileManager.removeObserver(fileManagerObserver);
+ }
+
+ protected WorkflowBundle openDataflow() throws OpenException {
+ URL url = getClass().getResource(DUMMY_WORKFLOW_T2FLOW);
+ assertNotNull(url);
+ WorkflowBundle dataflow = fileManager.openDataflow(T2_FLOW_FILE_TYPE, url);
+ assertNotNull(dataflow);
+ return dataflow;
+ }
+
+ private final class FileManagerObserver implements Observer<FileManagerEvent> {
+ protected List<FileManagerEvent> messages = new ArrayList<FileManagerEvent>();
+
+ @Override
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message) throws Exception {
+ messages.add(message);
+ if (message instanceof SetCurrentDataflowEvent) {
+ assertTrue("Dataflow was not listed as open when set current",
+ fileManager.getOpenDataflows().contains(
+ ((SetCurrentDataflowEvent) message).getDataflow()));
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow b/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow
new file mode 100644
index 0000000..b9a1075
--- /dev/null
+++ b/taverna-workbench-file-impl/src/test/resources/net/sf/taverna/t2/workbench/file/impl/dummy-workflow.t2flow
@@ -0,0 +1,157 @@
+<workflow xmlns="http://taverna.sf.net/2008/xml/t2flow" version="1" producedBy="test">
+ <dataflow id="ec0991ba-275c-49ed-b1d6-38534180fb7c" role="top">
+ <name>simple_workflow_with_input</name>
+ <inputPorts>
+ <port>
+ <name>input</name>
+ <depth>0</depth>
+ <granularDepth>0</granularDepth>
+ </port>
+ </inputPorts>
+ <outputPorts>
+ <port>
+ <name>output</name>
+ </port>
+ </outputPorts>
+ <processors>
+ <processor>
+ <name>Concat_XXX</name>
+ <inputPorts>
+ <port>
+ <name>input</name>
+ <depth>0</depth>
+ </port>
+ </inputPorts>
+ <outputPorts>
+ <port>
+ <name>output</name>
+ <depth>0</depth>
+ <granularDepth>0</granularDepth>
+ </port>
+ </outputPorts>
+ <annotations />
+ <activities>
+ <activity>
+ <class>
+ net.sf.taverna.t2.activities.beanshell.BeanshellActivity
+ </class>
+ <inputMap>
+ <map from="input" to="input" />
+ </inputMap>
+ <outputMap>
+ <map from="output" to="output" />
+ </outputMap>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean
+ xmlns="">
+ <script>String output = input + "XXX";</script>
+ <dependencies />
+ <inputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ <handledReferenceSchemes />
+ <translatedElementType>java.lang.String</translatedElementType>
+ <allowsLiteralValues>true</allowsLiteralValues>
+ <name>input</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityInputPortDefinitionBean>
+ </inputs>
+ <outputs>
+ <net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ <granularDepth>0</granularDepth>
+ <name>output</name>
+ <depth>0</depth>
+ <mimeTypes>
+ <string>'text/plain'</string>
+ </mimeTypes>
+ </net.sf.taverna.t2.workflowmodel.processor.activity.config.ActivityOutputPortDefinitionBean>
+ </outputs>
+ </net.sf.taverna.t2.activities.beanshell.BeanshellActivityConfigurationBean>
+ </configBean>
+ </activity>
+ </activities>
+ <dispatchStack>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Parallelize
+ </class>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig
+ xmlns="">
+ <maxJobs>1</maxJobs>
+ </net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ParallelizeConfig>
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.ErrorBounce
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Failover
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Retry
+ </class>
+ <configBean encoding="xstream">
+ <net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig
+ xmlns="">
+ <backoffFactor>1.0</backoffFactor>
+ <initialDelay>0</initialDelay>
+ <maxDelay>0</maxDelay>
+ <maxRetries>0</maxRetries>
+ </net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.RetryConfig>
+ </configBean>
+ </dispatchLayer>
+ <dispatchLayer>
+ <class>
+ net.sf.taverna.t2.workflowmodel.processor.dispatch.layers.Invoke
+ </class>
+ <configBean encoding="xstream">
+ <null xmlns="" />
+ </configBean>
+ </dispatchLayer>
+ </dispatchStack>
+ <iterationStrategyStack>
+ <iteration>
+ <strategy>
+ <port name="input" depth="0" />
+ </strategy>
+ </iteration>
+ </iterationStrategyStack>
+ </processor>
+ </processors>
+ <conditions />
+ <datalinks>
+ <datalink>
+ <sink type="processor">
+ <processor>Concat_XXX</processor>
+ <port>input</port>
+ </sink>
+ <source type="dataflow">
+ <port>input</port>
+ </source>
+ </datalink>
+ <datalink>
+ <sink type="dataflow">
+ <port>output</port>
+ </sink>
+ <source type="processor">
+ <processor>Concat_XXX</processor>
+ <port>output</port>
+ </source>
+ </datalink>
+ </datalinks>
+ </dataflow>
+</workflow>
diff --git a/taverna-workbench-helper/pom.xml b/taverna-workbench-helper/pom.xml
new file mode 100644
index 0000000..70c0621
--- /dev/null
+++ b/taverna-workbench-helper/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>helper</artifactId>
+ <name>Help System (legacy dependency)</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-httpproxy-config/pom.xml b/taverna-workbench-httpproxy-config/pom.xml
new file mode 100644
index 0000000..f1a8328
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>httpproxy-config</artifactId>
+ <packaging>bundle</packaging>
+ <name>HTTP Proxy configuration</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java
new file mode 100644
index 0000000..1229d57
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationPanel.java
@@ -0,0 +1,582 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.httpproxy.config;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static net.sf.taverna.t2.workbench.helper.Helper.showHelp;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.PROXY_USE_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_NON_PROXY_HOSTS;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_HOST;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_PASSWORD;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_PORT;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.SYSTEM_PROXY_USER;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_NON_PROXY_HOSTS;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_HOST;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_PASSWORD;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_PORT;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.TAVERNA_PROXY_USER;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_NO_PROXY_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_SPECIFIED_VALUES_OPTION;
+import static uk.org.taverna.configuration.proxy.HttpProxyConfiguration.USE_SYSTEM_PROPERTIES_OPTION;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import uk.org.taverna.configuration.proxy.HttpProxyConfiguration;
+
+/**
+ * The HttpProxyConfigurationPanel provides the user interface to a
+ * {@link HttpProxyConfiguration} to determine how HTTP Connections are made by
+ * Taverna.
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public class HttpProxyConfigurationPanel extends JPanel {
+ static final long serialVersionUID = 3668473431971125038L;
+ /**
+ * The size of the field for the JTextFields.
+ */
+ private static int TEXTFIELD_SIZE = 25;
+
+ private final HttpProxyConfiguration httpProxyConfiguration;
+ /**
+ * RadioButtons that are in a common ButtonGroup. Selecting one of them
+ * indicates whether the system http proxy settings, the ad hoc specified
+ * values or no proxy settings at all should be used.
+ */
+ private JRadioButton useSystemProperties;
+ private JRadioButton useSpecifiedValues;
+ private JRadioButton useNoProxy;
+ /**
+ * JTextFields and one DialogTextArea to hold the settings for the HTTP
+ * proxy properties. The values are only editable if the user picks
+ * useSpecifiedValues.
+ */
+ private JTextField proxyHostField;
+ private JTextField proxyPortField;
+ private JTextField proxyUserField;
+ private JTextField proxyPasswordField;
+ private DialogTextArea nonProxyHostsArea;
+ private JScrollPane nonProxyScrollPane;
+ /**
+ * A string that indicates which HTTP setting option the user has currently
+ * picked. This does not necesarily match that which has been applied.
+ */
+ private String shownOption = USE_SYSTEM_PROPERTIES_OPTION;
+
+ /**
+ * The HttpProxyConfigurationPanel consists of a set of properties where the
+ * configuration values for HTTP can be specified and a set of buttons where
+ * the more general apply, help etc. appear.
+ */
+ public HttpProxyConfigurationPanel(
+ HttpProxyConfiguration httpProxyConfiguration) {
+ this.httpProxyConfiguration = httpProxyConfiguration;
+ initComponents();
+ }
+
+ /**
+ * Populates the panel with a representation of the current HTTP proxy
+ * settings for the specified {@link HttpProxyConfiguration} and also the
+ * capability to alter them.
+ */
+ private void initComponents() {
+ shownOption = httpProxyConfiguration.getProperty(PROXY_USE_OPTION);
+
+ this.setLayout(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Title describing what kind of settings we are configuring here
+ JTextArea descriptionText = new JTextArea("HTTP proxy configuration");
+ descriptionText.setLineWrap(true);
+ descriptionText.setWrapStyleWord(true);
+ descriptionText.setEditable(false);
+ descriptionText.setFocusable(false);
+ descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+ gbc.anchor = WEST;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.fill = HORIZONTAL;
+ this.add(descriptionText, gbc);
+
+ /**
+ * Generate the three radio buttons and put them in a group. Each button
+ * is bound to an action that alters the shownOption and re-populates
+ * the shown HTTP property fields.
+ */
+ useNoProxy = new JRadioButton("Do not use a proxy");
+ useNoProxy.setAlignmentX(LEFT_ALIGNMENT);
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(useNoProxy, gbc);
+ ActionListener useNoProxyListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ shownOption = USE_NO_PROXY_OPTION;
+ populateFields();
+ }
+ };
+ useNoProxy.addActionListener(useNoProxyListener);
+
+ useSystemProperties = new JRadioButton("Use system properties");
+ useSystemProperties.setAlignmentX(LEFT_ALIGNMENT);
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ this.add(useSystemProperties, gbc);
+ ActionListener systemPropertiesListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ shownOption = USE_SYSTEM_PROPERTIES_OPTION;
+ populateFields();
+ }
+ };
+ useSystemProperties.addActionListener(systemPropertiesListener);
+
+ useSpecifiedValues = new JRadioButton("Use specified values");
+ useSpecifiedValues.setAlignmentX(LEFT_ALIGNMENT);
+ gbc.gridx = 0;
+ gbc.gridy = 3;
+ this.add(useSpecifiedValues, gbc);
+ ActionListener specifiedValuesListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ shownOption = USE_SPECIFIED_VALUES_OPTION;
+ populateFields();
+ }
+ };
+ useSpecifiedValues.addActionListener(specifiedValuesListener);
+
+ ButtonGroup bg = new ButtonGroup();
+ bg.add(useSystemProperties);
+ bg.add(useSpecifiedValues);
+ bg.add(useNoProxy);
+
+ /**
+ * Create the fields to show the HTTP proxy property values. These
+ * become editable if the shown option is to use specified values.
+ */
+ proxyHostField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 4;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(new JLabel("Proxy host"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 4;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyHostField, gbc);
+
+ proxyPortField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 5;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ this.add(new JLabel("Proxy port"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 5;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyPortField, gbc);
+
+ proxyUserField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 6;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ this.add(new JLabel("Proxy user"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 6;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyUserField, gbc);
+
+ proxyPasswordField = new JTextField(TEXTFIELD_SIZE);
+ gbc.gridx = 0;
+ gbc.gridy = 7;
+ gbc.gridwidth = 1;
+ gbc.fill = NONE;
+ this.add(new JLabel("Proxy password"), gbc);
+ gbc.gridx = 1;
+ gbc.gridy = 7;
+ gbc.gridwidth = 1;
+ gbc.fill = HORIZONTAL;
+ this.add(proxyPasswordField, gbc);
+
+ nonProxyHostsArea = new DialogTextArea(10, 40);
+ nonProxyScrollPane = new JScrollPane(nonProxyHostsArea);
+ nonProxyScrollPane
+ .setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ nonProxyScrollPane
+ .setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+ // nonProxyScrollPane.setPreferredSize(new Dimension(300, 500));
+ gbc.gridx = 0;
+ gbc.gridy = 8;
+ gbc.gridwidth = 2;
+ gbc.fill = NONE;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(new JLabel("Non-proxy hosts"), gbc);
+ gbc.gridx = 0;
+ gbc.gridy = 9;
+ gbc.weightx = 1.0;
+ gbc.weighty = 1.0;
+ gbc.gridwidth = 2;
+ gbc.insets = new Insets(0, 0, 0, 0);
+ gbc.fill = BOTH;
+ this.add(nonProxyScrollPane, gbc);
+
+ // Add buttons panel
+ gbc.gridx = 0;
+ gbc.gridy = 10;
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridwidth = 2;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = CENTER;
+ gbc.insets = new Insets(10, 0, 0, 0);
+ this.add(createButtonPanel(), gbc);
+
+ setFields();
+ }
+
+ /**
+ * Populate the fields in the property panel according to which option is
+ * being shown and the stored values within the
+ * {@link HttpProxyConfiguration}.
+ */
+ private void populateFields() {
+ /**
+ * Editing of the property fields is only available when the option is
+ * to use the specified values.
+ */
+ boolean editingEnabled = shownOption
+ .equals(USE_SPECIFIED_VALUES_OPTION);
+
+ if (shownOption.equals(USE_SYSTEM_PROPERTIES_OPTION)) {
+ proxyHostField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_HOST));
+ proxyPortField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_PORT));
+ proxyUserField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_USER));
+ proxyPasswordField.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_PROXY_PASSWORD));
+ nonProxyHostsArea.setText(httpProxyConfiguration
+ .getProperty(SYSTEM_NON_PROXY_HOSTS));
+ } else if (shownOption.equals(USE_SPECIFIED_VALUES_OPTION)) {
+ proxyHostField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_HOST));
+ proxyPortField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_PORT));
+ proxyUserField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_USER));
+ proxyPasswordField.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_PROXY_PASSWORD));
+ nonProxyHostsArea.setText(httpProxyConfiguration
+ .getProperty(TAVERNA_NON_PROXY_HOSTS));
+ } else {
+ proxyHostField.setText(null);
+ proxyPortField.setText(null);
+ proxyUserField.setText(null);
+ proxyPasswordField.setText(null);
+ nonProxyHostsArea.setText(null);
+ }
+
+ proxyHostField.setEnabled(editingEnabled);
+ proxyPortField.setEnabled(editingEnabled);
+ proxyUserField.setEnabled(editingEnabled);
+ proxyPasswordField.setEnabled(editingEnabled);
+ nonProxyHostsArea.setEnabled(editingEnabled);
+ nonProxyHostsArea.setEditable(editingEnabled);
+ nonProxyScrollPane.setEnabled(editingEnabled);
+ }
+
+ /**
+ * Create the panel to contain the buttons
+ *
+ * @return
+ */
+ @SuppressWarnings("serial")
+ private JPanel createButtonPanel() {
+ final JPanel panel = new JPanel();
+
+ /**
+ * The helpButton shows help about the current component
+ */
+ JButton helpButton = new JButton(new AbstractAction("Help") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ showHelp(panel);
+ }
+ });
+ panel.add(helpButton);
+
+ /**
+ * The resetButton changes the property values shown to those
+ * corresponding to the configuration currently applied.
+ */
+ JButton resetButton = new JButton(new AbstractAction("Reset") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ setFields();
+ }
+ });
+ panel.add(resetButton);
+
+ /**
+ * The applyButton applies the shown field values to the
+ * {@link HttpProxyConfiguration} and saves them for future.
+ */
+ JButton applyButton = new JButton(new AbstractAction("Apply") {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ applySettings();
+ setFields();
+ }
+ });
+ panel.add(applyButton);
+
+ return panel;
+ }
+
+ /**
+ * Checks that the specified values for the HTTP properties are a valid
+ * combination and, if so, saves them for future use. It does not apply them
+ * to the currently executing Taverna.
+ */
+ private void saveSettings() {
+ if (useSystemProperties.isSelected()) {
+ httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+ USE_SYSTEM_PROPERTIES_OPTION);
+ } else if (useNoProxy.isSelected()) {
+ httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+ USE_NO_PROXY_OPTION);
+ } else {
+ if (validateFields()) {
+ httpProxyConfiguration.setProperty(PROXY_USE_OPTION,
+ USE_SPECIFIED_VALUES_OPTION);
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_HOST,
+ proxyHostField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_PORT,
+ proxyPortField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_USER,
+ proxyUserField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_PROXY_PASSWORD,
+ proxyPasswordField.getText());
+ httpProxyConfiguration.setProperty(TAVERNA_NON_PROXY_HOSTS,
+ nonProxyHostsArea.getText());
+ }
+ }
+ }
+
+ /**
+ * Validates and, where appropriate formats, the properties values specified
+ * for HTTP Proxy configuration.
+ *
+ * @return
+ */
+ private boolean validateFields() {
+ boolean result = true;
+ result = result && validateHostField();
+ result = result && validatePortField();
+ result = result && validateUserField();
+ result = result && validatePasswordField();
+ result = result && validateNonProxyHostsArea();
+ return result;
+ }
+
+ /**
+ * Checks that, if a value is specified for non-proxy hosts then a proxy
+ * host has also been specified. Formats the non-proxy hosts string so that
+ * if the user has entered the hosts on separate lines, then the stored
+ * values are separated by bars.
+ *
+ * @return
+ */
+ private boolean validateNonProxyHostsArea() {
+ boolean result = true;
+ String value = nonProxyHostsArea.getText();
+ if ((value != null) && (!value.equals(""))) {
+ value = value.replaceAll("\\n", "|");
+ nonProxyHostsArea.setText(value);
+ result = result
+ && dependsUpon("non-proxy host", "host",
+ proxyHostField.getText());
+ }
+ return result;
+ }
+
+ /**
+ * Checks that, if a password has been specified, then a user has also been
+ * specified.
+ *
+ * @return
+ */
+ private boolean validatePasswordField() {
+ boolean result = true;
+ String value = proxyPasswordField.getText();
+ if ((value != null) && !value.isEmpty())
+ result = result
+ && dependsUpon("password", "user", proxyHostField.getText());
+ return result;
+ }
+
+ /**
+ * Checks that if a user has been specified, then a host has also been
+ * specified.
+ *
+ * @return
+ */
+ private boolean validateUserField() {
+ boolean result = true;
+ String value = proxyUserField.getText();
+ if ((value != null) && !value.isEmpty())
+ result = result
+ && dependsUpon("user", "host", proxyHostField.getText());
+ return result;
+ }
+
+ /**
+ * Checks that if a port has been specified then a host has also been
+ * specified. Checks that the port number is a non-negative integer. If the
+ * port has not been specified, then if a host has been specified, the
+ * default value 80 is used.
+ *
+ * @return
+ */
+ private boolean validatePortField() {
+ boolean result = true;
+ String value = proxyPortField.getText();
+ if ((value != null) && (!value.equals(""))) {
+ result = result
+ && dependsUpon("port", "host", proxyHostField.getText());
+ try {
+ int parsedNumber = Integer.parseInt(value);
+ if (parsedNumber <= 0) {
+ showMessageDialog(this, "The port must be non-negative");
+ result = false;
+ }
+ } catch (NumberFormatException e) {
+ showMessageDialog(this, "The port must be an integer");
+ result = false;
+ }
+ } else {
+ String hostField = proxyHostField.getText();
+ if ((hostField != null) && !hostField.isEmpty())
+ proxyPortField.setText("80");
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the targetValue has been specified. If not then a message is
+ * displayed indicating that the dependent cannot be specified with the
+ * target.
+ *
+ * @param dependent
+ * @param target
+ * @param targetValue
+ * @return
+ */
+ private boolean dependsUpon(String dependent, String target,
+ String targetValue) {
+ boolean result = true;
+ if ((targetValue == null) || target.equals("")) {
+ showMessageDialog(this, "A " + dependent
+ + " cannot be specified without a " + target);
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Could validate the host field e.g. by establishing a connection.
+ * Currently no validation is done.
+ *
+ * @return
+ */
+ private boolean validateHostField() {
+ boolean result = true;
+ // String value = proxyHostField.getText();
+ return result;
+ }
+
+ /**
+ * Save the currently set field values (if valid) to the
+ * {@link HttpProxyConfiguration}. Also applies those values to the
+ * currently running Taverna.
+ */
+ private void applySettings() {
+ if (validateFields()) {
+ saveSettings();
+ httpProxyConfiguration.changeProxySettings();
+ }
+ }
+
+ /**
+ * Set the shown field values to those currently in use (i.e. last saved
+ * configuration).
+ */
+ private void setFields() {
+ shownOption = httpProxyConfiguration.getProperty(PROXY_USE_OPTION);
+ useSystemProperties.setSelected(shownOption
+ .equals(USE_SYSTEM_PROPERTIES_OPTION));
+ useSpecifiedValues.setSelected(shownOption
+ .equals(USE_SPECIFIED_VALUES_OPTION));
+ useNoProxy.setSelected(shownOption.equals(USE_NO_PROXY_OPTION));
+ populateFields();
+ }
+}
diff --git a/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java
new file mode 100644
index 0000000..9f6ac8c
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/java/net/sf/taverna/t2/workbench/httpproxy/config/HttpProxyConfigurationUIFactory.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.httpproxy.config;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.configuration.Configurable;
+import uk.org.taverna.configuration.ConfigurationUIFactory;
+import uk.org.taverna.configuration.proxy.HttpProxyConfiguration;
+
+/**
+ * A Factory to create a HttpProxyConfiguration
+ *
+ * @author alanrw
+ * @author David Withers
+ */
+public class HttpProxyConfigurationUIFactory implements ConfigurationUIFactory {
+ private HttpProxyConfiguration httpProxyConfiguration;
+
+ @Override
+ public boolean canHandle(String uuid) {
+ return uuid.equals(getConfigurable().getUUID());
+ }
+
+ @Override
+ public JPanel getConfigurationPanel() {
+ return new HttpProxyConfigurationPanel(httpProxyConfiguration);
+ }
+
+ @Override
+ public Configurable getConfigurable() {
+ return httpProxyConfiguration;
+ }
+
+ public void setHttpProxyConfiguration(HttpProxyConfiguration httpProxyConfiguration) {
+ this.httpProxyConfiguration = httpProxyConfiguration;
+ }
+}
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
new file mode 100644
index 0000000..d87772b
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.configuration.ConfigurationUIFactory
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.httpproxy.config.HttpProxyConfigurationUIFactory
\ No newline at end of file
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml
new file mode 100644
index 0000000..631bdb4
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="HttpProxyConfigurationUIFactory" interface="uk.org.taverna.configuration.ConfigurationUIFactory" />
+
+ <reference id="httpProxyConfiguration" interface="uk.org.taverna.configuration.proxy.HttpProxyConfiguration" />
+
+</beans:beans>
diff --git a/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml
new file mode 100644
index 0000000..6d6060f
--- /dev/null
+++ b/taverna-workbench-httpproxy-config/src/main/resources/META-INF/spring/httpproxy-config-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="HttpProxyConfigurationUIFactory" class="net.sf.taverna.t2.workbench.httpproxy.config.HttpProxyConfigurationUIFactory">
+ <property name="httpProxyConfiguration" ref="httpProxyConfiguration" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-menu-impl/pom.xml b/taverna-workbench-menu-impl/pom.xml
new file mode 100644
index 0000000..d95bf49
--- /dev/null
+++ b/taverna-workbench-menu-impl/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>menu-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Menu generation implementation</name>
+ <description>The main workbench ui</description>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Embed-Dependency>javahelp</Embed-Dependency>
+ <Import-Package>org.jdesktop.jdic.browser;resolution:=optional,*</Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java
new file mode 100644
index 0000000..ee1fd3d
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/ui/menu/impl/MenuManagerImpl.java
@@ -0,0 +1,880 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.ui.menu.impl;
+
+import static java.lang.Math.min;
+import static javax.help.CSH.setHelpIDString;
+import static javax.swing.Action.NAME;
+import static javax.swing.Action.SHORT_DESCRIPTION;
+import static net.sf.taverna.t2.lang.ui.ShadedLabel.GREEN;
+import static net.sf.taverna.t2.ui.menu.AbstractMenuSection.SECTION_COLOR;
+import static net.sf.taverna.t2.ui.menu.DefaultContextualMenu.DEFAULT_CONTEXT_MENU;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.WeakHashMap;
+
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.lang.ui.ShadedLabel;
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.ui.menu.AbstractMenuOptionGroup;
+import net.sf.taverna.t2.ui.menu.ContextualMenuComponent;
+import net.sf.taverna.t2.ui.menu.ContextualSelection;
+import net.sf.taverna.t2.ui.menu.DesignOnlyAction;
+import net.sf.taverna.t2.ui.menu.DesignOrResultsAction;
+import net.sf.taverna.t2.ui.menu.MenuComponent;
+import net.sf.taverna.t2.ui.menu.MenuComponent.MenuType;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementation of {@link MenuManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class MenuManagerImpl implements MenuManager {
+ private static Logger logger = Logger.getLogger(MenuManagerImpl.class);
+
+ private boolean needsUpdate;
+ /**
+ * Cache used by {@link #getURIByComponent(Component)}
+ */
+ private WeakHashMap<Component, URI> componentToUri;
+ /**
+ * {@link MenuElementComparator} used for sorting menu components from the
+ * SPI registry.
+ */
+ private MenuElementComparator menuElementComparator = new MenuElementComparator();
+ /**
+ * Map of {@link URI} to it's discovered children. Populated by
+ * {@link #findChildren()}.
+ */
+ private HashMap<URI, List<MenuComponent>> menuElementTree;
+ /**
+ * Multicaster to distribute messages to {@link Observer}s of this menu
+ * manager.
+ */
+ private MultiCaster<MenuManagerEvent> multiCaster;
+ /**
+ * Lock for {@link #update()}
+ */
+ private final Object updateLock = new Object();
+ /**
+ * True if {@link #doUpdate()} is running, subsequents call to
+ * {@link #update()} will return immediately.
+ */
+ private boolean updating;
+ /**
+ * Cache used by {@link #getComponentByURI(URI)}
+ */
+ private Map<URI, WeakReference<Component>> uriToComponent;
+ /**
+ * Map from {@link URI} to defining {@link MenuComponent}. Children are in
+ * {@link #menuElementTree}.
+ */
+ private Map<URI, MenuComponent> uriToMenuElement;
+ // Note: Not reset by #resetCollections()
+ private Map<URI, List<WeakReference<Component>>> uriToPublishedComponents = new HashMap<>();
+ private List<MenuComponent> menuComponents = new ArrayList<>();
+
+ /**
+ * Construct the MenuManagerImpl. Observes the SPI registry and does an
+ * initial {@link #update()}.
+ */
+ public MenuManagerImpl() {
+ multiCaster = new MultiCaster<>(this);
+ needsUpdate = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addMenuItemsWithExpansion(List<JMenuItem> menuItems,
+ JMenu parentMenu, int maxItemsInMenu,
+ ComponentFactory headerItemFactory) {
+ if (menuItems.size() <= maxItemsInMenu) {
+ // Just add them directly
+ for (JMenuItem menuItem : menuItems)
+ parentMenu.add(menuItem);
+ return;
+ }
+ int index = 0;
+ while (index < menuItems.size()) {
+ int toIndex = min(menuItems.size(), index + maxItemsInMenu);
+ if (toIndex == menuItems.size() - 1)
+ // Don't leave a single item left for the last subMenu
+ toIndex--;
+ List<JMenuItem> subList = menuItems.subList(index, toIndex);
+ JMenuItem firstItem = subList.get(0);
+ JMenuItem lastItem = subList.get(subList.size() - 1);
+ JMenu subMenu = new JMenu(firstItem.getText() + " ... "
+ + lastItem.getText());
+ if (headerItemFactory != null)
+ subMenu.add(headerItemFactory.makeComponent());
+ for (JMenuItem menuItem : subList)
+ subMenu.add(menuItem);
+ parentMenu.add(subMenu);
+ index = toIndex;
+ }
+ }
+
+ @Override
+ public void addObserver(Observer<MenuManagerEvent> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ @Override
+ public JPopupMenu createContextMenu(Object parent, Object selection,
+ Component relativeToComponent) {
+ ContextualSelection contextualSelection = new ContextualSelection(
+ parent, selection, relativeToComponent);
+ JPopupMenu popupMenu = new JPopupMenu();
+ populateContextMenu(popupMenu, DEFAULT_CONTEXT_MENU,
+ contextualSelection);
+ registerComponent(DEFAULT_CONTEXT_MENU, popupMenu, true);
+ return popupMenu;
+ }
+
+ @Override
+ public JMenuBar createMenuBar() {
+ return createMenuBar(DEFAULT_MENU_BAR);
+ }
+
+ @Override
+ public JMenuBar createMenuBar(URI id) {
+ JMenuBar menuBar = new JMenuBar();
+ if (needsUpdate)
+ update();
+ populateMenuBar(menuBar, id);
+ registerComponent(id, menuBar, true);
+ return menuBar;
+ }
+
+ @Override
+ public JToolBar createToolBar() {
+ return createToolBar(DEFAULT_TOOL_BAR);
+ }
+
+ @Override
+ public JToolBar createToolBar(URI id) {
+ JToolBar toolbar = new JToolBar();
+ if (needsUpdate)
+ update();
+ populateToolBar(toolbar, id);
+ registerComponent(id, toolbar, true);
+ return toolbar;
+ }
+
+ @Override
+ public synchronized Component getComponentByURI(URI id) {
+ WeakReference<Component> componentRef = uriToComponent.get(id);
+ if (componentRef == null)
+ return null;
+ // Might also be null it reference has gone dead
+ return componentRef.get();
+ }
+
+ @Override
+ public List<Observer<MenuManagerEvent>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ @Override
+ public synchronized URI getURIByComponent(Component component) {
+ return componentToUri.get(component);
+ }
+
+ @Override
+ public void removeObserver(Observer<MenuManagerEvent> observer) {
+ multiCaster.removeObserver(observer);
+ }
+
+ @Override
+ public void update() {
+ synchronized (updateLock) {
+ if (updating && !needsUpdate)
+ return;
+ updating = true;
+ }
+ try {
+ doUpdate();
+ } finally {
+ synchronized (updateLock) {
+ updating = false;
+ needsUpdate = false;
+ }
+ }
+ }
+
+ public void update(Object service, Map<?, ?> properties) {
+ needsUpdate = true;
+ update();
+ }
+
+ /**
+ * Add a {@link JMenu} to the list of components as described by the menu
+ * component. If there are no children, the menu is not added.
+ *
+ * @param components
+ * List of components where to add the created {@link JMenu}
+ * @param menuComponent
+ * The {@link MenuComponent} definition for this menu
+ * @param isToolbar
+ * True if the list of components is to be added to a toolbar
+ */
+ private void addMenu(List<Component> components,
+ MenuComponent menuComponent, MenuOptions menuOptions) {
+ URI menuId = menuComponent.getId();
+ if (menuOptions.isToolbar()) {
+ logger.warn("Can't have menu " + menuComponent
+ + " within toolBar element");
+ return;
+ }
+ MenuOptions childOptions = new MenuOptions(menuOptions);
+ List<Component> subComponents = makeComponents(menuId, childOptions);
+ if (subComponents.isEmpty()) {
+ logger.warn("No sub components found for menu " + menuId);
+ return;
+ }
+
+ JMenu menu = new JMenu(menuComponent.getAction());
+ for (Component menuItem : subComponents)
+ if (menuItem == null)
+ menu.addSeparator();
+ else
+ menu.add(menuItem);
+ registerComponent(menuId, menu);
+ components.add(menu);
+ }
+
+ /**
+ * Add <code>null</code> to the list of components, meaning that a separator
+ * is to be created. Subsequent separators are ignored, and if there are no
+ * components on the list already no separator will be added.
+ *
+ * @param components
+ * List of components
+ */
+ private void addNullSeparator(List<Component> components) {
+ if (components.isEmpty())
+ // Don't start with a separator
+ return;
+ if (components.get(components.size() - 1) == null)
+ // Already a separator in last position
+ return;
+ components.add(null);
+ }
+
+ /**
+ * Add an {@link AbstractMenuOptionGroup option group} to the list of
+ * components
+ *
+ * @param components
+ * List of components where to add the created {@link JMenu}
+ * @param optionGroupId
+ * The {@link URI} identifying the option group
+ * @param isToolbar
+ * True if the option group is to be added to a toolbar
+ */
+ private void addOptionGroup(List<Component> components, URI optionGroupId,
+ MenuOptions menuOptions) {
+ MenuOptions childOptions = new MenuOptions(menuOptions);
+ childOptions.setOptionGroup(true);
+
+ List<Component> buttons = makeComponents(optionGroupId, childOptions);
+ addNullSeparator(components);
+ if (buttons.isEmpty()) {
+ logger.warn("No sub components found for option group "
+ + optionGroupId);
+ return;
+ }
+ ButtonGroup buttonGroup = new ButtonGroup();
+
+ for (Component button : buttons) {
+ if (button instanceof AbstractButton)
+ buttonGroup.add((AbstractButton) button);
+ else
+ logger.warn("Component of button group " + optionGroupId
+ + " is not an AbstractButton: " + button);
+ if (button == null) {
+ logger.warn("Separator found within button group");
+ addNullSeparator(components);
+ } else
+ components.add(button);
+ }
+ addNullSeparator(components);
+ }
+
+ /**
+ * Add a section to a list of components.
+ *
+ * @param components
+ * List of components
+ * @param sectionId
+ * The {@link URI} identifying the section
+ * @param menuOptions
+ * {@link MenuOptions options} for creating the menu
+ */
+ private void addSection(List<Component> components, URI sectionId,
+ MenuOptions menuOptions) {
+ List<Component> childComponents = makeComponents(sectionId, menuOptions);
+
+ MenuComponent sectionDef = uriToMenuElement.get(sectionId);
+ addNullSeparator(components);
+ if (childComponents.isEmpty()) {
+ logger.warn("No sub components found for section " + sectionId);
+ return;
+ }
+ Action sectionAction = sectionDef.getAction();
+ if (sectionAction != null) {
+ String sectionLabel = (String) sectionAction.getValue(NAME);
+ if (sectionLabel != null) {
+ // No separators before the label
+ stripTrailingNullSeparator(components);
+ Color labelColor = (Color) sectionAction.getValue(SECTION_COLOR);
+ if (labelColor == null)
+ labelColor = GREEN;
+ ShadedLabel label = new ShadedLabel(sectionLabel, labelColor);
+ components.add(label);
+ }
+ }
+ for (Component childComponent : childComponents)
+ if (childComponent == null) {
+ logger.warn("Separator found within section " + sectionId);
+ addNullSeparator(components);
+ } else
+ components.add(childComponent);
+ addNullSeparator(components);
+ }
+
+ /**
+ * Remove the last <code>null</code> separator from the list of components
+ * if it's present.
+ *
+ * @param components
+ * List of components
+ */
+ private void stripTrailingNullSeparator(List<Component> components) {
+ if (!components.isEmpty()) {
+ int lastIndex = components.size() - 1;
+ if (components.get(lastIndex) == null)
+ components.remove(lastIndex);
+ }
+ }
+
+ /**
+ * Perform the actual update, called by {@link #update()}. Reset all the
+ * collections, refresh from SPI, modify any previously published components
+ * and notify any observers.
+ */
+ protected synchronized void doUpdate() {
+ resetCollections();
+ findChildren();
+ updatePublishedComponents();
+ multiCaster.notify(new UpdatedMenuManagerEvent());
+ }
+
+ /**
+ * Find all children for all known menu components. Populates
+ * {@link #uriToMenuElement}.
+ *
+ */
+ protected void findChildren() {
+ for (MenuComponent menuElement : menuComponents) {
+ uriToMenuElement.put(menuElement.getId(), menuElement);
+ logger.debug("Found menu element " + menuElement.getId() + " "
+ + menuElement);
+ if (menuElement.getParentId() == null)
+ continue;
+ List<MenuComponent> siblings = menuElementTree.get(menuElement
+ .getParentId());
+ if (siblings == null) {
+ siblings = new ArrayList<>();
+ synchronized (menuElementTree) {
+ menuElementTree.put(menuElement.getParentId(), siblings);
+ }
+ }
+ siblings.add(menuElement);
+ }
+// if (uriToMenuElement.isEmpty()) {
+// logger.error("No menu elements found, check classpath/Raven/SPI");
+// }
+ }
+
+ /**
+ * Get the children which have the given URI specified as their parent, or
+ * an empty list if no children exist.
+ *
+ * @param id
+ * The {@link URI} of the parent
+ * @return The {@link List} of {@link MenuComponent} which have the given
+ * parent
+ */
+ protected List<MenuComponent> getChildren(URI id) {
+ List<MenuComponent> children = null;
+ synchronized (menuElementTree) {
+ children = menuElementTree.get(id);
+ if (children != null)
+ children = new ArrayList<>(children);
+ }
+ if (children == null)
+ children = Collections.<MenuComponent> emptyList();
+ else
+ Collections.sort(children, menuElementComparator);
+ return children;
+ }
+
+ /**
+ * Make the list of Swing {@link Component}s that are the children of the
+ * given {@link URI}.
+ *
+ * @param id
+ * The {@link URI} of the parent which children are to be made
+ * @param menuOptions
+ * Options of the created menu, for instance
+ * {@link MenuOptions#isToolbar()}.
+ * @return A {@link List} of {@link Component}s that can be added to a
+ * {@link JMenuBar}, {@link JMenu} or {@link JToolBar}.
+ */
+ protected List<Component> makeComponents(URI id, MenuOptions menuOptions) {
+ List<Component> components = new ArrayList<>();
+ for (MenuComponent childElement : getChildren(id)) {
+ if (childElement instanceof ContextualMenuComponent)
+ ((ContextualMenuComponent) childElement)
+ .setContextualSelection(menuOptions
+ .getContextualSelection());
+ /*
+ * Important - check this AFTER setContextualSelection so the item
+ * can change it's enabled-state if needed.
+ */
+ if (!childElement.isEnabled())
+ continue;
+ MenuType type = childElement.getType();
+ Action action = childElement.getAction();
+ URI childId = childElement.getId();
+ if (type.equals(MenuType.action)) {
+ if (action == null) {
+ logger.warn("Skipping invalid action " + childId + " for "
+ + id);
+ continue;
+ }
+
+ Component actionComponent;
+ if (menuOptions.isOptionGroup()) {
+ if (menuOptions.isToolbar()) {
+ actionComponent = new JToggleButton(action);
+ toolbarizeButton((AbstractButton) actionComponent);
+ } else
+ actionComponent = new JRadioButtonMenuItem(action);
+ } else {
+ if (menuOptions.isToolbar()) {
+ actionComponent = new JButton(action);
+ toolbarizeButton((AbstractButton) actionComponent);
+ } else
+ actionComponent = new JMenuItem(action);
+ }
+ registerComponent(childId, actionComponent);
+ components.add(actionComponent);
+ } else if (type.equals(MenuType.toggle)) {
+ if (action == null) {
+ logger.warn("Skipping invalid toggle " + childId + " for "
+ + id);
+ continue;
+ }
+ Component toggleComponent;
+ if (menuOptions.isToolbar())
+ toggleComponent = new JToggleButton(action);
+ else
+ toggleComponent = new JCheckBoxMenuItem(action);
+ registerComponent(childId, toggleComponent);
+ components.add(toggleComponent);
+ } else if (type.equals(MenuType.custom)) {
+ Component customComponent = childElement.getCustomComponent();
+ if (customComponent == null) {
+ logger.warn("Skipping null custom component " + childId
+ + " for " + id);
+ continue;
+ }
+ registerComponent(childId, customComponent);
+ components.add(customComponent);
+ } else if (type.equals(MenuType.optionGroup))
+ addOptionGroup(components, childId, menuOptions);
+ else if (type.equals(MenuType.section))
+ addSection(components, childId, menuOptions);
+ else if (type.equals(MenuType.menu))
+ addMenu(components, childElement, menuOptions);
+ else {
+ logger.warn("Skipping invalid/unknown type " + type + " for "
+ + id);
+ continue;
+ }
+ }
+ stripTrailingNullSeparator(components);
+ return components;
+ }
+
+ /**
+ * Fill the specified menu bar with the menu elements that have the given
+ * URI as their parent.
+ * <p>
+ * Existing elements on the menu bar will be removed.
+ *
+ * @param menuBar
+ * The {@link JMenuBar} to update
+ * @param id
+ * The {@link URI} of the menu bar
+ */
+ protected void populateMenuBar(JMenuBar menuBar, URI id) {
+ menuBar.removeAll();
+ MenuComponent menuDef = uriToMenuElement.get(id);
+ if (menuDef == null)
+ throw new IllegalArgumentException("Unknown menuBar " + id);
+ if (!menuDef.getType().equals(MenuType.menu))
+ throw new IllegalArgumentException("Element " + id
+ + " is not a menu, but a " + menuDef.getType());
+ MenuOptions menuOptions = new MenuOptions();
+ for (Component component : makeComponents(id, menuOptions))
+ if (component == null)
+ logger.warn("Ignoring separator in menu bar " + id);
+ else
+ menuBar.add(component);
+ }
+
+ /**
+ * Fill the specified menu bar with the menu elements that have the given
+ * URI as their parent.
+ * <p>
+ * Existing elements on the menu bar will be removed.
+ *
+ * @param popupMenu
+ * The {@link JPopupMenu} to update
+ * @param id
+ * The {@link URI} of the menu bar
+ * @param contextualSelection
+ * The current selection for the context menu
+ */
+ protected void populateContextMenu(JPopupMenu popupMenu, URI id,
+ ContextualSelection contextualSelection) {
+ popupMenu.removeAll();
+ MenuComponent menuDef = uriToMenuElement.get(id);
+ if (menuDef == null)
+ throw new IllegalArgumentException("Unknown menuBar " + id);
+ if (!menuDef.getType().equals(MenuType.menu))
+ throw new IllegalArgumentException("Element " + id
+ + " is not a menu, but a " + menuDef.getType());
+ MenuOptions menuOptions = new MenuOptions();
+ menuOptions.setContextualSelection(contextualSelection);
+ for (Component component : makeComponents(id, menuOptions))
+ if (component == null)
+ popupMenu.addSeparator();
+ else
+ popupMenu.add(component);
+ }
+
+ /**
+ * Fill the specified tool bar with the elements that have the given URI as
+ * their parent.
+ * <p>
+ * Existing elements on the tool bar will be removed.
+ *
+ * @param toolbar
+ * The {@link JToolBar} to update
+ * @param id
+ * The {@link URI} of the tool bar
+ */
+ protected void populateToolBar(JToolBar toolbar, URI id) {
+ toolbar.removeAll();
+ MenuComponent toolbarDef = uriToMenuElement.get(id);
+ if (toolbarDef == null)
+ throw new IllegalArgumentException("Unknown toolBar " + id);
+ if (!toolbarDef.getType().equals(MenuType.toolBar))
+ throw new IllegalArgumentException("Element " + id
+ + " is not a toolBar, but a " + toolbarDef.getType());
+ if (toolbarDef.getAction() != null) {
+ String name = (String) toolbarDef.getAction().getValue(Action.NAME);
+ toolbar.setName(name);
+ } else
+ toolbar.setName("");
+ MenuOptions menuOptions = new MenuOptions();
+ menuOptions.setToolbar(true);
+ for (Component component : makeComponents(id, menuOptions)) {
+ if (component == null) {
+ toolbar.addSeparator();
+ continue;
+ }
+ if (component instanceof JButton) {
+ JButton toolbarButton = (JButton) component;
+ toolbarButton.putClientProperty("hideActionText", true);
+ }
+ toolbar.add(component);
+ }
+ }
+
+ /**
+ * Register a component that has been created. Such a component can be
+ * resolved through {@link #getComponentByURI(URI)}.
+ *
+ * @param id
+ * The {@link URI} that defined the component
+ * @param component
+ * The {@link Component} that was created.
+ */
+ protected synchronized void registerComponent(URI id, Component component) {
+ registerComponent(id, component, false);
+ }
+
+ /**
+ * Register a component that has been created. Such a component can be
+ * resolved through {@link #getComponentByURI(URI)}.
+ *
+ * @param id
+ * The {@link URI} that defined the component
+ * @param component
+ * The {@link Component} that was created.
+ * @param published
+ * <code>true</code> if the component has been published through
+ * {@link #createMenuBar()} or similar, and is to be
+ * automatically updated by later calls to {@link #update()}.
+ */
+ protected synchronized void registerComponent(URI id, Component component,
+ boolean published) {
+ uriToComponent.put(id, new WeakReference<>(component));
+ componentToUri.put(component, id);
+ if (published) {
+ List<WeakReference<Component>> publishedComponents = uriToPublishedComponents
+ .get(id);
+ if (publishedComponents == null) {
+ publishedComponents = new ArrayList<>();
+ uriToPublishedComponents.put(id, publishedComponents);
+ }
+ publishedComponents.add(new WeakReference<>(component));
+ }
+ setHelpStringForComponent(component, id);
+ }
+
+ /**
+ * Reset all collections
+ *
+ */
+ protected synchronized void resetCollections() {
+ menuElementTree = new HashMap<>();
+ componentToUri = new WeakHashMap<>();
+ uriToMenuElement = new HashMap<>();
+ uriToComponent = new HashMap<>();
+ }
+
+ /**
+ * Set javax.help string to identify the component for later references to
+ * the help document. Note that the component (ie. the
+ * {@link AbstractMenuAction} must have an ID for an registration to take
+ * place.
+ *
+ * @param component
+ * The {@link Component} to set help string for
+ * @param componentId
+ * The {@link URI} to be used as identifier
+ */
+ protected void setHelpStringForComponent(Component component,
+ URI componentId) {
+ if (componentId != null) {
+ String helpId = componentId.toASCIIString();
+ setHelpIDString(component, helpId);
+ }
+ }
+
+ /**
+ * Make an {@link AbstractButton} be configured in a "toolbar-like" way, for
+ * instance showing only the icon.
+ *
+ * @param actionButton
+ * Button to toolbarise
+ */
+ protected void toolbarizeButton(AbstractButton actionButton) {
+ Action action = actionButton.getAction();
+ if (action.getValue(SHORT_DESCRIPTION) == null)
+ action.putValue(SHORT_DESCRIPTION, action.getValue(NAME));
+ actionButton.setBorder(new EmptyBorder(0, 2, 0, 2));
+ // actionButton.setHorizontalTextPosition(JButton.CENTER);
+ // actionButton.setVerticalTextPosition(JButton.BOTTOM);
+ if (action.getValue(Action.SMALL_ICON) != null) {
+ // Don't show the text
+ actionButton.putClientProperty("hideActionText", true);
+ // Since hideActionText seems to be broken in Java 5 and/or OS X
+ actionButton.setText(null);
+ }
+ }
+
+ /**
+ * Update all components that have been published using
+ * {@link #createMenuBar()} and similar. Content of such components will be
+ * removed and replaced by fresh components.
+ */
+ protected void updatePublishedComponents() {
+ for (Entry<URI, List<WeakReference<Component>>> entry : uriToPublishedComponents
+ .entrySet())
+ for (WeakReference<Component> reference : entry.getValue()) {
+ URI id = entry.getKey();
+ Component component = reference.get();
+ if (component == null)
+ continue;
+ if (component instanceof JToolBar)
+ populateToolBar((JToolBar) component, id);
+ else if (component instanceof JMenuBar)
+ populateMenuBar((JMenuBar) component, id);
+ else
+ logger.warn("Could not update published component " + id
+ + ": " + component.getClass());
+ }
+ }
+
+ public void setMenuComponents(List<MenuComponent> menuComponents) {
+ this.menuComponents = menuComponents;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ selectionManager.addObserver(new SelectionManagerObserver());
+ }
+
+ /**
+ * {@link Comparator} that can order {@link MenuComponent}s by their
+ * {@link MenuComponent#getPositionHint()}.
+ */
+ protected static class MenuElementComparator implements
+ Comparator<MenuComponent> {
+ @Override
+ public int compare(MenuComponent a, MenuComponent b) {
+ return a.getPositionHint() - b.getPositionHint();
+ }
+ }
+
+ /**
+ * Various options for
+ * {@link MenuManagerImpl#makeComponents(URI, MenuOptions)} and friends.
+ *
+ * @author Stian Soiland-Reyes
+ */
+ public static class MenuOptions {
+ private boolean isToolbar = false;
+ private boolean isOptionGroup = false;
+ private ContextualSelection contextualSelection = null;
+
+ public ContextualSelection getContextualSelection() {
+ return contextualSelection;
+ }
+
+ public void setContextualSelection(
+ ContextualSelection contextualSelection) {
+ this.contextualSelection = contextualSelection;
+ }
+
+ public MenuOptions(MenuOptions original) {
+ this.isOptionGroup = original.isOptionGroup();
+ this.isToolbar = original.isToolbar();
+ this.contextualSelection = original.getContextualSelection();
+ }
+
+ public MenuOptions() {
+ }
+
+ @Override
+ protected MenuOptions clone() {
+ return new MenuOptions(this);
+ }
+
+ public boolean isToolbar() {
+ return isToolbar;
+ }
+
+ public void setToolbar(boolean isToolbar) {
+ this.isToolbar = isToolbar;
+ }
+
+ public boolean isOptionGroup() {
+ return isOptionGroup;
+ }
+
+ public void setOptionGroup(boolean isOptionGroup) {
+ this.isOptionGroup = isOptionGroup;
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+ private static final String RESULTS_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective";
+
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (!(message instanceof PerspectiveSelectionEvent))
+ return;
+ handlePerspectiveSelect((PerspectiveSelectionEvent) message);
+ }
+
+ private void handlePerspectiveSelect(PerspectiveSelectionEvent event) {
+ String perspectiveID = event.getSelectedPerspective().getID();
+ boolean isDesign = DESIGN_PERSPECTIVE_ID.equals(perspectiveID);
+ boolean isResults = RESULTS_PERSPECTIVE_ID.equals(perspectiveID);
+
+ for (MenuComponent menuComponent : menuComponents)
+ if (!(menuComponent instanceof ContextualMenuComponent)) {
+ Action action = menuComponent.getAction();
+ if (action instanceof DesignOnlyAction)
+ action.setEnabled(isDesign);
+ else if (action instanceof DesignOrResultsAction)
+ action.setEnabled(isDesign || isResults);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java
new file mode 100644
index 0000000..9a2f37b
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/AdvancedMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_A;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class AdvancedMenu extends AbstractMenu {
+ public static final URI ADVANCED_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+ public AdvancedMenu() {
+ super(DEFAULT_MENU_BAR, 1000, ADVANCED_URI, makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Advanced");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_A));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java
new file mode 100644
index 0000000..a15237c
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/EditMenu.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_E;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class EditMenu extends AbstractMenu {
+ public EditMenu() {
+ super(DEFAULT_MENU_BAR, 20, URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#edit"),
+ makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Edit");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_E));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java
new file mode 100644
index 0000000..6b6eb7c
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FeedbackMenuAction.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.Desktop.getDesktop;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu.HELP_URI;
+
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+/**
+ * MenuItem for feedback
+ *
+ * @author alanrw
+ */
+public class FeedbackMenuAction extends AbstractMenuAction {
+ private static Logger logger = Logger.getLogger(FeedbackMenuAction.class);
+
+ private static String FEEDBACK_URL = "http://www.taverna.org.uk/about/contact-us/feedback/";
+
+ public FeedbackMenuAction() {
+ super(HELP_URI, 20);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new FeedbackAction();
+ }
+
+ @SuppressWarnings("serial")
+ private final class FeedbackAction extends AbstractAction {
+ private FeedbackAction() {
+ super("Contact us");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ getDesktop().browse(new URI(FEEDBACK_URL));
+ } catch (IOException e1) {
+ logger.error("Unable to open URL", e1);
+ } catch (URISyntaxException e1) {
+ logger.error("Invalid URL syntax", e1);
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java
new file mode 100644
index 0000000..61f963b
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/FileMenu.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_F;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+/**
+ * File menu
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class FileMenu extends AbstractMenu {
+ public FileMenu() {
+ super(DEFAULT_MENU_BAR, 10, URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#file"),
+ makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("File");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_F));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java
new file mode 100644
index 0000000..c4169cc
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/HelpMenu.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_H;
+import static javax.swing.Action.MNEMONIC_KEY;
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+public class HelpMenu extends AbstractMenu {
+ public static final URI HELP_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#help");
+
+ public HelpMenu() {
+ super(DEFAULT_MENU_BAR, 1024, HELP_URI, makeAction());
+ }
+
+ public static DummyAction makeAction() {
+ DummyAction action = new DummyAction("Help");
+ action.putValue(MNEMONIC_KEY, Integer.valueOf(VK_H));
+ return action;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java
new file mode 100644
index 0000000..d091c8e
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/OnlineHelpMenuAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.awt.event.KeyEvent.VK_F1;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.helper.Helper.displayDefaultHelp;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu.HELP_URI;
+
+import java.awt.AWTEvent;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+/**
+ * MenuItem for help
+ *
+ * @author alanrw
+ */
+public class OnlineHelpMenuAction extends AbstractMenuAction {
+ public OnlineHelpMenuAction() {
+ super(HELP_URI, 10);
+ }
+
+ @Override
+ protected Action createAction() {
+ return new OnlineHelpAction();
+ }
+
+ @SuppressWarnings("serial")
+ private final class OnlineHelpAction extends AbstractAction {
+ private OnlineHelpAction() {
+ super("Online help");
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F1, 0));
+
+ }
+
+ /**
+ * When selected, use the Helper to display the default help.
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ displayDefaultHelp((AWTEvent) e);
+ // TODO change helper to bean?
+ }
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java
new file mode 100644
index 0000000..308d51d
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ShowLogsAndDataMenuAction.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import static java.lang.Runtime.getRuntime;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+import static javax.swing.JOptionPane.showInputDialog;
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+import static net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu.ADVANCED_URI;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class ShowLogsAndDataMenuAction extends AbstractMenuAction {
+ private static final String OPEN = "open";
+ private static final String EXPLORER = "explorer";
+ // TODO Consider using xdg-open instead of gnome-open
+ private static final String GNOME_OPEN = "gnome-open";
+ private static final String WINDOWS = "Windows";
+ private static final String MAC_OS_X = "Mac OS X";
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ public ShowLogsAndDataMenuAction() {
+ super(ADVANCED_URI, 200);
+ }
+
+ private static Logger logger = Logger.getLogger(ShowLogsAndDataMenuAction.class);
+
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Show logs and data folder") {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File logsAndDataDir = applicationConfiguration.getApplicationHomeDir();
+ showDirectory(logsAndDataDir, "Taverna logs and data folder");
+ }
+ };
+ }
+
+ public static void showDirectory(File dir, String title) {
+ String path = dir.getAbsolutePath();
+ String os = System.getProperty("os.name");
+ String cmd;
+ boolean isWindows = false;
+ if (os.equals(MAC_OS_X))
+ cmd = OPEN;
+ else if (os.startsWith(WINDOWS)) {
+ cmd = EXPLORER;
+ isWindows = true;
+ } else
+ // Assume Unix - best option is gnome-open
+ cmd = GNOME_OPEN;
+
+ String[] cmdArray = new String[2];
+ cmdArray[0] = cmd;
+ cmdArray[1] = path;
+ try {
+ Process exec = getRuntime().exec(cmdArray);
+ Thread.sleep(300);
+ exec.getErrorStream().close();
+ exec.getInputStream().close();
+ exec.getOutputStream().close();
+ exec.waitFor();
+ if (exec.exitValue() == 0 || isWindows && exec.exitValue() == 1)
+ // explorer.exe thinks 1 means success
+ return;
+ logger.warn("Exit value from " + cmd + " " + path + ": " + exec.exitValue());
+ } catch (Exception ex) {
+ logger.warn("Could not call " + cmd + " " + path, ex);
+ }
+ // Fall-back - just show a dialogue with the path
+ showInputDialog(getMainWindow(), "Copy path from below:", title,
+ INFORMATION_MESSAGE, null, null, path);
+ }
+
+ public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java
new file mode 100644
index 0000000..2df05e5
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ViewShowMenuSection.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author Alex Nenadic
+ * @author Alan R Williams
+ */
+public class ViewShowMenuSection extends AbstractMenuSection {
+ public static final URI DIAGRAM_MENU = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#diagram");
+ public static final URI VIEW_SHOW_MENU_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#viewShowMenuSection");
+
+ public ViewShowMenuSection() {
+ super(DIAGRAM_MENU, 10, VIEW_SHOW_MENU_SECTION);
+ }
+}
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager b/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager
new file mode 100644
index 0000000..3b06fd9
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl
\ No newline at end of file
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml
new file mode 100644
index 0000000..3a1eadf
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context-osgi.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="MenuManagerImpl" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+
+ <service ref="FileMenu" auto-export="interfaces" />
+ <service ref="EditMenu" auto-export="interfaces" />
+ <service ref="AdvancedMenu" auto-export="interfaces" />
+ <service ref="HelpMenu" auto-export="interfaces" />
+ <service ref="OnlineHelpMenuAction" auto-export="interfaces" />
+ <service ref="FeedbackMenuAction" auto-export="interfaces" />
+ <service ref="ShowLogsAndDataMenuAction" auto-export="interfaces" />
+
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+ <list id="menuComponents" interface="net.sf.taverna.t2.ui.menu.MenuComponent" cardinality="0..N" greedy-proxying="true">
+ <listener ref="MenuManagerImpl" bind-method="update" unbind-method="update" />
+ </list>
+
+</beans:beans>
diff --git a/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml
new file mode 100644
index 0000000..62fd24e
--- /dev/null
+++ b/taverna-workbench-menu-impl/src/main/resources/META-INF/spring/menu-impl-context.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="MenuManagerImpl" class="net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl">
+ <property name="menuComponents" ref="menuComponents" />
+ <property name="selectionManager" ref="selectionManager" />
+ </bean>
+
+ <bean id="FileMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.FileMenu" />
+ <bean id="EditMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.EditMenu" />
+ <bean id="AdvancedMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu" />
+ <bean id="HelpMenu" class="net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu" />
+ <bean id="OnlineHelpMenuAction"
+ class="net.sf.taverna.t2.workbench.ui.impl.menu.OnlineHelpMenuAction" />
+ <bean id="FeedbackMenuAction"
+ class="net.sf.taverna.t2.workbench.ui.impl.menu.FeedbackMenuAction" />
+ <bean id="ShowLogsAndDataMenuAction"
+ class="net.sf.taverna.t2.workbench.ui.impl.menu.ShowLogsAndDataMenuAction">
+ <property name="applicationConfiguration" ref="applicationConfiguration" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-plugin-manager/pom.xml b/taverna-workbench-plugin-manager/pom.xml
new file mode 100644
index 0000000..4e9f0c1
--- /dev/null
+++ b/taverna-workbench-plugin-manager/pom.xml
@@ -0,0 +1,49 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>plugin-manager</artifactId>
+ <packaging>bundle</packaging>
+ <name>Taverna Workbench Plugin Manager</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-api</artifactId>
+ <version> ${taverna.commons.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>${osgi.core.version}</version>
+ </dependency>
+
+ <!-- <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-download-impl</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-impl</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency> -->
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java
new file mode 100644
index 0000000..f189f91
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/AvailablePluginPanel.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class AvailablePluginPanel extends PluginPanel {
+ private final PluginManager pluginManager;
+ private final PluginVersions pluginVersions;
+
+ public AvailablePluginPanel(PluginVersions pluginVersions,
+ PluginManager pluginManager) {
+ super(pluginVersions.getName(), pluginVersions.getOrganization(),
+ pluginVersions.getLatestVersion().getVersion(), pluginVersions
+ .getDescription());
+ this.pluginVersions = pluginVersions;
+ this.pluginManager = pluginManager;
+ }
+
+ @Override
+ public Action getPluginAction() {
+ return new PluginAction();
+ }
+
+ class PluginAction extends AbstractAction {
+ public PluginAction() {
+ putValue(NAME, "Install");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setEnabled(false);
+ putValue(NAME, "Installing");
+ try {
+ pluginManager.installPlugin(pluginVersions.getPluginSiteUrl(),
+ pluginVersions.getLatestVersion().getFile()).start();
+ } catch (PluginException ex) {
+ ex.printStackTrace();
+ }
+ putValue(NAME, "Installed");
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java
new file mode 100644
index 0000000..a08cf14
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/InstalledPluginPanel.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class InstalledPluginPanel extends PluginPanel {
+ private final Plugin plugin;
+
+ public InstalledPluginPanel(Plugin plugin) {
+ super(plugin.getName(), plugin.getOrganization(), plugin.getVersion()
+ .toString(), plugin.getDescription());
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Action getPluginAction() {
+ return new PluginAction();
+ }
+
+ class PluginAction extends AbstractAction {
+ public PluginAction() {
+ putValue(NAME, "Uninstall");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setEnabled(false);
+ putValue(NAME, "Uninstalling");
+ try {
+ plugin.uninstall();
+ } catch (PluginException ex) {
+ ex.printStackTrace();
+ }
+ putValue(NAME, "Uninstalled");
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java
new file mode 100644
index 0000000..dfef2fe
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerPanel.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.EAST;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.lang.Math.max;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
+import static javax.swing.SwingConstants.CENTER;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.LayoutManager;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.plugin.Plugin;
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class PluginManagerPanel extends JPanel {
+ private static final Logger logger = Logger
+ .getLogger(PluginManagerPanel.class);
+
+ private PluginManager pluginManager;
+ private JLabel message = new JLabel("");
+
+ public PluginManagerPanel(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ public void initialize() {
+ removeAll();
+ setLayout(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = WEST;
+ gbc.gridy = 0;
+ gbc.insets.left = 5;
+ gbc.insets.right = 5;
+ gbc.insets.top = 5;
+
+ JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.addTab("Available", createAvailablePluginsPanel());
+ tabbedPane.addTab("Installed", createInstalledPluginsPanel());
+ tabbedPane.addTab("Updates", createUpdatePluginsPanel());
+
+ gbc.weightx = 1;
+ gbc.weighty = 1;
+ gbc.gridy = 1;
+ gbc.fill = BOTH;
+ add(tabbedPane, gbc);
+
+ gbc.anchor = EAST;
+ gbc.fill = NONE;
+ gbc.weightx = 0;
+ gbc.weighty = 0;
+ gbc.gridy = 2;
+ gbc.insets.bottom = 5;
+ add(message, gbc);
+ }
+
+ public void checkForUpdates() {
+ message.setText("Checking for updates");
+ try {
+ pluginManager.checkForUpdates();
+ } catch (PluginException e) {
+ logger.info("Error checking for plugin updates", e);
+ } finally {
+ message.setText("");
+ }
+ }
+
+ private static Component scrolled(Component view) {
+ JScrollPane scrollPane = new JScrollPane(view);
+ scrollPane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
+ scrollPane.setBorder(null);
+ return scrollPane;
+ }
+
+ private Component createAvailablePluginsPanel() {
+ try {
+ List<PluginVersions> availablePlugins = pluginManager
+ .getAvailablePlugins();
+ if (availablePlugins.size() == 0)
+ return new JLabel("No new plugins available", CENTER);
+
+ JPanel panel = new JPanel(new ListLayout());
+ for (PluginVersions plugin : availablePlugins)
+ panel.add(new AvailablePluginPanel(plugin, pluginManager));
+ return scrolled(panel);
+ } catch (PluginException e) {
+ logger.info("Error looking for new plugins", e);
+ return new JLabel("No new plugins available", CENTER);
+ }
+ }
+
+ private Component createInstalledPluginsPanel() {
+ try {
+ List<Plugin> installedPlugins = pluginManager.getInstalledPlugins();
+ if (installedPlugins.size() == 0)
+ return new JLabel("No installed plugins", CENTER);
+
+ JPanel panel = new JPanel(new ListLayout());
+ for (Plugin plugin : installedPlugins)
+ panel.add(new InstalledPluginPanel(plugin));
+ return scrolled(panel);
+ } catch (PluginException e) {
+ return new JLabel("No installed plugins", CENTER);
+ }
+ }
+
+ private Component createUpdatePluginsPanel() {
+ try {
+ List<PluginVersions> pluginUpdates = pluginManager
+ .getPluginUpdates();
+ if (pluginUpdates.size() == 0)
+ return new JLabel("All plugins are up to date", CENTER);
+
+ JPanel panel = new JPanel(new ListLayout());
+ for (PluginVersions plugin : pluginUpdates)
+ panel.add(new UpdatePluginPanel(plugin, pluginManager));
+ return scrolled(panel);
+ } catch (PluginException e) {
+ return new JLabel("All plugins are up to date", CENTER);
+ }
+ }
+
+ private final class ListLayout implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension preferredLayoutSize = new Dimension(0, 1);
+ for (Component component : parent.getComponents()) {
+ Dimension preferredSize = component.getPreferredSize();
+ preferredLayoutSize.width = max(preferredSize.width,
+ preferredLayoutSize.width);
+ preferredLayoutSize.height = preferredSize.height
+ + preferredLayoutSize.height - 1;
+ }
+ return preferredLayoutSize;
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ Dimension minimumLayoutSize = new Dimension(0, 1);
+ for (Component component : parent.getComponents()) {
+ Dimension minimumSize = component.getMinimumSize();
+ minimumLayoutSize.width = max(minimumSize.width,
+ minimumLayoutSize.width);
+ minimumLayoutSize.height = minimumSize.height
+ + minimumLayoutSize.height - 1;
+ }
+ return minimumLayoutSize;
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ int y = 0;
+ for (Component component : parent.getComponents()) {
+ component.setLocation(0, y);
+ component.setSize(parent.getSize().width,
+ component.getPreferredSize().height);
+ y += component.getHeight() - 1;
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java
new file mode 100644
index 0000000..27822a6
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginManagerView.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
+
+import javax.swing.JDialog;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+/**
+ * @author David Withers
+ */
+public class PluginManagerView implements EventHandler {
+ private JDialog dialog;
+ private PluginManagerPanel pluginManagerPanel;
+ private PluginManager pluginManager;
+
+ public void showDialog() {
+ getDialog().setVisible(true);
+ getPluginManagerPanel().checkForUpdates();
+ }
+
+ private JDialog getDialog() {
+ if (dialog == null) {
+ dialog = new JDialog(getMainWindow(), "Plugin Manager");
+ dialog.add(getPluginManagerPanel());
+ dialog.setSize(700, 500);
+ dialog.setLocationRelativeTo(dialog.getOwner());
+ dialog.setVisible(true);
+ }
+ return dialog;
+ }
+
+ private PluginManagerPanel getPluginManagerPanel() {
+ if (pluginManagerPanel == null)
+ pluginManagerPanel = new PluginManagerPanel(pluginManager);
+ return pluginManagerPanel;
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ pluginManagerPanel.initialize();
+ pluginManagerPanel.revalidate();
+ }
+
+ public void setPluginManager(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java
new file mode 100644
index 0000000..c156630
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/PluginPanel.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.NORTHWEST;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tavernaCogs64x64Icon;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicButtonUI;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class PluginPanel extends JPanel {
+ @SuppressWarnings("unused")
+ private static final int logoSize = 64;
+
+ private JLabel descriptionLabel;
+ private JLabel descriptionTitle;
+ private JButton actionButton;
+
+ public PluginPanel(String name, String organization, String version,
+ String description) {
+ setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = NORTHWEST;
+ gbc.insets.left = 10;
+ gbc.insets.right = 10;
+ gbc.insets.top = 10;
+ gbc.insets.bottom = 10;
+
+ gbc.gridx = 0;
+ gbc.weightx = 0;
+ gbc.gridheight = 4;
+ JLabel logo = new JLabel(tavernaCogs64x64Icon);
+ add(logo, gbc);
+
+ gbc.gridx = 2;
+ gbc.anchor = CENTER;
+ actionButton = new JButton(getPluginAction());
+ add(actionButton, gbc);
+
+ gbc.gridx = 1;
+ gbc.weightx = 1;
+ gbc.gridheight = 1;
+ gbc.insets.top = 7;
+ gbc.insets.bottom = 0;
+ gbc.anchor = NORTHWEST;
+ JLabel nameLabel = new JLabel(name);
+ nameLabel.setFont(getFont().deriveFont(BOLD));
+ add(nameLabel, gbc);
+
+ gbc.insets.top = 0;
+ add(new JLabel(organization), gbc);
+
+ add(new JLabel("Version " + version), gbc);
+
+ JButton information = new JButton(new InfoAction());
+ information.setFont(information.getFont().deriveFont(BOLD));
+ information.setUI(new BasicButtonUI());
+ information.setBorder(null);
+ add(information, gbc);
+
+ descriptionTitle = new JLabel("Description");
+ descriptionTitle.setFont(getFont().deriveFont(BOLD));
+ descriptionLabel = new JLabel("<html>" + description);
+
+ setBorder(new PluginBorder());
+ }
+
+ private void showInformation() {
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.anchor = NORTHWEST;
+ gbc.insets.left = 10;
+ gbc.insets.right = 10;
+ gbc.insets.bottom = 10;
+ gbc.gridx = 0;
+ gbc.gridwidth = 3;
+
+ add(descriptionTitle, gbc);
+ add(descriptionLabel, gbc);
+ revalidate();
+ }
+
+ private void hideInformation() {
+ remove(descriptionTitle);
+ remove(descriptionLabel);
+ revalidate();
+ }
+
+ public abstract Action getPluginAction();
+
+ class InfoAction extends AbstractAction {
+ private boolean showInformation = true;
+
+ public InfoAction() {
+ putValue(NAME, "Show information");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (showInformation) {
+ showInformation();
+ putValue(NAME, "Hide information");
+ showInformation = false;
+ } else {
+ hideInformation();
+ putValue(NAME, "Show information");
+ showInformation = true;
+ }
+ }
+ }
+
+ class PluginBorder implements Border {
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(getBackground().darker());
+ g.drawLine(x, y, x + width, y);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 0, 0, 0);
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java
new file mode 100644
index 0000000..bcd427f
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/UpdatePluginPanel.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.commons.plugin.xml.jaxb.PluginVersions;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class UpdatePluginPanel extends PluginPanel {
+ private final PluginVersions pluginVersions;
+ private final PluginManager pluginManager;
+
+ public UpdatePluginPanel(PluginVersions pluginVersions,
+ PluginManager pluginManager) {
+ super(pluginVersions.getName(), pluginVersions.getOrganization(),
+ pluginVersions.getLatestVersion().getVersion(), pluginVersions
+ .getDescription());
+ this.pluginVersions = pluginVersions;
+ this.pluginManager = pluginManager;
+ }
+
+ @Override
+ public Action getPluginAction() {
+ return new AbstractAction("Update") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setEnabled(false);
+ putValue(NAME, "Updating");
+ boolean succeeded = doUpdate();
+ putValue(NAME, succeeded ? "Updated" : "Failed to update");
+ }
+ };
+ }
+
+ private boolean doUpdate() {
+ try {
+ pluginManager.updatePlugin(pluginVersions);
+ return true;
+ } catch (PluginException e) {
+ // FIXME Log exception properly
+ e.printStackTrace();
+ return false;
+ }
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java
new file mode 100644
index 0000000..6d132de
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/java/net/sf/taverna/t2/workbench/plugin/impl/menu/PluginMenuAction.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.plugin.impl.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.plugin.impl.PluginManagerView;
+
+public class PluginMenuAction extends AbstractMenuAction {
+ private static final URI ADVANCED_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+ private PluginManagerView pluginManagerView;
+
+ public PluginMenuAction() {
+ super(ADVANCED_MENU_URI, 1100);
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ protected Action createAction() {
+ return new AbstractAction("Plugin Manager") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ pluginManagerView.showDialog();
+ }
+ };
+ }
+
+ public void setPluginManagerView(PluginManagerView pluginManagerView) {
+ this.pluginManagerView = pluginManagerView;
+ }
+}
diff --git a/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml
new file mode 100644
index 0000000..5e5b207
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="PluginMenuAction" auto-export="interfaces" />
+
+ <service ref="pluginManagerView" auto-export="interfaces">
+ <service-properties value-type="java.lang.String[]">
+ <beans:entry key="event.topics"
+ value="uk/org/taverna/commons/plugin/PluginManager/*" />
+ </service-properties>
+ </service>
+
+ <reference id="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml
new file mode 100644
index 0000000..2634fb3
--- /dev/null
+++ b/taverna-workbench-plugin-manager/src/main/resources/META-INF/spring/plugin-manager-context.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="PluginMenuAction"
+ class="net.sf.taverna.t2.workbench.plugin.impl.menu.PluginMenuAction">
+ <property name="pluginManagerView" ref="pluginManagerView" />
+ </bean>
+
+ <bean id="pluginManagerView" class="net.sf.taverna.t2.workbench.plugin.impl.PluginManagerView">
+ <property name="pluginManager" ref="pluginManager" />
+ </bean>
+</beans>
diff --git a/taverna-workbench-plugins-gui/pom.xml b/taverna-workbench-plugins-gui/pom.xml
new file mode 100644
index 0000000..0468c67
--- /dev/null
+++ b/taverna-workbench-plugins-gui/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>plugins-gui</artifactId>
+ <name>Raven plugin manager GUI</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>0.1.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-api</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java
new file mode 100644
index 0000000..0ada996
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/AddPluginSiteFrame.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: AddPluginSiteFrame.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 8 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+@SuppressWarnings("serial")
+public class AddPluginSiteFrame extends HelpEnabledDialog {
+
+ private JPanel jContentPane = null;
+ private JButton okButton = null;
+ private JButton cancelButton = null;
+ private JTextField urlText = null;
+ private JTextField nameText = null;
+
+ private String name = null;
+ private String url = null;
+
+ /**
+ * This method initializes
+ *
+ */
+ public AddPluginSiteFrame(JDialog parent) {
+ super(parent,"Add plugin site", true);
+ initialize();
+ this.getRootPane().setDefaultButton(okButton);
+ }
+
+
+ /**
+ * This method initializes this
+ *
+ */
+ private void initialize() {
+ this.setSize(new Dimension(350, 140));
+ this.setContentPane(getJContentPane());
+
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ if (jContentPane == null) {
+ jContentPane = new JPanel();
+
+ GridBagConstraints gridBagContraintHeading = new GridBagConstraints();
+ gridBagContraintHeading.ipadx = 10;
+ gridBagContraintHeading.ipady = 5;
+ gridBagContraintHeading.gridx = 0;
+ gridBagContraintHeading.gridy = 0;
+ gridBagContraintHeading.gridwidth = 2;
+ gridBagContraintHeading.anchor = GridBagConstraints.CENTER;
+ gridBagContraintHeading.fill = GridBagConstraints.BOTH;
+
+ GridBagConstraints gridBagContraintNameLabel = new GridBagConstraints();
+
+ gridBagContraintNameLabel.ipadx = 10;
+ gridBagContraintNameLabel.ipady = 5;
+ gridBagContraintNameLabel.gridx = 0;
+ gridBagContraintNameLabel.gridy = 1;
+ gridBagContraintNameLabel.gridwidth = 1;
+ gridBagContraintNameLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintNameLabel.fill = GridBagConstraints.NONE;
+
+ GridBagConstraints gridBagContraintURLLabel = new GridBagConstraints();
+
+ gridBagContraintURLLabel.ipadx = 10;
+ gridBagContraintURLLabel.ipady = 5;
+ gridBagContraintURLLabel.gridx = 0;
+ gridBagContraintURLLabel.gridy = 2;
+ gridBagContraintURLLabel.gridwidth = 1;
+ gridBagContraintURLLabel.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintURLLabel.fill = GridBagConstraints.NONE;
+
+
+
+
+ GridBagConstraints gridBagContraintNameText = new GridBagConstraints();
+ gridBagContraintNameText.ipadx = 10;
+ gridBagContraintNameText.ipady = 5;
+ gridBagContraintNameText.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintNameText.fill = GridBagConstraints.HORIZONTAL;
+ gridBagContraintNameText.gridx = 1;
+ gridBagContraintNameText.gridy = 1;
+ gridBagContraintNameText.weightx = 0.1;
+
+ GridBagConstraints gridBagContraintURLText = new GridBagConstraints();
+ gridBagContraintURLText.ipadx = 10;
+ gridBagContraintURLText.ipady = 5;
+ gridBagContraintURLText.anchor = GridBagConstraints.FIRST_LINE_START;
+ gridBagContraintURLText.fill = GridBagConstraints.HORIZONTAL;
+ gridBagContraintURLText.gridx = 1;
+ gridBagContraintURLText.gridy = 2;
+ gridBagContraintURLText.weightx = 0.1;
+
+
+ GridBagConstraints gridBagContraintButtons = new GridBagConstraints();
+ gridBagContraintButtons.gridwidth=2;
+ gridBagContraintButtons.ipadx = 10;
+ gridBagContraintButtons.ipady = 5;
+ gridBagContraintButtons.anchor = GridBagConstraints.SOUTH;
+ gridBagContraintButtons.fill = GridBagConstraints.BOTH;
+ gridBagContraintButtons.weightx = 0;
+ gridBagContraintButtons.weighty = 0.2;
+ gridBagContraintButtons.gridy = 3;
+ gridBagContraintButtons.gridx = 0;
+
+ JLabel name = new JLabel("Site Name:");
+ name.setHorizontalAlignment(SwingConstants.RIGHT);
+ JLabel url = new JLabel("Site URL:");
+ url.setHorizontalAlignment(SwingConstants.RIGHT);
+
+ urlText=new JTextField("http://");
+ nameText=new JTextField();
+
+
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(new JLabel("Enter update site name and url"),gridBagContraintHeading);
+ jContentPane.add(name, gridBagContraintNameLabel);
+ jContentPane.add(url, gridBagContraintURLLabel);
+ jContentPane.add(nameText, gridBagContraintNameText);
+ jContentPane.add(urlText, gridBagContraintURLText);
+ jContentPane.add(getButtonPanel(), gridBagContraintButtons);
+
+ }
+ return jContentPane;
+ }
+
+ public JPanel getButtonPanel() {
+ return new ButtonPanel(getOKButton(),getCancelButton());
+ }
+
+ public JButton getOKButton() {
+ if (okButton==null) {
+ okButton=new JButton("OK");
+ final ActionListener okAction = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ name=nameText.getText();
+ url=urlText.getText();
+ setVisible(false);
+ dispose();
+ }
+ };
+ okButton.addActionListener(okAction);
+ okButton.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyPressed(java.awt.event.KeyEvent evt) {
+ if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
+ okAction.actionPerformed(null);
+ }
+ }
+ });
+ }
+ return okButton;
+ }
+
+ public JButton getCancelButton() {
+ if (cancelButton==null) {
+ cancelButton=new JButton("Cancel");
+ final ActionListener cancelAction = new ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ };
+ cancelButton.addActionListener(cancelAction);
+ cancelButton.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyPressed(java.awt.event.KeyEvent evt) {
+ if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
+ cancelAction.actionPerformed(null);
+ }
+ }
+ });
+ }
+ return cancelButton;
+ }
+
+
+ public String getName() {
+ if (name!=null) name=name.trim();
+ return name;
+ }
+
+
+ public String getUrl() {
+ if (url!=null) url=url.trim();
+ if (!url.endsWith("/")) url+="/";
+ return url;
+ }
+
+} // @jve:decl-index=0:visual-constraint="73,21"
+
+
+@SuppressWarnings("serial")
+class ButtonPanel extends JPanel {
+ public ButtonPanel(JButton ok, JButton cancel) {
+ super(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.ipadx = 5;
+ c.gridy = GridBagConstraints.RELATIVE;
+ c.fill = GridBagConstraints.BOTH;
+ add(ok);
+ add(cancel);
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java
new file mode 100644
index 0000000..c40cfcf
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForNoticeStartupHook.java
@@ -0,0 +1,143 @@
+/**
+ *
+ */
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.GraphicsEnvironment;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JOptionPane;
+
+import org.apache.log4j.Logger;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.io.FileUtils;
+
+import net.sf.taverna.raven.appconfig.ApplicationConfig;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.spi.Profile;
+import net.sf.taverna.raven.spi.ProfileFactory;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.icons.WorkbenchIcons;
+
+/**
+ *
+ * This class looks for a notice on the myGrid website that is later than the
+ * one (if any) in the application directory. It then displays the notice. This
+ * is intended to allow simple messages to be sent to all users.
+ *
+ * @author alanrw
+ *
+ */
+public class CheckForNoticeStartupHook implements StartupSPI {
+
+ private static Logger logger = Logger
+ .getLogger(CheckForNoticeStartupHook.class);
+
+ private static final String LAST_NOTICE_CHECK_FILE_NAME = "last_notice";
+
+
+ private static File checkForUpdatesDirectory = CheckForUpdatesStartupHook
+ .getCheckForUpdatesDirectory();
+ private static File lastNoticeCheckFile = new File(checkForUpdatesDirectory,
+ LAST_NOTICE_CHECK_FILE_NAME);
+
+ private static String pattern = "EEE, dd MMM yyyy HH:mm:ss Z";
+ private static SimpleDateFormat format = new SimpleDateFormat(pattern);
+
+ private static Profile profile = ProfileFactory.getInstance().getProfile();
+ private static String version = profile.getVersion();
+
+ private static String BASE_URL = "http://www.mygrid.org.uk/taverna/updates";
+ private static String SUFFIX = "notice";
+
+ private static int TIMEOUT = 5000;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.StartupSPI#positionHint()
+ */
+ public int positionHint() {
+ return 95;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.StartupSPI#startup()
+ */
+ public boolean startup() {
+
+ if (GraphicsEnvironment.isHeadless()) {
+ return true; // if we are running headlessly just return
+ }
+
+ long noticeTime = -1;
+ long lastCheckedTime = -1;
+
+ HttpClient client = new HttpClient();
+ client.setConnectionTimeout(TIMEOUT);
+ client.setTimeout(TIMEOUT);
+ PluginManager.setProxy(client);
+ String message = null;
+
+ try {
+ URI noticeURI = new URI(BASE_URL + "/" + version + "/" + SUFFIX);
+ HttpMethod method = new GetMethod(noticeURI.toString());
+ int statusCode = client.executeMethod(method);
+ if (statusCode != HttpStatus.SC_OK) {
+ logger.warn("HTTP status " + statusCode + " while getting "
+ + noticeURI);
+ return true;
+ }
+ String noticeTimeString = null;
+ Header h = method.getResponseHeader("Last-Modified");
+ message = method.getResponseBodyAsString();
+ if (h != null) {
+ noticeTimeString = h.getValue();
+ noticeTime = format.parse(noticeTimeString).getTime();
+ logger.info("NoticeTime is " + noticeTime);
+ }
+
+ } catch (URISyntaxException e) {
+ logger.error("URI problem", e);
+ return true;
+ } catch (IOException e) {
+ logger.info("Could not read notice", e);
+ } catch (ParseException e) {
+ logger.error("Could not parse last-modified time", e);
+ }
+
+ if (lastNoticeCheckFile.exists()) {
+ lastCheckedTime = lastNoticeCheckFile.lastModified();
+ }
+
+ if ((message != null) && (noticeTime != -1)) {
+ if (noticeTime > lastCheckedTime) {
+ // Show the notice dialog
+ JOptionPane.showMessageDialog(null, message, "Taverna notice",
+ JOptionPane.INFORMATION_MESSAGE,
+ WorkbenchIcons.tavernaCogs64x64Icon);
+ try {
+ FileUtils.touch(lastNoticeCheckFile);
+ } catch (IOException e) {
+ logger.error("Unable to touch file", e);
+ }
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java
new file mode 100644
index 0000000..3849c1b
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesDialog.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * Dialog that lets user know that there are updates available.
+ *
+ * @author Alex Nenadic
+ *
+ */
+@SuppressWarnings("serial")
+public class CheckForUpdatesDialog extends HelpEnabledDialog {
+
+ private Logger logger = Logger.getLogger(CheckForUpdatesDialog.class);
+
+ public CheckForUpdatesDialog(){
+ super((Frame)null, "Updates available", true);
+ initComponents();
+ }
+
+ // For testing
+ public static void main (String[] args){
+ CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+ dialog.setVisible(true);
+ }
+
+ private void initComponents() {
+ // Base font for all components on the form
+ Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+
+ // Message saying that updates are available
+ JPanel messagePanel = new JPanel(new BorderLayout());
+ messagePanel.setBorder(new CompoundBorder(new EmptyBorder(10,10,10,10), new EtchedBorder(EtchedBorder.LOWERED)));
+ JLabel message = new JLabel(
+ "<html><body>Updates are available for some Taverna components. To review and <br>install them go to 'Updates and plugins' in the 'Advanced' menu.</body><html>");
+ message.setFont(baseFont.deriveFont(12f));
+ message.setBorder(new EmptyBorder(5,5,5,5));
+ message.setIcon(UpdatesAvailableIcon.updateIcon);
+ messagePanel.add(message, BorderLayout.CENTER);
+
+ // Buttons
+ JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ JButton okButton = new JButton("OK"); // we'll check for updates again in 2 weeks
+ okButton.setFont(baseFont);
+ okButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e) {
+ okPressed();
+ }
+ });
+
+ buttonsPanel.add(okButton);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(messagePanel, BorderLayout.CENTER);
+ getContentPane().add(buttonsPanel, BorderLayout.SOUTH);
+
+ pack();
+ setResizable(false);
+ // Center the dialog on the screen (we do not have the parent)
+ Dimension dimension = getToolkit().getScreenSize();
+ Rectangle abounds = getBounds();
+ setLocation((dimension.width - abounds.width) / 2,
+ (dimension.height - abounds.height) / 2);
+ setSize(getPreferredSize());
+ }
+
+ protected void okPressed() {
+ try {
+ FileUtils.touch(CheckForUpdatesStartupHook.lastUpdateCheckFile);
+ } catch (IOException ioex) {
+ logger.error("Failed to touch the 'Last update check' file for Taverna updates.", ioex);
+ }
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java
new file mode 100644
index 0000000..6e8df5f
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/CheckForUpdatesStartupHook.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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.raven.plugins.ui;
+
+import java.io.File;
+import java.util.Date;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+
+/**
+ * Startup hook for checking if there are available updates for Taverna plugins.
+ *
+ * @author Alex Nenadic
+ * @author Stian Soiland-Reyes
+ *
+ */
+public class CheckForUpdatesStartupHook implements StartupSPI {
+
+ public static final String CHECK_FOR_UPDATES_DIRECTORY_NAME = "updates";
+ public static final String LAST_UPDATE_CHECK_FILE_NAME = "last_update_check";
+
+ private PluginManager pluginManager;
+ private ApplicationConfiguration applicationConfiguration;
+
+ public static File checkForUpdatesDirectory = getCheckForUpdatesDirectory();
+ public static File lastUpdateCheckFile = new File(checkForUpdatesDirectory,
+ LAST_UPDATE_CHECK_FILE_NAME);
+
+ public int positionHint() {
+ return 90;
+ }
+
+ public boolean startup() {
+
+ // Check if more than 2 weeks passed since we checked for updates.
+ if (lastUpdateCheckFile.exists()) {
+ long lastModified = lastUpdateCheckFile.lastModified();
+ long now = new Date().getTime();
+
+ if (now - lastModified < 14 * 24 * 3600 * 1000) { // 2 weeks have not passed since we
+ // last asked
+ return true;
+ } else { // Check again for updates
+ if (pluginManager.checkForUpdates()) {
+ CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+ dialog.setVisible(true);
+ }
+ return true;
+ }
+ } else {
+ // If we are here - then this is the first time to check for updates
+ if (pluginManager.checkForUpdates()) {
+ CheckForUpdatesDialog dialog = new CheckForUpdatesDialog();
+ dialog.setVisible(true);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Gets the registration directory where info about registration will be saved to.
+ */
+ public File getCheckForUpdatesDirectory() {
+
+ File home = applicationConfiguration.getApplicationHomeDir();
+
+ File registrationDirectory = new File(home, CHECK_FOR_UPDATES_DIRECTORY_NAME);
+ if (!registrationDirectory.exists()) {
+ registrationDirectory.mkdir();
+ }
+ return registrationDirectory;
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java
new file mode 100644
index 0000000..094f25e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListCellRenderer.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginListCellRenderer.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 28 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.AbstractBorder;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+/**
+ *
+ * @author David Withers
+ */
+public class PluginListCellRenderer extends JPanel implements ListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private PluginManager pluginManager;
+
+ private JLabel name = null;
+
+ private JLabel description = null;
+
+ private JLabel version = null;
+
+ private JLabel status = null;
+ private JLabel status2 = null;
+
+ /**
+ * This is the default constructor
+ */
+ public PluginListCellRenderer(PluginManager pluginManager) {
+ super();
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ * @return void
+ */
+ private void initialize() {
+ GridBagConstraints gridBagStatus = new GridBagConstraints();
+ gridBagStatus.gridx = 0;
+ gridBagStatus.gridwidth = 2;
+ gridBagStatus.anchor = GridBagConstraints.NORTHWEST;
+ gridBagStatus.insets = new Insets(3, 3, 3, 3);
+ gridBagStatus.gridy = 2;
+
+ GridBagConstraints gridBagStatus2 = new GridBagConstraints();
+ gridBagStatus2.gridx = 0;
+ gridBagStatus2.gridwidth = 2;
+ gridBagStatus2.anchor = GridBagConstraints.NORTHWEST;
+ gridBagStatus2.insets = new Insets(3, 3, 3, 3);
+ gridBagStatus2.gridy = 3;
+
+ status = new JLabel();
+ status.setFont(getFont().deriveFont(Font.BOLD));
+ status.setForeground(Color.BLUE);
+ status.setText("status");
+ status2 = new JLabel();
+ status2.setFont(getFont().deriveFont(Font.BOLD));
+ status2.setForeground(Color.RED);
+ status2.setText("Status");
+
+
+ GridBagConstraints gridBagVersion = new GridBagConstraints();
+ gridBagVersion.gridx = 1;
+ gridBagVersion.insets = new Insets(3, 8, 3, 3);
+ gridBagVersion.anchor = GridBagConstraints.NORTHWEST;
+ gridBagVersion.fill = GridBagConstraints.NONE;
+ gridBagVersion.gridy = 0;
+
+ version = new JLabel();
+ version.setFont(getFont().deriveFont(Font.PLAIN));
+ version.setText("Version");
+
+ GridBagConstraints gridBagDescription = new GridBagConstraints();
+ gridBagDescription.gridx = 0;
+ gridBagDescription.anchor = GridBagConstraints.NORTHWEST;
+ gridBagDescription.fill = GridBagConstraints.HORIZONTAL;
+ gridBagDescription.weightx = 1.0;
+ gridBagDescription.insets = new Insets(3, 3, 3, 3);
+ gridBagDescription.gridwidth = 2;
+ gridBagDescription.gridy = 1;
+ description = new JLabel();
+ description.setFont(getFont().deriveFont(Font.PLAIN));
+ description.setText("Plugin description");
+
+ GridBagConstraints gridBagName = new GridBagConstraints();
+ gridBagName.gridx = 0;
+ gridBagName.anchor = GridBagConstraints.NORTHWEST;
+ gridBagName.fill = GridBagConstraints.NONE;
+ gridBagName.weightx = 0.0;
+ gridBagName.ipadx = 0;
+ gridBagName.insets = new Insets(3, 3, 3, 3);
+ gridBagName.gridwidth = 1;
+ gridBagName.gridy = 0;
+ name = new JLabel();
+ name.setFont(getFont().deriveFont(Font.BOLD));
+ name.setText("Plugin name");
+
+ this.setSize(297, 97);
+ this.setLayout(new GridBagLayout());
+ this.setBorder(new AbstractBorder() {
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Color oldColor = g.getColor();
+ g.setColor(Color.LIGHT_GRAY);
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ g.setColor(oldColor);
+ }
+ });
+ this.add(name, gridBagName);
+ this.add(description, gridBagDescription);
+ this.add(version, gridBagVersion);
+ this.add(status, gridBagStatus);
+ this.add(status2,gridBagStatus2);
+ }
+
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ if (isSelected) {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ } else {
+ setBackground(list.getBackground());
+ setForeground(list.getForeground());
+ }
+
+ if (value instanceof Plugin) {
+ Plugin plugin = (Plugin) value;
+ name.setText(plugin.getName());
+ version.setText(plugin.getVersion());
+ description.setText("<html>"+plugin.getDescription());
+
+ status2.setText("");
+ if (!plugin.isCompatible()) {
+ status2.setText("This plugin is incompatible.");
+ }
+
+ status.setText("");
+ if (pluginManager.isUpdateAvailable(plugin)) {
+ status.setText("An update is available for this plugin");
+ } else if (!plugin.isEnabled()) {
+ status.setText("This plugin is disabled");
+ }
+ }
+ return this;
+ }
+} // @jve:decl-index=0:visual-constraint="10,10"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java
new file mode 100644
index 0000000..5da76b3
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginListModel.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginListModel.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 28 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import javax.swing.AbstractListModel;
+
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class PluginListModel extends AbstractListModel implements PluginManagerListener {
+ private PluginManager pluginManager;
+
+ private static Logger logger = Logger.getLogger(PluginListModel.class);
+
+ public PluginListModel(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ PluginManager.addPluginManagerListener(this);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.ListModel#getElementAt(int)
+ */
+ public Object getElementAt(int index) {
+ return pluginManager.getPlugins().get(index);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.ListModel#getSize()
+ */
+ public int getSize() {
+ return pluginManager.getPlugins().size();
+ }
+
+ public void pluginAdded(PluginManagerEvent event) {
+ fireIntervalAdded(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginRemoved(PluginManagerEvent event) {
+ fireIntervalRemoved(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginUpdated(PluginManagerEvent event) {
+ //fireContentsChanged(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginStateChanged(PluginManagerEvent event) {
+ fireContentsChanged(this, event.getPluginIndex(), event.getPluginIndex());
+ }
+
+ public void pluginIncompatible(PluginManagerEvent event) {
+
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java
new file mode 100644
index 0000000..f9f374e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginManagerFrame.java
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginManagerFrame.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 27 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Frame;
+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.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+/**
+ * GUI component for the <code>PluginManager</code>.
+ *
+ * @author David Withers
+ */
+public class PluginManagerFrame extends HelpEnabledDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private JPanel jContentPane = null;
+
+ private JButton updateButton = null;
+
+ private JButton findPluginsButton = null;
+
+ private PluginManager pluginManager;
+
+ private JScrollPane jScrollPane = null;
+
+ private JList jList = null;
+
+ private JButton enableButton = null;
+
+ private JButton uninstallButton = null;
+
+ private JButton findUpdatesButton = null;
+
+ private JButton closeButton = null;
+
+ private PluginManagerListener managerListener;
+
+ /**
+ * This is the default constructor
+ */
+ public PluginManagerFrame(PluginManager pluginManager) {
+ this((Frame)null, pluginManager);
+ }
+
+ /**
+ * This is the default constructor
+ */
+ public PluginManagerFrame(Frame parent,PluginManager pluginManager) {
+ super(parent, "Plugin manager", true);
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ /**
+ * This is the default constructor
+ */
+ public PluginManagerFrame(JDialog parent,PluginManager pluginManager) {
+ super(parent, "Plugin manager", true);
+ this.pluginManager = pluginManager;
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ * @return void
+ */
+ private void initialize() {
+ this.setSize(613, 444);
+ this.setContentPane(getJContentPane());
+ this.setTitle("Updates and plugins");
+ this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ this.addWindowListener(new WindowAdapter() {
+
+ @Override
+ public void windowClosed(WindowEvent e) {
+ if (managerListener!=null) PluginManager.removePluginManagerListener(managerListener);
+ }
+
+ });
+ managerListener = new PluginManagerListener() {
+
+ public void pluginAdded(PluginManagerEvent event) {
+ // For some reason even if a plugin does not declare dependencies to system
+ // artifacts it is required for Taverna to be restarted to pick the new plugin up
+ // (probably because things like Service Panel and Perspectives where new plugins can
+ // have some effect are not listening to plugin changes).
+ // So, we have to show the "Restart Taverna" message in any case until this is fixed.
+ //if (event.getPlugin().getProfile().getSystemArtifacts().size()!=0) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this,"The plugin '"+event.getPlugin().getName()+"' will not be fully functional until Taverna is restarted","Restart Required", JOptionPane.WARNING_MESSAGE);
+ //}
+ }
+
+ public void pluginStateChanged(PluginManagerEvent event)
+ {
+ // As in the pluginAdded() method, it is currently always required
+ // to restart Taverna for any changes to the plugins to take effect
+ // (probably because things like Service Panel and Perspectives where
+ // changes to plugin can have some effect are not listening to these changes).
+ // So, we have to show the "Restart Taverna" message in any case until this is fixed.
+ //if (event.getPlugin().getProfile().getSystemArtifacts().size()!=0) {
+ if (event.getSource() instanceof PluginEvent) {
+ PluginEvent pluginEvent = (PluginEvent)event.getSource();
+ if (pluginEvent.getAction()==PluginEvent.ENABLED) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely enabled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+ else if (pluginEvent.getAction()==PluginEvent.DISABLED) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely disabled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ //}
+ }
+
+ public void pluginIncompatible(PluginManagerEvent event) {
+
+ }
+
+ public void pluginUpdated(PluginManagerEvent event) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely updated until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+
+ public void pluginRemoved(PluginManagerEvent event) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "The plugin '"+event.getPlugin().getName()+"' will not be completely uninstalled until Taverna is restarted.","Restart Required", JOptionPane.WARNING_MESSAGE);
+ }
+
+ };
+ PluginManager.addPluginManagerListener(managerListener);
+
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ if (jContentPane == null) {
+ GridBagConstraints findUpdatesConstraints = new GridBagConstraints();
+ findUpdatesConstraints.gridx = 0;
+ findUpdatesConstraints.insets = new Insets(5, 5, 5, 5);
+ findUpdatesConstraints.gridy = 3;
+ GridBagConstraints uninstallButtonConstraints = new GridBagConstraints();
+ uninstallButtonConstraints.gridx = 2;
+ uninstallButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+ uninstallButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+ uninstallButtonConstraints.insets = new Insets(5, 0, 0, 5);
+ uninstallButtonConstraints.gridy = 1;
+ GridBagConstraints enableButtonConstraints = new GridBagConstraints();
+ enableButtonConstraints.gridx = 2;
+ enableButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+ enableButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+ enableButtonConstraints.insets = new Insets(5, 0, 0, 5);
+ enableButtonConstraints.gridy = 0;
+ GridBagConstraints scrollPaneConstraints = new GridBagConstraints();
+ scrollPaneConstraints.fill = GridBagConstraints.BOTH;
+ scrollPaneConstraints.gridy = 0;
+ scrollPaneConstraints.weightx = 1.0;
+ scrollPaneConstraints.weighty = 1.0;
+ scrollPaneConstraints.gridwidth = 2;
+ scrollPaneConstraints.insets = new Insets(5, 5, 5, 5);
+ scrollPaneConstraints.gridx = 0;
+ scrollPaneConstraints.gridheight = 3;
+ scrollPaneConstraints.anchor = GridBagConstraints.NORTHWEST;
+ GridBagConstraints findPluginsConstraints = new GridBagConstraints();
+ findPluginsConstraints.gridx = 1;
+ findPluginsConstraints.anchor = GridBagConstraints.WEST;
+ findPluginsConstraints.insets = new Insets(5, 5, 5, 5);
+ findPluginsConstraints.gridy = 3;
+ GridBagConstraints updateButtonConstraints = new GridBagConstraints();
+ updateButtonConstraints.gridx = 2;
+ updateButtonConstraints.gridwidth = 1;
+ updateButtonConstraints.anchor = GridBagConstraints.NORTHEAST;
+ updateButtonConstraints.insets = new Insets(5, 0, 0, 5);
+ updateButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+ updateButtonConstraints.gridy = 2;
+
+ GridBagConstraints closeButtonConstraints = new GridBagConstraints();
+ closeButtonConstraints.gridx = 2;
+ closeButtonConstraints.insets = new Insets(5, 5, 5, 5);
+ closeButtonConstraints.gridy = 3;
+ closeButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+// closeButtonConstraints.gridx = 2;
+// closeButtonConstraints.gridwidth = 1;
+// closeButtonConstraints.anchor = GridBagConstraints.SOUTHEAST;
+// closeButtonConstraints.insets = new Insets(5, 0, 0, 5);
+// closeButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
+// closeButtonConstraints.gridy = 3;
+
+
+ jContentPane = new JPanel();
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(getUpdateButton(), updateButtonConstraints);
+ jContentPane.add(getFindPluginsButton(), findPluginsConstraints);
+ jContentPane.add(getJScrollPane(), scrollPaneConstraints);
+ jContentPane.add(getEnableButton(), enableButtonConstraints);
+ jContentPane.add(getUninstallButton(), uninstallButtonConstraints);
+ jContentPane.add(getFindUpdatesButton(), findUpdatesConstraints);
+ jContentPane.add(getCloseButton(),closeButtonConstraints);
+ }
+ return jContentPane;
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getUpdateButton() {
+ if (updateButton == null) {
+ updateButton = new JButton();
+ updateButton.setText("Update");
+ updateButton.setEnabled(false);
+ updateButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = getJList().getSelectedValue();
+ if (selectedObject instanceof Plugin) {
+ pluginManager.updatePlugin((Plugin) selectedObject);
+ }
+ jList.setSelectedValue(selectedObject, true);
+ updateButton.setEnabled(false);
+ }
+ });
+ }
+ return updateButton;
+ }
+
+ /**
+ * This method initializes jScrollPane
+ *
+ * @return javax.swing.JScrollPane
+ */
+ private JScrollPane getJScrollPane() {
+ if (jScrollPane == null) {
+ jScrollPane = new JScrollPane();
+ jScrollPane.setViewportView(getJList());
+ }
+ return jScrollPane;
+ }
+
+ /**
+ * This method initializes jList
+ *
+ * @return javax.swing.JList
+ */
+ private JList getJList() {
+ if (jList == null) {
+ jList = new JList();
+ jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ jList.setModel(new PluginListModel(pluginManager));
+ jList.setSelectionBackground(new Color(135,206,250)); //LightSkyBlue
+ jList.setCellRenderer(new PluginListCellRenderer(pluginManager));
+ jList.addListSelectionListener(new ListSelectionListener() {
+
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ respondToSelectedPlugin();
+ }
+ }
+
+ });
+ if (jList.getComponentCount() > 0) {
+ jList.setSelectedIndex(0);
+ respondToSelectedPlugin();
+ }
+ }
+ return jList;
+ }
+
+ /**
+ * This method initializes jButton2
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getEnableButton() {
+ if (enableButton == null) {
+ enableButton = new JButton();
+ enableButton.setText("Enable");
+ enableButton.setEnabled(false);
+ enableButton.setActionCommand("enable");
+ enableButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = jList.getSelectedValue();
+ if (selectedObject instanceof Plugin) {
+ Plugin plugin = (Plugin) selectedObject;
+ if ("enable".equals(e.getActionCommand())) {
+ plugin.setEnabled(true);
+ enableButton.setText("Disable");
+ enableButton.setActionCommand("disable");
+ } else if ("disable".equals(e.getActionCommand())) {
+ plugin.setEnabled(false);
+ enableButton.setText("Enable");
+ enableButton.setActionCommand("enable");
+ }
+ }
+ jList.setSelectedValue(selectedObject, true);
+ }
+ });
+ }
+ return enableButton;
+ }
+
+ /**
+ * This method initializes jButton3
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getUninstallButton() {
+ if (uninstallButton == null) {
+ uninstallButton = new JButton();
+ uninstallButton.setText("Uninstall");
+ uninstallButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ int index = jList.getSelectedIndex();
+ Object selectedObject = jList.getSelectedValue();
+ if (selectedObject instanceof Plugin) {
+ pluginManager.removePlugin((Plugin) selectedObject);
+ pluginManager.savePlugins();
+ }
+ int listSize = jList.getModel().getSize();
+ if (listSize > index) {
+ jList.setSelectedIndex(index);
+ } else {
+ jList.setSelectedIndex(listSize - 1);
+ }
+ }
+ });
+ }
+ return uninstallButton;
+ }
+
+ private JButton getCloseButton() {
+ if (closeButton==null) {
+ closeButton = new JButton("Close");
+ closeButton.setEnabled(true);
+ closeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ });
+ }
+ return closeButton;
+ }
+
+ /**
+ * This method initializes jButton1
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getFindPluginsButton() {
+ if (findPluginsButton == null) {
+ findPluginsButton = new JButton();
+ findPluginsButton.setText("Find New Plugins");
+ findPluginsButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = getJList().getSelectedValue();
+ PluginSiteFrame pluginSiteFrame = new PluginSiteFrame(PluginManagerFrame.this);
+ pluginSiteFrame.setLocationRelativeTo(PluginManagerFrame.this);
+ pluginSiteFrame.setVisible(true);
+ if (selectedObject != null) {
+ jList.setSelectedValue(selectedObject, true);
+ } else {
+ jList.setSelectedIndex(0);
+ }
+ }
+ });
+ }
+ return findPluginsButton;
+ }
+
+ /**
+ * This method initializes jButton4
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getFindUpdatesButton() {
+ if (findUpdatesButton == null) {
+ findUpdatesButton = new JButton();
+ findUpdatesButton.setText("Find Updates");
+ findUpdatesButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ Object selectedObject = getJList().getSelectedValue();
+ if (!pluginManager.checkForUpdates()) {
+ JOptionPane.showMessageDialog(PluginManagerFrame.this, "No updates available");
+ }
+ if (selectedObject != null) {
+ jList.setSelectedValue(selectedObject, true);
+ } else {
+ jList.setSelectedIndex(0);
+ }
+ // Respond to selected plugin - i.e. enable/disable action buttons as appropriate
+ respondToSelectedPlugin();
+ }
+ });
+ }
+ return findUpdatesButton;
+ }
+
+ private void respondToSelectedPlugin() {
+ Object selectedObject = jList.getSelectedValue();
+ if (selectedObject!=null && selectedObject instanceof Plugin) {
+ Plugin plugin = (Plugin) selectedObject;
+
+ // If this is a build-in plugin - set the text of the enableButton
+ // to 'Disable' but also disable the button to indicate that
+ // built-in plugins cannot be disabled.
+ // Similarly, uninstallButton should be disabled in this case.
+ if (plugin.isBuiltIn()){
+ getEnableButton().setText("Disable");
+ getEnableButton().setActionCommand("disable");
+ getEnableButton().setEnabled(false);
+ }
+ else{
+ if (plugin.isEnabled()) {
+ getEnableButton().setText("Disable");
+ getEnableButton().setActionCommand("disable");
+ } else {
+ getEnableButton().setText("Enable");
+ getEnableButton().setActionCommand("enable");
+ }
+
+ //only allow plugin to be enabled if it is compatible.
+ if (plugin.isCompatible()) {
+ getEnableButton().setEnabled(true);
+ }
+ else {
+ getEnableButton().setEnabled(false);
+ }
+ }
+
+ if (pluginManager.isUpdateAvailable(plugin)) {
+ getUpdateButton().setEnabled(true);
+ } else {
+ getUpdateButton().setEnabled(false);
+ }
+
+ //disable the uninstall button if this is a built in plugin
+ getUninstallButton().setEnabled(!plugin.isBuiltIn());
+ }
+ }
+
+} // @jve:decl-index=0:visual-constraint="33,9"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java
new file mode 100644
index 0000000..c952e9c
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginRepositoryListener.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginRepositoryListener.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:51:52 $
+ * by $Author: sowen70 $
+ * Created on 7 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import javax.swing.JProgressBar;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.raven.RavenException;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.repository.Artifact;
+import net.sf.taverna.raven.repository.ArtifactStatus;
+import net.sf.taverna.raven.repository.DownloadStatus;
+import net.sf.taverna.raven.repository.RepositoryListener;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class PluginRepositoryListener implements
+ RepositoryListener {
+
+ private final JProgressBar bar = new JProgressBar();
+
+ private static Logger logger = Logger
+ .getLogger(PluginRepositoryListener.class);
+
+ public PluginRepositoryListener() {
+ bar.setMaximum(100);
+ bar.setMinimum(0);
+ bar.setStringPainted(true);
+ }
+
+ public void statusChanged(final Artifact a, ArtifactStatus oldStatus,
+ ArtifactStatus newStatus) {
+
+ bar.setString(a.getArtifactId() + "-" + a.getVersion() + " : "
+ + newStatus.toString());
+
+ if (newStatus.equals(ArtifactStatus.JarFetching)) {
+
+ final DownloadStatus dls;
+ try {
+ dls = PluginManager.getInstance().getRepository()
+ .getDownloadStatus(a);
+ } catch (RavenException ex) {
+ logger.warn("Could not get download status for: " + a, ex);
+ return;
+ }
+
+ Thread progressThread = new Thread(new Runnable() {
+ public void run() {
+ while (true) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ logger.warn("Progress thread interrupted", e);
+ return;
+ }
+ int progress = Math.min(100, (dls.getReadBytes() * 100)
+ / dls.getTotalBytes());
+ setProgress(progress);
+ if (dls.isFinished()) {
+ return;
+ }
+ }
+ }
+ }, "Update repository progress bar");
+ progressThread.start();
+ }
+ }
+
+
+ public JProgressBar getProgressBar() {
+ return bar;
+ }
+
+ public void setVisible(final boolean val) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ bar.setVisible(val);
+ }
+ });
+
+ }
+
+ public void setProgress(final int percentage) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (percentage > 0) {
+ bar.setValue(percentage);
+ } else {
+ bar.setValue(0);
+ }
+ }
+ });
+
+ }
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java
new file mode 100644
index 0000000..ecdb06e
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/PluginSiteFrame.java
@@ -0,0 +1,542 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: PluginSiteFrame.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/10/27 13:39:56 $
+ * by $Author: stain $
+ * Created on 29 Nov 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Frame;
+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.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.border.EtchedBorder;
+
+import uk.org.taverna.commons.plugin.PluginManager;
+
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+
+/**
+ *
+ * @author David Withers
+ */
+public class PluginSiteFrame extends HelpEnabledDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private PluginManager pluginManager;
+
+ private List<PluginSite> pluginSites;
+
+ private List<Plugin> installationScheduled = new ArrayList<Plugin>(); // @jve:decl-index=0:
+
+ private JButton installButton = null; // @jve:decl-index=0:visual-constraint="446,247"
+
+ private JButton cancelButton = null;
+
+ private JButton addSiteButton = null;
+
+ private Map<Plugin, PluginRepositoryListener> listeners = new HashMap<Plugin, PluginRepositoryListener>();
+
+ private AddPluginSiteFrame addSiteFrame = null;
+
+ private JScrollPane scrollPane = null;
+
+ /**
+ * This is the default constructor
+ */
+ public PluginSiteFrame(Frame owner) {
+ super(owner, "Update sites", true);
+ initialize();
+ }
+
+ /**
+ * This is the default constructor
+ */
+ public PluginSiteFrame(JDialog owner) {
+ super(owner, "Update sites", true);
+ initialize();
+ }
+
+ /**
+ * This method initializes this
+ *
+ * @return void
+ */
+ private void initialize() {
+ this.pluginManager = PluginManager.getInstance();
+ this.pluginSites = this.pluginManager.getPluginSites();
+ this.setSize(600, 450);
+ this.setContentPane(getJContentPane());
+ }
+
+ /**
+ * This method initializes jContentPane
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJContentPane() {
+ GridBagConstraints gridBagConstraints14 = new GridBagConstraints();
+ gridBagConstraints14.gridx = 1;
+ gridBagConstraints14.anchor = GridBagConstraints.SOUTHWEST;
+ gridBagConstraints14.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints14.insets = new Insets(5, 5, 5, 5);
+ GridBagConstraints gridBagConstraints12 = new GridBagConstraints();
+ gridBagConstraints12.fill = GridBagConstraints.BOTH;
+ gridBagConstraints12.gridx = 0;
+ gridBagConstraints12.gridy = 0;
+ gridBagConstraints12.weightx = 1.0;
+ gridBagConstraints12.weighty = 1.0;
+ gridBagConstraints12.gridwidth = 3;
+ gridBagConstraints12.anchor = GridBagConstraints.NORTHWEST;
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints.anchor = GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new Insets(5, 5, 5, 5);
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.gridx = 2;
+ gridBagConstraints1.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints1.anchor = GridBagConstraints.SOUTHEAST;
+ gridBagConstraints1.insets = new Insets(5, 5, 5, 5);
+ JPanel jContentPane = new JPanel();
+ jContentPane.setLayout(new GridBagLayout());
+ jContentPane.add(getJScrollPane(), gridBagConstraints12);
+ jContentPane.add(getInstallButton(), gridBagConstraints);
+ jContentPane.add(getCloseButton(), gridBagConstraints1);
+ jContentPane.add(getAddPluginSiteButton(), gridBagConstraints14);
+ return jContentPane;
+ }
+
+ /**
+ * This method initializes jScrollPane
+ *
+ * @return javax.swing.JScrollPane
+ */
+ private JScrollPane getJScrollPane() {
+ scrollPane = new JScrollPane();
+ scrollPane.setViewportView(getJPanel());
+ return scrollPane;
+ }
+
+ /**
+ * This method initializes jPanel
+ *
+ * @return javax.swing.JPanel
+ */
+ private JPanel getJPanel() {
+ JPanel jPanel = new JPanel();
+ jPanel.setLayout(new GridBagLayout());
+ for (PluginSite pluginSite : pluginSites) {
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 0.0;
+ gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new Insets(5, 5, 0, 5);
+ jPanel.add(getJPanel1(pluginSite), gridBagConstraints);
+ }
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.fill = GridBagConstraints.BOTH;
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = GridBagConstraints.RELATIVE;
+ gridBagConstraints.weighty = 1.0;
+ jPanel.add(new JPanel(), gridBagConstraints);
+ return jPanel;
+ }
+
+ private JPanel getJPanel1(final PluginSite pluginSite) {
+ final JPanel pluginSitePanel = new JPanel();
+ pluginSitePanel.setBackground(Color.WHITE);
+ pluginSitePanel.setLayout(new GridBagLayout());
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = GridBagConstraints.WEST;
+ gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.insets = new Insets(5, 5, 5, 5);
+ gridBagConstraints.ipadx = 5;
+ gridBagConstraints.ipady = 5;
+ gridBagConstraints.weightx = 1.0;
+ //ShadedLabel siteNameLabel = getSiteLabel(pluginSite);
+ JLabel siteNameLabel = getSiteLabel(pluginSite);
+
+ pluginSitePanel.add(siteNameLabel, gridBagConstraints);
+
+ final GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.gridx = 0;
+ gridBagConstraints1.gridy = 1;
+ gridBagConstraints1.anchor = GridBagConstraints.WEST;
+ gridBagConstraints1.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraints1.gridwidth = 2;
+ gridBagConstraints1.insets = new Insets(5, 5, 5, 5);
+ gridBagConstraints1.weightx = 1.0;
+ final JProgressBar progressBar = new JProgressBar();
+ progressBar.setIndeterminate(true);
+ progressBar.setStringPainted(true);
+ progressBar.setString("Checking for new plugins");
+ pluginSitePanel.add(progressBar, gridBagConstraints1);
+
+ new Thread("Checking update site " + pluginSite) {
+ public void run() {
+ try {
+ List<Plugin> plugins = pluginManager
+ .getUninstalledPluginsFromSite(pluginSite);
+ if (plugins.size() > 0) {
+ Collections.sort(plugins, new Comparator<Plugin>() {
+
+ public int compare(Plugin o1, Plugin o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+
+ });
+
+ pluginSitePanel.remove(progressBar);
+ int gridY = 0;
+ for (Plugin plugin : plugins) {
+ gridY++;
+ GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
+ gridBagConstraints1.gridx = 0;
+ gridBagConstraints1.gridy = gridY;
+ gridBagConstraints1.anchor = GridBagConstraints.WEST;
+ gridBagConstraints1.insets = new Insets(5, 20, 0, 0);
+ pluginSitePanel.add(getJCheckBox(plugin),
+ gridBagConstraints1);
+
+ gridY++;
+
+ GridBagConstraints gridBagConstraintsDescription = new GridBagConstraints();
+ gridBagConstraintsDescription.gridx = 0;
+ gridBagConstraintsDescription.ipadx = 50;
+ gridBagConstraintsDescription.gridy = gridY;
+ gridBagConstraintsDescription.anchor = GridBagConstraints.WEST;
+ gridBagConstraintsDescription.insets = new Insets(5, 25, 10, 5);
+
+ gridY++;
+
+ GridBagConstraints gridBagConstraintsProgress = new GridBagConstraints();
+ gridBagConstraintsProgress.gridx = 0;
+ gridBagConstraintsProgress.gridy = gridY;
+ gridBagConstraintsProgress.fill = GridBagConstraints.HORIZONTAL;
+ gridBagConstraintsProgress.anchor = GridBagConstraints.WEST;
+ gridBagConstraintsProgress.insets = new Insets(5, 5, 0, 0);
+
+ JLabel description = new JLabel();
+ description.setText("<html>"+plugin.getDescription());
+ description.setFont(getFont().deriveFont(Font.PLAIN));
+ pluginSitePanel.add(description,gridBagConstraintsDescription);
+
+ PluginRepositoryListener progress = new PluginRepositoryListener();
+ listeners.put(plugin, progress);
+ progress.setVisible(false);
+
+ pluginSitePanel.add(progress.getProgressBar(),
+ gridBagConstraintsProgress);
+ }
+ } else {
+ pluginSitePanel.remove(progressBar);
+ pluginSitePanel.add(new JLabel(
+ "This update site contains no new plugins"),
+ gridBagConstraints1);
+ }
+ } catch (Exception e) {
+ pluginSitePanel.remove(progressBar);
+ pluginSitePanel.add(new JLabel(
+ "Unable to contact the update site"),
+ gridBagConstraints1);
+ } finally {
+ pluginSitePanel.revalidate();
+ pluginSitePanel.repaint();
+ }
+ }
+ }.start();
+
+ pluginSitePanel.setBorder(new EtchedBorder());
+ return pluginSitePanel;
+ }
+
+ private JLabel getSiteLabel(final PluginSite pluginSite) {
+ //ShadedLabel result = new ShadedLabel(pluginSite.getName(),Color.LIGHT_GRAY);
+ JLabel result = new JLabel(pluginSite.getName());
+
+
+ //add popup remove option, except on the Taverna main plugin site.
+ if (!(pluginSite instanceof TavernaPluginSite)) {
+ result.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ popup(e);
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ popup(e);
+ }
+
+ private void popup(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ JPopupMenu menu=new JPopupMenu();
+ //JMenuItem item = new JMenuItem("Remove site",TavernaIcons.deleteIcon);
+ JMenuItem item = new JMenuItem("Remove site");
+
+ menu.add(item);
+ menu.show(e.getComponent(), e.getX(), e.getY());
+ item.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ int response=JOptionPane.showConfirmDialog(PluginSiteFrame.this, "Are you sure you want to remove the update site:"+pluginSite.getName(),"Remove update site",JOptionPane.YES_NO_OPTION);
+ if (response==JOptionPane.YES_OPTION) {
+ pluginManager.removePluginSite(pluginSite);
+ pluginManager.savePluginSites();
+ scrollPane.setViewportView(getJPanel());
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JCheckBox getJCheckBox(Plugin plugin) {
+ PluginCheckBox checkBox = new PluginCheckBox();
+ checkBox.plugin = plugin;
+ checkBox.setText(plugin.getName() + " " + plugin.getVersion());
+ checkBox.setOpaque(false);
+ checkBox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() instanceof PluginCheckBox) {
+ PluginCheckBox checkBox = (PluginCheckBox) e.getSource();
+ if (checkBox.isSelected()) {
+ installationScheduled.add(checkBox.plugin);
+
+
+
+ getInstallButton().setEnabled(true);
+ } else {
+ installationScheduled.remove(checkBox.plugin);
+ if (installationScheduled.size() == 0) {
+ getInstallButton().setEnabled(false);
+ }
+ }
+ }
+ }
+
+ });
+ return checkBox;
+ }
+
+ private final Thread getUpdateRepositoryThread() {
+ return new Thread("Update Repository Progress") {
+
+ public void run() {
+ cancelButton.setEnabled(false);
+ for (int i = 0; i < installationScheduled.size(); i++) {
+ final Plugin plugin = installationScheduled.get(i);
+ PluginRepositoryListener listener = listeners.get(plugin);
+ if (listener != null) {
+ pluginManager.getRepository().addRepositoryListener(
+ listener);
+ listener.getProgressBar().setVisible(true);
+ }
+
+ pluginManager.addPlugin(plugin);
+
+ if (listener != null) {
+ pluginManager.getRepository().removeRepositoryListener(
+ listener);
+ listener.getProgressBar().setVisible(false);
+ }
+ plugin.setEnabled(true);
+
+ }
+ pluginManager.savePlugins();
+ installationScheduled.clear();
+ setVisible(false);
+ dispose();
+ }
+
+ };
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getInstallButton() {
+ if (installButton == null) {
+ installButton = new JButton();
+ installButton.setText("Install");
+ installButton.setEnabled(false);
+ installButton
+ .addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ installButton.setEnabled(false);
+ getUpdateRepositoryThread().start();
+ }
+
+ });
+ }
+ return installButton;
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getCloseButton() {
+ if (cancelButton == null) {
+ cancelButton = new JButton();
+ cancelButton.setText("Close");
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ });
+ }
+ return cancelButton;
+ }
+
+ @SuppressWarnings("serial")
+ class PluginCheckBox extends JCheckBox {
+ public Plugin plugin;
+ }
+
+ private final void addPluginSite() {
+
+ addSiteFrame=new AddPluginSiteFrame(this);
+
+ addSiteFrame.setLocationRelativeTo(this);
+ addSiteFrame.setVisible(true);
+
+ if (addSiteFrame.getName()!=null) {
+ if (addSiteFrame.getName().length()==0) {
+ JOptionPane.showMessageDialog(this, "You must provide a name for your site.","Error adding update site",JOptionPane.ERROR_MESSAGE);
+ addPluginSite();
+ }
+ else {
+ if (addSiteFrame.getUrl()!=null) {
+ try {
+ PluginSite site = new PluginSite(addSiteFrame.getName(), new URL(addSiteFrame.getUrl()));
+ pluginManager.addPluginSite(site);
+ pluginManager.savePluginSites();
+
+ //refresh
+ scrollPane.setViewportView(getJPanel());
+ addSiteFrame=null; //so that the name and url are reset.
+ }
+ catch(Exception e) {
+ JOptionPane.showMessageDialog(this, "There was a problem adding the site you provided: "+e.getMessage(),"Error adding update site",JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This method initializes jButton
+ *
+ * @return javax.swing.JButton
+ */
+ private JButton getAddPluginSiteButton() {
+ if (addSiteButton == null) {
+ addSiteButton = new JButton();
+ addSiteButton.setText("Add update site");
+ addSiteButton.setEnabled(true);
+ addSiteButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ addSiteButton.setEnabled(false);
+ addPluginSite();
+ addSiteButton.setEnabled(true);
+ }
+ });
+
+ }
+ return addSiteButton;
+ }
+
+} // @jve:decl-index=0:visual-constraint="10,10"
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java
new file mode 100644
index 0000000..a0b15bf
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/plugins/ui/UpdatesAvailableIcon.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: UpdatesAvailableIcon.java,v $
+ * Revision $Revision: 1.5 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/12/01 12:32:40 $
+ * by $Author: alaninmcr $
+ * Created on 12 Dec 2006
+ *****************************************************************/
+package net.sf.taverna.raven.plugins.ui;
+
+import java.awt.Component;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+
+import org.apache.log4j.Logger;
+
+/**
+ * A JLabel that periodically checks for updates, running on a daemon thread. If
+ * updates are available it makes itself visible and responds to click events to
+ * display the appropriate update response.
+ *
+ * Also acts as a pluginmanager listener to refresh itself whenever a new plugin
+ * is added.
+ *
+ * @author Stuart Owen
+ *
+ */
+
+@SuppressWarnings("serial")
+public class UpdatesAvailableIcon extends JLabel implements PluginManagerListener {
+
+ private UpdatePluginsMouseAdaptor updatePluginMouseAdaptor = new UpdatePluginsMouseAdaptor();
+ private static Logger logger = Logger.getLogger(UpdatesAvailableIcon.class);
+
+ private final int CHECK_INTERVAL = 1800000; // every 30 minutes
+
+ public static final Icon updateIcon = new ImageIcon(
+ UpdatesAvailableIcon.class.getResource("update.png"));
+ public static final Icon updateRecommendedIcon = new ImageIcon(
+ UpdatesAvailableIcon.class.getResource("updateRecommended.png"));
+
+ public UpdatesAvailableIcon() {
+ super();
+ setVisible(false);
+
+ startCheckThread();
+ PluginManager.addPluginManagerListener(this);
+ }
+
+ public void pluginAdded(PluginManagerEvent event) {
+ logger.info("Plugin Added");
+ if (!isVisible())
+ checkForUpdates();
+ }
+
+ public void pluginStateChanged(PluginManagerEvent event) {
+
+ }
+
+ public void pluginUpdated(PluginManagerEvent event) {
+ logger.info("Plugin Updated");
+ }
+
+ public void pluginRemoved(PluginManagerEvent event) {
+ logger.info("Plugin Removed");
+ if (isVisible())
+ checkForUpdates();
+ }
+
+ public void pluginIncompatible(PluginManagerEvent event) {
+ logger
+ .warn("Plugin found to be incompatible with the current version of Taverna: "
+ + event.getPlugin());
+ }
+
+ private void startCheckThread() {
+ Thread checkThread = new Thread("Check for updates thread") {
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ checkForUpdates();
+ Thread.sleep(CHECK_INTERVAL);
+ } catch (InterruptedException e) {
+ logger.warn("Interruption exception in checking for updates thread",
+ e);
+ }
+ }
+ }
+ };
+ checkThread.setDaemon(true); // daemon so that taverna will stop the
+ // thread and close on exit.
+ checkThread.start();
+ }
+
+ private Object updateLock = new Object();
+
+ private void checkForUpdates() {
+ synchronized (updateLock) {
+ if (pluginUpdateAvailable()) {
+ logger.info("Plugin update available");
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ // TODO Auto-generated method stub
+ setToolTipText("Plugin updates are available");
+
+ setVisible(true);
+ setIcon(updateIcon);
+ if (!Arrays.asList(getMouseListeners()).contains(
+ updatePluginMouseAdaptor)) {
+ addMouseListener(updatePluginMouseAdaptor);
+ }
+
+ }
+ });
+ } catch (InterruptedException e) {
+ logger.error("Could not check for updates", e);
+ } catch (InvocationTargetException e) {
+ logger.error("Could not check for updates", e);
+ }
+ } else {
+ setToolTipText("");
+ setVisible(false);
+
+ }
+ }
+
+ }
+
+ private boolean pluginUpdateAvailable() {
+ return PluginManager.getInstance().checkForUpdates();
+ }
+
+ private final class UpdatePluginsMouseAdaptor extends MouseAdapter {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ // FIXME: this assumes the button is on the toolbar.
+ Component parent = UpdatesAvailableIcon.this.getParent()
+ .getParent();
+
+ final PluginManagerFrame pluginManagerUI = new PluginManagerFrame(
+ PluginManager.getInstance());
+ pluginManagerUI.setLocationRelativeTo(parent);
+ pluginManagerUI.setVisible(true);
+ }
+
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java
new file mode 100644
index 0000000..b0c036c
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionCellRenderer.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: ProfileVersionCellRenderer.java,v $
+ * Revision $Revision: 1.2 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:52:06 $
+ * by $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.AbstractBorder;
+
+import net.sf.taverna.raven.profile.ProfileHandler;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.spi.Profile;
+import net.sf.taverna.raven.spi.ProfileFactory;
+
+public class ProfileVersionCellRenderer extends JPanel implements
+ ListCellRenderer {
+
+ private JLabel name;
+ private JLabel version;
+ private JLabel description;
+ private String currentVersion;
+
+ public ProfileVersionCellRenderer() {
+ super();
+ initialise();
+ }
+
+ private void initialise() {
+ Profile currentProfile = ProfileFactory.getInstance().getProfile();
+ if (currentProfile!=null) {
+ currentVersion=currentProfile.getVersion();
+ }
+ else {
+ currentVersion="UNKNOWN";
+ }
+ GridBagConstraints gridBagVersion = new GridBagConstraints();
+ gridBagVersion.gridx = 1;
+ gridBagVersion.insets = new Insets(3, 8, 3, 3);
+ gridBagVersion.anchor = GridBagConstraints.NORTHWEST;
+ gridBagVersion.fill = GridBagConstraints.NONE;
+ gridBagVersion.gridy = 0;
+ version = new JLabel();
+ version.setFont(getFont().deriveFont(Font.BOLD));
+ version.setText("Version");
+
+ GridBagConstraints gridBagDescription = new GridBagConstraints();
+ gridBagDescription.gridx = 0;
+ gridBagDescription.anchor = GridBagConstraints.NORTHWEST;
+ gridBagDescription.fill = GridBagConstraints.HORIZONTAL;
+ gridBagDescription.weightx = 1.0;
+ gridBagDescription.insets = new Insets(3, 3, 3, 3);
+ gridBagDescription.gridwidth = 2;
+ gridBagDescription.gridy = 1;
+ description = new JLabel();
+ description.setFont(getFont().deriveFont(Font.PLAIN));
+ description.setText("Plugin description");
+
+ GridBagConstraints gridBagName = new GridBagConstraints();
+ gridBagName.gridx = 0;
+ gridBagName.anchor = GridBagConstraints.NORTHWEST;
+ gridBagName.fill = GridBagConstraints.NONE;
+ gridBagName.weightx = 0.0;
+ gridBagName.ipadx = 0;
+ gridBagName.insets = new Insets(3, 3, 3, 3);
+ gridBagName.gridwidth = 1;
+ gridBagName.gridy = 0;
+ name = new JLabel();
+ name.setFont(getFont().deriveFont(Font.PLAIN));
+ name.setText("Plugin name");
+
+ this.setSize(297, 97);
+ this.setLayout(new GridBagLayout());
+ this.setBorder(new AbstractBorder() {
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Color oldColor = g.getColor();
+ g.setColor(Color.LIGHT_GRAY);
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ g.setColor(oldColor);
+ }
+ });
+ this.add(name, gridBagName);
+ this.add(description, gridBagDescription);
+ this.add(version, gridBagVersion);
+ }
+
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ if (isSelected) {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ } else {
+ setBackground(list.getBackground());
+ setForeground(list.getForeground());
+ }
+
+ if (value instanceof ProfileVersion) {
+ ProfileVersion version = (ProfileVersion) value;
+ this.name.setText(version.getName());
+ if (version.getVersion().equalsIgnoreCase(currentVersion)) {
+ this.name.setText(version.getName()+" (Current)");
+ this.name.setForeground(Color.BLUE);
+ }
+ else {
+ this.name.setText(version.getName());
+ this.name.setForeground(Color.BLACK);
+ }
+ this.version.setText(version.getVersion());
+ this.description.setText("<html>"+version.getDescription());
+ }
+
+
+ return this;
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java
new file mode 100644
index 0000000..861ed9a
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListFrame.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: ProfileVersionListFrame.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:52:06 $
+ * by $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.raven.appconfig.bootstrap.RavenProperties;
+import net.sf.taverna.raven.plugins.Plugin;
+import net.sf.taverna.raven.plugins.PluginManager;
+import net.sf.taverna.raven.profile.ProfileHandler;
+import net.sf.taverna.raven.profile.ProfileUpdateHandler;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.spi.ProfileFactory;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ProfileVersionListFrame extends HelpEnabledDialog {
+
+ private JPanel contentPane = null;
+ private JScrollPane scrollPane = null;
+ private JList list = null;
+ private JButton switchButton = null;
+ private JButton closeButton = null;
+ private String currentVersion = null;
+
+ private static Logger logger = Logger
+ .getLogger(ProfileVersionListFrame.class);
+
+ public ProfileVersionListFrame() {
+ this(null);
+ }
+
+ public ProfileVersionListFrame(Frame parent) {
+ super(parent,"Taverna versions", true);
+ initialise();
+ }
+
+ protected JPanel getJContentPane() {
+ if (contentPane==null) {
+ GridBagConstraints scrollPaneConstraints = new GridBagConstraints();
+ scrollPaneConstraints.fill = GridBagConstraints.BOTH;
+ scrollPaneConstraints.gridy = 0;
+ scrollPaneConstraints.weightx = 1.0;
+ scrollPaneConstraints.weighty = 1.0;
+ scrollPaneConstraints.gridwidth = 2;
+ scrollPaneConstraints.insets = new Insets(5, 5, 5, 5);
+ scrollPaneConstraints.gridx = 0;
+ scrollPaneConstraints.gridheight = 3;
+
+ GridBagConstraints switchButtonConstraints = new GridBagConstraints();
+ switchButtonConstraints.gridy=0;
+ switchButtonConstraints.gridx=2;
+ switchButtonConstraints.insets = new Insets(5,5,5,5);
+ switchButtonConstraints.fill=GridBagConstraints.HORIZONTAL;
+
+ GridBagConstraints closeButtonConstraints = new GridBagConstraints();
+ closeButtonConstraints.gridy=2;
+ closeButtonConstraints.gridx=2;
+ closeButtonConstraints.insets = new Insets(5,5,5,5);
+ closeButtonConstraints.anchor=GridBagConstraints.SOUTH;
+ closeButtonConstraints.fill=GridBagConstraints.HORIZONTAL;
+
+ contentPane = new JPanel();
+ contentPane.setLayout(new GridBagLayout());
+ contentPane.add(getScrollPane(),scrollPaneConstraints);
+ contentPane.add(getSwitchButton(),switchButtonConstraints);
+ contentPane.add(getCloseButton(),closeButtonConstraints);
+
+ }
+ return contentPane;
+ }
+
+ protected JScrollPane getScrollPane() {
+ if (scrollPane==null) {
+ scrollPane=new JScrollPane();
+ scrollPane.setViewportView(getJList());
+ }
+ return scrollPane;
+ }
+
+ protected JList getJList() {
+ if (list==null) {
+ list=new JList();
+ list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ list.setModel(new ProfileVersionListModel());
+ list.setCellRenderer(new ProfileVersionCellRenderer());
+ list.addListSelectionListener(new ListSelectionListener() {
+
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ respondToSelection();
+ }
+ }
+
+ });
+ if (list.getComponentCount() > 0) {
+ list.setSelectedIndex(0);
+ respondToSelection();
+
+ }
+ }
+ return list;
+ }
+
+ protected void respondToSelection() {
+ Object selected=list.getSelectedValue();
+ if (selected!=null && selected instanceof ProfileVersion) {
+ ProfileVersion version = (ProfileVersion)selected;
+ if (currentVersion==null || version.getVersion().equals(currentVersion)) {
+ getSwitchButton().setEnabled(false);
+ }
+ else {
+ getSwitchButton().setEnabled(true);
+ }
+ }
+ }
+
+ protected JButton getSwitchButton() {
+ if (switchButton==null) {
+ switchButton=new JButton("Switch");
+ switchButton.setEnabled(true);
+ switchButton.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ Object selected = getJList().getSelectedValue();
+ if (selected!=null && selected instanceof ProfileVersion) {
+ performSwitch((ProfileVersion)selected);
+ }
+ }
+
+ });
+ }
+ return switchButton;
+ }
+
+ protected JButton getCloseButton() {
+ if (closeButton==null) {
+ closeButton=new JButton("Close");
+ closeButton.setEnabled(true);
+ closeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispose();
+ }
+ });
+ }
+ return closeButton;
+ }
+
+ protected void performSwitch(ProfileVersion newVersion) {
+ List<Plugin> incompatiblePlugins = PluginManager.getInstance().getIncompatiblePlugins(newVersion.getVersion(),true);
+ if (incompatiblePlugins.size()>0) {
+ int response=JOptionPane.showConfirmDialog(this, "Some plugins will be incompatible with the new version and will be disabled. Do you wish to continue?","Confirm version switch",JOptionPane.YES_OPTION);
+ if (response!=JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+ try {
+ URL localProfile = new URL(RavenProperties.getInstance().getRavenProfileLocation());
+ URL profileList = new URL(RavenProperties.getInstance().getRavenProfileListLocation());
+
+ ProfileUpdateHandler handler=new ProfileUpdateHandler(profileList,localProfile);
+ handler.updateLocalProfile(newVersion,new File(localProfile.toURI()));
+
+ //disable plugins after everything else has been acheived
+ for (Plugin plugin : incompatiblePlugins) {
+ plugin.setEnabled(false);
+ }
+ JOptionPane.showMessageDialog(this, "You must restart taverna for the version switch to be activated");
+ }
+ catch(Exception e) {
+ logger.error("Error occurred switching to a new profile",e);
+ JOptionPane.showMessageDialog(this, "An error occurred switching to your new profile, try again later.");
+ }
+ }
+
+
+ protected void initialise() {
+ try {
+ currentVersion = ProfileFactory.getInstance().getProfile().getVersion();
+ } catch (Exception e) {
+ logger.error("Unable to determine current taverna version",e);
+ currentVersion=null;
+ }
+ setSize(600,400);
+ setContentPane(getJContentPane());
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java
new file mode 100644
index 0000000..c952413
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/java/net/sf/taverna/raven/profile/ui/ProfileVersionListModel.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/*
+ * Copyright (C) 2003 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate. Authorship
+ * of the modifications may be determined from the ChangeLog placed at
+ * the end of this file.
+ *
+ * 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
+ * USA.
+ *
+ ****************************************************************
+ * Source code information
+ * -----------------------
+ * Filename $RCSfile: ProfileVersionListModel.java,v $
+ * Revision $Revision: 1.3 $
+ * Release status $State: Exp $
+ * Last modified on $Date: 2008/09/04 14:52:06 $
+ * by $Author: sowen70 $
+ * Created on 16 Jan 2007
+ *****************************************************************/
+package net.sf.taverna.raven.profile.ui;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+
+import net.sf.taverna.raven.appconfig.bootstrap.RavenProperties;
+import net.sf.taverna.raven.profile.ProfileVersion;
+import net.sf.taverna.raven.profile.ProfileVersions;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class ProfileVersionListModel extends AbstractListModel {
+
+ private static Logger logger = Logger
+ .getLogger(ProfileVersionListModel.class);
+
+ private List<ProfileVersion> versions;
+
+
+ public ProfileVersionListModel() {
+ try {
+ URL url = getProfileListLocation();
+ versions = ProfileVersions.getProfileVersions(url);
+ }
+ catch(MalformedURLException e) {
+ logger.error("Error with profile list URL",e);
+ versions = new ArrayList<ProfileVersion>();
+ }
+ }
+
+ public Object getElementAt(int index) {
+ return versions.get(index);
+ }
+
+ public int getSize() {
+ return versions.size();
+ }
+
+ private URL getProfileListLocation() throws MalformedURLException{
+ return new URL(RavenProperties.getInstance().getRavenProfileListLocation());
+ }
+
+}
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..a58f87a
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.raven.plugins.ui.CheckForUpdatesStartupHook
+net.sf.taverna.raven.plugins.ui.CheckForNoticeStartupHook
\ No newline at end of file
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml
new file mode 100644
index 0000000..38c21e5
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context-osgi.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="CheckForUpdatesStartupHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+
+</beans:beans>
diff --git a/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml
new file mode 100644
index 0000000..33e2207
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/META-INF/spring/plugins-gui-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="CheckForUpdatesStartupHook" class="net.sf.taverna.raven.plugins.ui.CheckForUpdatesStartupHook" />
+
+</beans>
diff --git a/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png
new file mode 100644
index 0000000..e3695cb
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/update.png
Binary files differ
diff --git a/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png
new file mode 100644
index 0000000..5adc4b1
--- /dev/null
+++ b/taverna-workbench-plugins-gui/src/main/resources/net/sf/taverna/raven/plugins/ui/updateRecommended.png
Binary files differ
diff --git a/taverna-workbench-renderers-impl/pom.xml b/taverna-workbench-renderers-impl/pom.xml
new file mode 100644
index 0000000..24e06c0
--- /dev/null
+++ b/taverna-workbench-renderers-impl/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>renderers-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Renderers Implementation</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Private-Package>org.fife.ui.hex.*,net.sf.taverna.t2.renderers.impl</Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>renderers-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.databundle</groupId>
+ <artifactId>databundle</artifactId>
+ <version>${taverna.databundle.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>com.springsource.org.jdom</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.fife.ui.hex</groupId>
+ <artifactId>hexeditor</artifactId>
+ <version>1.1-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java
new file mode 100644
index 0000000..14ef62f
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AbstractRenderer.java
@@ -0,0 +1,124 @@
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static net.sf.taverna.t2.renderers.RendererUtils.getSizeInBytes;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.BIG_DATA_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.CANCELLED_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.NO_DATA_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.NO_SIZE_MSG;
+import static uk.org.taverna.databundle.DataBundles.isValue;
+import static uk.org.taverna.databundle.DataBundles.isReference;
+
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements some of the common code across the renderers.
+ *
+ * @author Donal Fellows
+ */
+abstract class AbstractRenderer implements Renderer {
+ protected Logger logger = Logger.getLogger(AbstractRenderer.class);
+ /** Size of a <s>mibibyte</s> megabyte. */
+ private int MEGABYTE = 1024 * 1024;
+
+ /**
+ * Work out size of file in megabytes
+ *
+ * @param bytes
+ * Number of bytes
+ * @return Number of megabytes
+ */
+ private int bytesToMeg(long bytes) {
+ float f = bytes / MEGABYTE;
+ return Math.round(f);
+ }
+
+ /**
+ * Implements basic checks on the entity to render before delegating to
+ * subclasses to actually do the rendering.
+ *
+ * @see #getRendererComponent(Path)
+ */
+ @Override
+ public JComponent getComponent(Path path) throws RendererException {
+ if (!isValue(path) && !isReference(path)) {
+ logger.error("unrenderable: data is not a value or reference");
+ return new JTextArea(NO_DATA_MSG);
+ }
+
+ // Get the size
+ long sizeInBytes;
+ try {
+ sizeInBytes = getSizeInBytes(path);
+ } catch (Exception ex) {
+ logger.error("unrenderable: failed to get data size", ex);
+ return new JTextArea(NO_SIZE_MSG + ex.getMessage());
+ }
+
+ // over the limit for this renderer?
+ if (sizeInBytes > MEGABYTE * getSizeLimit()) {
+ JComponent alternative = sizeCheck(path, bytesToMeg(sizeInBytes));
+ if (alternative != null)
+ return alternative;
+ }
+
+ return getRendererComponent(path);
+ }
+
+ /**
+ * Implements the user-visible part of the size check. Default version just
+ * does a simple yes/no proceed.
+ *
+ * @return <tt>null</tt> if the normal flow is to continue, or a component
+ * to show to the user if it is to be used instead (e.g., to show a
+ * message that the entity was too large).
+ */
+ protected JComponent sizeCheck(Path path, int approximateSizeInMB) {
+ int response = showConfirmDialog(null,
+ format(BIG_DATA_MSG, getResultType(), approximateSizeInMB),
+ getSizeQueryTitle(), YES_NO_OPTION);
+ if (response != YES_OPTION) // NO_OPTION or ESCAPE key
+ return new JTextArea(CANCELLED_MSG);
+ return null;
+ }
+
+ /**
+ * How should we describe the data to the user? Should be capitalised.
+ */
+ protected String getResultType() {
+ return "Result";
+ }
+
+ /**
+ * At what size (in megabytes) do we query the user?
+ *
+ * @see #sizeCheck(Path,log)
+ */
+ protected int getSizeLimit() {
+ return 1;
+ }
+
+ /**
+ * What title do we use on the dialog used to query the user?
+ *
+ * @see #sizeCheck(Path,log)
+ */
+ protected String getSizeQueryTitle() {
+ return "Render this image?";
+ }
+
+ /** Actually get the renderer; the basic checks have passed. */
+ protected abstract JComponent getRendererComponent(Path path)
+ throws RendererException;
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java
new file mode 100644
index 0000000..6c15834
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/AdvancedImageRenderer.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static javax.imageio.ImageIO.getReaderMIMETypes;
+import static javax.imageio.ImageIO.getWriterFormatNames;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.awt.Image;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextArea;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Advanced renderer for mime type image/* that can render tiff files. Uses Java
+ * Advanced Imaging (JAI) ImageIO from https://jai-imageio.dev.java.net/.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class AdvancedImageRenderer extends AbstractRenderer {
+ private static final String BAD_FORMAT_MSG = "Data does not seem to contain an image in any of the recognised formats:\n";
+ private static final String UNRENDERABLE_MSG = "Failed to create image renderer " + SEE_LOG_MSG;
+
+ private Logger logger = Logger.getLogger(AdvancedImageRenderer.class);
+ private List<String> mimeTypesList;
+
+ public AdvancedImageRenderer() {
+ mimeTypesList = Arrays.asList(getReaderMIMETypes());
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return mimeTypesList.contains(mimeType);
+ }
+
+ @Override
+ public String getType() {
+ return "Image";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) {
+ // Render into a label
+ try (InputStream inputStream = getInputStream(path)) {
+ Image image = ImageIO.read(inputStream);
+ if (image == null)
+ return new JTextArea(BAD_FORMAT_MSG
+ + Arrays.asList(getWriterFormatNames()));
+ return new JLabel(new ImageIcon(image));
+ } catch (Exception e) {
+ logger.error("unrenderable: failed to create image renderer", e);
+ return new JTextArea(UNRENDERABLE_MSG + e.getMessage());
+ }
+ }
+
+ @Override
+ protected int getSizeLimit() {
+ return 4;
+ }
+
+ @Override
+ protected String getResultType() {
+ return "Image";
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java
new file mode 100644
index 0000000..2d90764
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/ExtensionFileFilter.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+/**
+ * This file is a component of the Taverna project,
+ * and is licensed under the GNU LGPL.
+ * Copyright Tom Oinn, EMBL-EBI
+ */
+package net.sf.taverna.t2.renderers.impl;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A FileFilter implementation that can be configured to show only specific file
+ * suffixes.
+ *
+ * @author Tom Oinn
+ */
+public class ExtensionFileFilter extends FileFilter {
+ String[] allowedExtensions;
+
+ public ExtensionFileFilter(String[] allowedExtensions) {
+ this.allowedExtensions = allowedExtensions;
+ }
+
+ @Override
+ public boolean accept(File f) {
+ if (f.isDirectory())
+ return true;
+ String extension = getExtension(f);
+ if (extension != null)
+ for (int i = 0; i < allowedExtensions.length; i++)
+ if (extension.equalsIgnoreCase(allowedExtensions[i]))
+ return true;
+ return false;
+ }
+
+ String getExtension(File f) {
+ String ext = null;
+ String s = f.getName();
+ int i = s.lastIndexOf('.');
+ if (i > 0 && i < s.length() - 1)
+ ext = s.substring(i + 1).toLowerCase();
+ return ext;
+ }
+
+ @Override
+ public String getDescription() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Filter for extensions : ");
+ String sep = "";
+ for (String ext : allowedExtensions) {
+ sb.append(sep).append(ext);
+ sep = ", ";
+ }
+ return sb.toString();
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java
new file mode 100644
index 0000000..f1d8f29
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/HexBinaryRenderer.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.io.InputStream;
+import java.nio.file.Path;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererException;
+
+import org.fife.ui.hex.swing.HexEditor;
+
+/**
+ * Renderer for binary data. Uses HexEditor from
+ * http://www.fifesoft.com/hexeditor/.
+ *
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class HexBinaryRenderer extends AbstractRenderer {
+ private static final String RENDER_FAILED_MSG = "Failed to create binary hexadecimal renderer "
+ + SEE_LOG_MSG;
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return false;
+ /*
+ * can handle any data but return false here as we do not want this to
+ * be default renderer
+ */
+ }
+
+ @Override
+ protected String getSizeQueryTitle() {
+ return "Render binary data (in hexadecimal viewer)?";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) throws RendererException {
+ try (InputStream inputStream = getInputStream(path)) {
+ HexEditor editor = new HexEditor();
+ editor.open(inputStream);
+ return editor;
+ } catch (Exception e) {
+ logger.error("unrenderable: failed to create binhex renderer", e);
+ return new JTextArea(RENDER_FAILED_MSG + e.getMessage());
+ }
+ }
+
+ @Override
+ public String getType() {
+ return "Binary (HexDec)";
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java
new file mode 100644
index 0000000..d51c94c
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererConstants.java
@@ -0,0 +1,13 @@
+package net.sf.taverna.t2.renderers.impl;
+
+interface RendererConstants {
+ String SEE_LOG_MSG = "(see error log for more details):\n";
+ String NO_DATA_MSG = "Failed to obtain the data to render: "
+ + "data is not a value or reference";
+ String NO_SIZE_MSG = "Failed to get the size of the data " + SEE_LOG_MSG;
+ String BIG_DATA_MSG = "%s is approximately %d MB in size, "
+ + "there could be issues with rendering this inside Taverna\n"
+ + "Do you want to continue?";
+ String CANCELLED_MSG = "Rendering cancelled due to size of image. "
+ + "Try saving and viewing in an external application.";
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java
new file mode 100644
index 0000000..eb41292
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/RendererRegistryImpl.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.renderers.Renderer;
+import net.sf.taverna.t2.renderers.RendererRegistry;
+
+/**
+ * Implementation of a RendererRegistry.
+ *
+ * @author David Withers
+ */
+public class RendererRegistryImpl implements RendererRegistry {
+ private List<Renderer> renderers;
+
+ @Override
+ public List<Renderer> getRenderersForMimeType(String mimeType) {
+ ArrayList<Renderer> list = new ArrayList<>();
+ for (Renderer renderer : renderers)
+ if (renderer.canHandle(mimeType))
+ list.add(renderer);
+ return list;
+ }
+
+ @Override
+ public List<Renderer> getRenderers() {
+ return renderers;
+ }
+
+ public void setRenderers(List<Renderer> renderers) {
+ this.renderers = renderers;
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java
new file mode 100644
index 0000000..87fa364
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRenderer.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.awt.Font.PLAIN;
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.NO_OPTION;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.YES_NO_CANCEL_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showOptionDialog;
+import static net.sf.taverna.t2.renderers.RendererUtils.getInputStream;
+import static net.sf.taverna.t2.renderers.RendererUtils.getString;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.CANCELLED_MSG;
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.awt.Font;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.renderers.RendererException;
+
+/**
+ * Renderer for mime type text/*
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class TextRenderer extends AbstractRenderer {
+ private static final String UNREADABLE_MSG = "Reference Service failed to render data "
+ + SEE_LOG_MSG;
+ private static final String RENDERER_FAILED_MSG = "Failed to create text renderer "
+ + SEE_LOG_MSG;
+ private static final String QUERY_MSG = "Result is approximately %d MB "
+ + "in size, there could be issues with rendering this inside "
+ + "Taverna\nDo you want to cancel, render all of the result, "
+ + "or only the first part?";
+ private static final Pattern pattern = Pattern.compile(".*text/.*");
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "Text";
+ }
+
+ private JComponent textRender(String text) {
+ DialogTextArea theTextArea = new DialogTextArea();
+ theTextArea.setWrapStyleWord(true);
+ theTextArea.setEditable(false);
+ theTextArea.setText(text);
+ theTextArea.setFont(new Font("Monospaced", PLAIN, 12));
+ theTextArea.setCaretPosition(0);
+ return theTextArea;
+ }
+
+ private static final Object[] SIZE_OPTIONS = { "Continue rendering",
+ "Render partial", "Cancel" };
+
+ @Override
+ protected JComponent sizeCheck(Path path, int approximateSizeInMB) {
+ // allow partial rendering of text files
+ int response = showOptionDialog(null,
+ format(QUERY_MSG, approximateSizeInMB),
+ "Rendering large result", YES_NO_CANCEL_OPTION,
+ QUESTION_MESSAGE, null, SIZE_OPTIONS, SIZE_OPTIONS[2]);
+ if (response == YES_OPTION)
+ return null;
+ else if (response != NO_OPTION)
+ // if (response == CANCEL_OPTION) or ESCAPE key pressed
+ return new JTextArea(CANCELLED_MSG);
+
+ // Construct a partial result.
+ byte[] smallStringBytes = new byte[1048576];
+ try (InputStream inputStream = getInputStream(path)) {
+ // just copy the first MEGABYTE
+ inputStream.read(smallStringBytes);
+ } catch (Exception ex) {
+ logger.error("unrenderable: Reference Service failed "
+ + "to render data as byte array", ex);
+ return new JTextArea(UNREADABLE_MSG + ex.getMessage());
+ }
+ try {
+ // TODO beware of encoding problems!
+ return textRender(new String(smallStringBytes));
+ } catch (Exception e1) {
+ logger.error("Failed to create text renderer", e1);
+ return new JTextArea(RENDERER_FAILED_MSG + e1.getMessage());
+ }
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) throws RendererException {
+ String resolve;
+ try {
+ // Resolve it as a string
+ resolve = getString(path);
+ } catch (Exception e) {
+ logger.error("unrenderable: Reference Service failed "
+ + "to render data as string", e);
+ return new JTextArea(UNREADABLE_MSG + e.getMessage());
+ }
+ try {
+ return textRender(resolve);
+ } catch (Exception e1) {
+ logger.error("Failed to create text renderer", e1);
+ return new JTextArea(RENDERER_FAILED_MSG + e1.getMessage());
+ }
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java
new file mode 100644
index 0000000..6c3bf7f
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextRtfRenderer.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererException;
+import net.sf.taverna.t2.renderers.RendererUtils;
+
+/**
+ * Renderer for mime type text/rtf
+ *
+ * @author Ian Dunlop
+ * @author Alex Nenadic
+ * @author David Withers
+ */
+public class TextRtfRenderer extends AbstractRenderer {
+ private static final String UNREADABLE_MSG = "Reference Service failed to render data "
+ + SEE_LOG_MSG;
+ private static final String RENDERER_FAILED_MSG = "Failed to create RTF renderer "
+ + SEE_LOG_MSG;
+ private static final Pattern pattern = Pattern.compile(".*text/rtf.*");
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "Text/RTF";
+ }
+
+ @Override
+ protected String getSizeQueryTitle() {
+ return "Render as RTF?";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) throws RendererException {
+ String resolve;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception e) {
+ logger.error("Reference Service failed to render data as string", e);
+ return new JTextArea(UNREADABLE_MSG + e.getMessage());
+ }
+ try {
+ return new JEditorPane("text/rtf", resolve);
+ } catch (Exception e) {
+ logger.error("Failed to create RTF renderer", e);
+ return new JTextArea(RENDERER_FAILED_MSG + e.getMessage());
+ }
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java
new file mode 100644
index 0000000..eaad9c6
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/TextXMLRenderer.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static net.sf.taverna.t2.renderers.impl.RendererConstants.SEE_LOG_MSG;
+
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.renderers.RendererUtils;
+
+/**
+ * Viewer to display XML as a tree.
+ *
+ * @author Matthew Pocock
+ * @auhor Ian Dunlop
+ * @author David Withers
+ */
+public class TextXMLRenderer extends AbstractRenderer {
+ private static final String UNREADABLE_MSG = "Reference Service failed to render data as string " + SEE_LOG_MSG;
+ private static final String RENDERER_FAILED_MSG = "Failed to create XML renderer " + SEE_LOG_MSG;
+ private Pattern pattern;
+
+ public TextXMLRenderer() {
+ pattern = Pattern.compile(".*text/xml.*");
+ }
+
+ public boolean isTerminal() {
+ return true;
+ }
+
+ @Override
+ public boolean canHandle(String mimeType) {
+ return pattern.matcher(mimeType).matches();
+ }
+
+ @Override
+ public String getType() {
+ return "XML tree";
+ }
+
+ @Override
+ protected String getSizeQueryTitle() {
+ return "Render this as XML?";
+ }
+
+ @Override
+ public JComponent getRendererComponent(Path path) {
+ String resolve = null;
+ try {
+ // Resolve it as a string
+ resolve = RendererUtils.getString(path);
+ } catch (Exception ex) {
+ logger.error("unrenderable: Reference Service failed to render data as string",
+ ex);
+ return new JTextArea(UNREADABLE_MSG + ex.getMessage());
+ }
+ try {
+ return new XMLTree(resolve);
+ } catch (Exception e) {
+ logger.error("unrenderable: failed to create XML renderer", e);
+ return new JTextArea(RENDERER_FAILED_MSG + e.getMessage());
+ }
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java
new file mode 100644
index 0000000..2d53218
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/java/net/sf/taverna/t2/renderers/impl/XMLTree.java
@@ -0,0 +1,329 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers.impl;
+
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
+import static org.jdom.output.Format.getPrettyFormat;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.prefs.Preferences;
+
+import javax.swing.JFileChooser;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import org.jdom.Attribute;
+import org.jdom.Content;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.Parent;
+import org.jdom.Text;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.XMLOutputter;
+
+/**
+ * An extension of the {@link JTree} class, constructed with a String of XML and
+ * used to display the XML structure as an interactive tree. Derived from <a
+ * href="http://www.devx.com/gethelpon/10MinuteSolution/16694/0/page/1">original
+ * code by Kyle Gabhart</a> and then subsequently heavily rewritten to move to
+ * JDOM, and moved lots of the setup code to the renderer to cut down
+ * initialisation time. Added text node size limit as well. Displaying large
+ * gene sequences as base64 encoded text in a single node really, <i>really</i>
+ * hurts performance.
+ *
+ * @author Kyle Gabhart
+ * @author Tom Oinn
+ * @author Kevin Glover
+ * @author Ian Dunlop
+ */
+@SuppressWarnings("serial")
+class XMLTree extends JTree {
+ private class XMLNode extends DefaultMutableTreeNode {
+ public XMLNode(Content userObject) {
+ super(userObject);
+ }
+ }
+
+ int textSizeLimit = 1000;
+ final JFileChooser fc = new JFileChooser();
+ Element rootElement = null;
+
+ /**
+ * Build a new XMLTree from the supplied String containing XML.
+ *
+ * @param text
+ * @throws IOException
+ * @throws JDOMException
+ */
+ public XMLTree(String text) throws IOException, JDOMException {
+ super();
+ Document document = new SAXBuilder(false).build(new StringReader(text));
+ init(document.getRootElement());
+ revalidate();
+ }
+
+ public String getText() {
+ if (rootElement == null)
+ return "";
+ XMLOutputter xo = new XMLOutputter(getPrettyFormat());
+ return xo.outputString(rootElement);
+ }
+
+ public XMLTree(String text, boolean limit) throws IOException,
+ JDOMException {
+ if (!limit)
+ textSizeLimit = -1;
+ Document document = new SAXBuilder(false).build(new StringReader(text));
+ init(document.getRootElement());
+ revalidate();
+ }
+
+ public XMLTree(Document document) {
+ this(document.getRootElement());
+ }
+
+ public XMLTree(Element element) {
+ super();
+ init(element);
+ revalidate();
+ }
+
+ private void init(Content content) {
+ rootElement = (Element) content;
+ /*
+ * Fix for platforms other than metal which can't otherwise cope with
+ * arbitrary size rows
+ */
+ setRowHeight(0);
+ getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
+ setShowsRootHandles(true);
+ setEditable(false);
+ setModel(new DefaultTreeModel(createTreeNode(content)));
+ setCellRenderer(new DefaultTreeCellRenderer() {
+ @Override
+ public Color getBackgroundNonSelectionColor() {
+ return null;
+ }
+
+ @Override
+ public Color getBackground() {
+ return null;
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value, boolean sel, boolean expanded, boolean leaf,
+ int row, boolean hasFocus) {
+ super.getTreeCellRendererComponent(tree, value, sel, expanded,
+ leaf, row, hasFocus);
+ setOpaque(false);
+ if (value instanceof XMLNode) {
+ XMLNode node = (XMLNode) value;
+ if (node.getUserObject() instanceof Element)
+ renderElementNode((Element) node.getUserObject());
+ else if (node.getUserObject() instanceof Text)
+ renderTextNode((Text) node.getUserObject());
+ // TODO what about other node types?
+ }
+ setBackground(new Color(0, 0, 0, 0));
+ return this;
+ }
+
+ private void renderElementNode(Element element) {
+ // setIcon(TavernaIcons.xmlNodeIcon);
+ StringBuilder nameBuffer = new StringBuilder("<html>")
+ .append(element.getQualifiedName());
+ /*
+ * Bit of a quick and dirty hack here to try to ensure that the
+ * element namespace is shown. There appears no way to get the
+ * actual xmlns declarations that are part of an element through
+ * jdom. Also, please note, there's no namespace handling at all
+ * for attributes...
+ */
+ if (element.getParent() instanceof Element) {
+ Element parent = (Element) element.getParent();
+ if (parent.getNamespace(element.getNamespacePrefix()) == null)
+ nameBuffer
+ .append(" <font color=\"purple\">xmlns:")
+ .append(element.getNamespacePrefix())
+ .append("</font>=\"<font color=\"green\">")
+ .append(element.getNamespaceURI() + "</font>\"");
+ } else
+ nameBuffer.append(" <font color=\"purple\">xmlns:")
+ .append(element.getNamespacePrefix())
+ .append("</font>=\"<font color=\"green\">")
+ .append(element.getNamespaceURI() + "</font>\"");
+
+ String sep = "";
+ for (Object a : element.getAttributes()) {
+ Attribute attribute = (Attribute) a;
+ String name = attribute.getName().trim();
+ String value = attribute.getValue().trim();
+ if (value != null && value.length() > 0) {
+ // TODO xml-quote name and value
+ nameBuffer.append(sep)
+ .append(" <font color=\"purple\">")
+ .append(name)
+ .append("</font>=\"<font color=\"green\">")
+ .append(value).append("</font>\"");
+ sep = ",";
+ }
+ }
+
+ nameBuffer.append("</html>");
+ setText(nameBuffer.toString());
+ }
+
+ private void renderTextNode(Text text) {
+ // setIcon(TavernaIcons.leafIcon);
+ String name = text.getText();
+ if (textSizeLimit > -1 && name.length() > textSizeLimit)
+ name = name.substring(0, textSizeLimit) + "...";
+ setText("<html><pre><font color=\"blue\">"
+ + name.replaceAll("<br>", "\n").replaceAll("<", "<")
+ + "</font></pre></html>");
+ }
+ });
+ setAllNodesExpanded();
+
+ // Add a listener to present the 'save as text' option
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (e.isPopupTrigger())
+ doEvent(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (e.isPopupTrigger())
+ doEvent(e);
+ }
+
+ public void doEvent(MouseEvent e) {
+ JPopupMenu menu = new JPopupMenu();
+ JMenuItem item = new JMenuItem("Save as XML text");
+ menu.add(item);
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ saveTreeXML();
+ }
+ });
+ menu.show(XMLTree.this, e.getX(), e.getY());
+ }
+ });
+ }
+
+ private void saveTreeXML() {
+ try {
+ Preferences prefs = userNodeForPackage(XMLTree.class);
+ String curDir = prefs.get("currentDir",
+ System.getProperty("user.home"));
+ fc.resetChoosableFileFilters();
+ fc.setFileFilter(new ExtensionFileFilter(new String[] { "xml" }));
+ fc.setCurrentDirectory(new File(curDir));
+ if (fc.showSaveDialog(this) == APPROVE_OPTION) {
+ prefs.put("currentDir", fc.getCurrentDirectory().toString());
+ saveTreeXML(fc.getSelectedFile());
+ }
+ } catch (Exception ex) {
+ showMessageDialog(this, "Problem saving XML:\n" + ex.getMessage(),
+ "Error!", ERROR_MESSAGE);
+ }
+ }
+
+ private void saveTreeXML(File file) throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(file))) {
+ out.print(this.getText());
+ }
+ }
+
+ public void setAllNodesExpanded() {
+ synchronized (this.getModel()) {
+ expandAll(this, new TreePath(this.getModel().getRoot()), true);
+ }
+ }
+
+ private void expandAll(JTree tree, TreePath parent, boolean expand) {
+ synchronized (this.getModel()) {
+ /*
+ * Traverse children
+ *
+ * Ignores nodes who's userObject is a Processor type to avoid
+ * overloading the UI with nodes at startup.
+ */
+ TreeNode node = (TreeNode) parent.getLastPathComponent();
+ for (Enumeration<?> e = node.children(); e.hasMoreElements(); ) {
+ TreeNode n = (TreeNode) e.nextElement();
+ expandAll(tree, parent.pathByAddingChild(n), expand);
+ }
+ // Expansion or collapse must be done bottom-up
+ if (expand)
+ tree.expandPath(parent);
+ else
+ tree.collapsePath(parent);
+ }
+ }
+
+ public void setTextNodeSizeLimit(int sizeLimit) {
+ textSizeLimit = sizeLimit;
+ }
+
+ private XMLNode createTreeNode(Content content) {
+ XMLNode node = new XMLNode(content);
+ if (content instanceof Parent) {
+ Parent parent = (Parent) content;
+ for (Object child : parent.getContent()) {
+ if (child instanceof Element)
+ node.add(createTreeNode((Content) child));
+ else if (textSizeLimit != 0 && child instanceof Text) {
+ Text text = (Text) child;
+ if (!text.getTextNormalize().isEmpty())
+ node.add(createTreeNode(text));
+ }
+ }
+ }
+ return node;
+ }
+}
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer b/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
new file mode 100644
index 0000000..044a396
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.renderers.Renderer
@@ -0,0 +1,7 @@
+net.sf.taverna.t2.renderers.impl.AdvancedImageRenderer
+net.sf.taverna.t2.renderers.impl.HexBinaryRenderer
+net.sf.taverna.t2.renderers.impl.TextRenderer
+net.sf.taverna.t2.renderers.impl.TextRtfRenderer
+net.sf.taverna.t2.renderers.impl.TextXMLRenderer
+
+
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml
new file mode 100644
index 0000000..436426a
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context-osgi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="AdvancedImageRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="HexBinaryRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="TextRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="TextRtfRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+ <service ref="TextXMLRenderer" interface="net.sf.taverna.t2.renderers.Renderer" />
+
+ <service ref="RendererRegistry" interface="net.sf.taverna.t2.renderers.RendererRegistry" />
+
+ <list id="renderers" interface="net.sf.taverna.t2.renderers.Renderer" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml
new file mode 100644
index 0000000..a041c98
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/main/resources/META-INF/spring/renderers-impl-context.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="AdvancedImageRenderer" class="net.sf.taverna.t2.renderers.impl.AdvancedImageRenderer" />
+ <bean id="HexBinaryRenderer" class="net.sf.taverna.t2.renderers.impl.HexBinaryRenderer" />
+ <bean id="TextRenderer" class="net.sf.taverna.t2.renderers.impl.TextRenderer" />
+ <bean id="TextRtfRenderer" class="net.sf.taverna.t2.renderers.impl.TextRtfRenderer" />
+ <bean id="TextXMLRenderer" class="net.sf.taverna.t2.renderers.impl.TextXMLRenderer" />
+
+ <bean id="RendererRegistry" class="net.sf.taverna.t2.renderers.impl.RendererRegistryImpl">
+ <property name="renderers" ref="renderers" />
+ </bean>
+
+
+</beans>
diff --git a/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java b/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
new file mode 100644
index 0000000..ece7cf5
--- /dev/null
+++ b/taverna-workbench-renderers-impl/src/test/java/net/sf/taverna/t2/renderers/TestRendererSPI.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.renderers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+@SuppressWarnings("unused")
+public class TestRendererSPI {
+
+ private static final String TEST_NS = "testNS";
+
+ @Test
+ public void doNothing() {
+ //do nothing for the moment
+ }
+
+// @Test
+// public void getAllRenderers() {
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// assertEquals(rendererRegistry.getInstances().size(), 10);
+// }
+//
+// @Test
+// public void checkTextHtmlMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType = "text/html";
+// String html = "<HTML><HEAD></HEAD><BODY>hello</BODY></HTML>";
+// EntityIdentifier entityIdentifier = facade.register(html, "utf-8");
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(),2);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextHtmlRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("text/html"));
+// }
+//
+// @Test
+// public void checkURLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType = "text/x-taverna-web-url.text";
+// String url = "http://google.com";
+// EntityIdentifier entityIdentifier = facade.register(url);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(),2);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextTavernaWebUrlRenderer");
+// assertTrue(renderersForMimeType.get(1).canHandle("text/x-taverna-web-url.text"));
+// }
+//
+// @Test
+// public void checkJMolMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="chemical/x-pdb";
+// String jmol = "jmol";
+// EntityIdentifier entityIdentifier = facade.register(jmol);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "JMolRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-mdl-molfile"));
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-cml"));
+// }
+//
+// @Test
+// public void checkSeqVistaMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="chemical/x-swissprot";
+// String type = "seqvista";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "SeqVistaRenderer");
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-embl-dl-nucleotide"));
+// assertTrue(renderersForMimeType.get(0).canHandle("chemical/x-fasta"));
+// }
+//
+// @Test
+// public void checkSVGMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="image/svg+xml";
+// String type = "SVG";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "SVGRenderer");
+// }
+//
+// @Test
+// public void checkTextMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/text";
+// String type = "text";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 1);
+// assertEquals(renderersForMimeType.get(0).getClass().getSimpleName(), "TextRenderer");
+// }
+//
+// @Test
+// public void checkTextRtfMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/rtf";
+// String type = "textRTF";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextRtfRenderer");
+// }
+//
+// @Test
+// public void checkTextXMLMimeType() throws EmptyListException, MalformedListException, UnsupportedObjectTypeException {
+// String mimeType ="text/xml";
+// String type = "textXML";
+// EntityIdentifier entityIdentifier = facade.register(type);
+// RendererRegistry rendererRegistry = new RendererRegistry();
+// List<Renderer> renderersForMimeType = rendererRegistry.getRenderersForMimeType(facade, entityIdentifier, mimeType);
+// assertEquals(renderersForMimeType.size(), 2);
+// assertEquals(renderersForMimeType.get(1).getClass().getSimpleName(), "TextXMLRenderer");
+// }
+//
+// @Before
+// public void setDataManager() {
+// // dManager = new FileDataManager("testNS",
+// // new HashSet<LocationalContext>(), new File("/tmp/fish"));
+// dManager = new InMemoryDataManager(TEST_NS,
+// new HashSet<LocationalContext>());
+// facade = new DataFacade(dManager);
+// }
+
+}
diff --git a/taverna-workbench-report-impl/pom.xml b/taverna-workbench-report-impl/pom.xml
new file mode 100644
index 0000000..c0e72bb
--- /dev/null
+++ b/taverna-workbench-report-impl/pom.xml
@@ -0,0 +1,40 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>ui-impl</artifactId>
+ <groupId>net.sf.taverna.t2</groupId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>report-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Reporting Implementation</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.core</groupId>
+ <artifactId>workflowmodel-api</artifactId>
+ <version>${t2.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>report-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java
new file mode 100644
index 0000000..b5986b5
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/config/impl/ReportManagerConfigurationImpl.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.config.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.taverna.configuration.AbstractConfigurable;
+import uk.org.taverna.configuration.ConfigurationManager;
+
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workflowmodel.health.RemoteHealthChecker;
+
+/**
+ * @author alanrw
+ *
+ */
+public final class ReportManagerConfigurationImpl extends AbstractConfigurable implements ReportManagerConfiguration {
+
+ private static final int DEFAULT_TIMEOUT = 10;
+
+ private Map<String, String> defaultPropertyMap;
+
+ public ReportManagerConfigurationImpl(ConfigurationManager configurationManager) {
+ super(configurationManager);
+ }
+
+ public String getCategory() {
+ return "general";
+ }
+
+ public Map<String, String> getDefaultPropertyMap() {
+
+ if (defaultPropertyMap == null) {
+ defaultPropertyMap = new HashMap<String, String>();
+ defaultPropertyMap.put(TIMEOUT, Integer.toString(DEFAULT_TIMEOUT));
+ defaultPropertyMap.put(ON_EDIT, QUICK_CHECK);
+ defaultPropertyMap.put(ON_OPEN, QUICK_CHECK);
+ defaultPropertyMap.put(BEFORE_RUN, FULL_CHECK);
+ defaultPropertyMap.put(QUERY_BEFORE_RUN, ERRORS_OR_WARNINGS);
+ defaultPropertyMap.put(REPORT_EXPIRATION, Integer.toString(DEFAULT_REPORT_EXPIRATION));
+ }
+ return defaultPropertyMap;
+ }
+
+ public String getDisplayName() {
+ return "Validation report";
+ }
+
+ public String getFilePrefix() {
+ return "ReportManager";
+ }
+
+ public String getUUID() {
+ return "F86378E5-0EC4-4DE9-8A55-6098595413DC";
+ }
+
+ @Override
+ public void applySettings() {
+ RemoteHealthChecker.setTimeoutInSeconds(Integer.parseInt(this.getProperty(TIMEOUT)));
+ }
+
+ public void setProperty(String key, String value) {
+ super.setProperty(key, value);
+ if (key.equals(TIMEOUT)) {
+ applySettings();
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java
new file mode 100644
index 0000000..825efee
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/java/net/sf/taverna/t2/workbench/report/impl/ReportManagerImpl.java
@@ -0,0 +1,564 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.report.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.visit.HierarchyTraverser;
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.visit.VisitReport.Status;
+import net.sf.taverna.t2.visit.Visitor;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.report.DataflowReportEvent;
+import net.sf.taverna.t2.workbench.report.FailedEntityKind;
+import net.sf.taverna.t2.workbench.report.IncompleteDataflowKind;
+import net.sf.taverna.t2.workbench.report.InvalidDataflowKind;
+import net.sf.taverna.t2.workbench.report.ReportManager;
+import net.sf.taverna.t2.workbench.report.ReportManagerEvent;
+import net.sf.taverna.t2.workbench.report.UnresolvedOutputKind;
+import net.sf.taverna.t2.workbench.report.UnsatisfiedEntityKind;
+import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.DataflowValidationReport;
+import net.sf.taverna.t2.workflowmodel.Processor;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author Alan R Williams
+ *
+ */
+public class ReportManagerImpl implements Observable<ReportManagerEvent>, ReportManager {
+
+ private static final long MAX_AGE_OUTDATED_MILLIS = 1 * 60 * 60 * 1000; // 1 hour
+ private static Logger logger = Logger.getLogger(ReportManagerImpl.class);
+
+ private ReportManagerConfiguration reportManagerConfiguration;
+ private HierarchyTraverser traverser = null;
+ private Map<Dataflow, Map<Object, Set<VisitReport>>> reportMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Set<VisitReport>>>());;
+ private Map<Dataflow, Map<Object, Status>> statusMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Status>>());
+ private Map<Dataflow, Map<Object, String>> summaryMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Map<Object, String>>());
+ private Map<Dataflow, Long> lastCheckedMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Long>());
+ private Map<Dataflow, Long> lastFullCheckedMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, Long>());
+ private Map<Dataflow, String> lastFullCheckedDataflowIdMap = Collections
+ .synchronizedMap(new WeakHashMap<Dataflow, String>());
+
+ private EditManager editManager;
+ private FileManager fileManager;
+
+ // private Set<Visitor<?>> visitors;
+
+ protected ReportManagerImpl(EditManager editManager, FileManager fileManager,
+ Set<Visitor<?>> visitors, ReportManagerConfiguration reportManagerConfiguration)
+ throws IllegalStateException {
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.reportManagerConfiguration = reportManagerConfiguration;
+ // this.visitors = visitors;
+ traverser = new HierarchyTraverser(visitors);
+ ReportManagerFileObserver fileObserver = new ReportManagerFileObserver();
+ fileManager.addObserver(fileObserver);
+ addEditObserver();
+ reportManagerConfiguration.applySettings();
+ }
+
+ private void addEditObserver() {
+ synchronized (editManager) {
+ List<Observer<EditManagerEvent>> currentObservers = editManager.getObservers();
+ for (Observer<EditManagerEvent> o : currentObservers) {
+ editManager.removeObserver(o);
+ }
+ editManager.addObserver(new ReportManagerEditObserver());
+ for (Observer<EditManagerEvent> o : currentObservers) {
+ editManager.addObserver(o);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#updateReport(net.sf.taverna.t2.workflowmodel
+ * .Dataflow, boolean, boolean)
+ */
+ @Override
+ public void updateReport(Dataflow d, boolean includeTimeConsuming, boolean remember) {
+ Set<VisitReport> oldTimeConsumingReports = null;
+ long time = System.currentTimeMillis();
+ long expiration = Integer.parseInt(reportManagerConfiguration
+ .getProperty(ReportManagerConfiguration.REPORT_EXPIRATION)) * 60 * 1000;
+ if (remember && !includeTimeConsuming
+ && ((expiration == 0) || ((time - getLastFullCheckedTime(d)) < expiration))) {
+ oldTimeConsumingReports = getTimeConsumingReports(d);
+ }
+ Map<Object, Set<VisitReport>> reportsEntry = new HashMap<Object, Set<VisitReport>>();
+ Map<Object, Status> statusEntry = new HashMap<Object, Status>();
+ Map<Object, String> summaryEntry = new HashMap<Object, String>();
+ reportMap.put(d, reportsEntry);
+ statusMap.put(d, statusEntry);
+ summaryMap.put(d, summaryEntry);
+ validateDataflow(d, reportsEntry, statusEntry, summaryEntry);
+
+ Set<VisitReport> newReports = new HashSet<VisitReport>();
+ traverser.traverse(d, new ArrayList<Object>(), newReports, includeTimeConsuming);
+ for (VisitReport vr : newReports) {
+ addReport(reportsEntry, statusEntry, summaryEntry, vr);
+ }
+ if (oldTimeConsumingReports != null) {
+ for (VisitReport vr : oldTimeConsumingReports) {
+ addReport(reportsEntry, statusEntry, summaryEntry, vr);
+ }
+ }
+ time = System.currentTimeMillis();
+ lastCheckedMap.put(d, time);
+ if (includeTimeConsuming) {
+ lastFullCheckedMap.put(d, time);
+ lastFullCheckedDataflowIdMap.put(d, d.getIdentifier());
+ }
+ multiCaster.notify(new DataflowReportEvent(d));
+ }
+
+ private void updateObjectReportInternal(Dataflow d, Object o) {
+ Map<Object, Set<VisitReport>> reportsEntry = reportMap.get(d);
+ Map<Object, Status> statusEntry = statusMap.get(d);
+ Map<Object, String> summaryEntry = summaryMap.get(d);
+ if (reportsEntry == null) {
+ logger.error("Attempt to update reports on an object in a dataflow that has not been checked");
+ reportsEntry = new HashMap<Object, Set<VisitReport>>();
+ statusEntry = new HashMap<Object, Status>();
+ summaryEntry = new HashMap<Object, String>();
+ reportMap.put(d, reportsEntry);
+ statusMap.put(d, statusEntry);
+ summaryMap.put(d, summaryEntry);
+ } else {
+ reportsEntry.remove(o);
+ statusEntry.remove(o);
+ summaryEntry.remove(o);
+ }
+ // Assume o is directly inside d
+ List<Object> ancestry = new ArrayList<Object>();
+ ancestry.add(d);
+ Set<VisitReport> newReports = new HashSet<VisitReport>();
+ traverser.traverse(o, ancestry, newReports, true);
+ for (VisitReport vr : newReports) {
+ addReport(reportsEntry, statusEntry, summaryEntry, vr);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectSetReport(net.sf.taverna.t2
+ * .workflowmodel.Dataflow, java.util.Set)
+ */
+ @Override
+ public void updateObjectSetReport(Dataflow d, Set<Object> objects) {
+ for (Object o : objects) {
+ updateObjectReportInternal(d, o);
+ }
+ multiCaster.notify(new DataflowReportEvent(d));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectReport(net.sf.taverna.t2.
+ * workflowmodel.Dataflow, java.lang.Object)
+ */
+ @Override
+ public void updateObjectReport(Dataflow d, Object o) {
+ updateObjectReportInternal(d, o);
+ multiCaster.notify(new DataflowReportEvent(d));
+ }
+
+ private Set<VisitReport> getTimeConsumingReports(Dataflow d) {
+ Set<VisitReport> result = new HashSet<VisitReport>();
+ Map<Object, Set<VisitReport>> currentReports = getReports(d);
+ if (currentReports != null) {
+ for (Object o : currentReports.keySet()) {
+ for (VisitReport vr : currentReports.get(o)) {
+ if (vr.wasTimeConsuming()) {
+ result.add(vr);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private void removeReport(Dataflow d) {
+ reportMap.remove(d);
+ statusMap.remove(d);
+ summaryMap.remove(d);
+ }
+
+ private void addReport(Map<Object, Set<VisitReport>> reports, Map<Object, Status> statusEntry,
+ Map<Object, String> summaryEntry, VisitReport newReport) {
+ if (newReport.getCheckTime() == 0) {
+ newReport.setCheckTime(System.currentTimeMillis());
+ }
+ Object subject = newReport.getSubject();
+ Set<VisitReport> currentReports = reports.get(subject);
+ Status newReportStatus = newReport.getStatus();
+ if (currentReports == null) {
+ currentReports = new HashSet<VisitReport>();
+ reports.put(subject, currentReports);
+ statusEntry.put(subject, newReportStatus);
+ summaryEntry.put(subject, newReport.getMessage());
+ } else {
+ Status currentStatus = statusEntry.get(subject);
+ if (currentStatus.compareTo(newReportStatus) < 0) {
+ statusEntry.put(subject, newReportStatus);
+ summaryEntry.put(subject, newReport.getMessage());
+ } else if (currentStatus.compareTo(newReportStatus) == 0) {
+ if (currentStatus.equals(Status.WARNING)) {
+ summaryEntry.put(subject, "Multiple warnings");
+ } else if (currentStatus.equals(Status.SEVERE)) {
+ summaryEntry.put(subject, "Multiple errors");
+ }
+ }
+ }
+ currentReports.add(newReport);
+ }
+
+ private void validateDataflow(Dataflow d, Map<Object, Set<VisitReport>> reportsEntry,
+ Map<Object, Status> statusEntry, Map<Object, String> summaryEntry) {
+ DataflowValidationReport validationReport = d.checkValidity();
+ if (validationReport.isWorkflowIncomplete()) {
+ addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
+ IncompleteDataflowKind.getInstance(), d, "Incomplete workflow",
+ IncompleteDataflowKind.INCOMPLETE_DATAFLOW, VisitReport.Status.SEVERE));
+ } else if (!validationReport.isValid()) {
+ addReport(reportsEntry, statusEntry, summaryEntry,
+ new VisitReport(InvalidDataflowKind.getInstance(), d, "Invalid workflow",
+ InvalidDataflowKind.INVALID_DATAFLOW, VisitReport.Status.SEVERE));
+ }
+ fillInReport(reportsEntry, statusEntry, summaryEntry, validationReport);
+ }
+
+ private void fillInReport(Map<Object, Set<VisitReport>> reportsEntry,
+ Map<Object, Status> statusEntry, Map<Object, String> summaryEntry,
+ DataflowValidationReport report) {
+ for (Object o : report.getUnresolvedOutputs()) {
+ addReport(reportsEntry, statusEntry, summaryEntry,
+ new VisitReport(UnresolvedOutputKind.getInstance(), o,
+ "Invalid workflow output", UnresolvedOutputKind.OUTPUT,
+ VisitReport.Status.SEVERE));
+ }
+ for (Object o : report.getFailedEntities()) {
+ addReport(reportsEntry, statusEntry, summaryEntry,
+ new VisitReport(FailedEntityKind.getInstance(), o,
+ "Mismatch of input list depths", FailedEntityKind.FAILED_ENTITY,
+ VisitReport.Status.SEVERE));
+ }
+ for (Object o : report.getUnsatisfiedEntities()) {
+ addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
+ UnsatisfiedEntityKind.getInstance(), o, "Unknown prior list depth",
+ UnsatisfiedEntityKind.UNSATISFIED_ENTITY, VisitReport.Status.SEVERE));
+ }
+ // for (DataflowValidationReport subReport : report.getInvalidDataflows().values()) {
+ // fillInReport(descriptionMap, subReport);
+ // }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
+ * .Dataflow, java.lang.Object)
+ */
+ @Override
+ public Set<VisitReport> getReports(Dataflow d, Object object) {
+ Set<VisitReport> result = new HashSet<VisitReport>();
+ Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+ if (objectReports != null) {
+ Set<Object> objects = new HashSet<Object>();
+ objects.add(object);
+ if (object instanceof Processor) {
+ objects.addAll(((Processor) object).getActivityList());
+ }
+ for (Object o : objects) {
+ if (objectReports.containsKey(o)) {
+ result.addAll(objectReports.get(o));
+ }
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
+ * .Dataflow)
+ */
+ @Override
+ public Map<Object, Set<VisitReport>> getReports(Dataflow d) {
+ return reportMap.get(d);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#isStructurallySound(net.sf.taverna.t2.
+ * workflowmodel.Dataflow)
+ */
+ @Override
+ public boolean isStructurallySound(Dataflow d) {
+ Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+ if (objectReports == null) {
+ return false;
+ }
+ for (Set<VisitReport> visitReportSet : objectReports.values()) {
+ for (VisitReport vr : visitReportSet) {
+ if (vr.getStatus().equals(Status.SEVERE)) {
+ VisitKind vk = vr.getKind();
+ if ((vk instanceof IncompleteDataflowKind)
+ || (vk instanceof InvalidDataflowKind)
+ || (vk instanceof UnresolvedOutputKind)
+ || (vk instanceof FailedEntityKind)
+ || (vk instanceof UnsatisfiedEntityKind)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
+ * .Dataflow)
+ */
+ @Override
+ public Status getStatus(Dataflow d) {
+ Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
+ if (objectReports == null) {
+ return Status.OK;
+ }
+ Status currentStatus = Status.OK;
+ for (Set<VisitReport> visitReportSet : objectReports.values()) {
+ for (VisitReport vr : visitReportSet) {
+ Status status = vr.getStatus();
+ if (status.compareTo(currentStatus) > 0) {
+ currentStatus = status;
+ }
+ if (currentStatus.equals(Status.SEVERE)) {
+ return currentStatus;
+ }
+ }
+ }
+ return currentStatus;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
+ * .Dataflow, java.lang.Object)
+ */
+ @Override
+ public Status getStatus(Dataflow d, Object object) {
+ Status result = Status.OK;
+ Map<Object, Status> statusEntry = statusMap.get(d);
+ if (statusEntry != null) {
+ Status value = statusEntry.get(object);
+ if (value != null) {
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getSummaryMessage(net.sf.taverna.t2.
+ * workflowmodel.Dataflow, java.lang.Object)
+ */
+ @Override
+ public String getSummaryMessage(Dataflow d, Object object) {
+ String result = null;
+ if (!getStatus(d, object).equals(Status.OK)) {
+ Map<Object, String> summaryEntry = summaryMap.get(d);
+ if (summaryEntry != null) {
+ result = summaryEntry.get(object);
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getLastCheckedTime(net.sf.taverna.t2.
+ * workflowmodel.Dataflow)
+ */
+ @Override
+ public long getLastCheckedTime(Dataflow d) {
+ Long l = lastCheckedMap.get(d);
+ if (l == null) {
+ return 0;
+ } else {
+ return l.longValue();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#getLastFullCheckedTime(net.sf.taverna.t2
+ * .workflowmodel.Dataflow)
+ */
+ @Override
+ public long getLastFullCheckedTime(Dataflow d) {
+ Long l = lastFullCheckedMap.get(d);
+ if (l == null) {
+ return 0;
+ } else {
+ return l.longValue();
+ }
+ }
+
+ /**
+ * @author alanrw
+ *
+ */
+ public class ReportManagerFileObserver implements Observer<FileManagerEvent> {
+
+ public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
+ throws Exception {
+ String onOpen = reportManagerConfiguration.getProperty(
+ ReportManagerConfiguration.ON_OPEN);
+ if (message instanceof ClosedDataflowEvent) {
+ ReportManagerImpl.this.removeReport(((ClosedDataflowEvent) message).getDataflow());
+ } else if (message instanceof SetCurrentDataflowEvent) {
+ Dataflow dataflow = ((SetCurrentDataflowEvent) message).getDataflow();
+ if (!reportMap.containsKey(dataflow)) {
+ if (!onOpen.equals(ReportManagerConfiguration.NO_CHECK)) {
+ updateReport(dataflow,
+ onOpen.equals(ReportManagerConfiguration.FULL_CHECK), true);
+ } else {
+ ReportManagerImpl.this.multiCaster
+ .notify(new DataflowReportEvent(dataflow));
+ }
+ } else {
+ ReportManagerImpl.this.multiCaster.notify(new DataflowReportEvent(dataflow));
+ }
+ }
+ }
+
+ }
+
+ private MultiCaster<ReportManagerEvent> multiCaster = new MultiCaster<ReportManagerEvent>(this);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#addObserver(net.sf.taverna.t2.lang.observer
+ * .Observer)
+ */
+ @Override
+ public void addObserver(Observer<ReportManagerEvent> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#getObservers()
+ */
+ @Override
+ public List<Observer<ReportManagerEvent>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.sf.taverna.t2.workbench.report.ReportManagerI#removeObserver(net.sf.taverna.t2.lang.observer
+ * .Observer)
+ */
+ @Override
+ public void removeObserver(Observer<ReportManagerEvent> observer) {
+ multiCaster.removeObserver(observer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.taverna.t2.workbench.report.ReportManagerI#isReportOutdated(net.sf.taverna.t2.
+ * workflowmodel.Dataflow)
+ */
+ @Override
+ public boolean isReportOutdated(Dataflow dataflow) {
+ String lastCheckedId = lastFullCheckedDataflowIdMap.get(dataflow);
+ Long lastCheck = lastFullCheckedMap.get(dataflow);
+ if (lastCheckedId == null || lastCheck == null) {
+ // Unknown, so outdated
+ return true;
+ }
+ if (!lastCheckedId.equals(dataflow.getIdentifier())) {
+ // Workflow changed, so outdaeted
+ return true;
+ }
+ long now = System.currentTimeMillis();
+ long age = now - lastCheck;
+ // Outdated if it is older than the maximum
+ return age > MAX_AGE_OUTDATED_MILLIS;
+ }
+
+ public class ReportManagerEditObserver implements Observer<EditManagerEvent> {
+ public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+ throws Exception {
+ String onEdit = reportManagerConfiguration
+ .getProperty(ReportManagerConfiguration.ON_EDIT);
+ Dataflow dataflow = fileManager.getCurrentDataflow();
+ if (message instanceof AbstractDataflowEditEvent) {
+ AbstractDataflowEditEvent adee = (AbstractDataflowEditEvent) message;
+ if (adee.getDataFlow().equals(dataflow)) {
+ if (onEdit.equals(ReportManagerConfiguration.QUICK_CHECK)) {
+ updateReport(dataflow, false, true);
+ } else if (onEdit.equals(ReportManagerConfiguration.FULL_CHECK)) {
+ updateReport(dataflow, true, true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml
new file mode 100644
index 0000000..31c7432
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context-osgi.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <!-- <service ref="ProcessorFragilityChecker" interface="net.sf.taverna.t2.visit.fragility.FragilityChecker" /> -->
+
+ <!-- <service ref="FragilityCheck" interface="net.sf.taverna.t2.visit.VisitKind" /> -->
+
+ <service ref="ReportManagerImpl" interface="net.sf.taverna.t2.workbench.report.ReportManager" />
+
+ <service ref="ReportManagerConfiguration" interface="net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="configurationManager" interface="uk.org.taverna.configuration.ConfigurationManager" />
+
+ <set id="visitors" interface="net.sf.taverna.t2.visit.Visitor" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml
new file mode 100644
index 0000000..966d50d
--- /dev/null
+++ b/taverna-workbench-report-impl/src/main/resources/META-INF/spring/report-impl-context.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <!-- <bean id="ProcessorFragilityChecker" class="net.sf.taverna.t2.visit.fragility.impl.ProcessorFragilityChecker" /> -->
+
+ <!-- <bean id="FragilityCheck" class="net.sf.taverna.t2.visit.fragility.impl.FragilityCheck" /> -->
+
+ <bean id="ReportManagerImpl" class="net.sf.taverna.t2.workbench.report.impl.ReportManagerImpl">
+ <constructor-arg ref="editManager" />
+ <constructor-arg ref="fileManager" />
+ <constructor-arg ref="visitors" />
+ <constructor-arg ref="ReportManagerConfiguration"/>
+ </bean>
+
+ <bean id="ReportManagerConfiguration" class="net.sf.taverna.t2.workbench.report.config.impl.ReportManagerConfigurationImpl">
+ <constructor-arg ref="configurationManager"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-selection-impl/pom.xml b/taverna-workbench-selection-impl/pom.xml
new file mode 100644
index 0000000..cb6cbcb
--- /dev/null
+++ b/taverna-workbench-selection-impl/pom.xml
@@ -0,0 +1,35 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>selection-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Selection Implementation</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>${osgi.core.version}</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java
new file mode 100644
index 0000000..09d6f71
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/DataflowSelectionModelImpl.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.selection.impl;
+
+import static net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage.Type.ADDED;
+import static net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage.Type.REMOVED;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+
+/**
+ * Default implementation of a <code>DataflowSelectionModel</code>.
+ *
+ * @author David Withers
+ */
+public class DataflowSelectionModelImpl implements DataflowSelectionModel {
+ private MultiCaster<DataflowSelectionMessage> multiCaster;
+ private Set<Object> selection = new TreeSet<>(new Comparator<Object>() {
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2)
+ return 0;
+ return o1.hashCode() - o2.hashCode();
+ }
+ });
+
+ /**
+ * Constructs a new instance of DataflowSelectionModelImpl.
+ */
+ public DataflowSelectionModelImpl() {
+ multiCaster = new MultiCaster<>(this);
+ }
+
+ @Override
+ public void addSelection(Object element) {
+ if (element != null) {
+ if (!selection.contains(element)) {
+ clearSelection();
+ selection.add(element);
+ multiCaster.notify(new DataflowSelectionMessage(ADDED, element));
+ }
+ }
+ }
+
+ @Override
+ public void clearSelection() {
+ for (Object element : new HashSet<>(selection))
+ removeSelection(element);
+ }
+
+ @Override
+ public Set<Object> getSelection() {
+ return new HashSet<>(selection);
+ }
+
+ @Override
+ public void removeSelection(Object element) {
+ if (element != null && selection.remove(element))
+ multiCaster.notify(new DataflowSelectionMessage(REMOVED, element));
+ }
+
+ @Override
+ public void setSelection(Set<Object> elements) {
+ if (elements == null) {
+ clearSelection();
+ return;
+ }
+ Set<Object> newSelection = new HashSet<>(elements);
+ for (Object element : new HashSet<>(selection))
+ if (!newSelection.remove(element))
+ removeSelection(element);
+ for (Object element : newSelection)
+ addSelection(element);
+ }
+
+ @Override
+ public void addObserver(Observer<DataflowSelectionMessage> observer) {
+ multiCaster.addObserver(observer);
+ }
+
+ @Override
+ public List<Observer<DataflowSelectionMessage>> getObservers() {
+ return multiCaster.getObservers();
+ }
+
+ @Override
+ public void removeObserver(Observer<DataflowSelectionMessage> observer) {
+ multiCaster.removeObserver(observer);
+ }
+}
diff --git a/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java
new file mode 100644
index 0000000..2e86f1a
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/java/net/sf/taverna/t2/workbench/selection/impl/SelectionManagerImpl.java
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.selection.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.file.events.OpenedDataflowEvent;
+import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
+import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.ProfileSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowRunSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowSelectionEvent;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * Implementation of the {@link SelectionManager}.
+ *
+ * @author David Withers
+ */
+public class SelectionManagerImpl implements SelectionManager {
+ private static final String RESULTS_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.results.ResultsPerspective";
+
+ private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+ private static final Logger logger = Logger.getLogger(SelectionManagerImpl.class);
+
+ private WorkflowBundle selectedWorkflowBundle;
+ private Map<WorkflowBundle, DataflowSelectionModel> workflowSelectionModels = new IdentityHashMap<>();
+ private Map<WorkflowBundle, Workflow> selectedWorkflows = new IdentityHashMap<>();
+ private Map<WorkflowBundle, Profile> selectedProfiles = new IdentityHashMap<>();
+ private String selectedWorkflowRun;
+ private Map<String, DataflowSelectionModel> workflowRunSelectionModels = new HashMap<>();
+ private PerspectiveSPI selectedPerspective;
+ private MultiCaster<SelectionManagerEvent> observers = new MultiCaster<>(this);
+ private FileManager fileManager;
+ private List<PerspectiveSPI> perspectives;
+
+ @Override
+ public DataflowSelectionModel getDataflowSelectionModel(WorkflowBundle dataflow) {
+ DataflowSelectionModel selectionModel;
+ synchronized (workflowSelectionModels) {
+ selectionModel = workflowSelectionModels.get(dataflow);
+ if (selectionModel == null) {
+ selectionModel = new DataflowSelectionModelImpl();
+ workflowSelectionModels.put(dataflow, selectionModel);
+ }
+ }
+ return selectionModel;
+ }
+
+ @Override
+ public WorkflowBundle getSelectedWorkflowBundle() {
+ return selectedWorkflowBundle;
+ }
+
+ @Override
+ public void setSelectedWorkflowBundle(WorkflowBundle workflowBundle) {
+ setSelectedWorkflowBundle(workflowBundle, true);
+ }
+
+ private void setSelectedWorkflowBundle(WorkflowBundle workflowBundle, boolean notifyFileManager) {
+ if (workflowBundle == null || workflowBundle == selectedWorkflowBundle)
+ return;
+ if (notifyFileManager) {
+ fileManager.setCurrentDataflow(workflowBundle);
+ return;
+ }
+ if (selectedWorkflows.get(workflowBundle) == null)
+ selectedWorkflows.put(workflowBundle,
+ workflowBundle.getMainWorkflow());
+ if (selectedProfiles.get(workflowBundle) == null)
+ selectedProfiles.put(workflowBundle,
+ workflowBundle.getMainProfile());
+ SelectionManagerEvent selectionManagerEvent = new WorkflowBundleSelectionEvent(
+ selectedWorkflowBundle, workflowBundle);
+ selectedWorkflowBundle = workflowBundle;
+ notify(selectionManagerEvent);
+ selectDesignPerspective();
+ }
+
+ private void removeWorkflowBundle(WorkflowBundle dataflow) {
+ synchronized (workflowSelectionModels) {
+ DataflowSelectionModel selectionModel = workflowSelectionModels.remove(dataflow);
+ if (selectionModel != null)
+ for (Observer<DataflowSelectionMessage> observer : selectionModel.getObservers())
+ selectionModel.removeObserver(observer);
+ }
+ synchronized (selectedWorkflows) {
+ selectedWorkflows.remove(dataflow);
+ }
+ synchronized (selectedProfiles) {
+ selectedProfiles.remove(dataflow);
+ }
+ }
+
+ @Override
+ public Workflow getSelectedWorkflow() {
+ return selectedWorkflows.get(selectedWorkflowBundle);
+ }
+
+ @Override
+ public void setSelectedWorkflow(Workflow workflow) {
+ if (workflow != null) {
+ Workflow selectedWorkflow = selectedWorkflows.get(workflow
+ .getParent());
+ if (selectedWorkflow != workflow) {
+ SelectionManagerEvent selectionManagerEvent = new WorkflowSelectionEvent(
+ selectedWorkflow, workflow);
+ selectedWorkflows.put(workflow.getParent(), workflow);
+ notify(selectionManagerEvent);
+ }
+ }
+ }
+
+ @Override
+ public Profile getSelectedProfile() {
+ return selectedProfiles.get(selectedWorkflowBundle);
+ }
+
+ @Override
+ public void setSelectedProfile(Profile profile) {
+ if (profile != null) {
+ Profile selectedProfile = selectedProfiles.get(profile.getParent());
+ if (selectedProfile != profile) {
+ SelectionManagerEvent selectionManagerEvent = new ProfileSelectionEvent(
+ selectedProfile, profile);
+ selectedProfiles.put(profile.getParent(), profile);
+ notify(selectionManagerEvent);
+ }
+ }
+ }
+
+ @Override
+ public String getSelectedWorkflowRun() {
+ return selectedWorkflowRun;
+ }
+
+ @Override
+ public void setSelectedWorkflowRun(String workflowRun) {
+ if ((selectedWorkflowRun == null && workflowRun != null)
+ || !selectedWorkflowRun.equals(workflowRun)) {
+ SelectionManagerEvent selectionManagerEvent = new WorkflowRunSelectionEvent(
+ selectedWorkflowRun, workflowRun);
+ selectedWorkflowRun = workflowRun;
+ notify(selectionManagerEvent);
+ selectResultsPerspective();
+ }
+ }
+
+ @Override
+ public DataflowSelectionModel getWorkflowRunSelectionModel(String workflowRun) {
+ DataflowSelectionModel selectionModel;
+ synchronized (workflowRunSelectionModels) {
+ selectionModel = workflowRunSelectionModels.get(workflowRun);
+ if (selectionModel == null) {
+ selectionModel = new DataflowSelectionModelImpl();
+ workflowRunSelectionModels.put(workflowRun, selectionModel);
+ }
+ }
+ return selectionModel;
+ }
+
+ @SuppressWarnings("unused")
+ private void removeWorkflowRun(String workflowRun) {
+ synchronized (workflowRunSelectionModels) {
+ DataflowSelectionModel selectionModel = workflowRunSelectionModels
+ .remove(workflowRun);
+ if (selectionModel != null)
+ for (Observer<DataflowSelectionMessage> observer : selectionModel
+ .getObservers())
+ selectionModel.removeObserver(observer);
+ }
+ }
+
+ @Override
+ public PerspectiveSPI getSelectedPerspective() {
+ return selectedPerspective;
+ }
+
+ @Override
+ public void setSelectedPerspective(PerspectiveSPI perspective) {
+ if (selectedPerspective != perspective) {
+ SelectionManagerEvent selectionManagerEvent = new PerspectiveSelectionEvent(
+ selectedPerspective, perspective);
+ selectedPerspective = perspective;
+ notify(selectionManagerEvent);
+ }
+ }
+
+ private void selectDesignPerspective() {
+ for (PerspectiveSPI perspective : perspectives)
+ if (DESIGN_PERSPECTIVE_ID.equals(perspective.getID())) {
+ setSelectedPerspective(perspective);
+ break;
+ }
+ }
+
+ private void selectResultsPerspective() {
+ for (PerspectiveSPI perspective : perspectives)
+ if (RESULTS_PERSPECTIVE_ID.equals(perspective.getID())) {
+ setSelectedPerspective(perspective);
+ break;
+ }
+ }
+
+ @Override
+ public void addObserver(Observer<SelectionManagerEvent> observer) {
+ synchronized (observers) {
+ WorkflowBundle selectedWorkflowBundle = getSelectedWorkflowBundle();
+ Workflow selectedWorkflow = getSelectedWorkflow();
+ Profile selectedProfile = getSelectedProfile();
+ String selectedWorkflowRun = getSelectedWorkflowRun();
+ PerspectiveSPI selectedPerspective = getSelectedPerspective();
+ try {
+ if (selectedWorkflowBundle != null)
+ observer.notify(this, new WorkflowBundleSelectionEvent(
+ null, selectedWorkflowBundle));
+ if (selectedWorkflow != null)
+ observer.notify(this, new WorkflowSelectionEvent(null,
+ selectedWorkflow));
+ if (selectedProfile != null)
+ observer.notify(this, new ProfileSelectionEvent(null,
+ selectedProfile));
+ if (selectedWorkflowRun != null)
+ observer.notify(this, new WorkflowRunSelectionEvent(null,
+ selectedWorkflowRun));
+ if (selectedPerspective != null)
+ observer.notify(this, new PerspectiveSelectionEvent(null,
+ selectedPerspective));
+ } catch (Exception e) {
+ logger.warn("Could not notify " + observer, e);
+ }
+ observers.addObserver(observer);
+ }
+ }
+
+ @Override
+ public void removeObserver(Observer<SelectionManagerEvent> observer) {
+ observers.removeObserver(observer);
+ }
+
+ @Override
+ public List<Observer<SelectionManagerEvent>> getObservers() {
+ return observers.getObservers();
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ setSelectedWorkflowBundle(fileManager.getCurrentDataflow());
+ fileManager.addObserver(new FileManagerObserver());
+ }
+
+ public void setEditManager(EditManager editManager) {
+ editManager.addObserver(new EditManagerObserver());
+ }
+
+ public void setPerspectives(List<PerspectiveSPI> perspectives) {
+ this.perspectives = perspectives;
+ }
+
+ public class FileManagerObserver implements Observer<FileManagerEvent> {
+ @Override
+ public void notify(Observable<FileManagerEvent> sender,
+ FileManagerEvent message) throws Exception {
+ if (message instanceof ClosedDataflowEvent) {
+ WorkflowBundle workflowBundle = ((ClosedDataflowEvent) message).getDataflow();
+ removeWorkflowBundle(workflowBundle);
+ } else if (message instanceof OpenedDataflowEvent) {
+ WorkflowBundle workflowBundle = ((OpenedDataflowEvent) message).getDataflow();
+ setSelectedWorkflowBundle(workflowBundle, false);
+ } else if (message instanceof SetCurrentDataflowEvent) {
+ WorkflowBundle workflowBundle = ((SetCurrentDataflowEvent) message).getDataflow();
+ setSelectedWorkflowBundle(workflowBundle, false);
+ }
+ }
+ }
+
+ private class EditManagerObserver implements Observer<EditManagerEvent> {
+ @Override
+ public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
+ throws Exception {
+ Edit<?> edit = message.getEdit();
+ considerEdit(edit, message instanceof DataFlowUndoEvent);
+ }
+
+ private void considerEdit(Edit<?> edit, boolean undoing) {
+ if (edit instanceof CompoundEdit) {
+ CompoundEdit compound = (CompoundEdit) edit;
+ for (Edit<?> e : compound.getChildEdits())
+ considerEdit(e, undoing);
+ } else if (edit instanceof AddChildEdit
+ && edit.getSubject() instanceof Workflow) {
+ Workflow subject = (Workflow) edit.getSubject();
+ DataflowSelectionModel selectionModel = getDataflowSelectionModel(subject
+ .getParent());
+ Object child = ((AddChildEdit<?>) edit).getChild();
+ if (child instanceof Processor
+ || child instanceof InputWorkflowPort
+ || child instanceof OutputWorkflowPort) {
+ if (undoing
+ && selectionModel.getSelection().contains(child))
+ selectionModel.clearSelection();
+ else {
+ Set<Object> selection = new HashSet<>();
+ selection.add(child);
+ selectionModel.setSelection(selection);
+ }
+ }
+ }
+ }
+ }
+
+ private void notify(SelectionManagerEvent event) {
+ synchronized (observers) {
+ observers.notify(event);
+ }
+ }
+}
diff --git a/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml
new file mode 100644
index 0000000..19faa00
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context-osgi.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <!-- <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" /> -->
+
+ <list id="perspectives" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" cardinality="0..N" />
+
+</beans:beans>
diff --git a/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml
new file mode 100644
index 0000000..84bbff1
--- /dev/null
+++ b/taverna-workbench-selection-impl/src/main/resources/META-INF/spring/selection-impl-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="selectionManager" class="net.sf.taverna.t2.workbench.selection.impl.SelectionManagerImpl" >
+ <property name="fileManager" ref="fileManager"/>
+ <property name="editManager" ref="editManager"/>
+ <property name="perspectives" ref="perspectives"/>
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-update-manager/pom.xml b/taverna-workbench-update-manager/pom.xml
new file mode 100644
index 0000000..c2f2003
--- /dev/null
+++ b/taverna-workbench-update-manager/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>update-manager</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>Taverna Workbench Update Manager</name>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-update-api</artifactId>
+ <version>${taverna.commons.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java
new file mode 100644
index 0000000..6b70101
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/UpdateManagerView.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.update.impl;
+
+import java.awt.GridBagLayout;
+
+import javax.swing.JPanel;
+
+import uk.org.taverna.commons.update.UpdateManager;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings({ "serial", "unused" })
+public class UpdateManagerView extends JPanel {
+
+ private UpdateManager updateManager;
+
+ public UpdateManagerView(UpdateManager updateManager) {
+ this.updateManager = updateManager;
+ initialize();
+ }
+
+ private void initialize() {
+ setLayout(new GridBagLayout());
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java
new file mode 100644
index 0000000..2c1459b
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/update/impl/menu/UpdateMenuAction.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.update.impl.menu;
+
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.commons.update.UpdateException;
+import uk.org.taverna.commons.update.UpdateManager;
+
+public class UpdateMenuAction extends AbstractMenuAction {
+ private static final Logger logger = Logger.getLogger(UpdateMenuAction.class);
+ private static final URI ADVANCED_MENU_URI = URI
+ .create("http://taverna.sf.net/2008/t2workbench/menu#advanced");
+
+ private UpdateManager updateManager;
+
+ public UpdateMenuAction() {
+ super(ADVANCED_MENU_URI, 1000);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction("Check for updates") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ findUpdates();
+ }
+ };
+ }
+
+ public void setUpdateManager(UpdateManager updateManager) {
+ this.updateManager = updateManager;
+ }
+
+ private void findUpdates() {
+ Component parent = null;
+ try {
+ if (!areUpdatesAvailable()) {
+ showMessageDialog(null, "No update available");
+ return;
+ }
+ if (showConfirmDialog(parent, "Update available. Update Now?") != YES_OPTION)
+ return;
+ applyUpdates();
+ showMessageDialog(parent,
+ "Update complete. Restart Taverna to apply update.");
+ } catch (UpdateException ex) {
+ showMessageDialog(parent, "Update failed: " + ex.getMessage());
+ logger.warn("Update failed", ex);
+ }
+ }
+
+ protected boolean areUpdatesAvailable() throws UpdateException {
+ return updateManager.checkForUpdates();
+ }
+
+ protected void applyUpdates() throws UpdateException {
+ updateManager.update();
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java
new file mode 100644
index 0000000..e6b4695
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/PluginMenuAction.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static java.awt.event.KeyEvent.VK_F12;
+import static javax.swing.KeyStroke.getKeyStroke;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+
+public class PluginMenuAction extends AbstractMenuAction {
+ private static final String UPDATES_AND_PLUGINS = "Updates and plugins";
+
+ @SuppressWarnings("serial")
+ protected class SoftwareUpdates extends AbstractAction {
+ public SoftwareUpdates() {
+ super(UPDATES_AND_PLUGINS, null/*UpdatesAvailableIcon.updateRecommendedIcon*/);
+ putValue(ACCELERATOR_KEY, getKeyStroke(VK_F12, 0));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ @SuppressWarnings("unused")
+ Component parent = null;
+ if (e.getSource() instanceof Component) {
+ parent = (Component) e.getSource();
+ }
+ //FIXME this does nothing!
+ //final PluginManagerFrame pluginManagerUI = new PluginManagerFrame(
+ // PluginManager.getInstance());
+ //if (parent != null) {
+ // pluginManagerUI.setLocationRelativeTo(parent);
+ //}
+ //pluginManagerUI.setVisible(true);
+ }
+ }
+
+ public PluginMenuAction() {
+ super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#advanced"),
+ 100);
+ }
+
+ @Override
+ protected Action createAction() {
+ //return new SoftwareUpdates();
+ return null;
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java
new file mode 100644
index 0000000..e2611be
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesAvailableMenuAction.java
@@ -0,0 +1,19 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static net.sf.taverna.t2.workbench.updatemanager.UpdatesToolbarSection.UPDATES_SECTION;
+
+import java.awt.Component;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuCustom;
+
+public class UpdatesAvailableMenuAction extends AbstractMenuCustom {
+ public UpdatesAvailableMenuAction() {
+ super(UPDATES_SECTION, 10);
+ }
+
+ @Override
+ protected Component createCustomComponent() {
+ //return new UpdatesAvailableIcon();
+ return null;
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java
new file mode 100644
index 0000000..b16d614
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/java/net/sf/taverna/t2/workbench/updatemanager/UpdatesToolbarSection.java
@@ -0,0 +1,16 @@
+package net.sf.taverna.t2.workbench.updatemanager;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class UpdatesToolbarSection extends AbstractMenuSection {
+ public static final URI UPDATES_SECTION = URI
+ .create("http://taverna.sf.net/2008/t2workbench/toolbar#updatesSection");
+
+ public UpdatesToolbarSection() {
+ super(DEFAULT_TOOL_BAR, 10000, UPDATES_SECTION);
+ }
+}
diff --git a/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml
new file mode 100644
index 0000000..ea637dd
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context-osgi.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UpdateMenuAction" auto-export="interfaces" />
+
+ <reference id="updateManager" interface="uk.org.taverna.commons.update.UpdateManager" />
+
+</beans:beans>
diff --git a/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml
new file mode 100644
index 0000000..c3adf1f
--- /dev/null
+++ b/taverna-workbench-update-manager/src/main/resources/META-INF/spring/update-manager-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UpdateMenuAction"
+ class="net.sf.taverna.t2.workbench.update.impl.menu.UpdateMenuAction">
+ <property name="updateManager" ref="updateManager" />
+ </bean>
+
+</beans>
diff --git a/taverna-workbench-workbench-impl/pom.xml b/taverna-workbench-workbench-impl/pom.xml
new file mode 100644
index 0000000..5b52d3a
--- /dev/null
+++ b/taverna-workbench-workbench-impl/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>net.sf.taverna.t2</groupId>
+ <artifactId>ui-impl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>net.sf.taverna.t2.ui-impl</groupId>
+ <artifactId>workbench-impl</artifactId>
+ <packaging>bundle</packaging>
+ <name>Workbench UI implementation</name>
+ <description>The main workbench ui</description>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Embed-Dependency>osxapplication</Embed-Dependency>
+ <Import-Package>com.apple.eawt;resolution:=optional,*</Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>workbench-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>edits-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>configuration-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>file-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>helper-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>menu-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.ui-api</groupId>
+ <artifactId>selection-api</artifactId>
+ <version>${t2.ui.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>observer</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.taverna.t2.lang</groupId>
+ <artifactId>ui</artifactId>
+ <version>${t2.lang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.commons</groupId>
+ <artifactId>taverna-plugin-api</artifactId>
+ <version>${taverna.commons.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.configuration</groupId>
+ <artifactId>taverna-app-configuration-api</artifactId>
+ <version>${taverna.configuration.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>uk.org.taverna.scufl2</groupId>
+ <artifactId>scufl2-api</artifactId>
+ <version>${scufl2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.simplericity.macify</groupId>
+ <artifactId>macify</artifactId>
+ <version>1.6</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java
new file mode 100644
index 0000000..4c493d1
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/DataflowEditsListener.java
@@ -0,0 +1,93 @@
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static uk.org.taverna.scufl2.api.container.WorkflowBundle.generateIdentifier;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.UpdateDataflowInternalIdentifierEdit;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Listens out for any edits on a dataflow and changes its internal id (or back
+ * to the old one in the case of redo/undo). Is first created when the workbench
+ * is initialised.
+ *
+ * @author Ian Dunlop
+ */
+public class DataflowEditsListener implements Observer<EditManagerEvent> {
+ private static Logger logger = Logger
+ .getLogger(DataflowEditsListener.class);
+
+ private Map<Edit<?>, URI> dataflowEditMap;
+
+ public DataflowEditsListener() {
+ super();
+ dataflowEditMap = new HashMap<>();
+ }
+
+ /**
+ * Receives {@link EditManagerEvent}s from the {@link EditManager} and
+ * changes the id of the {@link Dataflow} to a new one or back to its old
+ * one depending on whether it is a do/undo/redo event. Stores the actual
+ * edit and the pre-edit dataflow id in a Map and changes the id when it
+ * gets further actions against this same edit
+ */
+ @Override
+ public void notify(Observable<EditManagerEvent> observable,
+ EditManagerEvent event) throws Exception {
+ Edit<?> edit = event.getEdit();
+ WorkflowBundle dataFlow = ((AbstractDataflowEditEvent) event)
+ .getDataFlow();
+
+ if (event instanceof DataflowEditEvent) {
+ /*
+ * the dataflow has been edited in some way so change its internal
+ * id and store the old one against the edit that is changing
+ * 'something'
+ */
+ URI internalIdentifier = dataFlow.getGlobalBaseURI();
+ dataflowEditMap.put(edit, internalIdentifier);
+ URI newIdentifier = generateIdentifier();
+ new UpdateDataflowInternalIdentifierEdit(dataFlow, newIdentifier)
+ .doEdit();
+ logger.debug("Workflow edit, id changed from: "
+ + internalIdentifier + " to " + newIdentifier);
+ } else if (event instanceof DataFlowRedoEvent) {
+ /*
+ * change the id back to the old one and store the new one in case
+ * we want to change it back
+ */
+ URI newId = dataFlow.getGlobalBaseURI();
+ URI oldId = dataflowEditMap.get(edit);
+ dataflowEditMap.put(edit, newId);
+ new UpdateDataflowInternalIdentifierEdit(dataFlow, oldId).doEdit();
+ logger.debug("Workflow edit, id changed from: " + newId + " to "
+ + oldId);
+ } else if (event instanceof DataFlowUndoEvent) {
+ /*
+ * change the id back to the old one and store the new one in case
+ * we want to change it back
+ */
+ URI newId = dataFlow.getGlobalBaseURI();
+ URI oldId = dataflowEditMap.get(edit);
+ dataflowEditMap.put(edit, newId);
+ new UpdateDataflowInternalIdentifierEdit(dataFlow, oldId).doEdit();
+ logger.debug("Workflow edit, id changed from: " + newId + " to "
+ + oldId);
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java
new file mode 100644
index 0000000..fe13d4d
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/LoggerStream.java
@@ -0,0 +1,136 @@
+/***************************************
+ * *
+ * JBoss: The OpenSource J2EE WebOS *
+ * *
+ * Distributable under LGPL license. *
+ * See terms of license at gnu.org. *
+ * *
+ * Modified by Stian Soiland-Reyes *
+ * *
+ ***************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.Level;
+
+/**
+ * A subclass of PrintStream that redirects its output to a log4j Logger.
+ *
+ * <p>This class is used to map PrintStream/PrintWriter oriented logging onto
+ * the log4j Categories. Examples include capturing System.out/System.err
+ *
+ * @version <tt>$Revision: 1.1.1.1 $</tt>
+ * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
+ * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
+ */
+//FIXME Replace this class entirely
+class LoggerStream extends PrintStream {
+ /**
+ * Default flag to enable/disable tracing println calls. from the system
+ * property <tt>net.sf.taverna.t2.workbench.ui.impl.LoggerStream.trace</tt>
+ * or if not set defaults to <tt>false</tt>.
+ */
+ public static final boolean TRACE =
+ getBoolean(LoggerStream.class.getName() + ".trace", false);
+
+ /**
+ * Helper to get boolean value from system property or use default if not
+ * set.
+ */
+ private static boolean getBoolean(String name, boolean defaultValue) {
+ String value = System.getProperty(name, null);
+ if (value == null)
+ return defaultValue;
+ return new Boolean(value).booleanValue();
+ }
+
+ private Logger logger;
+ private Level level;
+ private boolean issuedWarning;
+
+ /**
+ * Redirect logging to the indicated logger using Level.INFO
+ */
+ public LoggerStream(Logger logger) {
+ this(logger, Level.INFO, System.out);
+ }
+
+ /**
+ * Redirect logging to the indicated logger using the given level. The ps is
+ * simply passed to super but is not used.
+ */
+ public LoggerStream(Logger logger, Level level, PrintStream ps) {
+ super(ps);
+ this.logger = logger;
+ this.level = level;
+ }
+
+ @Override
+ public void println(String msg) {
+ if (msg == null)
+ msg = "null";
+ byte[] bytes = msg.getBytes();
+ write(bytes, 0, bytes.length);
+ }
+
+ @Override
+ public void println(Object msg) {
+ if (msg == null)
+ msg = "null";
+ byte[] bytes = msg.toString().getBytes();
+ write(bytes, 0, bytes.length);
+ }
+
+ public void write(byte b) {
+ byte[] bytes = { b };
+ write(bytes, 0, 1);
+ }
+
+ private ThreadLocal<Boolean> recursiveCheck = new ThreadLocal<>();
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ Boolean recursed = recursiveCheck.get();
+ if (recursed != null && recursed) {
+ /*
+ * There is a configuration error that is causing looping. Most
+ * likely there are two console appenders so just return to prevent
+ * spinning.
+ */
+ if (issuedWarning == false) {
+ String msg = "ERROR: invalid log settings detected, console capturing is looping";
+ // out.write(msg.getBytes());
+ new Exception(msg).printStackTrace((PrintStream) out);
+ issuedWarning = true;
+ }
+ try {
+ out.write(b, off, len);
+ } catch (IOException e) {
+ }
+ return;
+ }
+
+ // Remove the end of line chars
+ while (len > 0 && (b[len - 1] == '\n' || b[len - 1] == '\r')
+ && len > off)
+ len--;
+
+ /*
+ * HACK, something is logging exceptions line by line (including
+ * blanks), but I can't seem to find it, so for now just ignore empty
+ * lines... they aren't very useful.
+ */
+ if (len != 0) {
+ String msg = new String(b, off, len);
+ recursiveCheck.set(true);
+ if (TRACE)
+ logger.log(level, msg, new Throwable());
+ else
+ logger.log(level, msg);
+ recursiveCheck.set(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java
new file mode 100644
index 0000000..a8bdfdd
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/SetConsoleLoggerStartup.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static org.apache.log4j.Level.ERROR;
+import static org.apache.log4j.Level.WARN;
+
+import java.io.PrintStream;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+
+public class SetConsoleLoggerStartup implements StartupSPI {
+ private static final PrintStream originalErr = System.err;
+ private static final PrintStream originalOut = System.out;
+
+ private final WorkbenchConfiguration workbenchConfiguration;
+
+ public SetConsoleLoggerStartup(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ @Override
+ public int positionHint() {
+ /*
+ * Must be <b>after</b> PrepareLoggerStarup in file-translator --
+ * otherwise Taverna 1 libraries will cause double logging
+ */
+ return 10;
+ }
+
+ @Override
+ public boolean startup() {
+ setSystemOutCapture();
+ return true;
+ }
+
+ public void setSystemOutCapture() {
+ if (!workbenchConfiguration.getCaptureConsole()) {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ return;
+ }
+ Logger systemOutLogger = Logger.getLogger("System.out");
+ Logger systemErrLogger = Logger.getLogger("System.err");
+
+ try {
+ /*
+ * This logger stream not loop with log4j > 1.2.13, which has
+ * getFollow method
+ */
+ ConsoleAppender.class.getMethod("getFollow");
+ System.setOut(new LoggerStream(systemOutLogger, WARN, originalOut));
+ } catch (SecurityException e) {
+ } catch (NoSuchMethodException e) {
+ System.err.println("Not capturing System.out, use log4j >= 1.2.13");
+ }
+
+ System.setErr(new LoggerStream(systemErrLogger, ERROR, originalErr));
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java
new file mode 100644
index 0000000..2537f0b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/StoreWindowStateOnShutdown.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2008-2010 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.workbench.ui.impl;
+
+import org.apache.log4j.Logger;
+
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+/**
+ * Store Workbench window size and perspectives, so that settings can be used on
+ * next startup.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class StoreWindowStateOnShutdown implements ShutdownSPI {
+ private static Logger logger = Logger
+ .getLogger(StoreWindowStateOnShutdown.class);
+
+ private Workbench workbench;
+
+ @Override
+ public int positionHint() {
+ return 1000;
+ }
+
+ @Override
+ public boolean shutdown() {
+ try {
+ workbench.storeSizeAndLocationPrefs();
+ } catch (Exception ex) {
+ logger.error("Error saving the Workbench size and position", ex);
+ }
+ return true;
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java
new file mode 100644
index 0000000..3e32f7b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationData.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+public class UserRegistrationData {
+ private String tavernaVersion = "";
+ private String firstName = "";
+ private String lastName = "";
+ private String emailAddress = "";
+ private String institutionOrCompanyName = "";
+ private String industry = "";
+ private String field = "";
+ private String purposeOfUsingTaverna = "";
+ private boolean keepMeInformed = false;
+
+ public void setTavernaVersion(String tavernaVersion) {
+ this.tavernaVersion = tavernaVersion;
+ }
+
+ public String getTavernaVersion() {
+ return tavernaVersion;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setEmailAddress(String emailAddress) {
+ this.emailAddress = emailAddress;
+ }
+
+ public String getEmailAddress() {
+ return emailAddress;
+ }
+
+ public void setInstitutionOrCompanyName(String institutionOrCompanyName) {
+ this.institutionOrCompanyName = institutionOrCompanyName;
+ }
+
+ public String getInstitutionOrCompanyName() {
+ return institutionOrCompanyName;
+ }
+
+ public void setIndustry(String industry) {
+ this.industry = industry;
+ }
+
+ public String getIndustry() {
+ return industry;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public void setPurposeOfUsingTaverna(String purposeOfUsingTaverna) {
+ this.purposeOfUsingTaverna = purposeOfUsingTaverna;
+ }
+
+ public String getPurposeOfUsingTaverna() {
+ return purposeOfUsingTaverna;
+ }
+
+ public void setKeepMeInformed(boolean keepMeInformed) {
+ this.keepMeInformed = keepMeInformed;
+ }
+
+ public boolean getKeepMeInformed() {
+ return keepMeInformed;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java
new file mode 100644
index 0000000..97f831f
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationForm.java
@@ -0,0 +1,995 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Color.LIGHT_GRAY;
+import static java.awt.Color.WHITE;
+import static java.awt.FlowLayout.LEFT;
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.FIRST_LINE_START;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static java.awt.event.KeyEvent.VK_ENTER;
+import static java.awt.event.KeyEvent.VK_TAB;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingConstants.BOTTOM;
+import static javax.swing.SwingConstants.TOP;
+import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.tavernaCogs32x32Icon;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Properties;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.Border;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+/**
+ * User registration form.
+ *
+ * @author Alex Nenadic
+ */
+@SuppressWarnings("serial")
+public class UserRegistrationForm extends HelpEnabledDialog {
+ private static final String FAILED = "User registration failed: ";
+
+ private static final String REGISTRATION_FAILED_MSG = "User registration failed. Please try again later.";
+
+ private static final String REGISTRATION_URL = "http://www.mygrid.org.uk/taverna/registration/";
+
+ public static final String TAVERNA_VERSION_PROPERTY_NAME = "Taverna version";
+ public static final String FIRST_NAME_PROPERTY_NAME = "First name";
+ public static final String LAST_NAME_PROPERTY_NAME = "Last name";
+ public static final String EMAIL_ADDRESS_PROPERTY_NAME = "Email address";
+ public static final String INSTITUTION_OR_COMPANY_PROPERTY_NAME = "Institution or company name";
+ public static final String INDUSTRY_PROPERTY_NAME = "Industry";
+ public static final String FIELD_PROPERTY_NAME = "Field of investigation";
+ public static final String PURPOSE_PROPERTY_NAME = "Purpose of using Taverna";
+ public static final String KEEP_ME_INFORMED_PROPERTY_NAME = "Keep me informed by email";
+
+ public static final String TAVERNA_REGISTRATION_POST_PARAMETER_NAME = "taverna_registration";
+ public static final String TAVERNA_VERSION_POST_PARAMETER_NAME = "taverna_version";
+ public static final String FIRST_NAME_POST_PARAMETER_NAME = "first_name";
+ public static final String LAST_NAME_POST_PARAMETER_NAME = "last_name";
+ public static final String EMAIL_ADDRESS_POST_PARAMETER_NAME = "email";
+ public static final String INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME = "institution_or_company";
+ public static final String INDUSTRY_TYPE_POST_PARAMETER_NAME = "industry_type";
+ public static final String FIELD_POST_PARAMETER_NAME = "field";
+ public static final String PURPOSE_POST_PARAMETER_NAME = "purpose";
+ public static final String KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME = "keep_me_informed";
+
+ private static String TRUE = Boolean.TRUE.toString();
+ private static String FALSE = Boolean.FALSE.toString();
+
+ private static final String WELCOME = "Welcome to the Taverna User Registration Form";
+ private static final String PLEASE_FILL_IN_THIS_REGISTRATION_FORM = "Please fill in this registration form to let us know that you are using Taverna";
+
+ private static final String WE_DO = "Note that by registering:\n"
+ + " \u25CF We do not have access to your data\n"
+ + " \u25CF We do not have access to your service usage\n"
+ + " \u25CF You will not be monitored\n"
+ + " \u25CF We do record the information you provide\n"
+ + " at registration time";
+
+ private static final String WHY_REGISTER = "By registering you will:\n"
+ + " \u25CF Allow us to support you better; future plans will be\n"
+ + " directed towards solutions Taverna users require\n"
+ + " \u25CF Help sustain Taverna development; our continued\n"
+ + " funding relies on us showing usage\n"
+ + " \u25CF (Optionally) Hear about news and product updates";
+
+ private static final String FIRST_NAME = "*First name:";
+ private static final String LAST_NAME = "*Last name:";
+ private static final String EMAIL_ADDRESS = "*Email address:";
+ private static final String KEEP_ME_INFORMED = "Keep me informed of news and product updates via email";
+ private static final String INSTITUTION_COMPANY_NAME = "*Institution/Company name:";
+ private static final String FIELD_OF_INVESTIGATION = " Field of investigation:\n"
+ + " (e.g. bioinformatics)";
+ private static final String WHY_YOU_INTEND_TO_USE_TAVERNA = " A brief description of how you intend\n"
+ + " to use Taverna: (e.g. genome analysis\n"
+ + " for bacterial strain identification)";
+
+ private static String[] industryTypes = { "", "Academia - Life Sciences",
+ "Academia - Social Sciences", "Academia - Physical Sciences",
+ "Academia - Environmental Sciences", "Academia - Other",
+ "Industry - Biotechnology", "Industry - Pharmaceutical",
+ "Industry - Engineering", "Industry - Other",
+ "Healthcare Services", "Goverment and Public Sector", "Other" };
+
+ private static final String I_AGREE_TO_THE_TERMS_AND_CONDITIONS = "I agree to the terms and conditions of registration at";
+ private static final String TERMS_AND_CONDITIONS_URL = "http://www.taverna.org.uk/legal/terms";
+
+ private Logger logger = Logger.getLogger(UserRegistrationForm.class);
+ private UserRegistrationData previousRegistrationData;
+ private JTextField firstNameTextField;
+ private JTextField lastNameTextField;
+ private JTextField emailTextField;
+ private JCheckBox keepMeInformedCheckBox;
+ private JTextField institutionOrCompanyTextField;
+ private JComboBox<String> industryTypeTextField;
+ private JTextField fieldTextField;
+ private JTextArea purposeTextArea;
+ private JCheckBox termsAndConditionsCheckBox;
+
+ private final File registrationDataFile;
+ private final File remindMeLaterFile;
+ private final File doNotRegisterMeFile;
+ private final String appName;
+
+ public UserRegistrationForm(String appName, File registrationDataFile,
+ File doNotRegisterMeFile, File remindMeLaterFile) {
+ super((Frame) null, "Taverna User Registration", true);
+ this.appName = appName;
+ this.registrationDataFile = registrationDataFile;
+ this.doNotRegisterMeFile = doNotRegisterMeFile;
+ this.remindMeLaterFile = remindMeLaterFile;
+ initComponents();
+ }
+
+ public UserRegistrationForm(String appName,
+ File previousRegistrationDataFile, File registrationDataFile,
+ File doNotRegisterMeFile, File remindMeLaterFile) {
+ super((Frame) null, "Taverna User Registration", true);
+ this.appName = appName;
+ this.registrationDataFile = registrationDataFile;
+ this.doNotRegisterMeFile = doNotRegisterMeFile;
+ this.remindMeLaterFile = remindMeLaterFile;
+ previousRegistrationData = loadUserRegistrationData(previousRegistrationDataFile);
+ initComponents();
+ }
+
+ // For testing only
+ // public static void main(String[] args) throws ClassNotFoundException,
+ // InstantiationException, IllegalAccessException,
+ // UnsupportedLookAndFeelException{
+ // WorkbenchImpl.setLookAndFeel();
+ // UserRegistrationForm form = new UserRegistrationForm();
+ // form.setVisible(true);
+ // }
+
+ private void initComponents() {
+ JPanel mainPanel = new JPanel(new GridBagLayout());
+
+ // Base font for all components on the form
+ Font baseFont = new JLabel("base font").getFont().deriveFont(11f);
+
+ // Title panel
+ JPanel titlePanel = new JPanel(new FlowLayout(LEFT));
+ titlePanel.setBackground(WHITE);
+ // titlePanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+ JLabel titleLabel = new JLabel(WELCOME);
+ titleLabel.setFont(baseFont.deriveFont(BOLD, 13.5f));
+ // titleLabel.setBorder(new EmptyBorder(10, 10, 0, 10));
+ JLabel titleIcon = new JLabel(tavernaCogs32x32Icon);
+ // titleIcon.setBorder(new EmptyBorder(10, 10, 10, 10));
+ DialogTextArea titleMessage = new DialogTextArea(
+ PLEASE_FILL_IN_THIS_REGISTRATION_FORM);
+ titleMessage.setMargin(new Insets(0, 20, 0, 10));
+ titleMessage.setFont(baseFont);
+ titleMessage.setEditable(false);
+ titleMessage.setFocusable(false);
+ // titlePanel.setBorder( new EmptyBorder(10, 10, 0, 10));
+ JPanel messagePanel = new JPanel(new BorderLayout());
+ messagePanel.add(titleLabel, NORTH);
+ messagePanel.add(titleMessage, CENTER);
+ messagePanel.setBackground(WHITE);
+ titlePanel.add(titleIcon);
+ titlePanel.add(messagePanel);
+ addDivider(titlePanel, BOTTOM, true);
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ // gbc.insets = new Insets(5, 10, 0, 0);
+ mainPanel.add(titlePanel, gbc);
+
+ // Registration messages
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ // gbc.insets = new Insets(5, 0, 0, 10);
+ DialogTextArea registrationMessage1 = new DialogTextArea(WHY_REGISTER);
+ registrationMessage1.setMargin(new Insets(0, 10, 0, 0));
+ registrationMessage1.setFont(baseFont);
+ registrationMessage1.setEditable(false);
+ registrationMessage1.setFocusable(false);
+ registrationMessage1.setBackground(getBackground());
+
+ DialogTextArea registrationMessage2 = new DialogTextArea(WE_DO);
+ registrationMessage2.setMargin(new Insets(0, 10, 0, 10));
+ registrationMessage2.setFont(baseFont);
+ registrationMessage2.setEditable(false);
+ registrationMessage2.setFocusable(false);
+ registrationMessage2.setBackground(getBackground());
+ JPanel registrationMessagePanel = new JPanel(new FlowLayout(
+ FlowLayout.CENTER));
+ registrationMessagePanel.add(registrationMessage1);
+ registrationMessagePanel.add(registrationMessage2);
+ addDivider(registrationMessagePanel, BOTTOM, true);
+ mainPanel.add(registrationMessagePanel, gbc);
+
+ // Mandatory label
+ // JLabel mandatoryLabel = new JLabel("* Mandatory fields");
+ // mandatoryLabel.setFont(baseFont);
+ // gbc.weightx = 0.0;
+ // gbc.weighty = 0.0;
+ // gbc.gridx = 0;
+ // gbc.gridy = 3;
+ // gbc.fill = NONE;
+ // gbc.anchor = GridBagConstraints.EAST;
+ // gbc.gridwidth = 2;
+ // gbc.insets = new Insets(0, 10, 0, 20);
+ // mainPanel.add(mandatoryLabel, gbc);
+
+ // First name
+ JLabel firstNameLabel = new JLabel(FIRST_NAME);
+ firstNameLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 4;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(0, 10, 0, 10);
+ mainPanel.add(firstNameLabel, gbc);
+
+ firstNameTextField = new JTextField();
+ firstNameTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ firstNameTextField.setText(previousRegistrationData.getFirstName());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 4;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(firstNameTextField, gbc);
+
+ // Last name
+ JLabel lastNameLabel = new JLabel(LAST_NAME);
+ lastNameLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 5;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(0, 10, 0, 10);
+ mainPanel.add(lastNameLabel, gbc);
+
+ lastNameTextField = new JTextField();
+ lastNameTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ lastNameTextField.setText(previousRegistrationData.getLastName());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 5;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(lastNameTextField, gbc);
+
+ // Email address
+ JLabel emailLabel = new JLabel(EMAIL_ADDRESS);
+ emailLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 6;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(emailLabel, gbc);
+
+ emailTextField = new JTextField();
+ emailTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ emailTextField.setText(previousRegistrationData.getEmailAddress());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 6;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(emailTextField, gbc);
+
+ // Keep me informed
+ keepMeInformedCheckBox = new JCheckBox(KEEP_ME_INFORMED);
+ keepMeInformedCheckBox.setFont(baseFont);
+ if (previousRegistrationData != null)
+ keepMeInformedCheckBox.setSelected(previousRegistrationData
+ .getKeepMeInformed());
+ keepMeInformedCheckBox.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ keepMeInformedCheckBox.setSelected(!keepMeInformedCheckBox
+ .isSelected());
+ }
+ }
+ });
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 7;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(keepMeInformedCheckBox, gbc);
+
+ // Institution name
+ JLabel institutionLabel = new JLabel(INSTITUTION_COMPANY_NAME);
+ institutionLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 8;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(institutionLabel, gbc);
+
+ institutionOrCompanyTextField = new JTextField();
+ institutionOrCompanyTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ institutionOrCompanyTextField.setText(previousRegistrationData
+ .getInstitutionOrCompanyName());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 8;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(institutionOrCompanyTextField, gbc);
+
+ // Industry type
+ JLabel industryLabel = new JLabel(" Industry type:");
+ industryLabel.setFont(baseFont);
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 9;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(industryLabel, gbc);
+
+ industryTypeTextField = new JComboBox<>(industryTypes);
+ industryTypeTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ industryTypeTextField.setSelectedItem(previousRegistrationData
+ .getIndustry());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 9;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(industryTypeTextField, gbc);
+
+ // Field of investigation
+ JTextArea fieldLabel = new JTextArea(FIELD_OF_INVESTIGATION);
+ fieldLabel.setFont(baseFont);
+ fieldLabel.setEditable(false);
+ fieldLabel.setFocusable(false);
+ fieldLabel.setBackground(getBackground());
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 10;
+ gbc.fill = NONE;
+ gbc.anchor = LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(fieldLabel, gbc);
+
+ fieldTextField = new JTextField();
+ fieldTextField.setFont(baseFont);
+ if (previousRegistrationData != null)
+ fieldTextField.setText(previousRegistrationData.getField());
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 10;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = FIRST_LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(fieldTextField, gbc);
+
+ // Purpose of using Taverna
+ JTextArea purposeLabel = new JTextArea(WHY_YOU_INTEND_TO_USE_TAVERNA);
+ purposeLabel.setFont(baseFont);
+ purposeLabel.setEditable(false);
+ purposeLabel.setFocusable(false);
+ purposeLabel.setBackground(getBackground());
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 11;
+ gbc.fill = NONE;
+ gbc.anchor = LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(purposeLabel, gbc);
+
+ purposeTextArea = new JTextArea(4, 30);
+ purposeTextArea.setFont(baseFont);
+ purposeTextArea.setLineWrap(true);
+ purposeTextArea.setAutoscrolls(true);
+ if (previousRegistrationData != null)
+ purposeTextArea.setText(previousRegistrationData
+ .getPurposeOfUsingTaverna());
+ purposeTextArea.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_TAB) {
+ if (evt.getModifiers() > 0)
+ purposeTextArea.transferFocusBackward();
+ else
+ purposeTextArea.transferFocus();
+ evt.consume();
+ }
+ }
+ });
+ JScrollPane purposeScrollPane = new JScrollPane(purposeTextArea);
+ gbc.weightx = 1.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 1;
+ gbc.gridy = 11;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = FIRST_LINE_START;
+ gbc.gridwidth = 1;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ mainPanel.add(purposeScrollPane, gbc);
+
+ // Terms and conditions
+ termsAndConditionsCheckBox = new JCheckBox(
+ I_AGREE_TO_THE_TERMS_AND_CONDITIONS);
+ termsAndConditionsCheckBox.setFont(baseFont);
+ termsAndConditionsCheckBox.setBorder(null);
+ termsAndConditionsCheckBox.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ termsAndConditionsCheckBox
+ .setSelected(!termsAndConditionsCheckBox
+ .isSelected());
+ }
+ }
+ });
+ // gbc.weightx = 0.0;
+ // gbc.weighty = 0.0;
+ // gbc.gridx = 0;
+ // gbc.gridy = 12;
+ // gbc.fill = NONE;
+ // gbc.anchor = WEST;
+ // gbc.gridwidth = 2;
+ // gbc.insets = new Insets(10, 10, 0, 0);
+ // mainPanel.add(termsAndConditionsCheckBox, gbc);
+
+ // Terms and conditions link
+ JEditorPane termsAndConditionsURL = new JEditorPane();
+ termsAndConditionsURL.setEditable(false);
+ termsAndConditionsURL.setBackground(getBackground());
+ termsAndConditionsURL.setFocusable(false);
+ HTMLEditorKit kit = new HTMLEditorKit();
+ termsAndConditionsURL.setEditorKit(kit);
+ StyleSheet styleSheet = kit.getStyleSheet();
+ // styleSheet.addRule("body {font-family:"+baseFont.getFamily()+"; font-size:"+baseFont.getSize()+";}");
+ // // base font looks bigger when rendered as HTML
+ styleSheet.addRule("body {font-family:" + baseFont.getFamily()
+ + "; font-size:9px;}");
+ Document doc = kit.createDefaultDocument();
+ termsAndConditionsURL.setDocument(doc);
+ termsAndConditionsURL.setText("<html><body><a href=\""
+ + TERMS_AND_CONDITIONS_URL + "\">" + TERMS_AND_CONDITIONS_URL
+ + "</a></body></html>");
+ termsAndConditionsURL.addHyperlinkListener(new HyperlinkListener() {
+ @Override
+ public void hyperlinkUpdate(HyperlinkEvent he) {
+ if (he.getEventType() == ACTIVATED)
+ followHyperlinkToTandCs();
+ }
+ });
+ gbc.weightx = 0.0;
+ gbc.weighty = 0.0;
+ gbc.gridx = 0;
+ gbc.gridy = 13;
+ gbc.fill = NONE;
+ gbc.anchor = WEST;
+ gbc.gridwidth = 2;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ JPanel termsAndConditionsPanel = new JPanel(new FlowLayout(LEFT));
+ termsAndConditionsPanel.add(termsAndConditionsCheckBox);
+ termsAndConditionsPanel.add(termsAndConditionsURL);
+ mainPanel.add(termsAndConditionsPanel, gbc);
+
+ // Button panel
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ JButton registerButton = new JButton("Register");
+ registerButton.setFont(baseFont);
+ registerButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ register();
+ }
+ }
+ });
+ registerButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ register();
+ }
+ });
+ JButton doNotRegisterButton = new JButton("Do not ask me again");
+ doNotRegisterButton.setFont(baseFont);
+ doNotRegisterButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ doNotRegister();
+ }
+ }
+ });
+ doNotRegisterButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ doNotRegister();
+ }
+ });
+ JButton remindMeLaterButton = new JButton("Remind me later"); // in 2 weeks
+ remindMeLaterButton.setFont(baseFont);
+ remindMeLaterButton.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ if (evt.getKeyCode() == VK_ENTER) {
+ evt.consume();
+ remindMeLater();
+ }
+ }
+ });
+ remindMeLaterButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ remindMeLater();
+ }
+ });
+ buttonPanel.add(registerButton);
+ buttonPanel.add(remindMeLaterButton);
+ buttonPanel.add(doNotRegisterButton);
+ addDivider(buttonPanel, TOP, true);
+ gbc.gridx = 0;
+ gbc.gridy = 14;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = GridBagConstraints.CENTER;
+ gbc.insets = new Insets(5, 10, 0, 10);
+ gbc.gridwidth = 2;
+ mainPanel.add(buttonPanel, gbc);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(mainPanel, CENTER);
+
+ pack();
+ setResizable(false);
+ // Center the dialog on the screen (we do not have the parent)
+ Dimension dimension = getToolkit().getScreenSize();
+ Rectangle abounds = getBounds();
+ setLocation((dimension.width - abounds.width) / 2,
+ (dimension.height - abounds.height) / 2);
+ setSize(getPreferredSize());
+ }
+
+ protected void remindMeLater() {
+ try {
+ FileUtils.touch(remindMeLaterFile);
+ } catch (IOException ioex) {
+ logger.error(
+ "Failed to touch the 'Remind me later' file at user registration.",
+ ioex);
+ }
+ closeDialog();
+ }
+
+ protected void doNotRegister() {
+ try {
+ FileUtils.touch(doNotRegisterMeFile);
+ if (remindMeLaterFile.exists())
+ remindMeLaterFile.delete();
+ } catch (IOException ioex) {
+ logger.error(
+ "Failed to touch the 'Do not register me' file at user registration.",
+ ioex);
+ }
+ closeDialog();
+ }
+
+ private void register() {
+ if (!validateForm())
+ return;
+ UserRegistrationData regData = new UserRegistrationData();
+ regData.setTavernaVersion(appName);
+ regData.setFirstName(firstNameTextField.getText());
+ regData.setLastName(lastNameTextField.getText());
+ regData.setEmailAddress(emailTextField.getText());
+ regData.setKeepMeInformed(keepMeInformedCheckBox.isSelected());
+ regData.setInstitutionOrCompanyName(institutionOrCompanyTextField
+ .getText());
+ regData.setIndustry(industryTypeTextField.getSelectedItem().toString());
+ regData.setField(fieldTextField.getText());
+ regData.setPurposeOfUsingTaverna(purposeTextArea.getText());
+
+ if (postUserRegistrationDataToServer(regData)) {
+ saveUserRegistrationData(regData, registrationDataFile);
+ if (remindMeLaterFile.exists())
+ remindMeLaterFile.delete();
+ closeDialog();
+ }
+ }
+
+ private boolean validateForm() {
+ String errorMessage = "";
+ if (firstNameTextField.getText().isEmpty())
+ errorMessage += "Please provide your first name.<br>";
+ if (lastNameTextField.getText().isEmpty())
+ errorMessage += "Please provide your last name.<br>";
+ if (emailTextField.getText().isEmpty())
+ errorMessage += "Please provide your email address.<br>";
+ if (institutionOrCompanyTextField.getText().isEmpty())
+ errorMessage += "Please provide your institution or company name.";
+ if (!errorMessage.isEmpty()) {
+ showMessageDialog(this, new JLabel("<html><body>"
+ + errorMessage + "</body></html>"), "Error in form",
+ ERROR_MESSAGE);
+ return false;
+ }
+ if (!termsAndConditionsCheckBox.isSelected()) {
+ showMessageDialog(this, new JLabel(
+ "You must agree to the terms and conditions."),
+ "Error in form", ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public UserRegistrationData loadUserRegistrationData(File propertiesFile) {
+ UserRegistrationData regData = new UserRegistrationData();
+ Properties props = new Properties();
+
+ // Try to retrieve data from file
+ try {
+ props.load(new FileInputStream(propertiesFile));
+ } catch (IOException e) {
+ logger.error("Failed to load old user registration data from "
+ + propertiesFile.getAbsolutePath(), e);
+ return null;
+ }
+ regData.setTavernaVersion(props
+ .getProperty(TAVERNA_VERSION_PROPERTY_NAME));
+ regData.setFirstName(props.getProperty(FIRST_NAME_PROPERTY_NAME));
+ regData.setLastName(props.getProperty(LAST_NAME_PROPERTY_NAME));
+ regData.setEmailAddress(props.getProperty(EMAIL_ADDRESS_PROPERTY_NAME));
+ regData.setKeepMeInformed(props.getProperty(
+ KEEP_ME_INFORMED_PROPERTY_NAME).equals(TRUE));
+ regData.setInstitutionOrCompanyName(props
+ .getProperty(INSTITUTION_OR_COMPANY_PROPERTY_NAME));
+ regData.setIndustry(props.getProperty(INDUSTRY_PROPERTY_NAME));
+ regData.setField(props.getProperty(FIELD_PROPERTY_NAME));
+ regData.setPurposeOfUsingTaverna(props
+ .getProperty(PURPOSE_PROPERTY_NAME));
+ return regData;
+ }
+
+ private void enc(StringBuilder buffer, String name, Object value)
+ throws UnsupportedEncodingException {
+ if (buffer.length() != 0)
+ buffer.append('&');
+ buffer.append(URLEncoder.encode(name, "UTF-8"));
+ buffer.append('=');
+ buffer.append(URLEncoder.encode(value.toString(), "UTF-8"));
+ }
+
+ /**
+ * Post registration data to our server.
+ */
+ private boolean postUserRegistrationDataToServer(
+ UserRegistrationData regData) {
+ StringBuilder parameters = new StringBuilder();
+
+ /*
+ * The 'submit' parameter - to let the server-side script know we are
+ * submitting the user's registration form - all other requests will be
+ * silently ignored
+ */
+ try {
+ // value does not matter
+ enc(parameters, TAVERNA_REGISTRATION_POST_PARAMETER_NAME, "submit");
+
+ enc(parameters, TAVERNA_VERSION_POST_PARAMETER_NAME,
+ regData.getTavernaVersion());
+ enc(parameters, FIRST_NAME_POST_PARAMETER_NAME,
+ regData.getFirstName());
+ enc(parameters, LAST_NAME_POST_PARAMETER_NAME,
+ regData.getLastName());
+ enc(parameters, EMAIL_ADDRESS_POST_PARAMETER_NAME,
+ regData.getEmailAddress());
+ enc(parameters, KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME,
+ regData.getKeepMeInformed());
+ enc(parameters, INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME,
+ regData.getInstitutionOrCompanyName());
+ enc(parameters, INDUSTRY_TYPE_POST_PARAMETER_NAME,
+ regData.getIndustry());
+ enc(parameters, FIELD_POST_PARAMETER_NAME, regData.getField());
+ enc(parameters, PURPOSE_POST_PARAMETER_NAME,
+ regData.getPurposeOfUsingTaverna());
+ } catch (UnsupportedEncodingException ueex) {
+ logger.error(FAILED + "Could not url encode post parameters", ueex);
+ showMessageDialog(null, REGISTRATION_FAILED_MSG,
+ "Error encoding registration data", ERROR_MESSAGE);
+ return false;
+ }
+ String server = REGISTRATION_URL;
+ logger.info("Posting user registartion to " + server
+ + " with parameters: " + parameters);
+ String response = "";
+ String failure;
+ try {
+ URL url = new URL(server);
+ URLConnection conn = url.openConnection();
+ /*
+ * Set timeout to e.g. 7 seconds, otherwise we might hang too long
+ * if server is not responding and it will block Taverna
+ */
+ conn.setConnectTimeout(7000);
+ // Set connection parameters
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ // Make server believe we are HTML form data...
+ conn.setRequestProperty("Content-Type",
+ "application/x-www-form-urlencoded");
+ // Write out the bytes of the content string to the stream.
+ try (DataOutputStream out = new DataOutputStream(
+ conn.getOutputStream())) {
+ out.writeBytes(parameters.toString());
+ out.flush();
+ }
+ // Read response from the input stream.
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(
+ conn.getInputStream()))) {
+ String temp;
+ while ((temp = in.readLine()) != null)
+ response += temp + "\n";
+ // Remove the last \n character
+ if (!response.isEmpty())
+ response = response.substring(0, response.length() - 1);
+ }
+ if (response.equals("Registration successful!"))
+ return true;
+ logger.error(FAILED + "Response form server was: " + response);
+ failure = "Error saving registration data on the server";
+ } catch (ConnectException ceex) {
+ /*
+ * the connection was refused remotely (e.g. no process is listening
+ * on the remote address/port).
+ */
+ logger.error(
+ FAILED
+ + "Registration server is not listening of the specified url.",
+ ceex);
+ failure = "Registration server is not listening at the specified url";
+ } catch (SocketTimeoutException stex) {
+ // timeout has occurred on a socket read or accept.
+ logger.error(FAILED + "Socket timeout occurred.", stex);
+ failure = "Registration server timeout";
+ } catch (MalformedURLException muex) {
+ logger.error(FAILED + "Registartion server's url is malformed.",
+ muex);
+ failure = "Error with registration server's url";
+ } catch (IOException ioex) {
+ logger.error(
+ FAILED
+ + "Failed to open url connection to registration server or writing/reading to/from it.",
+ ioex);
+ failure = "Error opening connection to the registration server";
+ }
+ showMessageDialog(null, REGISTRATION_FAILED_MSG, failure, ERROR_MESSAGE);
+ return false;
+ }
+
+ private void saveUserRegistrationData(UserRegistrationData regData,
+ File propertiesFile) {
+ Properties props = new Properties();
+ props.setProperty(TAVERNA_VERSION_PROPERTY_NAME,
+ regData.getTavernaVersion());
+ props.setProperty(FIRST_NAME_PROPERTY_NAME, regData.getFirstName());
+ props.setProperty(LAST_NAME_PROPERTY_NAME, regData.getLastName());
+ props.setProperty(EMAIL_ADDRESS_PROPERTY_NAME,
+ regData.getEmailAddress());
+ props.setProperty(KEEP_ME_INFORMED_PROPERTY_NAME,
+ regData.getKeepMeInformed() ? TRUE : FALSE);
+ props.setProperty(INSTITUTION_OR_COMPANY_PROPERTY_NAME,
+ regData.getInstitutionOrCompanyName());
+ props.setProperty(INDUSTRY_PROPERTY_NAME, regData.getIndustry());
+ props.setProperty(FIELD_PROPERTY_NAME,
+ regData.getPurposeOfUsingTaverna());
+ props.setProperty(PURPOSE_PROPERTY_NAME,
+ regData.getPurposeOfUsingTaverna());
+
+ // Write the properties file.
+ try {
+ props.store(new FileOutputStream(propertiesFile), null);
+ } catch (Exception e) {
+ logger.error("Failed to save user registration data locally on disk.");
+ }
+ }
+
+ private void closeDialog() {
+ setVisible(false);
+ dispose();
+ }
+
+ /**
+ * Adds a light gray or etched border to the top or bottom of a JComponent.
+ *
+ * @author David Withers
+ * @param component
+ */
+ protected void addDivider(JComponent component, final int position,
+ final boolean etched) {
+ component.setBorder(new Border() {
+ private final Color borderColor = new Color(.6f, .6f, .6f);
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ if (position == TOP)
+ return new Insets(5, 0, 0, 0);
+ else
+ return new Insets(0, 0, 5, 0);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ if (position == TOP) {
+ if (etched) {
+ g.setColor(borderColor);
+ g.drawLine(x, y, x + width, y);
+ g.setColor(WHITE);
+ g.drawLine(x, y + 1, x + width, y + 1);
+ } else {
+ g.setColor(LIGHT_GRAY);
+ g.drawLine(x, y, x + width, y);
+ }
+ } else {
+ if (etched) {
+ g.setColor(borderColor);
+ g.drawLine(x, y + height - 2, x + width, y + height - 2);
+ g.setColor(WHITE);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ } else {
+ g.setColor(LIGHT_GRAY);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+ }
+ }
+ });
+ }
+
+ private void followHyperlinkToTandCs() {
+ // Open a Web browser
+ try {
+ Desktop.getDesktop().browse(new URI(TERMS_AND_CONDITIONS_URL));
+ } catch (Exception ex) {
+ logger.error("User registration: Failed to launch browser to show terms and conditions at "
+ + TERMS_AND_CONDITIONS_URL);
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java
new file mode 100644
index 0000000..257c3f3
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationHook.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (C) 2009-2010 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.workbench.ui.impl;
+
+import static java.awt.GraphicsEnvironment.isHeadless;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.StartupSPI;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+public class UserRegistrationHook implements StartupSPI {
+ /** Delay between when we ask the user about registration, in milliseconds */
+ private static final int TWO_WEEKS = 14 * 24 * 3600 * 1000;
+ public static final String REGISTRATION_DIRECTORY_NAME = "registration";
+ public static final String REGISTRATION_DATA_FILE_NAME = "registration_data.properties";
+ public static final String REMIND_ME_LATER_FILE_NAME = "remind_me_later";
+ public static final String DO_NOT_REGISTER_ME_FILE_NAME = "do_not_register_me";
+
+ private ApplicationConfiguration applicationConfiguration;
+
+ @Override
+ public int positionHint() {
+ return 50;
+ }
+
+ @Override
+ public boolean startup() {
+ File registrationDirectory = getRegistrationDirectory();
+ File registrationDataFile = new File(registrationDirectory,
+ REGISTRATION_DATA_FILE_NAME);
+ File doNotRegisterMeFile = new File(registrationDirectory,
+ DO_NOT_REGISTER_ME_FILE_NAME);
+ File remindMeLaterFile = new File(registrationDirectory,
+ REMIND_ME_LATER_FILE_NAME);
+
+ // if we are running headlessly just return
+ if (isHeadless())
+ return true;
+ // For Taverna snapshots - do not ask user to register
+ if (applicationConfiguration.getName().toLowerCase().contains("snapshot"))
+ return true;
+
+ // If there is already user's registration data present - exit.
+ if (registrationDataFile.exists())
+ return true;
+
+ // If user did not want to register - exit.
+ if (doNotRegisterMeFile.exists())
+ return true;
+
+ /*
+ * If user said to remind them - check if more than 2 weeks passed since
+ * we asked previously.
+ */
+ if (remindMeLaterFile.exists()) {
+ long lastModified = remindMeLaterFile.lastModified();
+ long now = new Date().getTime();
+ if (now - lastModified < TWO_WEEKS)
+ // 2 weeks have not passed since we last asked
+ return true;
+
+ // Ask user again if they want to register
+ UserRegistrationForm form = new UserRegistrationForm(
+ applicationConfiguration.getName(), registrationDataFile,
+ doNotRegisterMeFile, remindMeLaterFile);
+ form.setVisible(true);
+ return true;
+ }
+
+ /*
+ * Check if there are previous Taverna versions installed and find the
+ * latest one that contains user registration data, if any. Ask user if
+ * they want to upload that previous data.
+ */
+ final File appHomeDirectory = applicationConfiguration.getApplicationHomeDir();
+ File parentDirectory = appHomeDirectory.getParentFile();
+ FileFilter fileFilter = new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !(file.getName().equals(appHomeDirectory.getName())
+ // Exclude Taverna home directory for this app
+ && file.isDirectory()
+ && file.getName().toLowerCase().startsWith("taverna-")
+ // exclude snapshots
+ && !file.getName().toLowerCase().contains("snapshot")
+ // exclude command line tool
+ && !file.getName().toLowerCase().contains("cmd")
+ // exclude dataviewer
+ && !file.getName().toLowerCase().contains("dataviewer"));
+ }
+ };
+ File[] tavernaDirectories = parentDirectory.listFiles(fileFilter);
+ // Find the latest previous registration data file, if any
+ File previousRegistrationDataFile = null;
+ for (File tavernaDirectory : tavernaDirectories) {
+ File regFile = new File(tavernaDirectory, REGISTRATION_DIRECTORY_NAME
+ + System.getProperty("file.separator") + REGISTRATION_DATA_FILE_NAME);
+ if (!regFile.exists())
+ continue;
+ if (previousRegistrationDataFile == null)
+ previousRegistrationDataFile = regFile;
+ else if (previousRegistrationDataFile.lastModified() < regFile
+ .lastModified())
+ previousRegistrationDataFile = regFile;
+ }
+
+ UserRegistrationForm form;
+ if (previousRegistrationDataFile == null)
+ // No previous registration file - ask user to register
+ form = new UserRegistrationForm(applicationConfiguration.getName(),
+ registrationDataFile, doNotRegisterMeFile,
+ remindMeLaterFile);
+ else
+ /*
+ * Fill in user's old registration data in the form and ask them to
+ * register
+ */
+ form = new UserRegistrationForm(applicationConfiguration.getName(),
+ previousRegistrationDataFile, registrationDataFile,
+ doNotRegisterMeFile, remindMeLaterFile);
+ form.setVisible(true);
+ return true;
+ }
+
+ /**
+ * Gets the registration directory where info about registration will be
+ * saved to.
+ */
+ public File getRegistrationDirectory() {
+ File home = applicationConfiguration.getApplicationHomeDir();
+
+ File registrationDirectory = new File(home, REGISTRATION_DIRECTORY_NAME);
+ if (!registrationDirectory.exists())
+ registrationDirectory.mkdir();
+ return registrationDirectory;
+ }
+
+ public void setApplicationConfiguration(
+ ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java
new file mode 100644
index 0000000..16f189e
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchImpl.java
@@ -0,0 +1,538 @@
+/*******************************************************************************
+ * Copyright (C) 2007-2010 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.workbench.ui.impl;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.LINE_START;
+import static java.lang.Thread.setDefaultUncaughtExceptionHandler;
+import static java.util.prefs.Preferences.userNodeForPackage;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.SwingUtilities.invokeLater;
+import static javax.swing.UIManager.getCrossPlatformLookAndFeelClassName;
+import static javax.swing.UIManager.getLookAndFeel;
+import static javax.swing.UIManager.getLookAndFeelDefaults;
+import static javax.swing.UIManager.getSystemLookAndFeelClassName;
+import static net.sf.taverna.t2.workbench.MainWindow.setMainWindow;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.errorMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.infoMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.questionMessageIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.warningMessageIcon;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.CardLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+import javax.swing.UIManager;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.ui.menu.MenuManager.MenuManagerEvent;
+import net.sf.taverna.t2.ui.menu.MenuManager.UpdatedMenuManagerEvent;
+import net.sf.taverna.t2.workbench.ShutdownSPI;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.helper.Helper;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+import org.simplericity.macify.eawt.Application;
+import org.simplericity.macify.eawt.ApplicationAdapter;
+import org.simplericity.macify.eawt.ApplicationEvent;
+import org.simplericity.macify.eawt.ApplicationListener;
+import org.simplericity.macify.eawt.DefaultApplication;
+
+import uk.org.taverna.commons.plugin.PluginException;
+import uk.org.taverna.commons.plugin.PluginManager;
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * The main workbench frame.
+ *
+ * @author David Withers
+ * @author Stian Soiland-Reyes
+ */
+public class WorkbenchImpl extends JFrame implements Workbench {
+ private static final String NIMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+ private static final String LAUNCHER_LOGO_PNG = "/launcher_logo.png";
+ private static final long serialVersionUID = 1L;
+ private static Logger logger = getLogger(WorkbenchImpl.class);
+ private static Preferences userPreferences = userNodeForPackage(WorkbenchImpl.class);
+
+ private Application osxApp = new DefaultApplication();
+ private ApplicationListener osxAppListener = new OSXAppListener();
+ private MenuManager menuManager;
+ private FileManager fileManager;
+ @SuppressWarnings("unused")
+ private EditManager editManager;
+ private PluginManager pluginManager;
+ private SelectionManager selectionManager;
+ private WorkbenchConfiguration workbenchConfiguration;
+ private ApplicationConfiguration applicationConfiguration;
+ private WorkbenchPerspectives workbenchPerspectives;
+ private T2ConfigurationFrame t2ConfigurationFrame;
+ private JToolBar perspectiveToolBar;
+ private List<StartupSPI> startupHooks;
+ private List<ShutdownSPI> shutdownHooks;
+ private final List<PerspectiveSPI> perspectives;
+ private JMenuBar menuBar;
+ private JToolBar toolBar;
+ private MenuManagerObserver menuManagerObserver;
+
+ public WorkbenchImpl(List<StartupSPI> startupHooks,
+ List<ShutdownSPI> shutdownHooks, List<PerspectiveSPI> perspectives) {
+ this.perspectives = perspectives;
+ this.startupHooks = startupHooks;
+ this.shutdownHooks = shutdownHooks;
+ setMainWindow(this);
+ }
+
+ protected void initialize() {
+ setExceptionHandler();
+ setLookAndFeel();
+
+ // Set icons for Error, Information, Question and Warning messages
+ UIManager.put("OptionPane.errorIcon", errorMessageIcon);
+ UIManager.put("OptionPane.informationIcon", infoMessageIcon);
+ UIManager.put("OptionPane.questionIcon", questionMessageIcon);
+ UIManager.put("OptionPane.warningIcon", warningMessageIcon);
+
+ // Call the startup hooks
+ if (!callStartupHooks()) {
+ System.exit(0);
+ }
+
+ makeGUI();
+ fileManager.newDataflow();
+ try {
+ pluginManager.loadPlugins();
+ } catch (PluginException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ /*
+ * the DataflowEditsListener changes the WorkflowBundle ID for every
+ * workflow edit and changes the URI so port definitions can't find the
+ * port they refer to
+ */
+ // TODO check if it's OK to not update the WorkflowBundle ID
+ //editManager.addObserver(new DataflowEditsListener());
+
+ closeTheSplashScreen();
+ setVisible(true);
+ }
+
+ private void closeTheSplashScreen() {
+// SplashScreen splash = SplashScreen.getSplashScreen();
+// if (splash != null) {
+// splash.setClosable();
+// splash.requestClose();
+// }
+ }
+
+ private void showAboutDialog() {
+ // TODO implement this!
+ }
+
+ private void makeGUI() {
+ setLayout(new GridBagLayout());
+
+ addWindowListener(new WindowClosingListener());
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ Helper.setKeyCatcher(this);
+
+ URL launcherLogo = getClass().getResource(LAUNCHER_LOGO_PNG);
+ if (launcherLogo != null) {
+ ImageIcon imageIcon = new ImageIcon(launcherLogo);
+ setIconImage(imageIcon.getImage());
+ }
+ setTitle(applicationConfiguration.getTitle());
+
+ osxApp.setEnabledPreferencesMenu(true);
+ osxApp.setEnabledAboutMenu(true);
+ osxApp.addApplicationListener(osxAppListener);
+
+ /*
+ * Set the size and position of the Workbench to the last saved values
+ * or use the default ones the first time it is launched
+ */
+ loadSizeAndLocationPrefs();
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.weightx = 0.1;
+ gbc.fill = HORIZONTAL;
+ gbc.anchor = LINE_START;
+
+ add(makeToolbarPanel(), gbc);
+
+ gbc.anchor = CENTER;
+ gbc.fill = BOTH;
+ gbc.gridy = 1;
+ gbc.weightx = 0.1;
+ gbc.weighty = 0.1;
+
+ add(makePerspectivePanel(), gbc);
+
+ menuBar = menuManager.createMenuBar();
+ setJMenuBar(menuBar);
+ }
+
+ protected JPanel makeToolbarPanel() {
+ JPanel toolbarPanel = new JPanel(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridwidth = 2;
+ gbc.anchor = LINE_START;
+
+ toolBar = menuManager.createToolBar();
+ toolBar.setFloatable(false);
+ toolbarPanel.add(toolBar, gbc);
+
+ perspectiveToolBar = new JToolBar("Perspectives");
+ perspectiveToolBar.setFloatable(false);
+ gbc.gridy = 1;
+ gbc.weightx = 0.1;
+ gbc.fill = HORIZONTAL;
+ toolbarPanel.add(perspectiveToolBar, gbc);
+
+ return toolbarPanel;
+ }
+
+ private JPanel makePerspectivePanel() {
+ CardLayout perspectiveLayout = new CardLayout();
+ JPanel perspectivePanel = new JPanel(perspectiveLayout);
+ workbenchPerspectives = new WorkbenchPerspectives(perspectiveToolBar,
+ perspectivePanel, perspectiveLayout, selectionManager);
+ workbenchPerspectives.setPerspectives(perspectives);
+ return perspectivePanel;
+ }
+
+ @Override
+ public void makeNamedComponentVisible(String componentName) {
+ // basePane.makeNamedComponentVisible(componentName);
+ }
+
+ protected void setExceptionHandler() {
+ setDefaultUncaughtExceptionHandler(new ExceptionHandler());
+ }
+
+ public void setStartupHooks(List<StartupSPI> startupHooks) {
+ this.startupHooks = startupHooks;
+ }
+
+ /**
+ * Calls the startup methods on all the {@link StartupSPI}s. If any startup
+ * method returns <code>false</code> (meaning that the Workbench will not
+ * function at all) then this method returns <code>false</code>.
+ */
+ private boolean callStartupHooks() {
+ for (StartupSPI startupSPI : startupHooks)
+ if (!startupSPI.startup())
+ return false;
+ return true;
+ }
+
+ @Override
+ public void exit() {
+ if (callShutdownHooks())
+ System.exit(0);
+ }
+
+ public void setShutdownHooks(List<ShutdownSPI> shutdownHooks) {
+ this.shutdownHooks = shutdownHooks;
+ }
+
+ /**
+ * Calls all the shutdown on all the {@link ShutdownSPI}s. If a shutdown
+ * returns <code>false</code> (meaning that the shutdown process should be
+ * aborted) then this method returns with a value of <code>false</code>
+ * immediately.
+ *
+ * @return <code>true</code> if all the <code>ShutdownSPIs</code> return
+ * <code>true</code> and the workbench shutdown should proceed
+ */
+ private boolean callShutdownHooks() {
+ for (ShutdownSPI shutdownSPI : shutdownHooks)
+ if (!shutdownSPI.shutdown())
+ return false;
+ return true;
+ }
+
+ /**
+ * Store current Workbench position and size.
+ */
+ @Override
+ public void storeSizeAndLocationPrefs() throws IOException {
+ userPreferences.putInt("width", getWidth());
+ userPreferences.putInt("height", getHeight());
+ userPreferences.putInt("x", getX());
+ userPreferences.putInt("y", getY());
+ }
+
+ /**
+ * Loads last saved Workbench position and size.
+ */
+ private void loadSizeAndLocationPrefs() {
+ Dimension screen = getToolkit().getScreenSize();
+
+ int width = userPreferences.getInt("width", (int) (screen.getWidth() * 0.75));
+ int height = userPreferences.getInt("height", (int) (screen.getHeight() * 0.75));
+ int x = userPreferences.getInt("x", 0);
+ int y = userPreferences.getInt("y", 0);
+
+ // Make sure our window is not too big
+ width = Math.min((int) screen.getWidth(), width);
+ height = Math.min((int) screen.getHeight(), height);
+
+ // Move to upper left corner if we are too far off
+ if (x > (screen.getWidth() - 50) || x < 0)
+ x = 0;
+ if (y > (screen.getHeight() - 50) || y < 0)
+ y = 0;
+
+ this.setSize(width, height);
+ this.setLocation(x, y);
+ }
+
+ public static void setLookAndFeel() {
+ String defaultLaf = System.getProperty("swing.defaultlaf");
+ try {
+ if (defaultLaf != null) {
+ UIManager.setLookAndFeel(defaultLaf);
+ return;
+ }
+ } catch (Exception e) {
+ logger.info("Can't set requested look and feel -Dswing.defaultlaf="
+ + defaultLaf, e);
+ }
+ String os = System.getProperty("os.name");
+ if (os.contains("Mac") || os.contains("Windows")) {
+ // For OSX and Windows use the system look and feel
+ String systemLF = getSystemLookAndFeelClassName();
+ try {
+ UIManager.setLookAndFeel(systemLF);
+ getLookAndFeelDefaults().put("ClassLoader",
+ WorkbenchImpl.class.getClassLoader());
+ logger.info("Using system L&F " + systemLF);
+ return;
+ } catch (Exception ex2) {
+ logger.error("Unable to load system look and feel " + systemLF,
+ ex2);
+ }
+ }
+ /*
+ * The system look and feel on *NIX
+ * (com.sun.java.swing.plaf.gtk.GTKLookAndFeel) looks like Windows 3.1..
+ * try to use Nimbus (Java 6e10 and later)
+ */
+ try {
+ UIManager.setLookAndFeel(NIMBUS);
+ logger.info("Using Nimbus look and feel");
+ return;
+ } catch (Exception e) {
+ }
+
+ // Metal should be better than GTK still
+ try {
+ String crossPlatform = getCrossPlatformLookAndFeelClassName();
+ UIManager.setLookAndFeel(crossPlatform);
+ logger.info("Using cross platform Look and Feel " + crossPlatform);
+ return;
+ } catch (Exception e){
+ }
+
+ // Final fallback
+ try {
+ String systemLF = getSystemLookAndFeelClassName();
+ UIManager.setLookAndFeel(systemLF);
+ logger.info("Using system platform Look and Feel " + systemLF);
+ } catch (Exception e){
+ logger.info("Using default Look and Feel " + getLookAndFeel());
+ }
+ }
+
+ public void setMenuManager(MenuManager menuManager) {
+ if (this.menuManager != null && menuManagerObserver != null)
+ this.menuManager.removeObserver(menuManagerObserver);
+ this.menuManager = menuManager;
+ menuManagerObserver = new MenuManagerObserver();
+ menuManager.addObserver(menuManagerObserver);
+ }
+
+ public void setFileManager(FileManager fileManager) {
+ this.fileManager = fileManager;
+ }
+
+ public void setEditManager(EditManager editManager) {
+ this.editManager = editManager;
+ }
+
+ public void refreshPerspectives(Object service, Map<?,?> properties) {
+ workbenchPerspectives.refreshPerspectives();
+ }
+
+ public void setWorkbenchConfiguration(WorkbenchConfiguration workbenchConfiguration) {
+ this.workbenchConfiguration = workbenchConfiguration;
+ }
+
+ public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+ this.applicationConfiguration = applicationConfiguration;
+ }
+
+ public void setT2ConfigurationFrame(T2ConfigurationFrame t2ConfigurationFrame) {
+ this.t2ConfigurationFrame = t2ConfigurationFrame;
+ }
+
+ public void setSelectionManager(SelectionManager selectionManager) {
+ this.selectionManager = selectionManager;
+ }
+
+ public void setPluginManager(PluginManager pluginManager) {
+ this.pluginManager = pluginManager;
+ }
+
+ private final class MenuManagerObserver extends
+ SwingAwareObserver<MenuManagerEvent> {
+ @Override
+ public void notifySwing(Observable<MenuManagerEvent> sender,
+ MenuManagerEvent message) {
+ if (message instanceof UpdatedMenuManagerEvent
+ && WorkbenchImpl.this.isVisible())
+ refreshMenus();
+ }
+ }
+
+ private void refreshMenus() {
+ if (menuBar != null) {
+ menuBar.revalidate();
+ menuBar.repaint();
+ }
+ if (toolBar != null) {
+ toolBar.revalidate();
+ toolBar.repaint();
+ }
+ }
+
+ private final class ExceptionHandler implements UncaughtExceptionHandler {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ logger.error("Uncaught exception in " + t, e);
+ if (e instanceof Exception
+ && !workbenchConfiguration.getWarnInternalErrors()) {
+ /*
+ * User preference disables warnings - but we'll show it anyway
+ * if it is an Error (which is more serious)
+ */
+ return;
+ }
+ final String message;
+ final String title;
+ final int style;
+ if (t.getClass().getName().equals("java.awt.EventDispatchThread")) {
+ message = "The user action could not be completed due to an unexpected error:\n"
+ + e;
+ title = "Could not complete user action";
+ style = ERROR_MESSAGE;
+ } else {
+ message = "An unexpected internal error occured in \n" + t + ":\n" + e;
+ title = "Unexpected internal error";
+ style = WARNING_MESSAGE;
+ }
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ showMessageDialog(WorkbenchImpl.this, message, title, style);
+ }
+ });
+ }
+ }
+
+ private class WindowClosingListener extends WindowAdapter {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ exit();
+ }
+ }
+
+ private class OSXAppListener extends ApplicationAdapter {
+ @Override
+ public void handleAbout(ApplicationEvent e) {
+ showAboutDialog();
+ e.setHandled(true);
+ }
+
+ @Override
+ public void handleQuit(ApplicationEvent e) {
+ e.setHandled(true);
+ exit();
+ }
+
+ @Override
+ public void handlePreferences(ApplicationEvent e) {
+ e.setHandled(true);
+ t2ConfigurationFrame.showFrame();
+ }
+
+ @Override
+ public void handleOpenFile(ApplicationEvent e) {
+ try {
+ if (e.getFilename() != null) {
+ fileManager.openDataflow(null, new File(e.getFilename()));
+ e.setHandled(true);
+ }
+ } catch (OpenException | IllegalStateException ex) {
+ logger.warn("Could not open file " + e.getFilename(), ex);
+ }
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java
new file mode 100644
index 0000000..921260a
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/WorkbenchPerspectives.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import static java.awt.Image.SCALE_SMOOTH;
+import static javax.swing.Action.NAME;
+import static javax.swing.Action.SMALL_ICON;
+import static javax.swing.SwingUtilities.invokeLater;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings("serial")
+public class WorkbenchPerspectives {
+ private static Logger logger = Logger
+ .getLogger(WorkbenchPerspectives.class);
+
+ private PerspectiveSPI currentPerspective;
+ private ButtonGroup perspectiveButtonGroup = new ButtonGroup();
+ private Map<String, JToggleButton> perspectiveButtonMap = new HashMap<>();
+ private JToolBar toolBar;
+ private JPanel panel;
+ private CardLayout cardLayout;
+ private List<PerspectiveSPI> perspectives = new ArrayList<>();
+ private boolean refreshing;
+ private final SelectionManager selectionManager;
+
+ public WorkbenchPerspectives(JToolBar toolBar, JPanel panel,
+ CardLayout cardLayout, SelectionManager selectionManager) {
+ this.panel = panel;
+ this.toolBar = toolBar;
+ this.cardLayout = cardLayout;
+ this.selectionManager = selectionManager;
+ refreshing = true;
+ selectionManager.addObserver(new SelectionManagerObserver());
+ refreshing = false;
+ }
+
+ public List<PerspectiveSPI> getPerspectives() {
+ return this.perspectives;
+ }
+
+ public void setPerspectives(List<PerspectiveSPI> perspectives) {
+ this.perspectives = perspectives;
+ initialisePerspectives();
+ }
+
+ private void initialisePerspectives() {
+ for (final PerspectiveSPI perspective : perspectives)
+ addPerspective(perspective, false);
+ selectFirstPerspective();
+ }
+
+ private void setPerspective(PerspectiveSPI perspective) {
+ if (perspective != currentPerspective) {
+ if (!perspectiveButtonMap.containsKey(perspective.getID()))
+ addPerspective(perspective, true);
+ if (!(perspective instanceof BlankPerspective))
+ perspectiveButtonMap.get(perspective.getID()).setSelected(true);
+ cardLayout.show(panel, perspective.getID());
+ currentPerspective = perspective;
+ }
+ }
+
+ private void addPerspective(final PerspectiveSPI perspective,
+ boolean makeActive) {
+ // ensure icon image is always 16x16
+ ImageIcon buttonIcon = null;
+ if (perspective.getButtonIcon() != null) {
+ Image buttonImage = perspective.getButtonIcon().getImage();
+ buttonIcon = new ImageIcon(buttonImage.getScaledInstance(16, 16,
+ SCALE_SMOOTH));
+ }
+
+ final JToggleButton toolbarButton = new JToggleButton(
+ perspective.getText(), buttonIcon);
+ toolbarButton.setToolTipText(perspective.getText() + " perspective");
+ Action action = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ selectionManager.setSelectedPerspective(perspective);
+ }
+ };
+ action.putValue(NAME, perspective.getText());
+ action.putValue(SMALL_ICON, buttonIcon);
+
+ toolbarButton.setAction(action);
+ toolBar.add(toolbarButton);
+ perspectiveButtonGroup.add(toolbarButton);
+ perspectiveButtonMap.put(perspective.getID(), toolbarButton);
+
+ panel.add(perspective.getPanel(), perspective.getID());
+ if (makeActive)
+ toolbarButton.doClick();
+ }
+
+ /**
+ * Recreates the toolbar buttons. Useful if a perspective has been removed.
+ */
+ public void refreshPerspectives() {
+ invokeLater(new RefreshRunner());
+ }
+
+ /** selects the first visible perspective by clicking on the toolbar button */
+ private void selectFirstPerspective() {
+ boolean set = false;
+ for (Component c : toolBar.getComponents())
+ if (c instanceof AbstractButton && c.isVisible()) {
+ ((AbstractButton) c).doClick();
+ set = true;
+ break;
+ }
+
+ if (!set) {
+ // no visible perspectives were found
+ logger.info("No visible perspectives.");
+ selectionManager.setSelectedPerspective(new BlankPerspective());
+ }
+ }
+
+ private final class RefreshRunner implements Runnable {
+ @Override
+ public void run() {
+ synchronized (WorkbenchPerspectives.this) {
+ if (refreshing)
+ // We only need one run
+ return;
+ refreshing = true;
+ }
+ try {
+ toolBar.removeAll();
+ toolBar.repaint();
+ initialisePerspectives();
+ } finally {
+ synchronized (WorkbenchPerspectives.this) {
+ refreshing = false;
+ }
+ }
+ }
+ }
+
+ private final class SelectionManagerObserver extends
+ SwingAwareObserver<SelectionManagerEvent> {
+ @Override
+ public void notifySwing(Observable<SelectionManagerEvent> sender,
+ SelectionManagerEvent message) {
+ if (message instanceof PerspectiveSelectionEvent) {
+ PerspectiveSPI selectedPerspective = ((PerspectiveSelectionEvent) message)
+ .getSelectedPerspective();
+ setPerspective(selectedPerspective);
+ }
+ }
+ }
+
+ /**
+ * A dummy blank perspective for when there are no visible perspectives
+ * available
+ *
+ * @author Stuart Owen
+ */
+ private class BlankPerspective implements PerspectiveSPI {
+ @Override
+ public String getID() {
+ return BlankPerspective.class.getName();
+ }
+
+ @Override
+ public JPanel getPanel() {
+ return new JPanel();
+ }
+
+ @Override
+ public ImageIcon getButtonIcon() {
+ return null;
+ }
+
+ @Override
+ public String getText() {
+ return null;
+ }
+
+ @Override
+ public int positionHint() {
+ return 0;
+ }
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java
new file mode 100644
index 0000000..d5573be
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/java/net/sf/taverna/t2/workbench/ui/impl/menu/ExitAction.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl.menu;
+
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+
+/**
+ * Exit the workbench
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class ExitAction extends AbstractMenuAction {
+ private static final String EXIT_LABEL = "Exit";
+ private static final String MAC_OS_X = "Mac OS X";
+ private Workbench workbench;
+
+ public ExitAction() {
+ super(URI.create("http://taverna.sf.net/2008/t2workbench/menu#file"),
+ 10000);
+ }
+
+ @SuppressWarnings("serial")
+ @Override
+ protected Action createAction() {
+ return new AbstractAction(EXIT_LABEL) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ workbench.exit();
+ }
+ };
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return !MAC_OS_X.equalsIgnoreCase(System.getProperty("os.name"));
+ }
+
+ public void setWorkbench(Workbench workbench) {
+ this.workbench = workbench;
+ }
+}
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
new file mode 100644
index 0000000..a9fa855
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.WorkbenchLauncher
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..0c43dec
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,18 @@
+
+net.sf.taverna.t2.workbench.ui.impl.menu.FileMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.ExitAction
+
+net.sf.taverna.t2.workbench.ui.impl.menu.EditMenu
+
+net.sf.taverna.t2.workbench.ui.impl.menu.AdvancedMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.DisplayPerspectivesMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.EditPerspectivesMenu
+
+net.sf.taverna.t2.workbench.ui.impl.menu.HelpMenu
+net.sf.taverna.t2.workbench.ui.impl.menu.OnlineHelpMenuAction
+net.sf.taverna.t2.workbench.ui.impl.menu.FeedbackMenuAction
+
+#net.sf.taverna.t2.workbench.ui.impl.menu.ViewShowMenuSection
+#net.sf.taverna.t2.workbench.ui.impl.menu.ChangePerspectiveMenuAction
+
+net.sf.taverna.t2.workbench.ui.impl.menu.ShowLogsAndDataMenuAction
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
new file mode 100644
index 0000000..c022389
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.ShutdownSPI
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.ui.impl.StoreWindowStateOnShutdown
\ No newline at end of file
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
new file mode 100644
index 0000000..86349aa
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.StartupSPI
@@ -0,0 +1,2 @@
+net.sf.taverna.t2.workbench.ui.impl.UserRegistrationHook
+net.sf.taverna.t2.workbench.ui.impl.SetConsoleLoggerStartup
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml
new file mode 100644
index 0000000..0dc7efc
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context-osgi.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/osgi
+ http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+ <service ref="UserRegistrationHook" interface="net.sf.taverna.t2.workbench.StartupSPI" />
+ <!-- <service ref="SetConsoleLoggerStartup" interface="net.sf.taverna.t2.workbench.StartupSPI" /> -->
+
+ <service ref="StoreWindowStateOnShutdown" interface="net.sf.taverna.t2.workbench.ShutdownSPI" />
+
+ <service ref="ExitAction" auto-export="interfaces">
+ <service-properties>
+ <beans:entry key="menu.action" value="file.exit" />
+ </service-properties>
+ </service>
+
+ <service ref="Workbench" interface="net.sf.taverna.t2.workbench.ui.Workbench" />
+
+ <reference id="editManager" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+ <reference id="fileManager" interface="net.sf.taverna.t2.workbench.file.FileManager" />
+ <reference id="menuManager" interface="net.sf.taverna.t2.ui.menu.MenuManager" />
+ <reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" />
+ <reference id="pluginManager" interface="uk.org.taverna.commons.plugin.PluginManager" />
+ <reference id="workbenchConfiguration" interface="net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration" />
+ <reference id="applicationConfiguration" interface="uk.org.taverna.configuration.app.ApplicationConfiguration" />
+ <reference id="t2ConfigurationFrame" interface="net.sf.taverna.t2.workbench.configuration.workbench.ui.T2ConfigurationFrame" />
+
+ <list id="perspectives" interface="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI" cardinality="0..N" comparator-ref="PerspectiveComparator" greedy-proxying="true">
+ <listener ref="Workbench" bind-method="refreshPerspectives" unbind-method="refreshPerspectives" />
+ </list>
+
+ <list id="startupHooks" interface="net.sf.taverna.t2.workbench.StartupSPI" cardinality="0..N" comparator-ref="StartupComparator"/>
+ <list id="shutdownHooks" interface="net.sf.taverna.t2.workbench.ShutdownSPI" cardinality="0..N" comparator-ref="ShutdownComparator"/>
+
+</beans:beans>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml
new file mode 100644
index 0000000..78c4eb2
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/META-INF/spring/workbench-impl-context.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="UserRegistrationHook" class="net.sf.taverna.t2.workbench.ui.impl.UserRegistrationHook">
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ </bean>
+ <bean id="SetConsoleLoggerStartup" class="net.sf.taverna.t2.workbench.ui.impl.SetConsoleLoggerStartup">
+ <constructor-arg ref="workbenchConfiguration" />
+ </bean>
+
+ <bean id="StoreWindowStateOnShutdown" class="net.sf.taverna.t2.workbench.ui.impl.StoreWindowStateOnShutdown">
+ <property name="workbench">
+ <ref local="Workbench"/>
+ </property>
+ </bean>
+
+ <bean id="ExitAction" class="net.sf.taverna.t2.workbench.ui.impl.menu.ExitAction">
+ <property name="workbench">
+ <ref local ="Workbench"/>
+ </property>
+ </bean>
+
+ <bean id="Workbench" class="net.sf.taverna.t2.workbench.ui.impl.WorkbenchImpl" init-method="initialize">
+ <constructor-arg ref="startupHooks"/>
+ <constructor-arg ref="shutdownHooks"/>
+ <constructor-arg ref="perspectives"/>
+ <property name="editManager" ref="editManager"/>
+ <property name="fileManager" ref="fileManager"/>
+ <property name="menuManager" ref="menuManager"/>
+ <property name="t2ConfigurationFrame" ref="t2ConfigurationFrame"/>
+ <property name="workbenchConfiguration" ref="workbenchConfiguration"/>
+ <property name="applicationConfiguration" ref="applicationConfiguration"/>
+ <property name="selectionManager" ref="selectionManager"/>
+ <property name="pluginManager" ref="pluginManager"/>
+ </bean>
+
+ <bean id="PerspectiveComparator" class="net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI$PerspectiveComparator" />
+ <bean id="StartupComparator" class="net.sf.taverna.t2.workbench.StartupSPI$StartupComparator" />
+ <bean id="ShutdownComparator" class="net.sf.taverna.t2.workbench.ShutdownSPI$ShutdownComparator" />
+
+</beans>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/Map.jhm b/taverna-workbench-workbench-impl/src/main/resources/Map.jhm
new file mode 100644
index 0000000..ab5f560
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/Map.jhm
@@ -0,0 +1,28 @@
+<?xml version='1.0' encoding='ISO-8859-1' ?>
+<!DOCTYPE map
+ PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN"
+ "http://java.sun.com/javase/technologies/desktop/javahelp/map_1_0.dtd">
+
+<map version="1.0">
+ <mapID target="toplevelfolder" url="images/toplevel.gif" />
+ <mapID target="top" url="help/welcome.html" />
+
+ <mapID target="intro" url="help/welcome.html" />
+ <mapID target="start" url="help/start.html" />
+ <mapID target="overview" url="help/welcome.html" />
+ <mapID target="one" url="help/start.html" />
+ <mapID target="two" url="help/start.html" />
+
+ <mapID target="bean.story" url="help/welcome.html" />
+ <mapID target="bean.story" url="help/start.html" />
+ <mapID target="bean.story" url="help/welcome.html" />
+
+ <mapID target="http://taverna.sf.net/2008/t2workbench/menu#help"
+ url="http://www.google.com" />
+
+ <mapID target="http://taverna.sf.net/2008/t2workbench/menu#fileOpen"
+ url="http://www.google.com" />
+
+
+
+</map>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml b/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml
new file mode 100644
index 0000000..5d84369
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/example-registration-form.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<registration_data>
+ <taverna_version>taverna-2.2.0</taverna_version>
+ <first_name>John</first_name>
+ <last_name>Doe</last_name>
+ <email_address>john.doe@jd-consulting.com</email_address>
+ <keep_me_informed>false</keep_me_informed>
+ <institution_or_company_name>JD Consulting</institution_or_company_name>
+ <industry_type>Industry - Pharmaceutical</industry_type>
+ <field_of_interest>bioinformatics</field_of_interest>
+ <purpose_of_using_taverna>pharmacogenomics</purpose_of_using_taverna>
+</registration_data>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd b/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd
new file mode 100644
index 0000000..776f8e5
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/registration-form.xsd
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:element name="registration_data">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="taverna_version"/>
+ <xs:element ref="first_name"/>
+ <xs:element ref="last_name"/>
+ <xs:element ref="email_address"/>
+ <xs:element ref="keep_me_informed"/>
+ <xs:element ref="institution_or_company_name"/>
+ <xs:element ref="industry_type"/>
+ <xs:element ref="field_of_interest"/>
+ <xs:element ref="purpose_of_using_taverna"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="taverna_version" type="xs:string"/>
+ <xs:element name="first_name" type="xs:string"/>
+ <xs:element name="last_name" type="xs:string"/>
+ <xs:element name="email_address" type="xs:string"/>
+ <xs:element name="keep_me_informed" type="xs:boolean"/>
+ <xs:element name="institution_or_company_name" type="xs:string"/>
+ <xs:element name="industry_type" type="xs:string"/>
+ <xs:element name="field_of_interest" type="xs:string"/>
+ <xs:element name="purpose_of_using_taverna" type="xs:string"/>
+</xs:schema>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/registration.php b/taverna-workbench-workbench-impl/src/main/resources/registration.php
new file mode 100644
index 0000000..902200b
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/registration.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * A folder where to write the registrations
+ */
+
+$registrations_folder = "/var/taverna-registration";
+
+
+
+function xmlescape($string){
+ $res = str_replace("&", "&",$string);
+ $res = str_replace("<", "<",$res);
+ $res = str_replace(">", ">",$res);
+ return $res;
+}
+
+
+
+/* From http://php.net/manual/en/function.uiqid.php
+ * Requires yum install uuid-php
+ and in .htaccess / php.ini:
+ php_value allow_call_time_pass_reference true
+ */
+
+class uuid {
+ /**
+ * This class enables you to get real uuids using the OSSP library.
+ * Note you need php-uuid installed.
+ * On my system 1000 UUIDs are created in 0.0064 seconds.
+ *
+ * @author Marius Karthaus
+ *
+ */
+
+ protected $uuidobject;
+
+ /**
+ * On long running deamons i've seen a lost resource. This checks the resource and creates it if needed.
+ *
+ */
+ protected function create() {
+ if (! is_resource ( $this->uuidobject )) {
+ uuid_create ( &$this->uuidobject );
+ }
+ }
+
+ /**
+ * Return a type 1 (MAC address and time based) uuid
+ *
+ * @return String
+ */
+ public function v1() {
+ $this->create ();
+ uuid_make ( $this->uuidobject, UUID_MAKE_V1 );
+ uuid_export ( $this->uuidobject, UUID_FMT_STR, &$uuidstring );
+ return trim ( $uuidstring );
+ }
+
+ /**
+ * Return a type 4 (random) uuid
+ *
+ * @return String
+ */
+ public function v4() {
+ $this->create ();
+ uuid_make ( $this->uuidobject, UUID_MAKE_V4 );
+ uuid_export ( $this->uuidobject, UUID_FMT_STR, &$uuidstring );
+ return trim ( $uuidstring );
+ }
+
+ /**
+ * Return a type 5 (SHA-1 hash) uuid
+ *
+ * @return String
+ */
+ public function v5() {
+ $this->create ();
+ uuid_make ( $this->uuidobject, UUID_MAKE_V5 );
+ uuid_export ( $this->uuidobject, UUID_FMT_STR, $uuidstring );
+ return trim ( $uuidstring );
+ }
+}
+
+ if(isset($_POST['taverna_registration'])){
+
+ $taverna_version = $_POST['taverna_version'];
+ $first_name = $_POST['first_name'];
+ $last_name = $_POST['last_name'];
+ $email = $_POST['email'];
+ $keep_me_informed = $_POST['keep_me_informed'];
+ $institution_or_company = $_POST['institution_or_company'];
+ $industry = $_POST['industry_type'];
+ $field = $_POST['field'];
+ $purpose = $_POST['purpose'];
+
+ $uuid=new uuid();
+
+
+ // Generate user registration data file name with a random identifier
+ $random_id = $uuid->v4();
+ $user_registration_file_name = $registrations_folder . "/user_registration_" . $random_id . ".xml";
+ $user_registration_file = fopen($user_registration_file_name,'w') or die ("Could not open file ". $user_registration_file_name . " for writing." );
+
+ // Save this to a file
+ /*
+ $registration_data = "";
+ $registration_data .= "Taverna version=" . $taverna_version . "\n";
+ $registration_data .= "First name=" . $first_name . "\n";
+ $registration_data .= "Last name=" . $last_name . "\n";
+ $registration_data .= "Email address=" . $email . "\n";
+ $registration_data .= "Keep me informed by email=" . $keep_me_informed . "\n";
+ $registration_data .= "Institution or company=" . $institution_or_company . "\n";
+ $registration_data .= "Industry=" . $industry_type . "\n";
+ $registration_data .= "Field of interest=" . $field . "\n";
+ $registration_data .= "Purpose of using Taverna=" . $purpose;
+ */
+
+ $registration_data = "";
+ $registration_data .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ $registration_data .= "<registration_data>\n";
+ $registration_data .= "\t<taverna_version>".xmlescape($taverna_version)."</taverna_version>\n";
+ $registration_data .= "\t<first_name>".xmlescape($first_name)."</first_name>\n";
+ $registration_data .= "\t<last_name>".xmlescape($last_name)."</last_name>\n";
+ $registration_data .= "\t<email_address>".xmlescape($email)."</email_address>\n";
+ $registration_data .= "\t<keep_me_informed>".xmlescape($keep_me_informed)."</keep_me_informed>\n";
+ $registration_data .= "\t<institution_or_company_name>".xmlescape($institution_or_company)."</institution_or_company_name>\n";
+ $registration_data .= "\t<industry_type>".xmlescape($industry)."</industry_type>\n";
+ $registration_data .= "\t<field_of_interest>".xmlescape($field)."</field_of_interest>\n";
+ $registration_data .= "\t<purpose_of_using_taverna>".xmlescape($purpose)."</purpose_of_using_taverna>\n";
+ $registration_data .= "</registration_data>\n";
+
+ fwrite($user_registration_file, $registration_data) or die ("Could not write to file ". $user_registration_file_name );
+ fclose($user_registration_file);
+ echo "Registration successful!";
+ }
+?>
diff --git a/taverna-workbench-workbench-impl/src/main/resources/sample.hs b/taverna-workbench-workbench-impl/src/main/resources/sample.hs
new file mode 100644
index 0000000..9b43dca
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/main/resources/sample.hs
@@ -0,0 +1,64 @@
+<?xml version='1.0' encoding='ISO-8859-1' ?>
+<!DOCTYPE helpset
+ PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN"
+ "../dtd/helpset_2_0.dtd">
+
+<helpset version="1.0">
+
+ <!-- title -->
+ <title>My Sample Help - Online</title>
+
+ <!-- maps -->
+ <maps>
+ <homeID>top</homeID>
+ <mapref location="Map.jhm"/>
+ </maps>
+
+ <!-- views -->
+ <view>
+ <name>TOC</name>
+ <label>Table Of Contents</label>
+ <type>javax.help.TOCView</type>
+ <data>SampleTOC.xml</data>
+ </view>
+
+ <view>
+ <name>Index</name>
+ <label>Index</label>
+ <type>javax.help.IndexView</type>
+ <data>SampleIndex.xml</data>
+ </view>
+
+ <view>
+ <name>Search</name>
+ <label>Search</label>
+ <type>javax.help.SearchView</type>
+ <data engine="com.sun.java.help.search.DefaultSearchEngine">
+ JavaHelpSearch
+ </data>
+ </view>
+
+ <presentation default="true" displayviewimages="false">
+ <name>main window</name>
+ <size width="700" height="400" />
+ <location x="200" y="200" />
+ <title>My Sample Help - Online</title>
+ <image>toplevelfolder</image>
+ <toolbar>
+ <helpaction>javax.help.BackAction</helpaction>
+ <helpaction>javax.help.ForwardAction</helpaction>
+ <helpaction>javax.help.SeparatorAction</helpaction>
+ <helpaction>javax.help.HomeAction</helpaction>
+ <helpaction>javax.help.ReloadAction</helpaction>
+ <helpaction>javax.help.SeparatorAction</helpaction>
+ <helpaction>javax.help.PrintAction</helpaction>
+ <helpaction>javax.help.PrintSetupAction</helpaction>
+ </toolbar>
+ </presentation>
+ <presentation>
+ <name>main</name>
+ <size width="400" height="400" />
+ <location x="200" y="200" />
+ <title>My Sample Help - Online</title>
+ </presentation>
+</helpset>
diff --git a/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java b/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java
new file mode 100644
index 0000000..4c64aa3
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/test/java/net/sf/taverna/t2/workbench/ui/impl/UserRegistrationTest.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ * Modifications to the initial code base are copyright of their
+ * respective authors, or their employers as appropriate.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.ui.impl;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import static org.junit.Assert.*;
+
+import net.sf.taverna.t2.workbench.ui.impl.UserRegistrationForm;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class UserRegistrationTest {
+
+ @Ignore
+ @Test
+ public void postUserRegistrationDataToServer() {
+
+ String parameters = "";
+
+ // The 'submit' parameter - to let the server-side script know we are
+ // submitting
+ // the user's registration form - all other requests will be silently
+ // ignored
+ try {
+ parameters = URLEncoder
+ .encode(
+ UserRegistrationForm.TAVERNA_REGISTRATION_POST_PARAMETER_NAME,
+ "UTF-8")
+ + "=" + URLEncoder.encode("submit", "UTF-8"); // value does
+ // not
+ // matter
+
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.TAVERNA_VERSION_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("snapshot", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.FIRST_NAME_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Alex", "UTF-8");
+ parameters += "&"
+ + URLEncoder.encode(
+ UserRegistrationForm.LAST_NAME_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Nenadic", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.EMAIL_ADDRESS_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("alex@alex.com", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.KEEP_ME_INFORMED_POST_PARAMETER_PROPERTY_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("true", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.INSTITUTION_OR_COMPANY_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Uni of Manchester", "UTF-8");
+ parameters += "&"
+ + URLEncoder
+ .encode(
+ UserRegistrationForm.INDUSTRY_TYPE_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Academia", "UTF-8");
+ parameters += "&"
+ + URLEncoder.encode(
+ UserRegistrationForm.FIELD_POST_PARAMETER_NAME,
+ "UTF-8") + "="
+ + URLEncoder.encode("Research", "UTF-8");
+ parameters += "&"
+ + URLEncoder.encode(
+ UserRegistrationForm.PURPOSE_POST_PARAMETER_NAME,
+ "UTF-8") + "=" + URLEncoder.encode("None", "UTF-8");
+ } catch (UnsupportedEncodingException ueex) {
+ System.out
+ .println("Failed to url encode post parameters when sending user registration data.");
+ }
+ String server = "http://cactus.cs.man.ac.uk/~alex/taverna_registration/registration.php";
+ server = "http://localhost/~alex/taverna_registration/registration.php";
+ // server = "https://somehost.co.uk";
+
+ System.out.println("Posting user registartion to " + server
+ + " with parameters: " + parameters);
+ String response = "";
+ try {
+ URL url = new URL(server);
+ URLConnection conn = url.openConnection();
+ System.out.println("Opened a connection");
+ // Set timeout for connection, otherwise we might hang too long
+ // if server is not responding and it will block Taverna
+ conn.setConnectTimeout(7000);
+ // Set connection parameters
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ // Make server believe we are HTML form data...
+ conn.setRequestProperty("Content-Type",
+ "application/x-www-form-urlencoded");
+ System.out
+ .println("Trying to get an output stream from the connection");
+ DataOutputStream out = new DataOutputStream(conn.getOutputStream());
+ // Write out the bytes of the content string to the stream.
+ out.writeBytes(parameters);
+ out.flush();
+ out.close();
+ // Read response from the input stream.
+ BufferedReader in = new BufferedReader(new InputStreamReader(conn
+ .getInputStream()));
+ String temp;
+ while ((temp = in.readLine()) != null) {
+ response += temp + "\n";
+ }
+ // Remove the last \n character
+ if (!response.equals("")) {
+ response = response.substring(0, response.length() - 1);
+ }
+ in.close();
+ System.out.println(response);
+ if (!response.equals("Registration successful!")) {
+ System.out
+ .println("Registration failed. Response form server was: "
+ + response);
+ }
+ assertTrue(response.equals("Registration successful!"));
+ }
+ // Catch some runtime exceptions
+ catch (ConnectException ceex) { // the connection was refused remotely
+ // (e.g. no process is listening on the
+ // remote address/port).
+ System.out
+ .println("User registration failed: Registration server is not listening of the specified url.");
+ ceex.printStackTrace();
+ }
+ // Catch some runtime exceptions
+ catch (SocketTimeoutException stex) { // timeout has occurred on a
+ // socket read or accept.
+ System.out
+ .println("User registration failed: Socket timeout occurred.");
+ stex.printStackTrace();
+ } catch (MalformedURLException muex) {
+ System.out
+ .println("User registration failed: Registartion server's url is malformed.");
+ muex.printStackTrace();
+ } catch (IOException ioex) {
+ System.out
+ .println("User registration failed: Failed to open url connection to registration server or writing to it or reading from it.");
+ ioex.printStackTrace();
+ }
+ }
+
+}
diff --git a/taverna-workbench-workbench-impl/src/test/resources/log4j.properties b/taverna-workbench-workbench-impl/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fa27070
--- /dev/null
+++ b/taverna-workbench-workbench-impl/src/test/resources/log4j.properties
@@ -0,0 +1,10 @@
+log4j.rootLogger=WARN, CONSOLE
+log4j.logger.net.sf.taverna.t2.workbench=INFO
+log4j.logger.net.sf.taverna.t2.ui=DEBUG
+
+#log4j.logger.org.apache.commons.httpclient=ERROR
+
+# Default output to console
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%-5p %d{ISO8601} (%c:%L) - %m%n
\ No newline at end of file