You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/03/20 15:22:29 UTC
[15/51] [abbrv] [partial] incubator-taverna-workbench git commit:
taverna-workbench-* -> taverna-*
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
new file mode 100644
index 0000000..a223fc8
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/ExpandableOnDemandLoadedListCellRenderer.java
@@ -0,0 +1,220 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public abstract class ExpandableOnDemandLoadedListCellRenderer extends JPanel implements ListCellRenderer
+{
+ protected static final int DESCRIPTION_MAX_LENGTH_COLLAPSED = 90;
+ protected static final int DESCRIPTION_MAX_LENGTH_EXPANDED = 500;
+
+ protected static final int LINE_LENGTH = 90;
+
+
+ protected static final int TOOLTIP_DESCRIPTION_LENGTH = 150;
+ protected static final int TOOLTIP_LINE_LENGTH = 60;
+
+ // list cells are not repainted by Swing by default - hence to use animated GIFs inside cells,
+ // need to have a special class that takes care of changing the frames as necessary
+ protected JLabel loaderBarAnimationOrange = new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_ORANGE), JLabel.CENTER);
+ protected JLabel loaderBarAnimationGrey = new JLabel(ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_GREY), JLabel.CENTER);
+ protected JLabel loaderBarAnimationGreyStill = new JLabel (ResourceManager.getImageIcon(ResourceManager.BAR_LOADER_GREY_STILL), JLabel.CENTER);
+
+
+ protected JPanel thisPanel;
+ private List<Class<? extends ResourceLink>> resourceClasses;
+
+
+ protected JLabel jlExpand;
+ protected static Rectangle expandRect;
+
+ public ExpandableOnDemandLoadedListCellRenderer()
+ {
+ this.thisPanel = this;
+
+ resourceClasses = new ArrayList<Class<? extends ResourceLink>>();
+ try {
+ for (Resource.TYPE resourceType : Resource.TYPE.values()) {
+ resourceClasses.add(resourceType.getXmlBeansGeneratedClass());
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
+ public static Rectangle getExpandRect() {
+ return (expandRect == null ? new Rectangle() : expandRect);
+ }
+
+
+ public Component getListCellRendererComponent(JList list, Object itemToRender, int itemIndex, boolean isSelected, boolean cellHasFocus)
+ {
+ // the same instance of the cell renderer is used for all cells, so
+ // need to remove everything from the current panel to ensure clean
+ // painting of the current cell
+ this.removeAll();
+
+ // GET THE DATA
+
+ // LoadingResource is a placeholder for the detailed data on the resource --
+ // it is being quickly fetched from the API and contanins just the name and the URL
+ // of the actual resource;
+ //
+ // these entries will be placed into the list when the initial part of the search
+ // is complete, further details will be loaded asynchronously and inserted into
+ // the same area
+ if (itemToRender instanceof LoadingResource) {
+ prepareInitiallyLoadingEntry(itemToRender);
+ }
+
+ // real data about some resource: details, but in the collapsed form
+ else if (isInstanceOfResourceType(itemToRender)) {
+ prepareLoadedEntry(itemToRender, isSelected);
+ }
+
+ // error case - unknown resource...
+ else {
+ prepareUnknownResourceTypeEntry();
+ }
+
+
+ // MAKE SURE CELL SELECTION WORKS AS DESIRED
+ if (shouldBeHidden(itemToRender)) {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+ BorderFactory.createLineBorder(Color.DARK_GRAY)));
+ setBackground(list.getBackground());
+ setForeground(list.getBackground());
+ }
+ else if (isSelected) {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+ BorderFactory.createLineBorder(Color.DARK_GRAY)));
+ setBackground(Color.decode("#BAE8FF")); // very light blue colour
+ setForeground(list.getSelectionForeground());
+ } else {
+ this.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(3, 4, 3, 4, list.getBackground()),
+ BorderFactory.createLineBorder(Color.DARK_GRAY)));
+ setBackground(Color.WHITE);
+ setForeground(list.getForeground());
+ }
+
+ this.revalidate();
+
+ if (expandRect == null && jlExpand != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ expandRect = jlExpand.getBounds();
+ expandRect.x -= Math.abs(thisPanel.getBounds().x);
+ }
+ });
+ }
+
+ return (this);
+ }
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected abstract GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender);
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param isSelected
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected abstract GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean isSelected);
+
+
+ private void prepareUnknownResourceTypeEntry()
+ {
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(8, 6, 6, 3);
+ this.add(new JLabel(ResourceManager.getImageIcon(ResourceManager.UNKNOWN_RESOURCE_TYPE_ICON)), c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(8, 3, 6, 3);
+ this.add(new JLabel("<html><font color=\"#FF0000\">ERROR: This item shoulnd't have been here...</font></html>"), c);
+
+ c.gridx = 1;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 1.0;
+ c.weighty = 0;
+ c.insets = new Insets(3, 3, 3, 3);
+ this.add(new JLabel(" "), c);
+
+ c.gridy++;
+ c.insets = new Insets(3, 3, 8, 3);
+ this.add(new JLabel(" "), c);
+ }
+
+
+ private boolean isInstanceOfResourceType(Object itemToRender)
+ {
+ for (Class<? extends ResourceLink> resourceClass : resourceClasses) {
+ if (resourceClass.isInstance(itemToRender)) {
+ return (true);
+ }
+ }
+
+ return (false);
+ }
+
+ protected TYPE determineResourceType(Object itemToRender) {
+ if (itemToRender instanceof ResourceLink) {
+ return (Resource.getResourceTypeFromResourceURL(((ResourceLink)itemToRender).getHref()));
+ }
+ else {
+ return (null);
+ }
+ }
+
+ abstract boolean shouldBeHidden(Object itemToRender);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
new file mode 100644
index 0000000..64bdfbf
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/RESTMethodListCellRenderer.java
@@ -0,0 +1,248 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JLabel;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestParameter;
+import org.biocatalogue.x2009.xml.rest.RestRepresentation;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.RestMethod.Ancestors;
+
+
+/**
+ *
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class RESTMethodListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+ private JLabel jlTypeIcon = new JLabel();
+ private JLabel jlItemStatus = new JLabel();
+ private JLabel jlItemTitle = new JLabel("X");
+ private JLabel jlPartOf = new JLabel("X");
+ private ReadOnlyTextArea jtDescription = new ReadOnlyTextArea(5, 80);
+ private JLabel jlMethodType = new JLabel("X");
+ private JLabel jlUrlTemplate = new JLabel("X");
+ private JLabel jlMethodParameters = new JLabel("X");
+ private JLabel jlInputRepresentations = new JLabel("X");
+ private JLabel jlOutputRepresentations = new JLabel("X");
+
+ private GridBagConstraints c;
+
+ private static Resource.TYPE resourceType = Resource.TYPE.RESTMethod;
+
+
+
+ public RESTMethodListCellRenderer() {
+ jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+ jtDescription.setOpaque(false);
+ jtDescription.setLineWrap(true);
+ jtDescription.setWrapStyleWord(true);
+ }
+
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender)
+ {
+ LoadingResource resource = (LoadingResource)itemToRender;
+
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ jlItemStatus.setIcon(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+
+ jlItemTitle.setText("<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(resource)) + "<font color=\"gray\"><i>- fetching more information</i></font></html>");
+
+ jlPartOf.setText("");
+ jtDescription.setText(" ");
+ jlMethodType.setText(" ");
+ jlUrlTemplate.setText(" ");
+ jlMethodParameters.setText(" ");
+ jlInputRepresentations.setText(" ");
+ jlOutputRepresentations.setText(" ");
+
+ return (arrangeLayout());
+ }
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean selected)
+ {
+ RestMethod restMethod = (RestMethod)itemToRender;;
+
+ Ancestors ancestors = restMethod.getAncestors();
+ Service service = ancestors.getService();
+ String title = "<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(restMethod));
+
+ if (restMethod.isSetArchived() || service.isSetArchived()) {
+ jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+ title = title + "<i> - this operation is archived and probably cannot be used</i></html>";
+ }
+ else {
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ title = title + "</html>";
+ }
+
+ // service status
+ jlItemStatus.setIcon(ServiceMonitoringStatusInterpreter.getStatusIcon(service, false));
+ jlItemTitle.setText(title);
+
+ jlPartOf.setText("<html><b>Part of: </b>" + restMethod.getAncestors().getRestService().getResourceName() + "</html>");
+
+ String strDescription = (restMethod.getDescription() == null || restMethod.getDescription().length() == 0 ?
+ "No description" :
+ Util.stripAllHTML(restMethod.getDescription()));
+ jtDescription.setText(strDescription);
+
+ jlMethodType.setText("<html><b>HTTP Method: </b>" + StringEscapeUtils.escapeHtml(restMethod.getHttpMethodType().toString()) + "</html>");
+ jlUrlTemplate.setText("<html><b>URL Template: </b>" + StringEscapeUtils.escapeHtml(restMethod.getUrlTemplate()) + "</html>");
+
+ List<String> names = new ArrayList<String>();
+ for (RestParameter restParameter : restMethod.getInputs().getParameters().getRestParameterList()) {
+ names.add(restParameter.getName() + (restParameter.getIsOptional() ? " (optional)" : ""));
+ }
+
+ String methodParameters = "<b>" + names.size() + " " + Util.pluraliseNoun("Parameter", names.size()) + "</b>";
+ if(names.size() > 0) {
+ methodParameters += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ methodParameters = "<html>" + methodParameters + "</html>";
+ jlMethodParameters.setText(methodParameters);
+
+ names.clear();
+ for (RestRepresentation restRepresentation : restMethod.getInputs().getRepresentations().getRestRepresentationList()) {
+ names.add(restRepresentation.getContentType());
+ }
+
+ String inputRepresentations = "<b>" + names.size() + " " + Util.pluraliseNoun("Input representation", names.size()) + "</b>";
+ if(names.size() > 0) {
+ inputRepresentations += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ inputRepresentations = "<html>" + inputRepresentations + "</html>";
+
+ jlInputRepresentations.setText(inputRepresentations);
+
+ // output representations
+ names.clear();
+ for (RestRepresentation restRepresentation : restMethod.getOutputs().getRepresentations().getRestRepresentationList()) {
+ names.add(restRepresentation.getContentType());
+ }
+
+ String outputRepresentations = "<b>" + names.size() + " " + Util.pluraliseNoun("Output representation", names.size()) + "</b>";
+ if(names.size() > 0) {
+ outputRepresentations += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ outputRepresentations = "<html>" + outputRepresentations + "</html>";
+
+ jlOutputRepresentations.setText(outputRepresentations);
+
+ return (arrangeLayout());
+ }
+
+
+ /**
+ * @return Final state of the {@link GridBagConstraints} instance
+ * that was used to lay out components in the panel.
+ */
+ private GridBagConstraints arrangeLayout()
+ {
+ // POPULATE PANEL WITH PREPARED COMPONENTS
+ this.setLayout(new GridBagLayout());
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(8, 6, 6, 3);
+ this.add(jlTypeIcon, c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(8, 3, 6, 3);
+ this.add(jlItemTitle, c);
+
+ c.gridx++;
+ c.gridheight = 8;
+ c.weightx = 0;
+ c.weighty = 1.0;
+ this.add(jlItemStatus, c);
+
+ c.gridx = 1;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 1.0;
+ c.weighty = 0;
+ this.add(jlPartOf, c);
+
+ c.fill = GridBagConstraints.NONE;
+ c.gridy++;
+ this.add(jtDescription, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy++;
+ this.add(jlMethodType, c);
+
+ c.gridy++;
+ this.add(jlUrlTemplate, c);
+
+ c.gridy++;
+ this.add(jlMethodParameters, c);
+
+ c.gridy++;
+ this.add(jlInputRepresentations, c);
+
+ c.gridy++;
+ this.add(jlOutputRepresentations, c);
+ return (c);
+ }
+
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+ if (!(itemToRender instanceof RestMethod)) {
+ return false;
+ }
+ RestMethod restMethod = (RestMethod)itemToRender;;
+
+ Ancestors ancestors = restMethod.getAncestors();
+ Service service = ancestors.getService();
+ String title = Resource.getDisplayNameForResource(restMethod);
+
+ if (restMethod.isSetArchived() || service.isSetArchived()) {
+ return true;
+ }
+ else {
+ return false;
+ }
+
+}
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
new file mode 100644
index 0000000..77939c7
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SOAPOperationListCellRenderer.java
@@ -0,0 +1,257 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+import net.sf.taverna.biocatalogue.model.LoadingExpandedResource;
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceMonitoringStatusInterpreter;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+import org.biocatalogue.x2009.xml.rest.SoapInput;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapOutput;
+import org.biocatalogue.x2009.xml.rest.SoapService;
+import org.biocatalogue.x2009.xml.rest.Service.ServiceTechnologyTypes;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType.Enum;
+import org.biocatalogue.x2009.xml.rest.SoapOperation.Ancestors;
+
+
+/**
+ *
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SOAPOperationListCellRenderer extends ExpandableOnDemandLoadedListCellRenderer
+{
+
+ private JLabel jlTypeIcon = new JLabel();
+ private JLabel jlItemStatus = new JLabel();
+ private JLabel jlItemTitle = new JLabel("X");
+ private JLabel jlPartOf = new JLabel("X");
+ private JLabel jlWsdlLocation = new JLabel("X");
+ private ReadOnlyTextArea jtDescription = new ReadOnlyTextArea(5,80);
+ private JLabel jlSoapInputs = new JLabel("X");
+ private JLabel jlSoapOutputs = new JLabel("X");
+
+ private GridBagConstraints c;
+
+ private static Resource.TYPE resourceType = Resource.TYPE.SOAPOperation;
+
+
+ public SOAPOperationListCellRenderer() {
+ jlItemTitle.setFont(jlItemTitle.getFont().deriveFont(Font.PLAIN, jlItemTitle.getFont().getSize() + 2));
+ jtDescription.setOpaque(false);
+ jtDescription.setLineWrap(true);
+ jtDescription.setWrapStyleWord(true);
+ }
+
+
+ /**
+ * This entry can be in one of two states:
+ * -- containing only the name of the resource and NOT loading further details;
+ * -- containing only the name of the resource and LOADING further details.
+ *
+ * @param itemToRender
+ * @return
+ */
+ protected GridBagConstraints prepareInitiallyLoadingEntry(Object itemToRender)
+ {
+ LoadingResource resource = (LoadingResource)itemToRender;
+
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ jlItemStatus.setIcon(ResourceManager.getImageIcon(ResourceManager.SERVICE_STATUS_UNCHECKED_ICON_LARGE));
+
+ jlItemTitle.setText("<html>" + StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(resource)) + "<font color=\"gray\"><i>- fetching more information</i></font></html>");
+
+ jlPartOf.setText(" ");
+ jlWsdlLocation.setText(" ");
+ jtDescription.setText("");
+ jlSoapInputs.setText(" ");
+ jlSoapOutputs.setText(" ");
+
+ return (arrangeLayout());
+ }
+
+
+ /**
+ *
+ * @param itemToRender
+ * @param selected
+ * @param expandedView <code>true</code> to indicate that this method generates the top
+ * fragment of the expanded list entry for this SOAP operation / REST method.
+ * @return
+ */
+ protected GridBagConstraints prepareLoadedEntry(Object itemToRender, boolean selected)
+ {
+ SoapOperation soapOp = (SoapOperation)itemToRender;
+
+ Ancestors ancestors = soapOp.getAncestors();
+ SoapService soapService = ancestors.getSoapService();
+ Service service = ancestors.getService();
+ String title = StringEscapeUtils.escapeHtml(Resource.getDisplayNameForResource(soapOp));
+
+ if (soapOp.isSetArchived() || service.isSetArchived()) {
+ jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+ title = "<html>" + title + "<i> - this operation is archived and probably cannot be used</i></html>";
+ } else if (isSoapLab(service)) {
+ jlTypeIcon.setIcon(ResourceManager.getImageIcon(ResourceManager.WARNING_ICON));
+ title = "<html>" + title + "<i> - this operation can only be used as part of a SoapLab service</i></html>";
+ }
+ else {
+ jlTypeIcon.setIcon(resourceType.getIcon());
+ title = "<html>" + title + "</html>";
+ }
+
+ // service status
+ jlItemStatus.setIcon(ServiceMonitoringStatusInterpreter.getStatusIcon(service, false));
+ jlItemTitle.setText(title);
+
+ jlPartOf.setText("<html><b>Part of: </b>" + StringEscapeUtils.escapeHtml(soapOp.getAncestors().getSoapService().getResourceName()) + "</html>");
+
+ jlWsdlLocation.setText("<html><b>WSDL location: </b>" + soapService.getWsdlLocation() + "</html>");
+
+ String strDescription = (soapOp.getDescription() == null || soapOp.getDescription().length() == 0 ?
+ "No description" :
+ Util.stripAllHTML(soapOp.getDescription()));
+
+ jtDescription.setText(strDescription);
+
+ // add SOAP inputs
+ List<String> names = new ArrayList<String>();
+ for (SoapInput soapInput : soapOp.getInputs().getSoapInputList()) {
+ names.add(soapInput.getName());
+ }
+
+ String soapInputs = "<b>" + names.size() + " " + Util.pluraliseNoun("Input", names.size()) + "</b>";
+ if(names.size() > 0) {
+ soapInputs += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ soapInputs = "<html>" + soapInputs + "</html>";
+ jlSoapInputs.setText(soapInputs);
+
+ c.gridy++;
+ this.add(jlSoapInputs, c);
+
+
+ // add SOAP outputs
+ names.clear();
+ for (SoapOutput soapOutput : soapOp.getOutputs().getSoapOutputList()) {
+ names.add(soapOutput.getName());
+ }
+
+ String soapOutputs = "<b>" + names.size() + " " + Util.pluraliseNoun("Output", names.size()) + "</b>";
+ if(names.size() > 0) {
+ soapOutputs += ": " + StringEscapeUtils.escapeHtml(Util.ensureLineLengthWithinString(Util.join(names, ", "), LINE_LENGTH, false));
+ }
+ soapOutputs = "<html>" + soapOutputs + "</html>";
+ jlSoapOutputs.setText(soapOutputs);
+
+ return (arrangeLayout());
+ }
+
+
+private boolean isSoapLab(Service service) {
+ boolean result = false;
+ ServiceTechnologyTypes serviceTechnologyTypes = service.getServiceTechnologyTypes();
+ if (serviceTechnologyTypes == null) {
+ return result;
+ }
+ List<Enum> typeList = serviceTechnologyTypes.getTypeList();
+ if (typeList == null) {
+ return result;
+ }
+ result = typeList.contains(ServiceTechnologyType.SOAPLAB);
+ return result;
+}
+
+
+ /**
+ * @return Final state of the {@link GridBagConstraints} instance
+ * that was used to lay out components in the panel.
+ */
+ private GridBagConstraints arrangeLayout()
+ {
+ // POPULATE PANEL WITH PREPARED COMPONENTS
+ this.setLayout(new GridBagLayout());
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.insets = new Insets(8, 6, 6, 3);
+ this.add(jlTypeIcon, c);
+
+ c.gridx++;
+ c.weightx = 1.0;
+ c.insets = new Insets(8, 3, 6, 3);
+ this.add(jlItemTitle, c);
+
+ c.gridx++;
+ c.gridheight = 7;
+ c.weightx = 0;
+ c.weighty = 1.0;
+ this.add(jlItemStatus, c);
+
+ c.gridx = 1;
+ c.gridy++;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ this.add(jlPartOf, c);
+
+ c.gridy++;
+ this.add(jlWsdlLocation, c);
+
+ c.fill = GridBagConstraints.NONE;
+ c.gridy++;
+ this.add(jtDescription, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy++;
+ this.add(jlSoapInputs, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy++;
+ this.add(jlSoapOutputs, c);
+
+ return (c);
+ }
+
+@Override
+boolean shouldBeHidden(Object itemToRender) {
+ if (!(itemToRender instanceof SoapOperation)) {
+ return false;
+ }
+ SoapOperation soapOp = (SoapOperation) itemToRender;
+ Ancestors ancestors = soapOp.getAncestors();
+ Service service = ancestors.getService();
+ if (soapOp.isSetArchived() || service.isSetArchived()) {
+ return true;
+ } else if (isSoapLab(service)) {
+ return true;
+ }
+ else {
+ return false;
+ }
+
+}
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
new file mode 100644
index 0000000..c88de40
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsListingPanel.java
@@ -0,0 +1,870 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.LoadingExpandedResource;
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.ui.JWaitDialog;
+import net.sf.taverna.t2.lang.ui.ModelMap;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponent;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.Integration;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.health_check.ServiceHealthChecker;
+import net.sf.taverna.t2.workbench.MainWindow;
+import net.sf.taverna.t2.workbench.ModelMapConstants;
+import net.sf.taverna.t2.workbench.ui.Workbench;
+import net.sf.taverna.t2.workbench.ui.zaria.PerspectiveSPI;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceTechnologyType;
+
+/**
+ * This class is responsible for producing search results listing panel. It only
+ * shows a single listing for a specified type. Multiple types are handled by
+ * having different tabs in {@link SearchResultsMainPanel} with instances of
+ * this class in each.
+ *
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+public class SearchResultsListingPanel extends JPanel implements MouseListener,
+ SearchResultsRenderer, MouseMotionListener {
+ public static final int SEARCH_STATUS_TOOLTIP_LINE_LENGTH = 65;
+
+ private static final Logger logger = Logger.getLogger(SearchResultsListingPanel.class);
+ private final SearchResultsMainPanel parentMainSearchResultsPanel;
+
+ // currently displayed search results
+ SearchInstance searchInstance;
+
+ // main UI components
+ private SearchResultsListingPanel thisPanel;
+ private DefaultListModel resultsListingModel;
+ private JList jlResultsListing;
+ private JScrollPane spResultsListing;
+
+ // contextual menu
+ private JPopupMenu contextualMenu;
+ private Action addToServicePanelAction;
+ private Action addToWorkflowDiagramAction;
+ private Action openInBioCatalogueAction;
+ private Action doHealthCheckAction;
+ private Action addAllOperationsToServicePanelAction;
+
+ // search status and actions on selected items in the list
+ private JToolBar tbSelectedItemActions;
+ protected JPanel jpSearchStatus;
+ private JLabel jlSearchStatus;
+
+ // this is used for previewing items from the result listing through
+ // contextual menu -
+ // value will be updated by mouse event accordingly
+ private ResourceLink potentialObjectToPreview;
+ private final TYPE typeToPreview;
+
+ // Design perspective - some actions require switching to it
+ PerspectiveSPI designPerspective;
+
+ private ListCellRenderer listingCellRenderer;
+
+ /**
+ * @param typeToPreview
+ * Resource type that will be previewed in this panel.
+ * @param parentMainSearchResultsPanel
+ * Reference to a "parent" of this panel - this is needed to
+ * notify the main results panel with the
+ */
+ public SearchResultsListingPanel(TYPE typeToPreview,
+ SearchResultsMainPanel parentMainSearchResultsPanel) {
+ this.thisPanel = this;
+
+ this.typeToPreview = typeToPreview;
+ listingCellRenderer = this.typeToPreview
+ .getResultListingCellRenderer();
+ this.parentMainSearchResultsPanel = parentMainSearchResultsPanel;
+ MainComponentFactory
+ .getSharedInstance();
+
+ initialiseUI();
+
+ this.setPreferredSize(new Dimension(800, 400));
+ }
+
+ private void initialiseUI() {
+
+ this.addToServicePanelAction = new AbstractAction(
+ "Add to Service Panel",
+ ResourceManager
+ .getImageIcon(ResourceManager.ADD_PROCESSOR_AS_FAVOURITE_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "Add selected "
+ + typeToPreview.getTypeName()
+ + " to the Service Panel");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final JWaitDialog jwd = new JWaitDialog(
+ MainComponent.dummyOwnerJFrame,
+ "Service Catalogue Plugin - Adding "
+ + typeToPreview.getTypeName(),
+ "<html><center>Please wait for selected "
+ + typeToPreview.getTypeName()
+ + " details to be fetched from the Service Catalogue<br>"
+ + "and to be added into the Service Panel.</center></html>");
+
+ new Thread("Adding " + typeToPreview.getTypeName()
+ + " into Service Panel") {
+ public void run() {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink processorResourceToAdd = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ JComponent insertionOutcome = Integration
+ .insertProcesorIntoServicePanel(processorResourceToAdd);
+ jwd.waitFinished(insertionOutcome);
+
+ // Switch to Design Perspective
+ switchToDesignPerspective();
+ }
+ }.start();
+
+ // NB! The modal dialog window needs to be made visible after
+ // the background
+ // process (i.e. adding a processor) has already been started!
+ jwd.setVisible(true);
+ }
+ };
+
+ // For a parent Web service, action to add all operations to the Service Panel.
+ // Works for SOAP services at the moment.
+ this.addAllOperationsToServicePanelAction = new AbstractAction(
+ "Add all operations to Service Panel",
+ ResourceManager
+ .getImageIcon(ResourceManager.ADD_ALL_SERVICES_AS_FAVOURITE_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "Add all associated services to the Service Panel");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final JWaitDialog jwd = new JWaitDialog(
+ MainComponent.dummyOwnerJFrame,
+ "Service Catalogue Plugin - Adding "
+ + typeToPreview.getTypeName(),
+ "<html><center>Please wait for selected "
+ + typeToPreview.getTypeName()
+ + " details to be fetched from the Service Catalogue<br>"
+ + "and to be added into the Service Panel.</center></html>");
+
+ new Thread("Adding all operations of " + typeToPreview.getTypeName()
+ + " to the Service Panel") {
+ public void run() {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink resourceLink = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ JComponent insertionOutcome = Integration
+ .insertAllOperationsIntoServicePanel(resourceLink);
+ jwd.waitFinished(insertionOutcome);
+
+ // Switch to Design Perspective
+ switchToDesignPerspective();
+ }
+ }.start();
+
+ // NB! The modal dialog window needs to be made visible after
+ // the background
+ // process (i.e. adding a processor) has already been started!
+ jwd.setVisible(true);
+ }
+ };
+
+ this.addToWorkflowDiagramAction = new AbstractAction(
+ "Add to workflow",
+ ResourceManager
+ .getImageIcon(ResourceManager.ADD_PROCESSOR_TO_WORKFLOW_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "<html>Insert selected "
+ + typeToPreview.getTypeName()
+ + " into the current workflow</html>");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ final JWaitDialog jwd = new JWaitDialog(
+ MainComponent.dummyOwnerJFrame,
+ "Service Catalogue Plugin - Adding "
+ + typeToPreview.getTypeName(),
+ "<html><center>Please wait for selected "
+ + typeToPreview.getTypeName()
+ + " details to be fetched from the Service Catalogue<br>"
+ + "and to be added into the current workflow.</center></html>");
+
+ new Thread("Adding " + typeToPreview.getTypeName()
+ + " into workflow") {
+ public void run() {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink processorResourceToAdd = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ JComponent insertionOutcome = Integration
+ .insertProcessorIntoCurrentWorkflow(processorResourceToAdd);
+ jwd.waitFinished(insertionOutcome);
+
+ // Switch to Design Perspective
+ switchToDesignPerspective();
+ }
+ }.start();
+
+ // NB! The modal dialog window needs to be made visible after
+ // the background
+ // process (i.e. adding a processor) has already been started!
+ jwd.setVisible(true);
+ }
+ };
+
+ this.openInBioCatalogueAction = new AbstractAction(
+ "Open in the Service Catalogue",
+ ResourceManager
+ .getImageIcon(ResourceManager.OPEN_IN_BIOCATALOGUE_ICON)) {
+ // Tooltip
+ {
+ this.putValue(SHORT_DESCRIPTION, "<html>View selected "
+ + typeToPreview.getTypeName()
+ + " on the Service Catalogue Web site.<br>"
+ + "This will open your standard Web browser.</html>");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String hrefString = potentialObjectToPreview.getHref();
+ try {
+ Desktop.getDesktop().browse(new URI(hrefString));
+ }
+ catch (Exception ex) {
+ logger.error("Failed while trying to open the URL in a standard browser; URL was: " +
+ hrefString + "\nException was: " + ex + "\n" + ex.getStackTrace());
+ };
+ }
+ };
+
+ this.doHealthCheckAction = new AbstractAction(
+ "Check monitoring status",
+ ResourceManager
+ .getImageIcon(ResourceManager.EXECUTE_HEALTH_CHECK_ICON)) {
+ // Tooltip
+ {
+ this
+ .putValue(
+ SHORT_DESCRIPTION,
+ "<html>Fetch the latest monitoring data for selected "
+ + typeToPreview.getTypeName()
+ + ".<br>"
+ + "Data will be obtained from the Service Catalogue and displayed in a popup window.</html>");
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ // if it is the expanded that we are looking at, need to extract
+ // the 'associated' object
+ ResourceLink resourceForHealthCheck = (potentialObjectToPreview instanceof LoadingExpandedResource ? ((LoadingExpandedResource) potentialObjectToPreview)
+ .getAssociatedObj()
+ : potentialObjectToPreview);
+
+ ServiceHealthChecker.checkResource(resourceForHealthCheck);
+ }
+ };
+
+ tbSelectedItemActions = new JToolBar(JToolBar.HORIZONTAL);
+ tbSelectedItemActions.setBorderPainted(true);
+ tbSelectedItemActions.setBorder(BorderFactory.createEmptyBorder(5, 5,
+ 5, 3));
+ tbSelectedItemActions.setFloatable(false);
+ if (typeToPreview.isSuitableForAddingToServicePanel()) {
+ tbSelectedItemActions.add(addToServicePanelAction);
+ }
+ if (typeToPreview.isSuitableForAddingAllToServicePanel()) {
+ tbSelectedItemActions.add(addAllOperationsToServicePanelAction);
+ }
+ if (typeToPreview.isSuitableForAddingToWorkflowDiagram()) {
+ tbSelectedItemActions.add(addToWorkflowDiagramAction);
+ }
+ if (typeToPreview.isSuitableForHealthCheck()) {
+ tbSelectedItemActions.add(doHealthCheckAction);
+ }
+ tbSelectedItemActions.add(openInBioCatalogueAction);
+
+ // *** Prepare search results status panel ***
+
+ GridBagConstraints c = new GridBagConstraints();
+ jpSearchStatus = new JPanel(new GridBagLayout());
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 0;
+ jpSearchStatus.add(tbSelectedItemActions, c);
+
+ jlSearchStatus = new JLabel();
+ jlSearchStatus.setIconTextGap(20);
+ c.weightx = 1.0;
+ c.insets = new Insets(0, 20, 0, 0);
+ jpSearchStatus.add(jlSearchStatus, c);
+
+ if (parentMainSearchResultsPanel.getFilterTreePaneFor(typeToPreview) != null) {
+ Dimension preferredSize = new Dimension(200,
+ parentMainSearchResultsPanel.getFilterTreePaneFor(
+ typeToPreview).getTreeToolbarPreferredSize().height);
+
+ // HACK: due to concurrency issues, sometimes this doesn't work
+ // correctly -
+ // to rectify the problem using the hard-coded value that was
+ // correct at
+ // the time of coding...
+ if (preferredSize.height < 30) {
+ preferredSize.height = 33;
+ }
+
+ jpSearchStatus.setPreferredSize(preferredSize);
+ }
+
+ // *** Create list to hold search results and wrap it into a scroll pane
+ // ***
+ resultsListingModel = new DefaultListModel();
+ jlResultsListing = new JList(resultsListingModel);
+ jlResultsListing.setDoubleBuffered(true);
+ jlResultsListing.setCellRenderer(listingCellRenderer);
+ jlResultsListing.addMouseListener(this);
+ jlResultsListing.addMouseMotionListener(this);
+ jlResultsListing.setBackground(thisPanel.getBackground());
+
+ jlResultsListing.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ jlResultsListing.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ // update value to be used in contextual menu click handler
+ // to act on the just-selected entry
+ potentialObjectToPreview = getResourceSelectedInJList();
+
+ if (potentialObjectToPreview != null) {
+
+ // only enable actions in the menu if the list entry
+ // that is being
+ // clicked on is beyond the initial 'loading' state
+ boolean shown = !isListEntryOnlyWithInitialDetails(potentialObjectToPreview);
+ boolean shownAndNotArchived = shown && !isArchived(potentialObjectToPreview);
+ addToServicePanelAction
+ .setEnabled(shownAndNotArchived);
+ addAllOperationsToServicePanelAction
+ .setEnabled(shownAndNotArchived && !(potentialObjectToPreview instanceof RestMethod));
+ addToWorkflowDiagramAction
+ .setEnabled(shownAndNotArchived);
+ openInBioCatalogueAction
+ .setEnabled(shown);
+ doHealthCheckAction
+ .setEnabled(shown);
+
+ return;
+ }
+ }
+
+ // disable actions if nothing is selected in the list or if
+ // selection is still "adjusting"
+ addToServicePanelAction.setEnabled(false);
+ addAllOperationsToServicePanelAction.setEnabled(false);
+ addToWorkflowDiagramAction.setEnabled(false);
+ openInBioCatalogueAction.setEnabled(false);
+ doHealthCheckAction.setEnabled(false);
+ }
+ });
+
+ spResultsListing = new JScrollPane(jlResultsListing);
+ spResultsListing.getVerticalScrollBar().addAdjustmentListener(
+ new AdjustmentListener() {
+ public void adjustmentValueChanged(AdjustmentEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ // load missing details on adjusting the scroll bar
+ //
+ // only start loading more results in case if the
+ // value is "not adjusting" -
+ // this means that the mouse has been released and
+ // is not dragging the scroll bar
+ // any more, so effectively the user has stopped
+ // scrolling
+ checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+ }
+ }
+ });
+
+ // tie components to the class panel itself
+ this.resetSearchResultsListing(true);
+
+ // *** Create CONTEXTUAL MENU ***
+
+ contextualMenu = new JPopupMenu();
+ if (typeToPreview.isSuitableForAddingToServicePanel()) {
+ contextualMenu.add(addToServicePanelAction);
+ contextualMenu.add(addAllOperationsToServicePanelAction);
+ }
+ if (typeToPreview.isSuitableForAddingToWorkflowDiagram()) {
+ contextualMenu.add(addToWorkflowDiagramAction);
+ }
+ if (typeToPreview.isSuitableForHealthCheck()) {
+ contextualMenu.add(doHealthCheckAction);
+ }
+ contextualMenu.add(openInBioCatalogueAction);
+ }
+
+ /**
+ * Allows to set the search status by supplying the message to display.
+ */
+ protected void setSearchStatusText(final String statusString,
+ final boolean spinnerActive) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jlSearchStatus
+ .setIcon(spinnerActive ? ResourceManager
+ .getImageIcon(ResourceManager.BAR_LOADER_ORANGE)
+ : null);
+
+ jlSearchStatus.setText(statusString);
+ jlSearchStatus.setToolTipText("<html>"
+ + Util.ensureLineLengthWithinString(statusString,
+ SEARCH_STATUS_TOOLTIP_LINE_LENGTH, false)
+ + "</html>");
+ }
+ });
+ }
+
+ /**
+ * This helper method is used to initialise this panel. Also invoked when
+ * search results need to be cleared.
+ *
+ * @param showSuggestion
+ * <code>true</code> should be used on first load of the panel -
+ * in that case a suggestion would be displayed to perform a
+ * search, tag search or start directly with filtering;<br/>
+ * <code>false</code> to be used when resetting the panel after
+ * perfoming the search, but not finding any results.
+ */
+ public void resetSearchResultsListing(boolean showSuggestion) {
+ setSearchStatusText("No searches were made yet", false);
+
+ String labelText = "<html><center>"
+ + (showSuggestion ? "You can find "
+ + this.typeToPreview.getCollectionName()
+ + " by typing a search query."
+ + (this.typeToPreview.isSuitableForFiltering() ? "<br><br>Alternatively, you can select some filters from the tree on the left."
+ : "")
+ : "There are no "
+ + this.typeToPreview.getCollectionName()
+ + " that match your search criteria<br><br>"
+ + "Please try making the search query shorter or selecting fewer filters")
+ + "</center></html>";
+
+ JLabel jlMainLabel = new JLabel(labelText, JLabel.CENTER);
+ jlMainLabel.setFont(jlMainLabel.getFont().deriveFont(Font.PLAIN, 16));
+ jlMainLabel.setBorder(BorderFactory.createEtchedBorder());
+
+ this.removeAll();
+ this.setLayout(new BorderLayout(0, 0));
+ this.add(jpSearchStatus, BorderLayout.NORTH);
+ this.add(jlMainLabel, BorderLayout.CENTER);
+ this.validate();
+
+ // disable the toolbar actions
+ this.addToServicePanelAction.setEnabled(false);
+ this.addToWorkflowDiagramAction.setEnabled(false);
+ this.openInBioCatalogueAction.setEnabled(false);
+ this.doHealthCheckAction.setEnabled(false);
+ this.addAllOperationsToServicePanelAction.setEnabled(false);
+ }
+
+ /**
+ * Statistics will be rendered along with the collection of found items.
+ *
+ * @param searchInstance
+ * SearchInstance containing search results to render.
+ */
+ public void renderResults(SearchInstance searchInstance) {
+ // make the current search instance available globally within this class
+ this.searchInstance = searchInstance;
+
+ // stop spinner icon on the tab that is populated and add number of
+ // results
+ parentMainSearchResultsPanel.setDefaultIconForTab(typeToPreview);
+ parentMainSearchResultsPanel.setDefaultTitleForTabWithSuffix(
+ typeToPreview, " ("
+ + searchInstance.getSearchResults()
+ .getTotalMatchingItemCount() + ")");
+
+ // if nothing was found - display notification and finish result
+ // processing
+ if (searchInstance.getSearchResults().getTotalMatchingItemCount() == 0) {
+ resetSearchResultsListing(false);
+
+ // must happen after resetting the listing, as it replaces the
+ // default status text
+ setSearchStatusText("No results found for "
+ + searchInstance.getDescriptionStringForSearchStatus(),
+ false);
+ return;
+ }
+
+ // populate results
+ if (searchInstance.getSearchResults().getTotalMatchingItemCount() > 0) {
+ // populate the list box with users
+
+ List<? extends ResourceLink> foundItems = searchInstance
+ .getSearchResults().getFoundItems();
+ for (ResourceLink item : foundItems) {
+ resultsListingModel.addElement(item);
+ }
+ }
+
+ // update the UI once contents are ready
+ thisPanel.removeAll();
+ thisPanel.setLayout(new BorderLayout(0, 0));
+ thisPanel.add(jpSearchStatus, BorderLayout.NORTH);
+ thisPanel.add(spResultsListing, BorderLayout.CENTER);
+ thisPanel.repaint();
+
+ // automatically start loading details for the first section of result
+ // listing
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+ }
+ });
+
+ // *** Also update status text ***
+
+ setSearchStatusText("Search results for "
+ + searchInstance.getDescriptionStringForSearchStatus(), false);
+ }
+
+ /**
+ * Check if details are fetched for all result entries that are currently
+ * visible in the JList.
+ *
+ * If some are not yet loaded, identifies the page in the index of
+ * corresponding resources to fetch details.
+ *
+ * When done, recursively calls itself again to verify that no more entries
+ * need further details loaded.
+ */
+ private void checkAllEntriesInTheVisiblePartOfJListAreLoaded() {
+ int firstVisibleIndex = jlResultsListing.getFirstVisibleIndex();
+
+ if (firstVisibleIndex >= 0) {
+ int lastVisibleIndex = jlResultsListing.getLastVisibleIndex();
+
+ final int firstNotFetchedMatchingItemIndex = searchInstance
+ .getSearchResults().getFirstMatchingItemIndexNotYetFetched(
+ firstVisibleIndex, lastVisibleIndex);
+ final int pageToFetchNumber = searchInstance.getSearchResults()
+ .getMatchingItemPageNumberFor(
+ firstNotFetchedMatchingItemIndex);
+
+ // check if found a valid page to load
+ if (pageToFetchNumber != -1) {
+ int numberOfResourcesPerPageForThisResourceType = searchInstance
+ .getSearchResults().getTypeOfResourcesInTheResultSet()
+ .getApiResourceCountPerIndexPage();
+
+ int firstListIndexToLoad = searchInstance.getSearchResults()
+ .getFirstItemIndexOn(pageToFetchNumber); // first
+ // element
+ // on the
+ // page that
+ // is about
+ // to be
+ // loaded
+ int countToLoad = Math.min(
+ numberOfResourcesPerPageForThisResourceType, // if the
+ // last
+ // page
+ // isn't
+ // full,
+ // need
+ // to
+ // mark
+ // less
+ // items
+ // than
+ // the
+ // full
+ // page
+ resultsListingModel.getSize() - firstListIndexToLoad);
+
+ // mark the next "page" of items in the JList as "loading" -
+ // but also mark them in the SearchResults backing list, so
+ // that next calls to this listener are aware of the previous
+ // items that were marked as "loading"
+ for (int i = firstListIndexToLoad; i < firstListIndexToLoad
+ + countToLoad; i++) {
+ ((LoadingResource) searchInstance.getSearchResults()
+ .getFoundItems().get(i)).setLoading(true);
+ }
+
+ // update the UI to show 'loading' state on relevant entries
+ renderFurtherResults(searchInstance, firstListIndexToLoad,
+ countToLoad);
+
+ // now start loading data for the 'loading' entries
+ final CountDownLatch latch = new CountDownLatch(1);
+ new Thread("Search via the API") {
+ public void run() {
+ try {
+ searchInstance.fetchMoreResults(
+ parentMainSearchResultsPanel, latch,
+ thisPanel, pageToFetchNumber);
+ } catch (Exception e) {
+ logger.error("Error while searching via the Service Catalogue API", e);
+
+ }
+ }
+ }.start();
+
+ // wait for the previous portion of results to load, then fetch
+ // the next portion
+ new Thread(
+ "Fetch more another page of details for search results") {
+ public void run() {
+ try {
+ latch.await();
+ checkAllEntriesInTheVisiblePartOfJListAreLoaded();
+ } catch (InterruptedException e) {
+ logger
+ .error(
+ "Failed to wait for the previous page of results to load to check if "
+ + "another one needs loading as well. Details in the attache exception.",
+ e);
+ }
+ }
+ }.start();
+
+ }
+ }
+ }
+
+ /**
+ * Tests whether {@link ResourceLink} object corresponding to an entry in
+ * the search results list is in the state where only the first (initial)
+ * fragment of data was loaded (through BioCatalogue LITE JSON API) that
+ * contains just the title + URL of the resource.
+ *
+ * @param resource
+ * @return
+ */
+ private boolean isListEntryOnlyWithInitialDetails(ResourceLink resource) {
+ return (resource instanceof LoadingResource);
+ }
+
+ private boolean isArchived(ResourceLink resource) {
+ if (listingCellRenderer instanceof ExpandableOnDemandLoadedListCellRenderer) {
+ ExpandableOnDemandLoadedListCellRenderer r = (ExpandableOnDemandLoadedListCellRenderer) listingCellRenderer;
+ return r.shouldBeHidden(resource);
+ }
+ return false;
+ }
+
+
+ // ***** Callbacks for MouseListener *****
+
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseEntered(MouseEvent e) { /* NOT IN USE */
+ }
+
+ public void mouseExited(MouseEvent e) { /* NOT IN USE */
+ }
+
+ public void mousePressed(MouseEvent e) {
+ // checked in both mousePressed() & mouseReleased() for cross-platform
+ // operation
+ maybeShowPopupMenu(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ // checked in both mousePressed() & mouseReleased() for cross-platform
+ // operation
+ maybeShowPopupMenu(e);
+ }
+
+ // ***** Callbacks for MouseMotionListener *****
+
+ public void mouseMoved(MouseEvent e) {
+ }
+
+ public void mouseDragged(MouseEvent e) { /* do nothing */
+ }
+
+ /**
+ * Gets the selected object from the specified list. Used for previewing
+ * items through double-clicks and contextual menu.
+ *
+ * @return <code>null</code> if no selection in the list,
+ * <code>ResourceLink</code> object that is currently selected
+ * otherwise.
+ */
+ private ResourceLink getResourceSelectedInJList() {
+ return (jlResultsListing.getSelectedIndex() == -1 ? null
+ : (ResourceLink) jlResultsListing.getSelectedValue());
+ }
+
+ private void maybeShowPopupMenu(MouseEvent e) {
+ if (e.getSource().equals(jlResultsListing) && e.isPopupTrigger()
+ && jlResultsListing.locationToIndex(e.getPoint()) != -1) {
+ // select the entry in the list that triggered the event to show
+ // this popup menu
+ jlResultsListing.setSelectedIndex(jlResultsListing
+ .locationToIndex(e.getPoint()));
+
+ // update value to be used in contextual menu click handler to act
+ // on the just-selected entry
+ potentialObjectToPreview = getResourceSelectedInJList();
+
+ // show the contextual menu
+ this.contextualMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ // *** Callbacks for SearchResultsRenderer ***
+
+ public void renderInitialResults(final SearchInstance si) {
+ // NB! critical to have UI update done within the invokeLater()
+ // method - this is to prevent UI from 'flashing' and to
+ // avoid concurrency-related errors
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // make sure to remove any old results from the list model!
+ resultsListingModel.clear();
+
+ // display the partial search results
+ logger.debug("Started rendering initial search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+ renderResults(si);
+ logger.debug("Finished rendering initial search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+ }
+ });
+ }
+
+ public void renderFurtherResults(SearchInstance si, int startIndex,
+ int count) {
+ renderFurtherResults(si, startIndex, count, false);
+ }
+
+ public void renderFurtherResults(final SearchInstance si,
+ final int startIndex, final int count,
+ final boolean disableListDataListeners) {
+ logger.debug("Started rendering further search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+
+ // NB! very important to remove all listeners here, so that the JList
+ // won't "freeze"
+ // on updating the components
+ ListDataListener[] listeners = null;
+ if (disableListDataListeners) {
+ listeners = resultsListingModel.getListDataListeners();
+ for (ListDataListener listener : listeners) {
+ resultsListingModel.removeListDataListener(listener);
+ }
+ }
+
+ for (int i = startIndex; i < startIndex + count
+ && i < resultsListingModel.getSize(); i++) {
+ resultsListingModel.set(i, searchInstance.getSearchResults()
+ .getFoundItems().get(i));
+ }
+
+ // reset all listeners in case they were removed
+ if (disableListDataListeners) {
+ for (ListDataListener listener : listeners) {
+ resultsListingModel.addListDataListener(listener);
+ }
+ }
+
+ // NB! critical to have UI update done within the invokeLater()
+ // method - this is to prevent UI from 'flashing' and to
+ // avoid some weird errors
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ jlResultsListing.validate();
+ jlResultsListing.repaint();
+
+ logger.debug("Finished rendering further search results for "
+ + si.getResourceTypeToSearchFor().getCollectionName());
+ }
+ });
+ }
+
+ private void switchToDesignPerspective() {
+ if (designPerspective == null) {
+ for (PerspectiveSPI perspective : Workbench.getInstance()
+ .getPerspectives().getPerspectives()) {
+ if (perspective.getText().equalsIgnoreCase("design")) {
+ designPerspective = perspective;
+ break;
+ }
+ }
+ }
+
+ if (designPerspective != null) {
+ ModelMap.getInstance().setModel(
+ ModelMapConstants.CURRENT_PERSPECTIVE, designPerspective);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
new file mode 100644
index 0000000..a3fca27
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsMainPanel.java
@@ -0,0 +1,498 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JToggleButton;
+
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+import net.sf.taverna.biocatalogue.model.search.SearchInstanceTracker;
+import net.sf.taverna.biocatalogue.model.search.SearchOptions;
+import net.sf.taverna.biocatalogue.model.search.ServiceFilteringSettings;
+import net.sf.taverna.biocatalogue.ui.filtertree.FilterTreePane;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+import org.apache.log4j.Logger;
+
+/**
+ * This class represents the main panel that deals with the status
+ * and results of the current search.
+ *
+ * It has a status label, spinner to depict search in progress,
+ * actual search results split into tabs by their type, a toolbar
+ * with search history, favourite searches settings, favourite filters,
+ * ability to restart last search, etc.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResultsMainPanel extends JPanel implements ActionListener, SearchInstanceTracker
+{
+ private final SearchResultsMainPanel instanceOfSelf;
+ private Logger logger;
+
+ private LinkedHashMap<TYPE, JComponent> searchResultTabs;
+ private Map<TYPE, SearchResultsListingPanel> searchResultListings;
+
+ // holds a reference to the instance of the search instances in the current context
+ // that should be active at the moment (will aid early termination of older searches
+ // when new ones are started)
+ private Map<TYPE, SearchInstance> currentSearchInstances;
+
+ // holds a map of references to the current instances of filter trees per resource type
+ private Map<TYPE, FilterTreePane> currentFilterPanes;
+
+
+ // COMPONENTS
+ private JTabbedPane tabbedSearchResultPanel;
+
+ protected JToggleButton bToggleSearchHistory;
+ protected JButton bRefreshLastSearch;
+ protected JButton bClearSearchResults;
+
+
+ public SearchResultsMainPanel()
+ {
+ this.instanceOfSelf = this;
+ MainComponentFactory.getSharedInstance();
+ this.logger = Logger.getLogger(SearchResultsMainPanel.class);
+
+ this.currentSearchInstances = new HashMap<TYPE,SearchInstance>();
+
+ this.searchResultListings = new HashMap<TYPE, SearchResultsListingPanel>();
+ this.currentFilterPanes = new HashMap<TYPE,FilterTreePane>();
+ this.searchResultTabs = new LinkedHashMap<TYPE, JComponent>(); // crucial to preserve the order -- so that these tabs always appear in the UI in the same order!
+ initialiseResultTabsMap();
+
+ initialiseUI();
+ }
+
+
+ private void initialiseUI()
+ {
+ // create a panel for tabbed listings of search results
+ this.tabbedSearchResultPanel = new JTabbedPane();
+ reloadResultTabsFromMap();
+
+ // pack all main components together
+ JPanel jpMainResultsPanel = new JPanel(new BorderLayout());
+ jpMainResultsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 3));
+
+ jpMainResultsPanel.add(tabbedSearchResultPanel, BorderLayout.CENTER);
+
+
+ // --- Put together all parts ---
+ // main components in the middle, toolbar on the right
+ this.setMinimumSize(new Dimension(450, 50));
+ this.setLayout(new BorderLayout());
+ this.add(jpMainResultsPanel, BorderLayout.CENTER);
+
+ // FIXME - add toolbar to the main window!
+// this.add(tbSearchActions, BorderLayout.EAST);
+ }
+
+
+
+ // ----- Hiding / Showing tabs for various search result types -----
+
+ /**
+ * Dynamically populates the map of resource types and components that represent these types
+ * in the tabbed pane -- this is only to be done once during the initialisation.
+ */
+ private void initialiseResultTabsMap()
+ {
+ for (TYPE t : TYPE.values()) {
+ toggleResultTabsInMap(t, t.isDefaultSearchType());
+ }
+ }
+
+
+ /**
+ * Adds or removes a tab for a specified type of resource.
+ *
+ * @param type Resource type for which the tab is to be added / removed.
+ * @param doShowTab Defines whether to add or remove tab for this resource type.
+ */
+ public void toggleResultTabsInMap(TYPE type, boolean doShowTab)
+ {
+ JPanel jpResultTabContent = null;
+
+ if (doShowTab)
+ {
+ jpResultTabContent = new JPanel(new GridLayout());
+
+ // decide if this resource type supports filtering
+ if (type.isSuitableForFiltering()) {
+ FilterTreePane filterTreePane = new FilterTreePane(type);
+ this.currentFilterPanes.put(type, filterTreePane);
+ }
+ else {
+ // not suitable for filtering - record this in a map
+ this.currentFilterPanes.put(type, null);
+ }
+
+
+ SearchResultsListingPanel resultsListingPanel = new SearchResultsListingPanel(type, this);
+ this.searchResultListings.put(type, resultsListingPanel);
+
+ if (this.currentFilterPanes.get(type) == null) {
+ jpResultTabContent.add(resultsListingPanel);
+ }
+ else {
+ JSplitPane spFiltersAndResultListing = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+ spFiltersAndResultListing.setLeftComponent(this.currentFilterPanes.get(type));
+ spFiltersAndResultListing.setRightComponent(resultsListingPanel);
+ jpResultTabContent.add(spFiltersAndResultListing);
+ }
+ }
+ else {
+ // tab for this type is being hidden - just remove the references
+ // to the search result listing and to filter pane
+ this.searchResultListings.put(type, null);
+ this.currentFilterPanes.put(type, null);
+ }
+
+ this.searchResultTabs.put(type, jpResultTabContent);
+ }
+
+
+ /**
+ * (Re-)loads the user interface from the internal map.
+ */
+ public void reloadResultTabsFromMap()
+ {
+ Component selectedTabsComponent = tabbedSearchResultPanel.getSelectedComponent();
+ tabbedSearchResultPanel.removeAll();
+ for (TYPE type : this.searchResultTabs.keySet()) {
+ JComponent c = this.searchResultTabs.get(type);
+ if (c != null) {
+ tabbedSearchResultPanel.addTab(type.getCollectionName(), type.getIcon(), c, type.getCollectionTabTooltip());
+ }
+ }
+
+ // attempt to re-select the same tab that was open before reloading
+ try {
+ tabbedSearchResultPanel.setSelectedComponent(selectedTabsComponent);
+ }
+ catch (IllegalArgumentException e) {
+ // failed - probably previously selected tab got removed - select the first one
+ tabbedSearchResultPanel.setSelectedIndex(0);
+ }
+ }
+
+
+ /**
+ * @param resourceType Resource type to look for.
+ * @return Current index of the tab in the results tabbed pane view
+ * that holds a component showing search results for this type.
+ * Returns <code>-1</code> if requested type is not currently displayed.
+ */
+ protected int getTabIndexForResourceType(TYPE resourceType) {
+ return (tabbedSearchResultPanel.indexOfComponent(searchResultTabs.get(resourceType)));
+ }
+
+
+ // ----- ------
+
+
+ /**
+ * This method is intended to be called when filter options in one of the tabs change.
+ * It starts the new filtering operation.
+ *
+ * Effectively it sets the filtering parameters for the SearchInstance
+ * and then starts a new search with that {@link SearchInstance} wrapped into {@link SearchOptions}.
+ *
+ * @param resourceType Resource type for which the new filtering operation is started
+ * @param filteringSettings Filtering settings for the current filtering operation
+ * obtained from the filter tree (or favourite filters).
+ */
+ public void startNewFiltering(TYPE resourceType, ServiceFilteringSettings filteringSettings)
+ {
+ SearchInstance siPreviousSearchForThisType = getCurrentSearchInstance(resourceType);
+
+ // pass on the filtering parameters to the relevant search instance (this will overwrite the old ones if any were present!)
+ if (siPreviousSearchForThisType == null) {
+ // no filterings have been done earlier for this resource type;
+ // we'll need a new (blank) query search SearchInstance and
+ // wrap it into a service filtering SearchInstance
+ siPreviousSearchForThisType = new SearchInstance(new SearchInstance("", resourceType), filteringSettings);
+ }
+ else {
+ if (!siPreviousSearchForThisType.isServiceFilteringSearch()) {
+ // just wrap existing search instance that was (probably) transferred from the Search tab
+ // into another SearchInstance that explicitly deals with service filtering
+ siPreviousSearchForThisType = new SearchInstance(siPreviousSearchForThisType, filteringSettings);
+ }
+ else {
+ // previous search instance dealt with filtering -
+ // simply update the filtering settings (but before that
+ // run a 'deep copy' of the original search instance, so
+ // that the new one gets a new reference; this will aid
+ // in early termination of older filterings)
+ siPreviousSearchForThisType = siPreviousSearchForThisType.deepCopy();
+ siPreviousSearchForThisType.setFilteringSettings(filteringSettings);
+ }
+ }
+
+ // proceed with "search" as usual - it will treat this search instance differently
+ // from "ordinary" search
+ startNewSearch(new SearchOptions(siPreviousSearchForThisType));
+ }
+
+
+ /**
+ * Worker method responsible for starting a new search via the API.
+ *
+ * This method is to be used when a *new* search is started. It will
+ * mainly make updates to the UI and store the new search in the history.
+ */
+ public void startNewSearch(final SearchOptions searchOptions)
+ {
+ try
+ {
+ for (final TYPE resourceType : searchOptions.getResourceTypesToSearchFor())
+ {
+ SearchInstance si = null;
+ switch (searchOptions.getSearchType()) {
+ case QuerySearch: si = new SearchInstance(searchOptions.getSearchString(), resourceType);
+ resetAllFilterPanes(searchOptions.getSearchString());
+ break;
+
+ case TagSearch: if (resourceType.isSuitableForTagSearch()) {
+ si = new SearchInstance(searchOptions.getSearchTags(), resourceType);
+ resetAllFilterPanes(searchOptions.getSearchString());
+ }
+ else {
+ // FIXME implement this... - show "no results" in the appropriate tab
+ JOptionPane.showMessageDialog(null, "'" + resourceType.getTypeName() + "' resource type is not suitable for tag search");
+ }
+ break;
+
+ case Filtering: if (resourceType.isSuitableForFiltering()) {
+ si = searchOptions.getPreconfiguredSearchInstance();
+ }
+ else {
+ // FIXME implement this... - show "no results" in the appropriate tab
+ JOptionPane.showMessageDialog(null, "'" + resourceType.getTypeName() + "' resource type is not suitable for filtering");
+ }
+ break;
+ }
+
+ if (si.isEmptySearch()) {
+ clearListingPanels();
+ return;
+ }
+
+ // Record 'this' search instance and set it as the new "primary" one for
+ // this resource type;
+ // (this way it if a new search thread starts afterwards, it is possible to
+ // detect this and stop the 'older' search, because it is no longer relevant)
+ registerSearchInstance(resourceType, si);
+
+ // start spinner icon for this tab to indicate search in progress - also show status message
+ setSpinnerIconForTab(resourceType);
+ setDefaultTitleForTab(resourceType);
+ searchResultListings.get(resourceType).setSearchStatusText("Searching for " + si.getDescriptionStringForSearchStatus() + "...", true);
+
+
+ // start the actual search
+ final SearchInstance siToStart = si;
+ new Thread(searchOptions.getSearchType() + " of " + resourceType.getCollectionName() + " via the API") {
+ public void run() {
+ siToStart.startNewSearch(instanceOfSelf, new CountDownLatch(1), searchResultListings.get(resourceType)); // FIXME - the new countdown latch is never used...
+ }
+ }.start();
+ }
+ }
+ catch (Exception e) {
+ logger.error("Error while searching via the Service Catalogue API. Error details attached.", e);
+ }
+
+}
+
+
+
+ /**
+ * Clears selection of filtering criteria and collapses any expanded nodes
+ * in all filter tree panes.<br/><br/>
+ *
+ * To be used for resetting all filter panes when the new query / tag
+ * search starts.
+ * @param queryString
+ */
+ private void resetAllFilterPanes(String queryString) {
+ for (FilterTreePane filterTreePane : this.currentFilterPanes.values()) {
+ if (filterTreePane != null) {
+ filterTreePane.clearSelection();
+ filterTreePane.collapseAll();
+ if ((queryString != null) && !queryString.isEmpty()) {
+ filterTreePane.applyQueryString(queryString);
+ }
+ }
+ }
+ }
+
+
+ protected void setSpinnerIconForTab(TYPE resourceType) {
+ tabbedSearchResultPanel.setIconAt(getTabIndexForResourceType(resourceType), ResourceManager.getImageIcon(ResourceManager.SPINNER));
+ }
+
+ protected void setDefaultIconForTab(TYPE resourceType) {
+ this.tabbedSearchResultPanel.setIconAt(getTabIndexForResourceType(resourceType), resourceType.getIcon());
+ }
+
+
+ /**
+ * Same as {@link SearchResultsMainPanel#setDefaultTitleForTab(TYPE)},
+ * but allows to append a specified string at the end of the default title.
+ *
+ * @param resourceType
+ * @param suffix
+ */
+ protected void setDefaultTitleForTabWithSuffix(TYPE resourceType, String suffix) {
+ tabbedSearchResultPanel.setTitleAt(getTabIndexForResourceType(resourceType),
+ resourceType.getCollectionName() + (suffix == null ? "" : suffix) );
+ }
+
+
+ /**
+ * Sets default title for a tab that contains panel representing
+ * search results of the specified resource type. Default title
+ * is just a name of the collections of resources in that tab.
+ *
+ * @param resourceType
+ */
+ protected void setDefaultTitleForTab(TYPE resourceType) {
+ setDefaultTitleForTabWithSuffix(resourceType, null);
+ }
+
+
+ /**
+ * @param resourceType Resource type for which the search result listing panel is requested.
+ * @return Reference to the requested panel or <code>null</code> if a tab for the specified
+ * <code>resourceType</code> does not exist.
+ */
+ protected SearchResultsListingPanel getResultsListingFor(TYPE resourceType) {
+ return (this.searchResultListings.get(resourceType));
+ }
+
+
+ /**
+ * @param resourceType Resource type for which filter tree pane is to be returned.
+ * @return Reference to the requested filter tree pane or <code>null</code> if
+ * there is no search result tab for the specified <code>resourceType</code>
+ * (or if that <code>resourceType</code> does not support filtering).
+ */
+ protected FilterTreePane getFilterTreePaneFor(TYPE resourceType) {
+ return (this.currentFilterPanes.get(resourceType));
+ }
+
+
+ // *** Callback for ActionListener interface ***
+
+ public void actionPerformed(ActionEvent e)
+ {
+ // FIXME -- remove this...
+// if (e.getSource().equals(bRefreshLastSearch))
+// {
+// // restore state of the search options panel
+// pluginPerspectiveMainComponent.getSearchTab().restoreSearchOptions(siPreviousSearch);
+//
+// // completely re-run the previous search
+// startNewSearch(siPreviousSearch);
+// }
+// else if (e.getSource().equals(bClearSearchResults))
+// {
+// // manual request to clear results of previous search
+//
+// // if any search thread was running, deactivate it as well
+// if (isSearchThreadRunning()) {
+// vCurrentSearchThreadID.set(0, null);
+// }
+//
+// // changing both - spinner image and the status text simultaneously
+// setSearchStatusText("No searches were made yet", false);
+//
+// // removed the previous search, hence makes no sense to allow to clear "previous" results again
+// bClearSearchResults.setEnabled(false);
+//
+// // only remove data about previous search and disable refresh button
+// // if no search thread is currently running - otherwise keep the button
+// // enabled in case there is a need to re-start the search if it's frozen
+// if (!isSearchThreadRunning()) {
+// siPreviousSearch = null;
+// bRefreshLastSearch.setEnabled(false);
+// }
+//
+// // also notify tabbed results panel, so that it removes the actual search results
+// searchResultsPanel.clearPreviousSearchResults();
+// }
+// else if (e.getSource().equals(this.jclPreviewCurrentFilteringCriteria))
+// {
+// // open a preview window showing current filtering settings
+// SwingUtilities.invokeLater(new Runnable()
+// {
+// public void run() {
+// ServiceFilteringSettingsPreview p = new ServiceFilteringSettingsPreview(siPreviousSearch.getFilteringSettings());
+// p.setVisible(true);
+// }
+// });
+//
+// }
+ }
+
+
+ // *** Callbacks for SearchInstanceTracker interface ***
+
+ public synchronized void clearPreviousSearchInstances() {
+ this.currentSearchInstances.clear();
+ }
+
+ public synchronized boolean isCurrentSearchInstance(TYPE searchType, SearchInstance searchInstance) {
+ // NB! it is crucial to perform test by reference here (hence the use of "==", not equals()!)
+ return (this.currentSearchInstances.get(searchType) == searchInstance);
+ }
+
+ public synchronized void registerSearchInstance(TYPE searchType, SearchInstance searchInstance) {
+ this.currentSearchInstances.put(searchType, searchInstance);
+ }
+
+ public synchronized SearchInstance getCurrentSearchInstance(TYPE searchType) {
+ return this.currentSearchInstances.get(searchType);
+ }
+
+public void clearListingPanels() {
+ for (SearchResultsListingPanel listingPanel : searchResultListings.values()) {
+ listingPanel.resetSearchResultsListing(true);
+ }
+ for (TYPE t : searchResultListings.keySet()) {
+ setDefaultTitleForTab(t);
+ }
+
+}
+
+public void clearSearch() {
+ clearPreviousSearchInstances();
+ clearListingPanels();
+ for (FilterTreePane treePanel : currentFilterPanes.values()) {
+ treePanel.reset();
+ }
+}
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/52fd79dd/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
----------------------------------------------------------------------
diff --git a/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
new file mode 100644
index 0000000..58ee5fd
--- /dev/null
+++ b/taverna-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/ui/search_results/SearchResultsRenderer.java
@@ -0,0 +1,47 @@
+package net.sf.taverna.biocatalogue.ui.search_results;
+
+import net.sf.taverna.biocatalogue.model.search.SearchInstance;
+
+/**
+ * This interfaces avoids coupling of search engine classes
+ * directly with the UI classes.
+ *
+ * Search engines would send new chunks of search results
+ * to the <code>SearchResultsRenderer</code> as soon as
+ * they become available.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public interface SearchResultsRenderer
+{
+ /**
+ * Render initial fragment of search results. This includes
+ * creating a new <code>ListModel</code> in the results listing
+ * and populating it with the first chunk of results and placeholders
+ * for those results that haven't been fetched yet.
+ *
+// * @param searchThreadID This is the ID of the thread that initiated search // FIXME
+// * from within the UI component, rather than the ID of
+// * the real worker search engine's search thread.
+// * It is used to test whether that thread is still active -
+// * to determine whether the partial results need to be rendered.
+ * @param si The search instance containing partial search results to be rendered.
+ */
+ void renderInitialResults(SearchInstance si);
+
+
+ /**
+ * Update the results listing with a specific fragment of the collection
+ * of search results.
+ *
+ * @param si The search instance containing partial search results to be rendered.
+ * @param startIndex First index in the result collection to update.
+ * @param count Number of result listing entries to update.
+ * <br/>
+ * At most <code>count</code> results will be rendered - less can be rendered
+ * if end of result list is reached earlier. <code>count</code> is normally
+ * just a page size for a specific resource type.
+ */
+ void renderFurtherResults(SearchInstance si, int startIndex, int count);
+
+}