JAL-2136 merged and resolved conflicts with 80edaa84d6d9beac9f0d2c71b50b7b56fd393427
authortcofoegbu <tcnofoegbu@dundee.ac.uk>
Mon, 20 Feb 2017 12:25:32 +0000 (12:25 +0000)
committertcofoegbu <tcnofoegbu@dundee.ac.uk>
Mon, 20 Feb 2017 12:25:32 +0000 (12:25 +0000)
15 files changed:
1  2 
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AppletJmol.java
src/jalview/appletgui/AppletJmolBinding.java
src/jalview/appletgui/ExtJmol.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/gui/AppJmol.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewerBase.java
src/jalview/structure/StructureSelectionManager.java
test/jalview/structures/models/AAStructureBindingModelTest.java

@@@ -26,7 -26,6 +26,7 @@@ import jalview.appletgui.FeatureRendere
  import jalview.appletgui.SequenceRenderer;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
 +import jalview.io.DataSourceType;
  import jalview.io.StructureFile;
  import jalview.structure.AtomSpec;
  import jalview.structure.StructureListener;
@@@ -146,7 -145,7 +146,7 @@@ public class AppletPDBCanvas extends Pa
    StructureSelectionManager ssm;
  
    public AppletPDBCanvas(PDBEntry pdbentry, SequenceI[] seq,
 -          String[] chains, AlignmentPanel ap, String protocol)
 +          String[] chains, AlignmentPanel ap, DataSourceType protocol)
  
    {
      this.ap = ap;
  
      try
      {
-       pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol);
+       pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol, null);
  
 -      if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
 +      if (protocol == DataSourceType.PASTE)
        {
          pdbentry.setFile("INLINE" + pdb.getId());
        }
      for (int i = 0; i < pdb.getChains().size(); i++)
      {
  
 -      mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
 -              + pdb.getChains().elementAt(i).sequence.getSequenceAsString());
 +      mappingDetails
 +              .append("\n\nPDB Sequence is :\nSequence = "
 +                      + pdb.getChains().elementAt(i).sequence
 +                              .getSequenceAsString());
        mappingDetails.append("\nNo of residues = "
                + pdb.getChains().elementAt(i).residues.size() + "\n\n");
  
        // Align the sequence to the pdb
        // TODO: DNa/Pep switch
        AlignSeq as = new AlignSeq(sequence,
 -              pdb.getChains().elementAt(i).sequence,
 -              pdb.getChains().elementAt(i).isNa ? AlignSeq.DNA : AlignSeq.PEP);
 +              pdb.getChains().elementAt(i).sequence, pdb.getChains()
 +                      .elementAt(i).isNa ? AlignSeq.DNA : AlignSeq.PEP);
        as.calcScoreMatrix();
        as.traceAlignment();
        PrintStream ps = new PrintStream(System.out)
@@@ -26,7 -26,6 +26,7 @@@ import jalview.datamodel.SequenceI
  import jalview.gui.AlignmentPanel;
  import jalview.gui.FeatureRenderer;
  import jalview.gui.SequenceRenderer;
 +import jalview.io.DataSourceType;
  import jalview.io.StructureFile;
  import jalview.structure.AtomSpec;
  import jalview.structure.StructureListener;
@@@ -142,7 -141,7 +142,7 @@@ public class PDBCanvas extends JPanel i
    String errorMessage;
  
    void init(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
 -          AlignmentPanel ap, String protocol)
 +          AlignmentPanel ap, DataSourceType protocol)
    {
      this.ap = ap;
      this.pdbentry = pdbentry;
  
      try
      {
-       pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol);
+       pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol,
+               ap.alignFrame);
  
 -      if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
 +      if (protocol.equals(jalview.io.DataSourceType.PASTE))
        {
          pdbentry.setFile("INLINE" + pdb.getId());
        }
      for (int i = 0; i < pdb.getChains().size(); i++)
      {
  
 -      mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
 -              + pdb.getChains().elementAt(i).sequence.getSequenceAsString());
 +      mappingDetails
 +              .append("\n\nPDB Sequence is :\nSequence = "
 +                      + pdb.getChains().elementAt(i).sequence
 +                              .getSequenceAsString());
        mappingDetails.append("\nNo of residues = "
                + pdb.getChains().elementAt(i).residues.size() + "\n\n");
  
@@@ -25,7 -25,6 +25,7 @@@ import jalview.analysis.AnnotationSorte
  import jalview.api.AlignViewControllerGuiI;
  import jalview.api.AlignViewControllerI;
  import jalview.api.AlignViewportI;
 +import jalview.api.FeatureColourI;
  import jalview.api.FeatureRenderer;
  import jalview.api.FeatureSettingsControllerI;
  import jalview.api.SequenceStructureBinding;
@@@ -49,9 -48,7 +49,9 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.io.AnnotationFile;
  import jalview.io.AppletFormatAdapter;
 +import jalview.io.DataSourceType;
  import jalview.io.FeaturesFile;
 +import jalview.io.FileFormat;
  import jalview.io.TCoffeeScoreFile;
  import jalview.schemes.Blosum62ColourScheme;
  import jalview.schemes.BuriedColourScheme;
@@@ -104,6 -101,7 +104,6 @@@ import java.net.URLEncoder
  import java.util.Arrays;
  import java.util.Deque;
  import java.util.HashMap;
 -import java.util.Hashtable;
  import java.util.List;
  import java.util.Map;
  import java.util.StringTokenizer;
@@@ -220,7 -218,6 +220,7 @@@ public class AlignFrame extends Embmenu
      {
        viewport.setColumnSelection(columnSelection);
      }
 +    viewport.setScaleAboveWrapped(scaleAbove.getState());
  
      alignPanel = new AlignmentPanel(this, viewport);
      avc = new jalview.controller.AlignViewController(this, viewport,
     *          is protocol for accessing data referred to by file
     */
  
 -  public boolean parseFeaturesFile(String file, String type)
 +  public boolean parseFeaturesFile(String file, DataSourceType type)
    {
      return parseFeaturesFile(file, type, true);
    }
     * 
     * @param file
     *          file URL, content, or other resolvable path
 -   * @param type
 +   * @param sourceType
     *          is protocol for accessing data referred to by file
     * @param autoenabledisplay
     *          when true, display features flag will be automatically enabled if
     *          features are loaded
     * @return true if data parsed as a features file
     */
 -  public boolean parseFeaturesFile(String file, String type,
 +  public boolean parseFeaturesFile(String file, DataSourceType sourceType,
            boolean autoenabledisplay)
    {
      boolean featuresFile = false;
      try
      {
 -      Map<String, Object> colours = alignPanel.seqPanel.seqCanvas
 +      Map<String, FeatureColourI> colours = alignPanel.seqPanel.seqCanvas
                .getFeatureRenderer().getFeatureColours();
        boolean relaxedIdMatching = viewport.applet.getDefaultParameter(
                "relaxedidmatch", false);
 -      featuresFile = new FeaturesFile(file, type).parse(
 +      featuresFile = new FeaturesFile(file, sourceType).parse(
                viewport.getAlignment(), colours, true, relaxedIdMatching);
      } catch (Exception ex)
      {
        // Hide everything by the current selection - this is a hack - we do the
        // invert and then hide
        // first check that there will be visible columns after the invert.
 -      if ((viewport.getColumnSelection() != null
 -              && viewport.getColumnSelection().getSelected() != null && viewport
 -              .getColumnSelection().getSelected().size() > 0)
 +      if (viewport.hasSelectedColumns()
                || (sg != null && sg.getSize() > 0 && sg.getStartRes() <= sg
                        .getEndRes()))
        {
          hide = true;
          viewport.hideAllSelectedSeqs();
        }
 -      else if (!(toggleCols && viewport.getColumnSelection().getSelected()
 -              .size() > 0))
 +      else if (!(toggleCols && viewport.hasSelectedColumns()))
        {
          viewport.showAllHiddenSeqs();
        }
  
      if (toggleCols)
      {
 -      if (viewport.getColumnSelection().getSelected().size() > 0)
 +      if (viewport.hasSelectedColumns())
        {
          viewport.hideSelectedColumns();
          if (!toggleSeqs)
      if (alignPanel.getAlignment().getAlignmentAnnotation() != null)
      {
        for (AlignmentAnnotation aa : alignPanel.getAlignment()
 -            .getAlignmentAnnotation())
 -    {
 -      boolean visible = (aa.sequenceRef == null ? showForAlignment
 -              : showForSequences);
 -      aa.visible = visible;
 +              .getAlignmentAnnotation())
 +      {
 +        boolean visible = (aa.sequenceRef == null ? showForAlignment
 +                : showForSequences);
 +        aa.visible = visible;
        }
      }
      alignPanel.validateAnnotationDimensions(true);
      CutAndPasteTransfer cap = new CutAndPasteTransfer(true, this);
      Frame frame = new Frame();
      frame.add(cap);
 -    jalview.bin.JalviewLite.addFrame(frame, MessageManager.formatMessage(
 +    JalviewLite.addFrame(frame, MessageManager.formatMessage(
              "label.alignment_output_command",
              new Object[] { e.getActionCommand() }), 600, 500);
  
 -    FeatureRenderer fr = this.alignPanel.cloneFeatureRenderer();
 +    FileFormat fileFormat = FileFormat.valueOf(e.getActionCommand());
      cap.setText(new AppletFormatAdapter(alignPanel).formatSequences(
 -            e.getActionCommand(), viewport.getAlignment(),
 +            fileFormat, viewport.getAlignment(),
              viewport.getShowJVSuffix()));
    }
  
      return annotation;
    }
  
 -  private Map<String, Object> getDisplayedFeatureCols()
 +  private Map<String, FeatureColourI> getDisplayedFeatureCols()
    {
      if (alignPanel.getFeatureRenderer() != null
              && viewport.getFeaturesDisplayed() != null)
      FeaturesFile formatter = new FeaturesFile();
      if (format.equalsIgnoreCase("Jalview"))
      {
 -      features = formatter.printJalviewFormat(viewport
 -              .getAlignment().getSequencesArray(),
 -              getDisplayedFeatureCols());
 +      features = formatter.printJalviewFormat(viewport.getAlignment()
 +              .getSequencesArray(), getDisplayedFeatureCols());
      }
      else
      {
        url.append(appendProtocol(viewport.applet.getParameter("annotations")));
      }
  
 -    if (viewport.applet.getParameter("jnetfile") != null)
 +    if (viewport.applet.getParameter("jnetfile") != null
 +            || viewport.applet.getParameter("jpredfile") != null)
      {
        url.append("&annotations=");
 -      url.append(appendProtocol(viewport.applet.getParameter("jnetfile")));
 +      url.append(appendProtocol(viewport.applet.getParameter("jnetfile") != null ? viewport.applet
 +              .getParameter("jnetfile") : viewport.applet
 +              .getParameter("jpredfile")));
      }
  
      if (viewport.applet.getParameter("defaultColour") != null)
      }
      sg.setEndRes(viewport.getAlignment().getWidth() - 1);
      viewport.setSelectionGroup(sg);
 -    alignPanel.paintAlignment(true);
 +    // JAL-2034 - should delegate to
 +    // alignPanel to decide if overview needs
 +    // updating.
 +    alignPanel.paintAlignment(false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
      viewport.setSelectionGroup(null);
      alignPanel.idPanel.idCanvas.searchResults = null;
      alignPanel.seqPanel.seqCanvas.highlightSearchResults(null);
 -    alignPanel.paintAlignment(true);
 +    // JAL-2034 - should delegate to
 +    // alignPanel to decide if overview needs
 +    // updating.
 +    alignPanel.paintAlignment(false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
      inputText.addActionListener(this);
      Menu outputTextboxMenu = new Menu(
              MessageManager.getString("label.out_to_textbox"));
 -    for (int i = 0; i < jalview.io.AppletFormatAdapter.WRITEABLE_FORMATS.length; i++)
 +    for (String ff : FileFormat.getWritableFormats(true))
      {
 -
 -      MenuItem item = new MenuItem(
 -              jalview.io.AppletFormatAdapter.WRITEABLE_FORMATS[i]);
 +      MenuItem item = new MenuItem(ff);
  
        item.addActionListener(new java.awt.event.ActionListener()
        {
      nucleotideColour.setLabel(MessageManager.getString("label.nucleotide"));
      nucleotideColour.addActionListener(this);
      modifyPID.setLabel(MessageManager
 -            .getString("label.modify_identity_thereshold"));
 +            .getString("label.modify_identity_threshold"));
      modifyPID.addActionListener(this);
      modifyConservation.setLabel(MessageManager
 -            .getString("label.modify_conservation_thereshold"));
 +            .getString("label.modify_conservation_threshold"));
      modifyConservation.addActionListener(this);
      annotationColour.setLabel(MessageManager
              .getString("action.by_annotation"));
        }
        // resolve data source
        // TODO: this code should be a refactored to an io package
 -      String protocol = AppletFormatAdapter.resolveProtocol(pdbFile, "PDB");
 +      DataSourceType protocol = AppletFormatAdapter.resolveProtocol(
 +              pdbFile, FileFormat.PDB);
        if (protocol == null)
        {
          return false;
        }
        if (needtoadd)
        {
 -        // make a note of the access mode and add
 -        if (pdbentry.getProperty() == null)
 -        {
 -          pdbentry.setProperty(new Hashtable());
 -        }
 -        pdbentry.getProperty().put("protocol", protocol);
 +        pdbentry.setProperty("protocol", protocol);
          toaddpdb.addPDBId(pdbentry);
          alignPanel.getStructureSelectionManager()
                  .registerPDBEntry(pdbentry);
    }
  
    public void newStructureView(JalviewLite applet, PDBEntry pdb,
 -          SequenceI[] seqs, String[] chains, String protocol)
 +          SequenceI[] seqs, String[] chains, DataSourceType protocol)
    {
      // Scrub any null sequences from the array
      Object[] sqch = cleanSeqChainArrays(seqs, chains);
        System.err
                .println("JalviewLite.AlignFrame:newStructureView: No sequence to bind structure to.");
      }
 -    if (protocol == null || protocol.trim().length() == 0
 -            || protocol.equals("null"))
 +    if (protocol == null)
      {
 -      protocol = (String) pdb.getProperty().get("protocol");
 +      String sourceType = (String) pdb.getProperty("protocol");
 +      try
 +      {
 +        protocol = DataSourceType.valueOf(sourceType);
 +      } catch (IllegalArgumentException e)
 +      {
 +        // ignore
 +      }
        if (protocol == null)
        {
          System.err.println("Couldn't work out protocol to open structure: "
      {
        // register the association(s) and quit, don't create any windows.
        if (StructureSelectionManager.getStructureSelectionManager(applet)
-               .setMapping(seqs, chains, pdb.getFile(), protocol) == null)
+               .setMapping(seqs, chains, pdb.getFile(), protocol, null) == null)
        {
          System.err.println("Failed to map " + pdb.getFile() + " ("
                  + protocol + ") to any sequences");
      {
        // can only do alignments with Jmol
        // find the last jmol window assigned to this alignment
 -      jalview.appletgui.AppletJmol ajm = null, tajm;
 -      Vector jmols = applet
 -              .getAppletWindow(jalview.appletgui.AppletJmol.class);
 +      AppletJmol ajm = null, tajm;
 +      Vector jmols = applet.getAppletWindow(AppletJmol.class);
        for (int i = 0, iSize = jmols.size(); i < iSize; i++)
        {
 -        tajm = (jalview.appletgui.AppletJmol) jmols.elementAt(i);
 +        tajm = (AppletJmol) jmols.elementAt(i);
          if (tajm.ap.alignFrame == this)
          {
            ajm = tajm;
      // otherwise, create a new window
      if (applet.jmolAvailable)
      {
 -      new jalview.appletgui.AppletJmol(pdb, seqs, chains, alignPanel,
 +      new AppletJmol(pdb, seqs, chains, alignPanel,
                protocol);
        applet.lastFrameX += 40;
        applet.lastFrameY += 40;
   */
  package jalview.appletgui;
  
 +import jalview.bin.JalviewLite;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
 -import jalview.io.AppletFormatAdapter;
  import jalview.io.FileParse;
 +import jalview.io.DataSourceType;
  import jalview.io.StructureFile;
  import jalview.schemes.BuriedColourScheme;
  import jalview.schemes.HelixColourScheme;
@@@ -61,6 -60,7 +61,6 @@@ import java.awt.event.KeyListener
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.util.ArrayList;
 -import java.util.Hashtable;
  import java.util.List;
  import java.util.Vector;
  
@@@ -176,16 -176,16 +176,16 @@@ public class AppletJmol extends Embmenu
    }
  
    public AppletJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
 -          AlignmentPanel ap, String protocol)
 +          AlignmentPanel ap, DataSourceType protocol)
    {
      this.ap = ap;
      jmb = new AppletJmolBinding(this, ap.getStructureSelectionManager(),
              new PDBEntry[] { pdbentry }, new SequenceI[][] { seq },
 -            new String[][] { chains }, protocol);
 +            protocol);
      jmb.setColourBySequence(true);
      if (pdbentry.getId() == null || pdbentry.getId().length() < 1)
      {
 -      if (protocol.equals(AppletFormatAdapter.PASTE))
 +      if (protocol == DataSourceType.PASTE)
        {
          pdbentry.setId("PASTED PDB"
                  + (chains == null ? "_" : chains.toString()));
        }
      }
  
 -    if (jalview.bin.JalviewLite.debug)
 +    if (JalviewLite.debug)
      {
        System.err
                .println("AppletJmol: PDB ID is '" + pdbentry.getId() + "'");
      {
        reader = StructureSelectionManager.getStructureSelectionManager(
                ap.av.applet).setMapping(seq, chains, pdbentry.getFile(),
-               protocol);
+               protocol, null);
        // PROMPT USER HERE TO ADD TO NEW OR EXISTING VIEW?
        // FOR NOW, LETS JUST OPEN A NEW WINDOW
      }
          closeViewer();
        }
      });
 -    if (pdbentry.getProperty() == null)
 -    {
 -      pdbentry.setProperty(new Hashtable());
 -      pdbentry.getProperty().put("protocol", protocol);
 -    }
 +    pdbentry.setProperty("protocol", protocol);
      if (pdbentry.getFile() != null)
 +    
      {
        // import structure data from pdbentry.getFile based on given protocol
 -      if (protocol.equals(AppletFormatAdapter.PASTE))
 +      if (protocol == DataSourceType.PASTE)
        {
          // TODO: JAL-623 : correctly record file contents for matching up later
          // pdbentry.getProperty().put("pdbfilehash",""+pdbentry.getFile().hashCode());
          loadInline(pdbentry.getFile());
        }
 -      else if (protocol.equals(AppletFormatAdapter.FILE)
 -              || protocol.equals(AppletFormatAdapter.URL))
 +      else if (protocol == DataSourceType.FILE
 +              || protocol == DataSourceType.URL)
        {
          jmb.viewer.openFile(pdbentry.getFile());
        }
      jmb.loadInline(string);
    }
  
 -  void setChainMenuItems(Vector<String> chains)
 +  void setChainMenuItems(List<String> chains)
    {
      chainMenu.removeAll();
  
        repaint();
        return;
      }
 -    setChainMenuItems(jmb.chainNames);
 +    setChainMenuItems(jmb.getChainNames());
      jmb.colourBySequence(ap);
  
      setTitle(jmb.getViewerTitle());
@@@ -24,12 -24,14 +24,13 @@@ import jalview.api.AlignmentViewPanel
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
  import jalview.ext.jmol.JalviewJmolBinding;
+ import jalview.gui.IProgressIndicator;
 +import jalview.io.DataSourceType;
  import jalview.structure.StructureSelectionManager;
  
  import java.awt.Container;
  import java.util.Map;
  
 -import javajs.awt.Dimension;
 -
  import org.jmol.api.JmolAppConsoleInterface;
  import org.jmol.console.AppletConsole;
  import org.jmol.java.BS;
@@@ -44,9 -46,9 +45,9 @@@ class AppletJmolBinding extends Jalview
  
    public AppletJmolBinding(AppletJmol appletJmol,
            StructureSelectionManager sSm, PDBEntry[] pdbentry,
 -          SequenceI[][] seq, String[][] chains, String protocol)
 +          SequenceI[][] seq, DataSourceType protocol)
    {
 -    super(sSm, pdbentry, seq, chains, protocol);
 +    super(sSm, pdbentry, seq, protocol);
      appletJmolBinding = appletJmol;
    }
  
    }
  
    @Override
 -  public Dimension resizeInnerPanel(String data)
 +  public int[] resizeInnerPanel(String data)
    {
      // TODO Auto-generated method stub
      return null;
      // TODO Auto-generated method stub
      return null;
    }
+   @Override
+   protected IProgressIndicator getIProgressIndicator()
+   {
+     // no progress indicators on the applet
+     return null;
+   }
  }
@@@ -26,7 -26,7 +26,8 @@@ import jalview.api.SequenceRenderer
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
  import jalview.ext.jmol.JalviewJmolBinding;
+ import jalview.gui.IProgressIndicator;
 +import jalview.io.DataSourceType;
  
  import java.awt.Container;
  import java.util.ArrayList;
@@@ -49,12 -49,12 +50,12 @@@ public class ExtJmol extends JalviewJmo
  
    private AlignmentPanel ap;
  
 -  protected ExtJmol(jalview.appletgui.AlignFrame alframe,
 -          PDBEntry[] pdbentry, SequenceI[][] seq, String[][] chains,
 -          String protocol)
 +  protected ExtJmol(AlignFrame alframe,
 +          PDBEntry[] pdbentry, SequenceI[][] seq,
 +          DataSourceType protocol)
    {
      super(alframe.alignPanel.getStructureSelectionManager(), pdbentry, seq,
 -            chains, protocol);
 +            protocol);
    }
  
    public ExtJmol(Viewer viewer, AlignmentPanel alignPanel,
    }
  
    @Override
+   protected IProgressIndicator getIProgressIndicator()
+   {
+     // no progress indicators on applet (could access javascript for this)
+     return null;
+   }
+   @Override
    public void updateColours(Object source)
    {
  
      }
    }
  
    @Override
    public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
    {
@@@ -27,7 -27,8 +27,8 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
+ import jalview.gui.IProgressIndicator;
 -import jalview.io.AppletFormatAdapter;
 +import jalview.io.DataSourceType;
  import jalview.io.StructureFile;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ResidueProperties;
@@@ -43,12 -44,13 +44,12 @@@ import java.awt.event.ComponentListener
  import java.io.File;
  import java.net.URL;
  import java.security.AccessControlException;
 +import java.util.ArrayList;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.Map;
  import java.util.Vector;
  
 -import javajs.awt.Dimension;
 -
  import org.jmol.adapter.smarter.SmarterJmolAdapter;
  import org.jmol.api.JmolAppConsoleInterface;
  import org.jmol.api.JmolSelectionListener;
@@@ -56,6 -58,7 +57,6 @@@ import org.jmol.api.JmolStatusListener
  import org.jmol.api.JmolViewer;
  import org.jmol.c.CBK;
  import org.jmol.script.T;
 -import org.jmol.viewer.JC;
  import org.jmol.viewer.Viewer;
  
  public abstract class JalviewJmolBinding extends AAStructureBindingModel
@@@ -72,7 -75,7 +73,7 @@@
  
    Vector<String> atomsPicked = new Vector<String>();
  
 -  public Vector<String> chainNames;
 +  private List<String> chainNames;
  
    Hashtable<String, String> chainFile;
  
  
    boolean loadedInline;
  
 -  /**
 -   * current set of model filenames loaded in the Jmol instance
 -   */
 -  String[] modelFileNames = null;
 -
    StringBuffer resetLastRes = new StringBuffer();
  
    public Viewer viewer;
  
    public JalviewJmolBinding(StructureSelectionManager ssm,
 -          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
 -          String protocol)
 +          PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
 +          DataSourceType protocol)
    {
 -    super(ssm, pdbentry, sequenceIs, chains, protocol);
 +    super(ssm, pdbentry, sequenceIs, protocol);
      /*
       * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
       * "jalviewJmol", ap.av.applet .getDocumentBase(),
  
    public void closeViewer()
    {
 -    viewer.acm.setModeMouse(JC.MOUSE_NONE);
      // remove listeners for all structures in viewer
      getSsm().removeStructureViewerListener(this, this.getPdbFile());
 -    // and shut down jmol
 -    viewer.evalStringQuiet("zap");
 -    viewer.setJmolStatusListener(null);
 +    viewer.dispose();
      lastCommand = null;
      viewer = null;
      releaseUIResources();
        } catch (InterruptedException i)
        {
        }
 -      ;
      }
 +
 +    /*
 +     * get the distinct structure files modelled
 +     * (a file with multiple chains may map to multiple sequences)
 +     */
      String[] files = getPdbFile();
      if (!waitForFileLoad(files))
      {
         * 'matched' array will hold 'true' for visible alignment columns where
         * all sequences have a residue with a mapping to the PDB structure
         */
 +      // TODO could use a BitSet for matched
        boolean matched[] = new boolean[alignment.getWidth()];
        for (int m = 0; m < matched.length; m++)
        {
         * generate select statements to select regions to superimpose structures
         */
        {
 +        // TODO extract method to construct selection statements
          for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
          {
            String chainCd = ":" + structures[pdbfnum].chain;
          }
        }
        StringBuilder command = new StringBuilder(256);
 +      // command.append("set spinFps 10;\n");
 +
        for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
        {
          if (pdbfnum == refStructure || selcom[pdbfnum] == null
        }
        if (selectioncom.length() > 0)
        {
 -        System.out.println("Select regions:\n" + selectioncom.toString());
 +        // TODO is performing selectioncom redundant here? is done later on
 +        // System.out.println("Select regions:\n" + selectioncom.toString());
          evalStateCommand("select *; cartoons off; backbone; select ("
                  + selectioncom.toString() + "); cartoons; ");
          // selcom.append("; ribbons; ");
          String cmdString = command.toString();
 -        System.out.println("Superimpose command(s):\n" + cmdString);
 +        // System.out.println("Superimpose command(s):\n" + cmdString);
  
          evalStateCommand(cmdString);
        }
        {
          selectioncom.setLength(selectioncom.length() - 1);
        }
 -      System.out.println("Select regions:\n" + selectioncom.toString());
 +      // System.out.println("Select regions:\n" + selectioncom.toString());
        evalStateCommand("select *; cartoons off; backbone; select ("
                + selectioncom.toString() + "); cartoons; ");
        // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
      }
      if (modelFileNames == null)
      {
 -      String mset[] = new String[viewer.ms.mc];
 -      _modelFileNameMap = new int[mset.length];
 +      List<String> mset = new ArrayList<String>();
 +      _modelFileNameMap = new int[viewer.ms.mc];
        String m = viewer.ms.getModelFileName(0);
        if (m != null)
        {
 -        mset[0] = m;
 +        String filePath = m;
          try
          {
 -          mset[0] = new File(m).getAbsolutePath();
 +          filePath = new File(m).getAbsolutePath();
          } catch (AccessControlException x)
          {
            // usually not allowed to do this in applet
                    .println("jmolBinding: Using local file string from Jmol: "
                            + m);
          }
 -        if (mset[0].indexOf("/file:") != -1)
 +        if (filePath.indexOf("/file:") != -1)
          {
            // applet path with docroot - discard as format won't match pdbfile
 -          mset[0] = m;
 +          filePath = m;
          }
 +        mset.add(filePath);
          _modelFileNameMap[0] = 0; // filename index for first model is always 0.
        }
        int j = 1;
 -      for (int i = 1; i < mset.length; i++)
 +      for (int i = 1; i < viewer.ms.mc; i++)
        {
          m = viewer.ms.getModelFileName(i);
 -        mset[j] = m;
 +        String filePath = m;
          if (m != null)
          {
            try
            {
 -            mset[j] = new File(m).getAbsolutePath();
 +            filePath = new File(m).getAbsolutePath();
            } catch (AccessControlException x)
            {
              // usually not allowed to do this in applet, so keep raw handle
              // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
            }
          }
 -        _modelFileNameMap[j] = i; // record the model index for the filename
 -        // skip any additional models in the same file (NMR structures)
 -        if ((mset[j] == null ? mset[j] != mset[j - 1]
 -                : (mset[j - 1] == null || !mset[j].equals(mset[j - 1]))))
 +
 +        /*
 +         * add this model unless it is read from a structure file we have
 +         * already seen (example: 2MJW is an NMR structure with 10 models)
 +         */
 +        if (!mset.contains(filePath))
          {
 +          mset.add(filePath);
 +          _modelFileNameMap[j] = i; // record the model index for the filename
            j++;
          }
        }
 -      modelFileNames = new String[j];
 -      System.arraycopy(mset, 0, modelFileNames, 0, j);
 +      modelFileNames = mset.toArray(new String[mset.size()]);
      }
      return modelFileNames;
    }
      fileLoadingError = null;
      String[] oldmodels = modelFileNames;
      modelFileNames = null;
 -    chainNames = new Vector<String>();
 +    chainNames = new ArrayList<String>();
      chainFile = new Hashtable<String, String>();
      boolean notifyLoaded = false;
      String[] modelfilenames = getPdbFile();
        for (int pe = 0; pe < getPdbCount(); pe++)
        {
          boolean matches = false;
 +        addSequence(pe, getSequence()[pe]);
          if (fileName == null)
          {
            if (false)
            // see JAL-623 - need method of matching pasted data up
            {
              pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
-                     pdbfile, DataSourceType.PASTE);
 -                    pdbfile, AppletFormatAdapter.PASTE,
 -                    getIProgressIndicator());
++                    pdbfile, DataSourceType.PASTE, getIProgressIndicator());
              getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
              matches = true;
              foundEntry = true;
              // needs
              // to be tested. See mantis bug
              // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
 -            String protocol = AppletFormatAdapter.URL;
 +            DataSourceType protocol = DataSourceType.URL;
              try
              {
                if (fl.exists())
                {
 -                protocol = AppletFormatAdapter.FILE;
 +                protocol = DataSourceType.FILE;
                }
              } catch (Exception e)
              {
              }
              // Explicitly map to the filename used by Jmol ;
              pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
-                     fileName, protocol);
+                     fileName, protocol, getIProgressIndicator());
              // pdbentry[pe].getFile(), protocol);
  
            }
              String chid = new String(pdb.getId() + ":"
                      + pdb.getChains().elementAt(i).id);
              chainFile.put(chid, fileName);
 -            chainNames.addElement(chid);
 +            chainNames.add(chid);
            }
            notifyLoaded = true;
          }
      setLoadingFromArchive(false);
    }
  
 +  @Override
 +  public List<String> getChainNames()
 +  {
 +    return chainNames;
 +  }
 +
+   protected abstract IProgressIndicator getIProgressIndicator();
    public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
    {
      notifyAtomPicked(iatom, strMeasure, null);
    }
  
    @Override
 -  public Dimension resizeInnerPanel(String data)
 +  public int[] resizeInnerPanel(String data)
    {
      // Jalview doesn't honour resize panel requests
      return null;
@@@ -41,7 -41,6 +41,7 @@@ import jalview.schemes.ZappoColourSchem
  import jalview.structures.models.AAStructureBindingModel;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
 +import jalview.ws.dbsources.Pdb;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
@@@ -50,6 -49,7 +50,6 @@@ import java.awt.Font
  import java.awt.Graphics;
  import java.awt.Rectangle;
  import java.awt.event.ActionEvent;
 -import java.awt.event.ActionListener;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.io.BufferedReader;
@@@ -66,10 -66,10 +66,10 @@@ import javax.swing.JCheckBoxMenuItem
  import javax.swing.JColorChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JMenu;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JSplitPane;
 +import javax.swing.SwingUtilities;
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  import javax.swing.event.MenuEvent;
@@@ -77,13 -77,6 +77,13 @@@ import javax.swing.event.MenuListener
  
  public class AppJmol extends StructureViewerBase
  {
 +  // ms to wait for Jmol to load files
 +  private static final int JMOL_LOAD_TIMEOUT = 20000;
 +
 +  private static final String SPACE = " ";
 +
 +  private static final String BACKSLASH = "\"";
 +
    AppJmolBinding jmb;
  
    JPanel scriptWindow;
      // / TODO: check if protocol is needed to be set, and if chains are
      // autodiscovered.
      jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
 -            pdbentrys, seqs, null, null);
 +            pdbentrys, seqs, null);
  
      jmb.setLoadingFromArchive(true);
      addAlignmentPanel(ap);
  
    IProgressIndicator progressBar = null;
  
+   @Override
+   protected IProgressIndicator getIProgressIndicator()
+   {
+     return progressBar;
+   }
    /**
     * add a single PDB structure to a new or existing Jmol view
     * 
    {
      progressBar = ap.alignFrame;
      jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
 -            pdbentrys, seqs, null, null);
 +            pdbentrys, seqs, null);
      addAlignmentPanel(ap);
      useAlignmentPanelForColourbyseq(ap);
      if (pdbentrys.length > 1)
      jmb.setColourBySequence(true);
      setSize(400, 400); // probably should be a configurable/dynamic default here
      initMenus();
 -    worker = null;
 -    {
 -      addingStructures = false;
 -      worker = new Thread(this);
 -      worker.start();
 -    }
 +    addingStructures = false;
 +    worker = new Thread(this);
 +    worker.start();
 +
      this.addInternalFrameListener(new InternalFrameAdapter()
      {
        @Override
        scriptWindow.setVisible(false);
      }
  
 -    jmb.allocateViewer(renderPanel, true, "", null, null, "", scriptWindow,
 -            null);
 +    jmb.allocateViewer(renderPanel, true, "", null, null, "",
 +            scriptWindow, null);
      // jmb.newJmolPopup("Jmol");
      if (command == null)
      {
      jmb.setFinishedInit(true);
    }
  
 -  void setChainMenuItems(Vector<String> chains)
 -  {
 -    chainMenu.removeAll();
 -    if (chains == null)
 -    {
 -      return;
 -    }
 -    JMenuItem menuItem = new JMenuItem(
 -            MessageManager.getString("label.all"));
 -    menuItem.addActionListener(new ActionListener()
 -    {
 -      @Override
 -      public void actionPerformed(ActionEvent evt)
 -      {
 -        allChainsSelected = true;
 -        for (int i = 0; i < chainMenu.getItemCount(); i++)
 -        {
 -          if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
 -          {
 -            ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
 -          }
 -        }
 -        centerViewer();
 -        allChainsSelected = false;
 -      }
 -    });
 -
 -    chainMenu.add(menuItem);
 -
 -    for (String chain : chains)
 -    {
 -      menuItem = new JCheckBoxMenuItem(chain, true);
 -      menuItem.addItemListener(new ItemListener()
 -      {
 -        @Override
 -        public void itemStateChanged(ItemEvent evt)
 -        {
 -          if (!allChainsSelected)
 -          {
 -            centerViewer();
 -          }
 -        }
 -      });
  
  
    boolean allChainsSelected = false;
  
 -  void centerViewer()
 +  @Override
 +  void showSelectedChains()
    {
      Vector<String> toshow = new Vector<String>();
      for (int i = 0; i < chainMenu.getItemCount(); i++)
    public void run()
    {
      _started = true;
 +    try
 +    {
 +      List<String> files = fetchPdbFiles();
 +      if (files.size() > 0)
 +      {
 +        showFilesInViewer(files);
 +      }
 +    } finally
 +    {
 +      _started = false;
 +      worker = null;
 +    }
 +  }
 +
 +  /**
 +   * Either adds the given files to a structure viewer or opens a new viewer to
 +   * show them
 +   * 
 +   * @param files
 +   *          list of absolute paths to structure files
 +   */
 +  void showFilesInViewer(List<String> files)
 +  {
 +    long lastnotify = jmb.getLoadNotifiesHandled();
 +    StringBuilder fileList = new StringBuilder();
 +    for (String s : files)
 +    {
 +      fileList.append(SPACE).append(BACKSLASH)
 +              .append(Platform.escapeString(s)).append(BACKSLASH);
 +    }
 +    String filesString = fileList.toString();
 +
 +    if (!addingStructures)
 +    {
 +      try
 +      {
 +        initJmol("load FILES " + filesString);
 +      } catch (OutOfMemoryError oomerror)
 +      {
 +        new OOMWarning("When trying to open the Jmol viewer!", oomerror);
 +        Cache.log.debug("File locations are " + filesString);
 +      } catch (Exception ex)
 +      {
 +        Cache.log.error("Couldn't open Jmol viewer!", ex);
 +      }
 +    }
 +    else
 +    {
 +      StringBuilder cmd = new StringBuilder();
 +      cmd.append("loadingJalviewdata=true\nload APPEND ");
 +      cmd.append(filesString);
 +      cmd.append("\nloadingJalviewdata=null");
 +      final String command = cmd.toString();
 +      lastnotify = jmb.getLoadNotifiesHandled();
 +
 +      try
 +      {
 +        jmb.evalStateCommand(command);
 +      } catch (OutOfMemoryError oomerror)
 +      {
 +        new OOMWarning("When trying to add structures to the Jmol viewer!",
 +                oomerror);
 +        Cache.log.debug("File locations are " + filesString);
 +      } catch (Exception ex)
 +      {
 +        Cache.log.error("Couldn't add files to Jmol viewer!", ex);
 +      }
 +    }
 +
 +    // need to wait around until script has finished
 +    int waitMax = JMOL_LOAD_TIMEOUT;
 +    int waitFor = 35;
 +    int waitTotal = 0;
 +    while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
 +            : !(jmb.isFinishedInit() && jmb.getPdbFile() != null && jmb
 +                    .getPdbFile().length == files.size()))
 +    {
 +      try
 +      {
 +        Cache.log.debug("Waiting around for jmb notify.");
 +        Thread.sleep(waitFor);
 +        waitTotal += waitFor;
 +      } catch (Exception e)
 +      {
 +      }
 +      if (waitTotal > waitMax)
 +      {
 +        System.err
 +                .println("Timed out waiting for Jmol to load files after "
 +                        + waitTotal + "ms");
 +//        System.err.println("finished: " + jmb.isFinishedInit()
 +//                + "; loaded: " + Arrays.toString(jmb.getPdbFile())
 +//                + "; files: " + files.toString());
 +        jmb.getPdbFile();
 +        break;
 +      }
 +    }
 +
 +    // refresh the sequence colours for the new structure(s)
 +    for (AlignmentPanel ap : _colourwith)
 +    {
 +      jmb.updateColours(ap);
 +    }
 +    // do superposition if asked to
 +    if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
 +    {
 +      alignAddedStructures();
 +    }
 +    addingStructures = false;
 +  }
 +
 +  /**
 +   * Queues a thread to align structures with Jalview alignments
 +   */
 +  void alignAddedStructures()
 +  {
 +    javax.swing.SwingUtilities.invokeLater(new Runnable()
 +    {
 +      @Override
 +      public void run()
 +      {
 +        if (jmb.viewer.isScriptExecuting())
 +        {
 +          SwingUtilities.invokeLater(this);
 +          try
 +          {
 +            Thread.sleep(5);
 +          } catch (InterruptedException q)
 +          {
 +          }
 +          return;
 +        }
 +        else
 +        {
 +          alignStructs_withAllAlignPanels();
 +        }
 +      }
 +    });
 +    alignAddedStructures = false;
 +  }
 +
 +  /**
 +   * Retrieves and saves as file any modelled PDB entries for which we do not
 +   * already have a file saved. Returns a list of absolute paths to structure
 +   * files which were either retrieved, or already stored but not modelled in
 +   * the structure viewer (i.e. files to add to the viewer display).
 +   * 
 +   * @return
 +   */
 +  List<String> fetchPdbFiles()
 +  {
 +    // todo - record which pdbids were successfully imported.
 +    StringBuilder errormsgs = new StringBuilder();
 +
 +    List<String> files = new ArrayList<String>();
      String pdbid = "";
 -    // todo - record which pdbids were successfuly imported.
 -    StringBuffer errormsgs = new StringBuffer(), files = new StringBuffer();
      try
      {
 -      String[] curfiles = jmb.getPdbFile(); // files currently in viewer
 +      String[] filesInViewer = jmb.getPdbFile();
        // TODO: replace with reference fetching/transfer code (validate PDBentry
        // as a DBRef?)
 -      jalview.ws.dbsources.Pdb pdbclient = new jalview.ws.dbsources.Pdb();
 +      Pdb pdbclient = new Pdb();
        for (int pi = 0; pi < jmb.getPdbCount(); pi++)
        {
          String file = jmb.getPdbEntry(pi).getFile();
            } catch (Exception ex)
            {
              ex.printStackTrace();
 -            errormsgs.append("'" + pdbid + "'");
 -          }
 -          if (progressBar != null)
 +            errormsgs.append("'").append(pdbid).append("'");
 +          } finally
            {
 -            progressBar.setProgressBar(
 -                    MessageManager.getString("label.state_completed"), hdl);
 +            if (progressBar != null)
 +            {
 +              progressBar.setProgressBar(
 +                      MessageManager.getString("label.state_completed"),
 +                      hdl);
 +            }
            }
            if (pdbseq != null)
            {
              file = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
                      .elementAt(0).getFile()).getAbsolutePath();
              jmb.getPdbEntry(pi).setFile(file);
 -
 -            files.append(" \"" + Platform.escapeString(file) + "\"");
 +            files.add(file);
            }
            else
            {
 -            errormsgs.append("'" + pdbid + "' ");
 +            errormsgs.append("'").append(pdbid).append("' ");
            }
          }
          else
          {
 -          if (curfiles != null && curfiles.length > 0)
 +          if (filesInViewer != null && filesInViewer.length > 0)
            {
              addingStructures = true; // already files loaded.
 -            for (int c = 0; c < curfiles.length; c++)
 +            for (int c = 0; c < filesInViewer.length; c++)
              {
 -              if (curfiles[c].equals(file))
 +              if (filesInViewer[c].equals(file))
                {
                  file = null;
                  break;
            }
            if (file != null)
            {
 -            files.append(" \"" + Platform.escapeString(file) + "\"");
 +            files.add(file);
            }
          }
        }
      } catch (Exception ex)
      {
        ex.printStackTrace();
 -      errormsgs.append("When retrieving pdbfiles : current was: '" + pdbid
 -              + "'");
 +      errormsgs.append("When retrieving pdbfiles : current was: '")
 +              .append(pdbid).append("'");
      }
      if (errormsgs.length() > 0)
      {
 -
 -      JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
 +      JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
                .formatMessage("label.pdb_entries_couldnt_be_retrieved",
                        new String[] { errormsgs.toString() }),
                MessageManager.getString("label.couldnt_load_file"),
 -              JOptionPane.ERROR_MESSAGE);
 -
 -    }
 -    long lastnotify = jmb.getLoadNotifiesHandled();
 -    if (files.length() > 0)
 -    {
 -      if (!addingStructures)
 -      {
 -
 -        try
 -        {
 -          initJmol("load FILES " + files.toString());
 -        } catch (OutOfMemoryError oomerror)
 -        {
 -          new OOMWarning("When trying to open the Jmol viewer!", oomerror);
 -          Cache.log.debug("File locations are " + files);
 -        } catch (Exception ex)
 -        {
 -          Cache.log.error("Couldn't open Jmol viewer!", ex);
 -        }
 -      }
 -      else
 -      {
 -        StringBuffer cmd = new StringBuffer();
 -        cmd.append("loadingJalviewdata=true\nload APPEND ");
 -        cmd.append(files.toString());
 -        cmd.append("\nloadingJalviewdata=null");
 -        final String command = cmd.toString();
 -        cmd = null;
 -        lastnotify = jmb.getLoadNotifiesHandled();
 -
 -        try
 -        {
 -          jmb.evalStateCommand(command);
 -        } catch (OutOfMemoryError oomerror)
 -        {
 -          new OOMWarning(
 -                  "When trying to add structures to the Jmol viewer!",
 -                  oomerror);
 -          Cache.log.debug("File locations are " + files);
 -        } catch (Exception ex)
 -        {
 -          Cache.log.error("Couldn't add files to Jmol viewer!", ex);
 -        }
 -      }
 -
 -      // need to wait around until script has finished
 -      while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
 -              : (!jmb.isFinishedInit() && jmb.getPdbFile() != null && jmb
 -                      .getPdbFile().length != jmb.getPdbCount()))
 -      {
 -        try
 -        {
 -          Cache.log.debug("Waiting around for jmb notify.");
 -          Thread.sleep(35);
 -        } catch (Exception e)
 -        {
 -        }
 -      }
 -
 -      // refresh the sequence colours for the new structure(s)
 -      for (AlignmentPanel ap : _colourwith)
 -      {
 -        jmb.updateColours(ap);
 -      }
 -      // do superposition if asked to
 -      if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
 -      {
 -        javax.swing.SwingUtilities.invokeLater(new Runnable()
 -        {
 -          @Override
 -          public void run()
 -          {
 -            if (jmb.viewer.isScriptExecuting())
 -            {
 -              javax.swing.SwingUtilities.invokeLater(this);
 -              try
 -              {
 -                Thread.sleep(5);
 -              } catch (InterruptedException q)
 -              {
 -              }
 -              ;
 -              return;
 -            }
 -            else
 -            {
 -              alignStructs_withAllAlignPanels();
 -            }
 -          }
 -        });
 -        alignAddedStructures = false;
 -      }
 -      addingStructures = false;
 -
 +              JvOptionPane.ERROR_MESSAGE);
      }
 -    _started = false;
 -    worker = null;
 +    return files;
    }
  
    @Override
        repaint();
        return;
      }
 -    setChainMenuItems(jmb.chainNames);
 +    setChainMenuItems(jmb.getChainNames());
  
      this.setTitle(jmb.getViewerTitle());
      if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
  package jalview.gui;
  
  import jalview.api.AlignmentViewPanel;
 +import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
  import jalview.ext.jmol.JalviewJmolBinding;
 +import jalview.io.DataSourceType;
  import jalview.structure.StructureSelectionManager;
  
  import java.awt.Container;
@@@ -43,13 -41,19 +43,18 @@@ public class AppJmolBinding extends Jal
    private FeatureRenderer fr = null;
  
    public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm,
 -          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
 -          String protocol)
 +          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, DataSourceType protocol)
    {
 -    super(sSm, pdbentry, sequenceIs, chains, protocol);
 +    super(sSm, pdbentry, sequenceIs, protocol);
      appJmolWindow = appJmol;
    }
  
    @Override
+   protected IProgressIndicator getIProgressIndicator()
+   {
+     return appJmolWindow.progressBar;
+   }
+   @Override
    public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
    {
      AlignmentPanel ap = (alignment == null) ? appJmolWindow
      // TODO Auto-generated method stub
      return null;
    }
 +
 +  @Override
 +  public JalviewStructureDisplayI getViewer()
 +  {
 +    return appJmolWindow;
 +  }
  }
@@@ -28,10 -28,9 +28,10 @@@ import jalview.datamodel.PDBEntry
  import jalview.datamodel.SequenceI;
  import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
  import jalview.gui.StructureViewer.ViewerType;
 -import jalview.io.AppletFormatAdapter;
 +import jalview.io.DataSourceType;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
 +import jalview.io.StructureFile;
  import jalview.schemes.BuriedColourScheme;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.HelixColourScheme;
@@@ -47,6 -46,7 +47,6 @@@ import jalview.util.Platform
  import jalview.ws.dbsources.Pdb;
  
  import java.awt.event.ActionEvent;
 -import java.awt.event.ActionListener;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.io.BufferedReader;
@@@ -59,14 -59,17 +59,13 @@@ import java.io.InputStream
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.List;
 -import java.util.Map;
  import java.util.Random;
 -import java.util.Set;
  import java.util.Vector;
  
  import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JColorChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JMenu;
- import javax.swing.JOptionPane;
 -import javax.swing.JMenuItem;
 -import javax.swing.JOptionPane;
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  import javax.swing.event.MenuEvent;
@@@ -193,7 -196,7 +192,7 @@@ public class ChimeraViewFrame extends S
    public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
            String[] chains, final AlignmentPanel ap)
    {
 -    super();
 +    this();
      String pdbId = pdbentry.getId();
  
      /*
            SequenceI[][] seqs)
    {
      createProgressBar();
 -    String[][] chains = extractChains(seqs);
 +    // FIXME extractChains needs pdbentries to match IDs to PDBEntry(s) on seqs
      jmb = new JalviewChimeraBindingModel(this,
 -            ap.getStructureSelectionManager(), pdbentrys, seqs, chains,
 -            null);
 +            ap.getStructureSelectionManager(), pdbentrys, seqs, null);
      addAlignmentPanel(ap);
      useAlignmentPanelForColourbyseq(ap);
      if (pdbentrys.length > 1)
  
    }
  
 -  /**
 -   * Retrieve chains for sequences by inspecting their PDB refs. The hope is
 -   * that the first will be to the sequence's own chain. Really need a more
 -   * managed way of doing this.
 -   * 
 -   * @param seqs
 -   * @return
 -   */
 -  protected String[][] extractChains(SequenceI[][] seqs)
 -  {
 -    String[][] chains = new String[seqs.length][];
 -    for (int i = 0; i < seqs.length; i++)
 -    {
 -      chains[i] = new String[seqs[i].length];
 -      int seqno = 0;
 -      for (SequenceI seq : seqs[i])
 -      {
 -        String chain = null;
 -        if (seq.getDatasetSequence() != null)
 -        {
 -          Vector<PDBEntry> pdbrefs = seq.getDatasetSequence()
 -                  .getAllPDBEntries();
 -          if (pdbrefs != null && pdbrefs.size() > 0)
 -          {
 -            chain = pdbrefs.get(0).getChainCode();
 -          }
 -        }
 -        chains[i][seqno++] = chain;
 -      }
 -    }
 -    return chains;
 -  }
 +
  
    /**
     * Create a new viewer from saved session state data including Chimera session
            SequenceI[][] seqsArray, boolean colourByChimera,
            boolean colourBySequence, String newViewId)
    {
 -    super();
 +    this();
      setViewId(newViewId);
      this.chimeraSessionFile = chimeraSessionFile;
      openNewChimera(alignPanel, pdbArray, seqsArray);
    public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs,
            AlignmentPanel ap)
    {
 -    super();
 +    this();
      openNewChimera(ap, pe, seqs);
    }
  
 -  public ChimeraViewFrame(Map<PDBEntry, List<SequenceI>> toView,
 -          AlignmentPanel alignPanel)
 +  /**
 +   * Default constructor
 +   */
 +  public ChimeraViewFrame()
    {
      super();
  
      /*
 -     * Convert the map of sequences per pdb entry into the tied arrays expected
 -     * by openNewChimera
 -     * 
 -     * TODO pass the Map down to openNewChimera and its callees instead
 +     * closeViewer will decide whether or not to close this frame
 +     * depending on whether user chooses to Cancel or not
       */
 -    final Set<PDBEntry> pdbEntries = toView.keySet();
 -    PDBEntry[] pdbs = pdbEntries.toArray(new PDBEntry[pdbEntries.size()]);
 -    SequenceI[][] seqsForPdbs = new SequenceI[pdbEntries.size()][];
 -    for (int i = 0; i < pdbs.length; i++)
 -    {
 -      final List<SequenceI> seqsForPdb = toView.get(pdbs[i]);
 -      seqsForPdbs[i] = seqsForPdb.toArray(new SequenceI[seqsForPdb.size()]);
 -    }
 -
 -    openNewChimera(alignPanel, pdbs, seqsForPdbs);
 +    setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
    }
  
    /**
  
      if (!jmb.launchChimera())
      {
 -      JOptionPane.showMessageDialog(Desktop.desktop,
 +      JvOptionPane.showMessageDialog(Desktop.desktop,
                MessageManager.getString("label.chimera_failed"),
                MessageManager.getString("label.error_loading_file"),
 -              JOptionPane.ERROR_MESSAGE);
 +              JvOptionPane.ERROR_MESSAGE);
        this.dispose();
        return;
      }
      jmb.startChimeraListener();
    }
  
 -  /**
 -   * If the list is not empty, add menu items for 'All' and each individual
 -   * chain to the "View | Show Chain" sub-menu. Multiple selections are allowed.
 -   * 
 -   * @param chainNames
 -   */
 -  void setChainMenuItems(List<String> chainNames)
 -  {
 -    chainMenu.removeAll();
 -    if (chainNames == null || chainNames.isEmpty())
 -    {
 -      return;
 -    }
 -    JMenuItem menuItem = new JMenuItem(
 -            MessageManager.getString("label.all"));
 -    menuItem.addActionListener(new ActionListener()
 -    {
 -      @Override
 -      public void actionPerformed(ActionEvent evt)
 -      {
 -        allChainsSelected = true;
 -        for (int i = 0; i < chainMenu.getItemCount(); i++)
 -        {
 -          if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
 -          {
 -            ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
 -          }
 -        }
 -        showSelectedChains();
 -        allChainsSelected = false;
 -      }
 -    });
 -
 -    chainMenu.add(menuItem);
 -
 -    for (String chainName : chainNames)
 -    {
 -      menuItem = new JCheckBoxMenuItem(chainName, true);
 -      menuItem.addItemListener(new ItemListener()
 -      {
 -        @Override
 -        public void itemStateChanged(ItemEvent evt)
 -        {
 -          if (!allChainsSelected)
 -          {
 -            showSelectedChains();
 -          }
 -        }
 -      });
 -
 -      chainMenu.add(menuItem);
 -    }
 -  }
  
    /**
     * Show only the selected chain(s) in the viewer
     */
 +  @Override
    void showSelectedChains()
    {
      List<String> toshow = new ArrayList<String>();
                  "label.confirm_close_chimera",
                  new Object[] { jmb.getViewerTitle("Chimera", false) });
          prompt = JvSwingUtils.wrapTooltip(true, prompt);
 -        int confirm = JOptionPane.showConfirmDialog(this, prompt,
 +        int confirm = JvOptionPane.showConfirmDialog(this, prompt,
                  MessageManager.getString("label.close_viewer"),
 -                JOptionPane.YES_NO_OPTION);
 -        closeChimera = confirm == JOptionPane.YES_OPTION;
 +                JvOptionPane.YES_NO_CANCEL_OPTION);
 +        /*
 +         * abort closure if user hits escape or Cancel
 +         */
 +        if (confirm == JvOptionPane.CANCEL_OPTION
 +                || confirm == JvOptionPane.CLOSED_OPTION)
 +        {
 +          return;
 +        }
 +        closeChimera = confirm == JvOptionPane.YES_OPTION;
        }
        jmb.closeViewer(closeChimera);
      }
      // TODO: check for memory leaks where instance isn't finalised because jmb
      // holds a reference to the window
      jmb = null;
 +    dispose();
    }
  
    /**
      List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
      List<Integer> filePDBpos = new ArrayList<Integer>();
      PDBEntry thePdbEntry = null;
 +    StructureFile pdb = null;
      try
      {
        String[] curfiles = jmb.getPdbFile(); // files currently in viewer
      if (errormsgs.length() > 0)
      {
  
 -      JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
 +      JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
                .formatMessage("label.pdb_entries_couldnt_be_retrieved",
                        new Object[] { errormsgs.toString() }),
                MessageManager.getString("label.couldnt_load_file"),
 -              JOptionPane.ERROR_MESSAGE);
 +              JvOptionPane.ERROR_MESSAGE);
      }
  
      if (files.length() > 0)
            {
              int pos = filePDBpos.get(num).intValue();
              long startTime = startProgressBar("Chimera "
 -                    + MessageManager.getString("status.opening_file"));
 +                    + MessageManager.getString("status.opening_file_for")
 +                    + " " + pe.getId());
              jmb.openFile(pe);
              jmb.addSequence(pos, jmb.getSequence()[pos]);
              File fl = new File(pe.getFile());
 -            String protocol = AppletFormatAdapter.URL;
 +            DataSourceType protocol = DataSourceType.URL;
              try
              {
                if (fl.exists())
                {
 -                protocol = AppletFormatAdapter.FILE;
 +                protocol = DataSourceType.FILE;
                }
              } catch (Throwable e)
              {
                stopProgressBar("", startTime);
              }
              // Explicitly map to the filename used by Chimera ;
 -            jmb.getSsm().setMapping(jmb.getSequence()[pos],
 +            pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
-                     jmb.getChains()[pos], pe.getFile(), protocol);
+                     jmb.getChains()[pos], pe.getFile(), protocol,
+                     progressBar);
 +            stashFoundChains(pdb, pe.getFile());
            } catch (OutOfMemoryError oomerror)
            {
              new OOMWarning(
            }
          }
        }
 +      jmb.refreshGUI();
        jmb.setFinishedInit(true);
        jmb.setLoadingFromArchive(false);
  
  
    /**
     * Fetch PDB data and save to a local file. Returns the full path to the file,
-    * or null if fetch fails.
+    * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
     * 
     * @param processingEntry
     * @return
     * @throws Exception
     */
 +
 +  private void stashFoundChains(StructureFile pdb, String file)
 +  {
 +    for (int i = 0; i < pdb.getChains().size(); i++)
 +    {
 +      String chid = new String(pdb.getId() + ":"
 +              + pdb.getChains().elementAt(i).id);
 +      jmb.getChainNames().add(chid);
 +      jmb.getChainFile().put(chid, file);
 +    }
 +  }
    private String fetchPdbFile(PDBEntry processingEntry) throws Exception
    {
 +    // FIXME: this is duplicated code with Jmol frame ?
      String filePath = null;
      Pdb pdbclient = new Pdb();
      AlignmentI pdbseq = null;
    {
      return jmb;
    }
+   @Override
+   protected IProgressIndicator getIProgressIndicator()
+   {
+     return progressBar;
+   }
  }
@@@ -20,8 -20,6 +20,8 @@@
   */
  package jalview.gui;
  
 +import jalview.analysis.Conservation;
 +import jalview.api.FeatureColourI;
  import jalview.api.ViewStyleI;
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
@@@ -37,8 -35,6 +37,8 @@@ import jalview.datamodel.StructureViewe
  import jalview.datamodel.StructureViewerModel.StructureData;
  import jalview.ext.varna.RnaModel;
  import jalview.gui.StructureViewer.ViewerType;
 +import jalview.io.DataSourceType;
 +import jalview.io.FileFormat;
  import jalview.schemabinding.version2.AlcodMap;
  import jalview.schemabinding.version2.AlcodonFrame;
  import jalview.schemabinding.version2.Annotation;
@@@ -75,7 -71,7 +75,7 @@@ import jalview.schemabinding.version2.V
  import jalview.schemes.AnnotationColourGradient;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemeProperty;
 -import jalview.schemes.GraduatedColor;
 +import jalview.schemes.FeatureColour;
  import jalview.schemes.ResidueColourScheme;
  import jalview.schemes.ResidueProperties;
  import jalview.schemes.UserColourScheme;
@@@ -111,7 -107,6 +111,7 @@@ import java.lang.reflect.InvocationTarg
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.ArrayList;
 +import java.util.Arrays;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.HashSet;
@@@ -129,7 -124,7 +129,6 @@@ import java.util.jar.JarInputStream
  import java.util.jar.JarOutputStream;
  
  import javax.swing.JInternalFrame;
--import javax.swing.JOptionPane;
  import javax.swing.SwingUtilities;
  
  import org.exolab.castor.xml.Marshaller;
@@@ -169,9 -164,7 +168,9 @@@ public class Jalview2XM
     */
    Map<String, SequenceI> seqRefIds = null;
  
 -  Vector<Object[]> frefedSequence = null;
 +  Map<String, SequenceI> incompleteSeqs = null;
 +
 +  List<SeqFref> frefedSequence = null;
  
    boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
  
        {
          seqsToIds.clear();
        }
 +      if (incompleteSeqs != null)
 +      {
 +        incompleteSeqs.clear();
 +      }
        // seqRefIds = null;
        // seqsToIds = null;
      }
      {
        seqRefIds = new HashMap<String, SequenceI>();
      }
 +    if (incompleteSeqs == null)
 +    {
 +      incompleteSeqs = new HashMap<String, SequenceI>();
 +    }
 +    if (frefedSequence == null)
 +    {
 +      frefedSequence = new ArrayList<SeqFref>();
 +    }
    }
  
    public Jalview2XML()
      this.raiseGUI = raiseGUI;
    }
  
 +  /**
 +   * base class for resolving forward references to sequences by their ID
 +   * 
 +   * @author jprocter
 +   *
 +   */
 +  abstract class SeqFref
 +  {
 +    String sref;
 +
 +    String type;
 +
 +    public SeqFref(String _sref, String type)
 +    {
 +      sref = _sref;
 +      this.type = type;
 +    }
 +
 +    public String getSref()
 +    {
 +      return sref;
 +    }
 +
 +    public SequenceI getSrefSeq()
 +    {
 +      return seqRefIds.get(sref);
 +    }
 +
 +    public boolean isResolvable()
 +    {
 +      return seqRefIds.get(sref) != null;
 +    }
 +
 +    public SequenceI getSrefDatasetSeq()
 +    {
 +      SequenceI sq = seqRefIds.get(sref);
 +      if (sq != null)
 +      {
 +        while (sq.getDatasetSequence() != null)
 +        {
 +          sq = sq.getDatasetSequence();
 +        }
 +      }
 +      return sq;
 +    }
 +
 +    /**
 +     * @return true if the forward reference was fully resolved
 +     */
 +    abstract boolean resolve();
 +
 +    @Override
 +    public String toString()
 +    {
 +      return type + " reference to " + sref;
 +    }
 +  }
 +
 +  /**
 +   * create forward reference for a mapping
 +   * 
 +   * @param sref
 +   * @param _jmap
 +   * @return
 +   */
 +  public SeqFref newMappingRef(final String sref,
 +          final jalview.datamodel.Mapping _jmap)
 +  {
 +    SeqFref fref = new SeqFref(sref, "Mapping")
 +    {
 +      public jalview.datamodel.Mapping jmap = _jmap;
 +
 +      @Override
 +      boolean resolve()
 +      {
 +        SequenceI seq = getSrefDatasetSeq();
 +        if (seq == null)
 +        {
 +          return false;
 +        }
 +        jmap.setTo(seq);
 +        return true;
 +      }
 +    };
 +    return fref;
 +  }
 +
 +  public SeqFref newAlcodMapRef(final String sref,
 +          final AlignedCodonFrame _cf, final jalview.datamodel.Mapping _jmap)
 +  {
 +
 +    SeqFref fref = new SeqFref(sref, "Codon Frame")
 +    {
 +      AlignedCodonFrame cf = _cf;
 +
 +      public jalview.datamodel.Mapping mp = _jmap;
 +
 +      @Override
 +      public boolean isResolvable()
 +      {
 +        return super.isResolvable() && mp.getTo() != null;
 +      };
 +
 +      @Override
 +      boolean resolve()
 +      {
 +        SequenceI seq = getSrefDatasetSeq();
 +        if (seq == null)
 +        {
 +          return false;
 +        }
 +        cf.addMap(seq, mp.getTo(), mp.getMap());
 +        return true;
 +      }
 +    };
 +    return fref;
 +  }
 +
    public void resolveFrefedSequences()
    {
 -    if (frefedSequence.size() > 0)
 +    Iterator<SeqFref> nextFref = frefedSequence.iterator();
 +    int toresolve = frefedSequence.size();
 +    int unresolved = 0, failedtoresolve = 0;
 +    while (nextFref.hasNext())
      {
 -      int r = 0, rSize = frefedSequence.size();
 -      while (r < rSize)
 +      SeqFref ref = nextFref.next();
 +      if (ref.isResolvable())
        {
 -        Object[] ref = frefedSequence.elementAt(r);
 -        if (ref != null)
 +        try
          {
 -          String sref = (String) ref[0];
 -          if (seqRefIds.containsKey(sref))
 +          if (ref.resolve())
            {
 -            if (ref[1] instanceof jalview.datamodel.Mapping)
 -            {
 -              SequenceI seq = seqRefIds.get(sref);
 -              while (seq.getDatasetSequence() != null)
 -              {
 -                seq = seq.getDatasetSequence();
 -              }
 -              ((jalview.datamodel.Mapping) ref[1]).setTo(seq);
 -            }
 -            else
 -            {
 -              if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
 -              {
 -                SequenceI seq = seqRefIds.get(sref);
 -                while (seq.getDatasetSequence() != null)
 -                {
 -                  seq = seq.getDatasetSequence();
 -                }
 -                if (ref[2] != null
 -                        && ref[2] instanceof jalview.datamodel.Mapping)
 -                {
 -                  jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2];
 -                  ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap(
 -                          seq, mp.getTo(), mp.getMap());
 -                }
 -                else
 -                {
 -                  System.err
 -                          .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving "
 -                                  + ref[2].getClass() + " type objects.");
 -                }
 -              }
 -              else
 -              {
 -                System.err
 -                        .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for "
 -                                + ref[1].getClass() + " type objects.");
 -              }
 -            }
 -            frefedSequence.remove(r);
 -            rSize--;
 +            nextFref.remove();
            }
            else
            {
 -            System.err
 -                    .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string "
 -                            + ref[0]
 -                            + " with objecttype "
 -                            + ref[1].getClass());
 -            r++;
 +            failedtoresolve++;
            }
 +        } catch (Exception x)
 +        {
 +          System.err
 +                  .println("IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
 +                          + ref.getSref());
 +          x.printStackTrace();
 +          failedtoresolve++;
          }
 -        else
 +      }
 +      else
 +      {
 +        unresolved++;
 +      }
 +    }
 +    if (unresolved > 0)
 +    {
 +      System.err.println("Jalview Project Import: There were " + unresolved
 +              + " forward references left unresolved on the stack.");
 +    }
 +    if (failedtoresolve > 0)
 +    {
 +      System.err.println("SERIOUS! " + failedtoresolve
 +              + " resolvable forward references failed to resolve.");
 +    }
 +    if (incompleteSeqs != null && incompleteSeqs.size() > 0)
 +    {
 +      System.err.println("Jalview Project Import: There are "
 +              + incompleteSeqs.size()
 +              + " sequences which may have incomplete metadata.");
 +      if (incompleteSeqs.size() < 10)
 +      {
 +        for (SequenceI s : incompleteSeqs.values())
          {
 -          // empty reference
 -          frefedSequence.remove(r);
 -          rSize--;
 +          System.err.println(s.toString());
          }
        }
 +      else
 +      {
 +        System.err
 +                .println("Too many to report. Skipping output of incomplete sequences.");
 +      }
      }
    }
  
      {
        return;
      }
 +    saveAllFrames(Arrays.asList(frames), jout);
 +  }
  
 +  /**
 +   * core method for storing state for a set of AlignFrames.
 +   * 
 +   * @param frames
 +   *          - frames involving all data to be exported (including containing
 +   *          splitframes)
 +   * @param jout
 +   *          - project output stream
 +   */
 +  private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
 +  {
      Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
  
      /*
        List<String> viewIds = new ArrayList<String>();
  
        // REVERSE ORDER
 -      for (int i = frames.length - 1; i > -1; i--)
 +      for (int i = frames.size() - 1; i > -1; i--)
        {
 -        AlignFrame af = frames[i];
 +        AlignFrame af = frames.get(i);
          // skip ?
          if (skipList != null
                  && skipList
    {
      try
      {
 -      int ap = 0;
 -      int apSize = af.alignPanels.size();
        FileOutputStream fos = new FileOutputStream(jarFile);
        JarOutputStream jout = new JarOutputStream(fos);
 -      Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
 -      List<String> viewIds = new ArrayList<String>();
 +      List<AlignFrame> frames = new ArrayList<AlignFrame>();
  
 -      for (AlignmentPanel apanel : af.alignPanels)
 +      // resolve splitframes
 +      if (af.getViewport().getCodingComplement() != null)
        {
 -        String jfileName = apSize == 1 ? fileName : fileName + ap;
 -        ap++;
 -        if (!jfileName.endsWith(".xml"))
 -        {
 -          jfileName = jfileName + ".xml";
 -        }
 -        saveState(apanel, jfileName, jout, viewIds);
 -        String dssid = getDatasetIdRef(af.getViewport().getAlignment()
 -                .getDataset());
 -        if (!dsses.containsKey(dssid))
 -        {
 -          dsses.put(dssid, af);
 -        }
 +        frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
        }
 -      writeDatasetFor(dsses, fileName, jout);
 +      else
 +      {
 +        frames.add(af);
 +      }
 +      saveAllFrames(frames, jout);
        try
        {
          jout.flush();
      object.setVersion(jalview.bin.Cache.getDefault("VERSION",
              "Development Build"));
  
 -    jalview.datamodel.AlignmentI jal = av.getAlignment();
 +    /**
 +     * rjal is full height alignment, jal is actual alignment with full metadata
 +     * but excludes hidden sequences.
 +     */
 +    jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
  
      if (av.hasHiddenRows())
      {
 -      jal = jal.getHiddenSequences().getFullAlignment();
 +      rjal = jal.getHiddenSequences().getFullAlignment();
      }
  
      SequenceSet vamsasSet = new SequenceSet();
        {
          // switch jal and the dataset
          jal = jal.getDataset();
 +        rjal = jal;
        }
      }
      if (jal.getProperties() != null)
  
      JSeq jseq;
      Set<String> calcIdSet = new HashSet<String>();
 -
 +    // record the set of vamsas sequence XML POJO we create.
 +    HashMap<String, Sequence> vamsasSetIds = new HashMap<String, Sequence>();
      // SAVE SEQUENCES
 -    for (int i = 0; i < jal.getHeight(); i++)
 +    for (final SequenceI jds : rjal.getSequences())
      {
        final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
                : jds.getDatasetSequence();
        String id = seqHash(jds);
 -
 -      if (seqRefIds.get(id) != null)
 -      {
 -        // This happens for two reasons: 1. multiple views are being serialised.
 -        // 2. the hashCode has collided with another sequence's code. This DOES
 -        // HAPPEN! (PF00072.15.stk does this)
 -        // JBPNote: Uncomment to debug writing out of files that do not read
 -        // back in due to ArrayOutOfBoundExceptions.
 -        // System.err.println("vamsasSeq backref: "+id+"");
 -        // System.err.println(jds.getName()+"
 -        // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
 -        // System.err.println("Hashcode: "+seqHash(jds));
 -        // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
 -        // System.err.println(rsq.getName()+"
 -        // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
 -        // System.err.println("Hashcode: "+seqHash(rsq));
 -      }
 -      else
 +      if (vamsasSetIds.get(id) == null)
        {
 -        vamsasSeq = createVamsasSequence(id, jds);
 -        vamsasSet.addSequence(vamsasSeq);
 -        seqRefIds.put(id, jds);
 +        if (seqRefIds.get(id) != null && !storeDS)
 +        {
 +          // This happens for two reasons: 1. multiple views are being
 +          // serialised.
 +          // 2. the hashCode has collided with another sequence's code. This
 +          // DOES
 +          // HAPPEN! (PF00072.15.stk does this)
 +          // JBPNote: Uncomment to debug writing out of files that do not read
 +          // back in due to ArrayOutOfBoundExceptions.
 +          // System.err.println("vamsasSeq backref: "+id+"");
 +          // System.err.println(jds.getName()+"
 +          // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
 +          // System.err.println("Hashcode: "+seqHash(jds));
 +          // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
 +          // System.err.println(rsq.getName()+"
 +          // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
 +          // System.err.println("Hashcode: "+seqHash(rsq));
 +        }
 +        else
 +        {
 +          vamsasSeq = createVamsasSequence(id, jds);
 +          vamsasSet.addSequence(vamsasSeq);
 +          vamsasSetIds.put(id, vamsasSeq);
 +          seqRefIds.put(id, jds);
 +        }
        }
 -
        jseq = new JSeq();
        jseq.setStart(jds.getStart());
        jseq.setEnd(jds.getEnd());
          // Store any sequences this sequence represents
          if (av.hasHiddenRows())
          {
 +          // use rjal, contains the full height alignment
            jseq.setHidden(av.getAlignment().getHiddenSequences()
                    .isHidden(jds));
  
 -          if (av.isHiddenRepSequence(jal.getSequenceAt(i)))
 +          if (av.isHiddenRepSequence(jds))
            {
              jalview.datamodel.SequenceI[] reps = av
 -                    .getRepresentedSequences(jal.getSequenceAt(i))
 -                    .getSequencesInOrder(jal);
 +                    .getRepresentedSequences(jds).getSequencesInOrder(rjal);
  
              for (int h = 0; h < reps.length; h++)
              {
 -              if (reps[h] != jal.getSequenceAt(i))
 +              if (reps[h] != jds)
                {
 -                jseq.addHiddenSequences(jal.findIndex(reps[h]));
 +                jseq.addHiddenSequences(rjal.findIndex(reps[h]));
                }
              }
            }
          }
 +        // mark sequence as reference - if it is the reference for this view
 +        if (jal.hasSeqrep())
 +        {
 +          jseq.setViewreference(jds == jal.getSeqrep());
 +        }
        }
  
 +      // TODO: omit sequence features from each alignment view's XML dump if we
 +      // are storing dataset
        if (jds.getSequenceFeatures() != null)
        {
          jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
              }
            }
  
 -          if (entry.getProperty() != null && !entry.getProperty().isEmpty())
 +          Enumeration<String> props = entry.getProperties();
 +          if (props.hasMoreElements())
            {
              PdbentryItem item = new PdbentryItem();
 -            Hashtable properties = entry.getProperty();
 -            Enumeration en2 = properties.keys();
 -            while (en2.hasMoreElements())
 +            while (props.hasMoreElements())
              {
                Property prop = new Property();
 -              String key = en2.nextElement().toString();
 +              String key = props.nextElement();
                prop.setName(key);
 -              prop.setValue(properties.get(key).toString());
 +              prop.setValue(entry.getProperty(key).toString());
                item.addProperty(prop);
              }
              pdb.addPdbentryItem(item);
        jal = av.getAlignment();
      }
      // SAVE MAPPINGS
 -    if (jal.getCodonFrames() != null)
 +    // FOR DATASET
 +    if (storeDS && jal.getCodonFrames() != null)
      {
        List<AlignedCodonFrame> jac = jal.getCodonFrames();
        for (AlignedCodonFrame acf : jac)
        {
          AlcodonFrame alc = new AlcodonFrame();
          if (acf.getProtMappings() != null
                  && acf.getProtMappings().length > 0)
          {
 +          boolean hasMap = false;
            SequenceI[] dnas = acf.getdnaSeqs();
            jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
            for (int m = 0; m < pmaps.length; m++)
              alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
                      false));
              alc.addAlcodMap(alcmap);
 +            hasMap = true;
 +          }
 +          if (hasMap)
 +          {
 +            vamsasSet.addAlcodonFrame(alc);
            }
          }
 -
 +        // TODO: delete this ? dead code from 2.8.3->2.9 ?
          // {
          // AlcodonFrame alc = new AlcodonFrame();
          // vamsasSet.addAlcodonFrame(alc);
                  .toArray(new String[0]);
  
          Vector<String> settingsAdded = new Vector<String>();
 -        Object gstyle = null;
 -        GraduatedColor gcol = null;
          if (renderOrder != null)
          {
            for (String featureType : renderOrder)
            {
 -            gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
 -                    .getFeatureStyle(featureType);
 +            FeatureColourI fcol = ap.getSeqPanel().seqCanvas
 +                    .getFeatureRenderer().getFeatureStyle(featureType);
              Setting setting = new Setting();
              setting.setType(featureType);
 -            if (gstyle instanceof GraduatedColor)
 +            if (!fcol.isSimpleColour())
              {
 -              gcol = (GraduatedColor) gstyle;
 -              setting.setColour(gcol.getMaxColor().getRGB());
 -              setting.setMincolour(gcol.getMinColor().getRGB());
 -              setting.setMin(gcol.getMin());
 -              setting.setMax(gcol.getMax());
 -              setting.setColourByLabel(gcol.isColourByLabel());
 -              setting.setAutoScale(gcol.isAutoScale());
 -              setting.setThreshold(gcol.getThresh());
 -              setting.setThreshstate(gcol.getThreshType());
 +              setting.setColour(fcol.getMaxColour().getRGB());
 +              setting.setMincolour(fcol.getMinColour().getRGB());
 +              setting.setMin(fcol.getMin());
 +              setting.setMax(fcol.getMax());
 +              setting.setColourByLabel(fcol.isColourByLabel());
 +              setting.setAutoScale(fcol.isAutoScaled());
 +              setting.setThreshold(fcol.getThreshold());
 +              // -1 = No threshold, 0 = Below, 1 = Above
 +              setting.setThreshstate(fcol.isAboveThreshold() ? 1 : (fcol
 +                      .isBelowThreshold() ? 0 : -1));
              }
              else
              {
 -              setting.setColour(((Color) gstyle).getRGB());
 +              setting.setColour(fcol.getColour().getRGB());
              }
  
              setting.setDisplay(av.getFeaturesDisplayed().isVisible(
  
          // is groups actually supposed to be a map here ?
          Iterator<String> en = ap.getSeqPanel().seqCanvas
 -                .getFeatureRenderer()
 -                .getFeatureGroups().iterator();
 +                .getFeatureRenderer().getFeatureGroups().iterator();
          Vector<String> groupsAdded = new Vector<String>();
          while (en.hasNext())
          {
            groupsAdded.addElement(grp);
          }
          jms.setFeatureSettings(fs);
 -
        }
  
        if (av.hasHiddenColumns())
      if (jds.getDatasetSequence() != null)
      {
        vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
      }
      else
      {
 -      vamsasSeq.setDsseqid(id); // so we can tell which sequences really are
 +      // seqId==dsseqid so we can tell which sequences really are
        // dataset sequences only
 +      vamsasSeq.setDsseqid(id);
        dbrefs = jds.getDBRefs();
 +      if (parentseq == null)
 +      {
 +        parentseq = jds;
 +      }
      }
      if (dbrefs != null)
      {
        if (jmp.getTo() != null)
        {
          MappingChoice mpc = new MappingChoice();
 -        if (recurse
 -                && (parentseq != jmp.getTo() || parentseq
 -                        .getDatasetSequence() != jmp.getTo()))
 +
 +        // check/create ID for the sequence referenced by getTo()
 +
 +        String jmpid = "";
 +        SequenceI ps = null;
 +        if (parentseq != jmp.getTo()
 +                && parentseq.getDatasetSequence() != jmp.getTo())
          {
 -          mpc.setSequence(createVamsasSequence(false, seqHash(jmp.getTo()),
 -                  jmp.getTo(), jds));
 +          // chaining dbref rather than a handshaking one
 +          jmpid = seqHash(ps = jmp.getTo());
          }
          else
          {
 -          String jmpid = "";
 -          SequenceI ps = null;
 -          if (parentseq != jmp.getTo()
 -                  && parentseq.getDatasetSequence() != jmp.getTo())
 -          {
 -            // chaining dbref rather than a handshaking one
 -            jmpid = seqHash(ps = jmp.getTo());
 -          }
 -          else
 -          {
 -            jmpid = seqHash(ps = parentseq);
 -          }
 -          mpc.setDseqFor(jmpid);
 -          if (!seqRefIds.containsKey(mpc.getDseqFor()))
 -          {
 -            jalview.bin.Cache.log.debug("creatign new DseqFor ID");
 -            seqRefIds.put(mpc.getDseqFor(), ps);
 -          }
 -          else
 -          {
 -            jalview.bin.Cache.log.debug("reusing DseqFor ID");
 -          }
 +          jmpid = seqHash(ps = parentseq);
 +        }
 +        mpc.setDseqFor(jmpid);
 +        if (!seqRefIds.containsKey(mpc.getDseqFor()))
 +        {
 +          jalview.bin.Cache.log.debug("creatign new DseqFor ID");
 +          seqRefIds.put(mpc.getDseqFor(), ps);
          }
 +        else
 +        {
 +          jalview.bin.Cache.log.debug("reusing DseqFor ID");
 +        }
 +
          mp.setMappingChoice(mpc);
        }
      }
      }
      if (seqRefIds == null)
      {
 -      seqRefIds = new HashMap<String, SequenceI>();
 -    }
 -    if (frefedSequence == null)
 -    {
 -      frefedSequence = new Vector<Object[]>();
 +      initSeqRefs();
      }
 -
      AlignFrame af = null, _af = null;
 +    IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<AlignmentI, AlignmentI>();
      Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
      final String file = jprovider.getFilename();
      try
            if (true) // !skipViewport(object))
            {
              _af = loadFromObject(object, file, true, jprovider);
 -            if (object.getJalviewModelSequence().getViewportCount() > 0)
 +            if (_af != null
 +                    && object.getJalviewModelSequence().getViewportCount() > 0)
              {
 -              af = _af;
 -              if (af.viewport.isGatherViewsHere())
 +              if (af == null)
 +              {
 +                // store a reference to the first view
 +                af = _af;
 +              }
 +              if (_af.viewport.isGatherViewsHere())
                {
 -                gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
 +                // if this is a gathered view, keep its reference since
 +                // after gathering views, only this frame will remain
 +                af = _af;
 +                gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
                }
 +              // Save dataset to register mappings once all resolved
 +              importedDatasets.put(af.viewport.getAlignment().getDataset(),
 +                      af.viewport.getAlignment().getDataset());
              }
            }
            entryCount++;
        e.printStackTrace();
      }
  
 -    if (Desktop.instance != null)
 -    {
 -      Desktop.instance.stopLoading();
 -    }
 -
      /*
       * Regather multiple views (with the same sequence set id) to the frame (if
       * any) that is flagged as the one to gather to, i.e. convert them to tabbed
      }
  
      restoreSplitFrames();
 -
 +    for (AlignmentI ds : importedDatasets.keySet())
 +    {
 +      if (ds.getCodonFrames() != null)
 +      {
 +        StructureSelectionManager.getStructureSelectionManager(
 +                Desktop.instance).registerMappings(ds.getCodonFrames());
 +      }
 +    }
      if (errorMessage != null)
      {
        reportErrors();
      }
 +
 +    if (Desktop.instance != null)
 +    {
 +      Desktop.instance.stopLoading();
 +    }
 +
      return af;
    }
  
            SplitFrame sf = createSplitFrame(dnaFrame, af);
            addedToSplitFrames.add(dnaFrame);
            addedToSplitFrames.add(af);
 +          dnaFrame.setMenusForViewport();
 +          af.setMenusForViewport();
            if (af.viewport.isGatherViewsHere())
            {
              gatherTo.add(sf);
          Viewport view = candidate.getKey();
          Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
                  view.getHeight());
 +        af.setMenusForViewport();
          System.err.println("Failed to restore view " + view.getTitle()
                  + " to split frame");
        }
            @Override
            public void run()
            {
 -            JOptionPane.showInternalMessageDialog(Desktop.desktop,
 +            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                      finalErrorMessage, "Error "
                              + (saving ? "saving" : "loading")
 -                            + " Jalview file", JOptionPane.WARNING_MESSAGE);
 +                            + " Jalview file", JvOptionPane.WARNING_MESSAGE);
            }
          });
        }
     * @param pdbId
     * @return
     */
 -  String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
 +  String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
 +          String origFile)
    {
      if (alreadyLoadedPDB.containsKey(pdbId))
      {
        return alreadyLoadedPDB.get(pdbId).toString();
      }
  
 -    String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
 +    String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
 +            origFile);
      if (tempFile != null)
      {
        alreadyLoadedPDB.put(pdbId, tempFile);
     * @param prefix
     *          a prefix for the temporary file name, must be at least three
     *          characters long
 +   * @param origFile
 +   *          null or original file - so new file can be given the same suffix
 +   *          as the old one
     * @return
     */
    protected String copyJarEntry(jarInputStreamProvider jprovider,
 -          String jarEntryName, String prefix)
 +          String jarEntryName, String prefix, String origFile)
    {
      BufferedReader in = null;
      PrintWriter out = null;
 -
 +    String suffix = ".tmp";
 +    if (origFile == null)
 +    {
 +      origFile = jarEntryName;
 +    }
 +    int sfpos = origFile.lastIndexOf(".");
 +    if (sfpos > -1 && sfpos < (origFile.length() - 3))
 +    {
 +      suffix = "." + origFile.substring(sfpos + 1);
 +    }
      try
      {
        JarInputStream jin = jprovider.getJarInputStream();
        if (entry != null)
        {
          in = new BufferedReader(new InputStreamReader(jin, UTF_8));
 -        File outFile = File.createTempFile(prefix, ".tmp");
 +        File outFile = File.createTempFile(prefix, suffix);
          outFile.deleteOnExit();
          out = new PrintWriter(new FileOutputStream(outFile));
          String data;
      // LOAD SEQUENCES
  
      List<SequenceI> hiddenSeqs = null;
 -    jalview.datamodel.Sequence jseq;
  
      List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
  
      boolean multipleView = false;
 -
 +    SequenceI referenceseqForView = null;
      JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
      int vi = 0; // counter in vamsasSeq array
      for (int i = 0; i < jseqs.length; i++)
      {
        String seqId = jseqs[i].getId();
  
 -      if (seqRefIds.get(seqId) != null)
 +      SequenceI tmpSeq = seqRefIds.get(seqId);
 +      if (tmpSeq != null)
        {
 -        tmpseqs.add(seqRefIds.get(seqId));
 -        multipleView = true;
 +        if (!incompleteSeqs.containsKey(seqId))
 +        {
 +          // may not need this check, but keep it for at least 2.9,1 release
 +          if (tmpSeq.getStart() != jseqs[i].getStart()
 +                  || tmpSeq.getEnd() != jseqs[i].getEnd())
 +          {
 +            System.err
 +                    .println("Warning JAL-2154 regression: updating start/end for sequence "
 +                            + tmpSeq.toString() + " to " + jseqs[i]);
 +          }
 +        }
 +        else
 +        {
 +          incompleteSeqs.remove(seqId);
 +        }
 +        if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId))
 +        {
 +          // most likely we are reading a dataset XML document so
 +          // update from vamsasSeq section of XML for this sequence
 +          tmpSeq.setName(vamsasSeq[vi].getName());
 +          tmpSeq.setDescription(vamsasSeq[vi].getDescription());
 +          tmpSeq.setSequence(vamsasSeq[vi].getSequence());
 +          vi++;
 +        }
 +        else
 +        {
 +          // reading multiple views, so vamsasSeq set is a subset of JSeq
 +          multipleView = true;
 +        }
 +        tmpSeq.setStart(jseqs[i].getStart());
 +        tmpSeq.setEnd(jseqs[i].getEnd());
 +        tmpseqs.add(tmpSeq);
        }
        else
        {
 -        jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
 +        tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
                  vamsasSeq[vi].getSequence());
 -        jseq.setDescription(vamsasSeq[vi].getDescription());
 -        jseq.setStart(jseqs[i].getStart());
 -        jseq.setEnd(jseqs[i].getEnd());
 -        jseq.setVamsasId(uniqueSetSuffix + seqId);
 -        seqRefIds.put(vamsasSeq[vi].getId(), jseq);
 -        tmpseqs.add(jseq);
 +        tmpSeq.setDescription(vamsasSeq[vi].getDescription());
 +        tmpSeq.setStart(jseqs[i].getStart());
 +        tmpSeq.setEnd(jseqs[i].getEnd());
 +        tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
 +        seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
 +        tmpseqs.add(tmpSeq);
          vi++;
        }
  
 +      if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
 +      {
 +        referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
 +      }
 +
        if (jseqs[i].getHidden())
        {
          if (hiddenSeqs == null)
            hiddenSeqs = new ArrayList<SequenceI>();
          }
  
 -        hiddenSeqs.add(seqRefIds.get(seqId));
 +        hiddenSeqs.add(tmpSeq);
        }
 -
      }
  
      // /
      SequenceI[] orderedSeqs = tmpseqs
              .toArray(new SequenceI[tmpseqs.size()]);
  
 -    Alignment al = new Alignment(orderedSeqs);
 -
 -    // / Add the alignment properties
 -    for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
 -    {
 -      SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
 -      al.setProperty(ssp.getKey(), ssp.getValue());
 -    }
 -
 -    // /
 -    // SequenceFeatures are added to the DatasetSequence,
 -    // so we must create or recover the dataset before loading features
 +    AlignmentI al = null;
 +    // so we must create or recover the dataset alignment before going further
      // ///////////////////////////////
      if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
      {
 -      // older jalview projects do not have a dataset id.
 +      // older jalview projects do not have a dataset - so creat alignment and
 +      // dataset
 +      al = new Alignment(orderedSeqs);
        al.setDataset(null);
      }
      else
      {
 -      // recover dataset - passing on flag indicating if this a 'viewless'
 -      // sequence set (a.k.a. a stored dataset for the project)
 -      recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
 -              .getViewportCount() == 0);
 +      boolean isdsal = object.getJalviewModelSequence().getViewportCount() == 0;
 +      if (isdsal)
 +      {
 +        // we are importing a dataset record, so
 +        // recover reference to an alignment already materialsed as dataset
 +        al = getDatasetFor(vamsasSet.getDatasetId());
 +      }
 +      if (al == null)
 +      {
 +        // materialse the alignment
 +        al = new Alignment(orderedSeqs);
 +      }
 +      if (isdsal)
 +      {
 +        addDatasetRef(vamsasSet.getDatasetId(), al);
 +      }
 +
 +      // finally, verify all data in vamsasSet is actually present in al
 +      // passing on flag indicating if it is actually a stored dataset
 +      recoverDatasetFor(vamsasSet, al, isdsal);
      }
 +
 +    if (referenceseqForView != null)
 +    {
 +      al.setSeqrep(referenceseqForView);
 +    }
 +    // / Add the alignment properties
 +    for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
 +    {
 +      SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
 +      al.setProperty(ssp.getKey(), ssp.getValue());
 +    }
 +
      // ///////////////////////////////
  
      Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
      {
        // load sequence features, database references and any associated PDB
        // structures for the alignment
 +      //
 +      // prior to 2.10, this part would only be executed the first time a
 +      // sequence was encountered, but not afterwards.
 +      // now, for 2.10 projects, this is also done if the xml doc includes
 +      // dataset sequences not actually present in any particular view.
 +      //
        for (int i = 0; i < vamsasSeq.length; i++)
        {
          if (jseqs[i].getFeaturesCount() > 0)
                }
  
              }
 -
 -            al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
 +            // adds feature to datasequence's feature set (since Jalview 2.10)
 +            al.getSequenceAt(i).addSequenceFeature(sf);
            }
          }
          if (vamsasSeq[i].getDBRefCount() > 0)
          {
 -          addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
 +          // adds dbrefs to datasequence's set (since Jalview 2.10)
 +          addDBRefs(
 +                  al.getSequenceAt(i).getDatasetSequence() == null ? al.getSequenceAt(i)
 +                          : al.getSequenceAt(i).getDatasetSequence(),
 +                  vamsasSeq[i]);
          }
          if (jseqs[i].getPdbidsCount() > 0)
          {
              entry.setId(ids[p].getId());
              if (ids[p].getType() != null)
              {
 -              if (ids[p].getType().equalsIgnoreCase("PDB"))
 +              if (PDBEntry.Type.getType(ids[p].getType()) != null)
                {
 -                entry.setType(PDBEntry.Type.PDB);
 +                entry.setType(PDBEntry.Type.getType(ids[p].getType()));
                }
                else
                {
                  entry.setType(PDBEntry.Type.FILE);
                }
              }
 -            if (ids[p].getFile() != null)
 +            // jprovider is null when executing 'New View'
 +            if (ids[p].getFile() != null && jprovider != null)
              {
                if (!pdbloaded.containsKey(ids[p].getFile()))
                {
 -                entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
 +                entry.setFile(loadPDBFile(jprovider, ids[p].getId(),
 +                        ids[p].getFile()));
                }
                else
                {
                  entry.setFile(pdbloaded.get(ids[p].getId()).toString());
                }
              }
 +            if (ids[p].getPdbentryItem() != null)
 +            {
 +              for (PdbentryItem item : ids[p].getPdbentryItem())
 +              {
 +                for (Property pr : item.getProperty())
 +                {
 +                  entry.setProperty(pr.getName(), pr.getValue());
 +                }
 +              }
 +            }
              StructureSelectionManager.getStructureSelectionManager(
                      Desktop.instance).registerPDBEntry(entry);
 -            al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
 +            // adds PDBEntry to datasequence's set (since Jalview 2.10)
 +            if (al.getSequenceAt(i).getDatasetSequence() != null)
 +            {
 +              al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
 +            }
 +            else
 +            {
 +              al.getSequenceAt(i).addPDBId(entry);
 +            }
            }
          }
        }
              if (maps[m].getMapping() != null)
              {
                mapping = addMapping(maps[m].getMapping());
 -            }
 -            if (dnaseq != null && mapping.getTo() != null)
 -            {
 -              cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
 -            }
 -            else
 -            {
 -              // defer to later
 -              frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
 -                  mapping });
 +              if (dnaseq != null && mapping.getTo() != null)
 +              {
 +                cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
 +              }
 +              else
 +              {
 +                // defer to later
 +                frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
 +                        mapping));
 +              }
              }
            }
 +          al.addCodonFrame(cf);
          }
 -        al.addCodonFrame(cf);
        }
      }
  
          }
          if (jGroup.getConsThreshold() != 0)
          {
 -          jalview.analysis.Conservation c = new jalview.analysis.Conservation(
 -                  "All", ResidueProperties.propHash, 3,
 -                  sg.getSequences(null), 0, sg.getWidth() - 1);
 +          Conservation c = new Conservation("All", sg.getSequences(null),
 +                  0, sg.getWidth() - 1);
            c.calculate();
            c.verdict(false, 25);
            sg.cs.setConservation(c);
            String rnaTitle = ss.getTitle();
            String sessionState = ss.getViewerState();
            String tempStateFile = copyJarEntry(jprovider, sessionState,
 -                  "varna");
 +                  "varna", null);
            RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
            appVarna.addModelSession(rna, rnaTitle, tempStateFile);
          }
              // Originally : ids[p].getFile()
              // : TODO: verify external PDB file recovery still works in normal
              // jalview project load
 -            jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
 +            jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(),
 +                    ids[p].getFile()));
              jpdb.setId(ids[p].getId());
  
              int x = structureState.getXpos();
              // Probably don't need to do this anymore...
              // Desktop.desktop.getComponentAt(x, y);
              // TODO: NOW: check that this recovers the PDB file correctly.
 -            String pdbFile = loadPDBFile(jprovider, ids[p].getId());
 +            String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
 +                    ids[p].getFile());
              jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
                      .getId() + "");
              if (sviewid == null)
       */
      String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
      chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
 -            "chimera");
 +            "chimera", null);
  
      Set<Entry<File, StructureData>> fileData = data.getFileData()
              .entrySet();
          // filename
          // translation differently.
          StructureData filedat = oldFiles.get(new File(oldfilenam));
 +        if (filedat == null)
 +        {
 +          String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
 +          filedat = oldFiles.get(new File(reformatedOldFilename));
 +        }
          newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
          pdbfilenames.add(filedat.getFilePath());
          pdbids.add(filedat.getPdbId());
        StructureData filedat = oldFiles.get(id);
        String pdbFile = filedat.getFilePath();
        SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
-       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE);
 -      binding.getSsm().setMapping(seq, null, pdbFile,
 -              jalview.io.AppletFormatAdapter.FILE, null);
++      binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
++              null);
        binding.addSequenceForStructFile(pdbFile, seq);
      }
      // and add the AlignmentPanel's reference to the view panel
    }
  
    AlignFrame loadViewport(String file, JSeq[] JSEQ,
 -          List<SequenceI> hiddenSeqs, Alignment al,
 +          List<SequenceI> hiddenSeqs, AlignmentI al,
            JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
            String viewId, List<JvAnnotRow> autoAlan)
    {
      af = new AlignFrame(al, view.getWidth(), view.getHeight(),
              uniqueSeqSetId, viewId);
  
 -    af.setFileName(file, "Jalview");
 +    af.setFileName(file, FileFormat.Jalview);
  
      for (int i = 0; i < JSEQ.length; i++)
      {
                .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
      }
  
 +    if (al.hasSeqrep())
 +    {
 +      af.getViewport().setColourByReferenceSeq(true);
 +      af.getViewport().setDisplayReferenceSeq(true);
 +    }
 +
      af.viewport.setGatherViewsHere(view.getGatheredViews());
  
      if (view.getSequenceSetId() != null)
      {
        for (int s = 0; s < JSEQ.length; s++)
        {
 -        jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
 -
 +        SequenceGroup hidden = new SequenceGroup();
 +        boolean isRepresentative = false;
          for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
          {
 -          hidden.addSequence(
 -                  al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
 +          isRepresentative = true;
 +          SequenceI sequenceToHide = al.getSequenceAt(JSEQ[s]
 +                  .getHiddenSequences(r));
 +          hidden.addSequence(sequenceToHide, false);
 +          // remove from hiddenSeqs list so we don't try to hide it twice
 +          hiddenSeqs.remove(sequenceToHide);
 +        }
 +        if (isRepresentative)
 +        {
 +          SequenceI representativeSequence = al.getSequenceAt(s);
 +          hidden.addSequence(representativeSequence, false);
 +          af.viewport.hideRepSequences(representativeSequence, hidden);
          }
 -        af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
        }
  
 -      // jalview.datamodel.SequenceI[] hseqs = new
 -      // jalview.datamodel.SequenceI[hiddenSeqs
 -      // .size()];
 -      //
 -      // for (int s = 0; s < hiddenSeqs.size(); s++)
 -      // {
 -      // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
 -      // }
 -
        SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
                .size()]);
        af.viewport.hideSequence(hseqs);
        af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
        String[] renderOrder = new String[jms.getFeatureSettings()
                .getSettingCount()];
 -      Hashtable featureGroups = new Hashtable();
 -      Hashtable featureColours = new Hashtable();
 -      Hashtable featureOrder = new Hashtable();
 +      Map<String, FeatureColourI> featureColours = new Hashtable<String, FeatureColourI>();
 +      Map<String, Float> featureOrder = new Hashtable<String, Float>();
  
        for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
        {
          Setting setting = jms.getFeatureSettings().getSetting(fs);
          if (setting.hasMincolour())
          {
 -          GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
 -                  new java.awt.Color(setting.getMincolour()),
 -                  new java.awt.Color(setting.getColour()),
 -                  setting.getMin(), setting.getMax()) : new GraduatedColor(
 -                  new java.awt.Color(setting.getMincolour()),
 -                  new java.awt.Color(setting.getColour()), 0, 1);
 +          FeatureColourI gc = setting.hasMin() ? new FeatureColour(
 +                  new Color(setting.getMincolour()), new Color(
 +                          setting.getColour()), setting.getMin(),
 +                  setting.getMax()) : new FeatureColour(new Color(
 +                  setting.getMincolour()), new Color(setting.getColour()),
 +                  0, 1);
            if (setting.hasThreshold())
            {
 -            gc.setThresh(setting.getThreshold());
 -            gc.setThreshType(setting.getThreshstate());
 +            gc.setThreshold(setting.getThreshold());
 +            int threshstate = setting.getThreshstate();
 +            // -1 = None, 0 = Below, 1 = Above threshold
 +            if (threshstate == 0)
 +            {
 +              gc.setBelowThreshold(true);
 +            }
 +            else if (threshstate == 1)
 +            {
 +              gc.setAboveThreshold(true);
 +            }
            }
            gc.setAutoScaled(true); // default
            if (setting.hasAutoScale())
          }
          else
          {
 -          featureColours.put(setting.getType(),
 -                  new java.awt.Color(setting.getColour()));
 +          featureColours.put(setting.getType(), new FeatureColour(
 +                  new Color(setting.getColour())));
          }
          renderOrder[fs] = setting.getType();
          if (setting.hasOrder())
            fdi.setVisible(setting.getType());
          }
        }
 -      Hashtable fgtable = new Hashtable();
 +      Map<String, Boolean> fgtable = new Hashtable<String, Boolean>();
        for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
        {
          Group grp = jms.getFeatureSettings().getGroup(gs);
        }
      }
      af.setMenusFromViewport(af.viewport);
 -
 +    af.setTitle(view.getTitle());
      // TODO: we don't need to do this if the viewport is aready visible.
      /*
       * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
    }
  
    private ColourSchemeI constructAnnotationColour(
 -          AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
 +          AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
            JalviewModelSequence jms, boolean checkGroupAnnColour)
    {
      boolean propagateAnnColour = false;
      return cs;
    }
  
 -  private void reorderAutoannotation(AlignFrame af, Alignment al,
 +  private void reorderAutoannotation(AlignFrame af, AlignmentI al,
            List<JvAnnotRow> autoAlan)
    {
      // copy over visualization settings for autocalculated annotation in the
      }
    }
  
 -  private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
 +  private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
            boolean ignoreUnrefed)
    {
 -    jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
 +    jalview.datamodel.AlignmentI ds = getDatasetFor(vamsasSet
 +            .getDatasetId());
      Vector dseqs = null;
      if (ds == null)
      {
      for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
      {
        Sequence vamsasSeq = vamsasSet.getSequence(i);
 -      ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
 +      ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
      }
      // create a new dataset
      if (ds == null)
     *          dataset alignment
     * @param dseqs
     *          vector to add new dataset sequence to
 +   * @param ignoreUnrefed
 +   *          - when true, don't create new sequences from vamsasSeq if it's id
 +   *          doesn't already have an asssociated Jalview sequence.
 +   * @param vseqpos
 +   *          - used to reorder the sequence in the alignment according to the
 +   *          vamsasSeq array ordering, to preserve ordering of dataset
     */
    private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
 -          AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
 +          AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
    {
      // JBP TODO: Check this is called for AlCodonFrames to support recovery of
      // xRef Codon Maps
      SequenceI sq = seqRefIds.get(vamsasSeq.getId());
 +    boolean reorder = false;
      SequenceI dsq = null;
      if (sq != null && sq.getDatasetSequence() != null)
      {
        dsq = sq.getDatasetSequence();
      }
 +    else
 +    {
 +      reorder = true;
 +    }
      if (sq == null && ignoreUnrefed)
      {
        return;
          // + (post ? "appended" : ""));
        }
      }
 +    else
 +    {
 +      // sequence refs are identical. We may need to update the existing dataset
 +      // alignment with this one, though.
 +      if (ds != null && dseqs == null)
 +      {
 +        int opos = ds.findIndex(dsq);
 +        SequenceI tseq = null;
 +        if (opos != -1 && vseqpos != opos)
 +        {
 +          // remove from old position
 +          ds.deleteSequence(dsq);
 +        }
 +        if (vseqpos < ds.getHeight())
 +        {
 +          if (vseqpos != opos)
 +          {
 +            // save sequence at destination position
 +            tseq = ds.getSequenceAt(vseqpos);
 +            ds.replaceSequenceAt(vseqpos, dsq);
 +            ds.addSequence(tseq);
 +          }
 +        }
 +        else
 +        {
 +          ds.addSequence(dsq);
 +        }
 +      }
 +    }
    }
  
    /*
     * TODO use AlignmentI here and in related methods - needs
     * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
     */
 -  Hashtable<String, Alignment> datasetIds = null;
 +  Hashtable<String, AlignmentI> datasetIds = null;
  
 -  IdentityHashMap<Alignment, String> dataset2Ids = null;
 +  IdentityHashMap<AlignmentI, String> dataset2Ids = null;
  
 -  private Alignment getDatasetFor(String datasetId)
 +  private AlignmentI getDatasetFor(String datasetId)
    {
      if (datasetIds == null)
      {
 -      datasetIds = new Hashtable<String, Alignment>();
 +      datasetIds = new Hashtable<String, AlignmentI>();
        return null;
      }
      if (datasetIds.containsKey(datasetId))
      return null;
    }
  
 -  private void addDatasetRef(String datasetId, Alignment dataset)
 +  private void addDatasetRef(String datasetId, AlignmentI dataset)
    {
      if (datasetIds == null)
      {
 -      datasetIds = new Hashtable<String, Alignment>();
 +      datasetIds = new Hashtable<String, AlignmentI>();
      }
      datasetIds.put(datasetId, dataset);
    }
     * @param dataset
     * @return
     */
 -  private String getDatasetIdRef(Alignment dataset)
 +  private String getDatasetIdRef(AlignmentI dataset)
    {
      if (dataset.getDataset() != null)
      {
        // make a new datasetId and record it
        if (dataset2Ids == null)
        {
 -        dataset2Ids = new IdentityHashMap<Alignment, String>();
 +        dataset2Ids = new IdentityHashMap<AlignmentI, String>();
        }
        else
        {
          }
          else
          {
 -          frefedSequence.add(new Object[] { dsfor, jmap });
 +          frefedSequence.add(newMappingRef(dsfor, jmap));
          }
        }
        else
            djs.setEnd(jmap.getMap().getToHighest());
            djs.setVamsasId(uniqueSetSuffix + sqid);
            jmap.setTo(djs);
 +          incompleteSeqs.put(sqid, djs);
            seqRefIds.put(sqid, djs);
  
          }
@@@ -32,14 -32,10 +32,14 @@@ import jalview.fts.api.FTSRestClientI
  import jalview.fts.core.FTSRestRequest;
  import jalview.fts.core.FTSRestResponse;
  import jalview.fts.service.pdb.PDBFTSRestClient;
 +import jalview.io.DataSourceType;
  import jalview.jbgui.GStructureChooser;
 +import jalview.structure.StructureMapping;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.MessageManager;
  import jalview.ws.DBRefFetcher;
 +import jalview.ws.phyre2.Phyre2Client;
 +import jalview.ws.phyre2.Phyre2SummaryPojo;
  import jalview.ws.sifts.SiftsSettings;
  
  import java.awt.event.ItemEvent;
@@@ -49,12 -45,12 +49,12 @@@ import java.util.HashSet
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Objects;
 +import java.util.Set;
  import java.util.Vector;
  
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JLabel;
 -import javax.swing.JOptionPane;
  import javax.swing.table.AbstractTableModel;
  
  /**
@@@ -67,7 -63,7 +67,7 @@@
  public class StructureChooser extends GStructureChooser implements
          IProgressIndicator
  {
 -  private boolean structuresDiscovered = false;
 +  private static int MAX_QLENGTH = 7820;
  
    private SequenceI selectedSequence;
  
  
    private FTSRestClientI pdbRestCleint;
  
 -  private String selectedPdbFileName;
 +  private String selectedStructureFileName;
  
    private boolean isValidPBDEntry;
  
 +  private boolean cachedPDBExists;
 +
    public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
            AlignmentPanel ap)
    {
        progressBar = new ProgressBar(this.statusPanel, this.statusBar);
      }
  
 +    // ensure a filter option is in force for search
 +    populateFilterComboBox(true, cachedPDBExists);
      Thread discoverPDBStructuresThread = new Thread(new Runnable()
      {
        @Override
                  .getString("status.searching_for_pdb_structures"),
                  startTime);
          fetchStructuresMetaData();
 -        populateFilterComboBox();
 +        // revise filter options if no results were found
 +        populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists);
          updateProgressIndicator(null, startTime);
          mainFrame.setVisible(true);
          updateCurrentView();
        pdbRequest.setAllowEmptySeq(false);
        pdbRequest.setResponseSize(500);
        pdbRequest.setFieldToSearchBy("(");
 +      FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
 +              .getSelectedItem());
 +      pdbRequest.setFieldToSortBy(selectedFilterOpt.getValue(),
 +              !chk_invertFilter.isSelected());
        pdbRequest.setWantedFields(wantedFields);
        pdbRequest.setSearchTerm(buildQuery(seq) + ")");
        pdbRequest.setAssociatedSequence(seq);
              && !discoveredStructuresSet.isEmpty())
      {
        getResultTable().setModel(
 -              FTSRestResponse.getTableModel(lastPdbRequest,
 -              discoveredStructuresSet));
 -      structuresDiscovered = true;
 +              JvSummaryTable.getTableModel(lastPdbRequest,
 +                      discoveredStructuresSet));
        noOfStructuresFound = discoveredStructuresSet.size();
        mainFrame.setTitle(MessageManager.formatMessage(
                "label.structure_chooser_no_of_structures",
          {
            errorMsg.append(error).append("\n");
          }
 -        JOptionPane.showMessageDialog(this, errorMsg.toString(),
 +        JvOptionPane.showMessageDialog(this, errorMsg.toString(),
                  MessageManager.getString("label.pdb_web-service_error"),
 -                JOptionPane.ERROR_MESSAGE);
 +                JvOptionPane.ERROR_MESSAGE);
        }
      }
    }
          }
        }
      }
 -
 +    cachedPDBExists = !entries.isEmpty();
      PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
      tbl_local_pdb.setModel(tableModelx);
    }
      boolean isPDBRefsFound = false;
      boolean isUniProtRefsFound = false;
      StringBuilder queryBuilder = new StringBuilder();
 -    HashSet<String> seqRefs = new LinkedHashSet<String>();
 +    Set<String> seqRefs = new LinkedHashSet<String>();
  
 -    if (seq.getAllPDBEntries() != null)
 +    if (seq.getAllPDBEntries() != null
 +            && queryBuilder.length() < MAX_QLENGTH)
      {
        for (PDBEntry entry : seq.getAllPDBEntries())
        {
          if (isValidSeqName(entry.getId()))
          {
 -          queryBuilder.append("pdb_id")
 -                  .append(":")
 -.append(entry.getId().toLowerCase())
 -                  .append(" OR ");
 +          queryBuilder.append("pdb_id:")
 +                  .append(entry.getId().toLowerCase()).append(" OR ");
            isPDBRefsFound = true;
 -          // seqRefs.add(entry.getId());
          }
        }
      }
      {
        for (DBRefEntry dbRef : seq.getDBRefs())
        {
 -        if (isValidSeqName(getDBRefId(dbRef)))
 +        if (isValidSeqName(getDBRefId(dbRef))
 +                && queryBuilder.length() < MAX_QLENGTH)
          {
            if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
            {
 -            queryBuilder
 -.append("uniprot_accession").append(":")
 -                    .append(getDBRefId(dbRef))
 -                    .append(" OR ");
 -            queryBuilder
 -.append("uniprot_id")
 -                    .append(":")
 +            queryBuilder.append("uniprot_accession:")
                      .append(getDBRefId(dbRef)).append(" OR ");
 +            queryBuilder.append("uniprot_id:").append(getDBRefId(dbRef))
 +                    .append(" OR ");
              isUniProtRefsFound = true;
            }
            else if (dbRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
            {
  
 -            queryBuilder.append("pdb_id")
 -                    .append(":").append(getDBRefId(dbRef).toLowerCase())
 -                    .append(" OR ");
 +            queryBuilder.append("pdb_id:")
 +                    .append(getDBRefId(dbRef).toLowerCase()).append(" OR ");
              isPDBRefsFound = true;
            }
            else
              .replaceAll("[^\\dA-Za-z|_]", "").replaceAll("\\s+", "+");
    }
  
 -
    /**
     * Ensures sequence ref names are not less than 3 characters and does not
     * contain a database name
            reorderedStructuresSet.addAll(filteredResponse);
            reorderedStructuresSet.addAll(discoveredStructuresSet);
            getResultTable().setModel(
 -                  FTSRestResponse.getTableModel(
 -                  lastPdbRequest, reorderedStructuresSet));
 +                  JvSummaryTable.getTableModel(lastPdbRequest,
 +                          reorderedStructuresSet));
  
 -          FTSRestResponse.configureTableColumn(getResultTable(),
 -                  wantedFields);
 +          JvSummaryTable.configureTableColumn(getResultTable(),
 +                  wantedFields, tempUserPrefs);
            getResultTable().getColumn("Ref Sequence").setPreferredWidth(120);
            getResultTable().getColumn("Ref Sequence").setMinWidth(100);
            getResultTable().getColumn("Ref Sequence").setMaxWidth(200);
              {
                errorMsg.append(error).append("\n");
              }
 -            JOptionPane.showMessageDialog(
 +            JvOptionPane.showMessageDialog(
                      null,
                      errorMsg.toString(),
                      MessageManager.getString("label.pdb_web-service_error"),
 -                    JOptionPane.ERROR_MESSAGE);
 +                    JvOptionPane.ERROR_MESSAGE);
            }
          }
  
      int value = chooser.showOpenDialog(null);
      if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
      {
 -      selectedPdbFileName = chooser.getSelectedFile().getPath();
 -      jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
 +      selectedStructureFileName = chooser.getSelectedFile().getPath();
 +      jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedStructureFileName);
        validateSelections();
      }
    }
     * Populates the filter combo-box options dynamically depending on discovered
     * structures
     */
 -  @Override
 -  protected void populateFilterComboBox()
 +  protected void populateFilterComboBox(boolean haveData,
 +          boolean cachedPDBExists)
    {
 -    if (isStructuresDiscovered())
 +    /*
 +     * temporarily suspend the change listener behaviour
 +     */
 +    cmb_filterOption.removeItemListener(this);
 +
 +    cmb_filterOption.removeAllItems();
 +    if (haveData)
      {
        cmb_filterOption.addItem(new FilterOption("Best Quality",
 -              "overall_quality", VIEWS_FILTER));
 +              "overall_quality", VIEWS_FILTER, false));
        cmb_filterOption.addItem(new FilterOption("Best Resolution",
 -              "resolution", VIEWS_FILTER));
 +              "resolution", VIEWS_FILTER, false));
        cmb_filterOption.addItem(new FilterOption("Most Protein Chain",
 -              "number_of_protein_chains", VIEWS_FILTER));
 +              "number_of_protein_chains", VIEWS_FILTER, false));
        cmb_filterOption.addItem(new FilterOption("Most Bound Molecules",
 -              "number_of_bound_molecules", VIEWS_FILTER));
 +              "number_of_bound_molecules", VIEWS_FILTER, false));
        cmb_filterOption.addItem(new FilterOption("Most Polymer Residues",
 -              "number_of_polymer_residues", VIEWS_FILTER));
 +              "number_of_polymer_residues", VIEWS_FILTER, true));
      }
      cmb_filterOption.addItem(new FilterOption("Enter PDB Id", "-",
 -            VIEWS_ENTER_ID));
 +            VIEWS_ENTER_ID, false));
      cmb_filterOption.addItem(new FilterOption("From File", "-",
 -            VIEWS_FROM_FILE));
 -    cmb_filterOption.addItem(new FilterOption("Cached PDB Entries", "-",
 -            VIEWS_LOCAL_PDB));
 +            VIEWS_FROM_FILE, false));
 +
 +    if (cachedPDBExists)
 +    {
 +      FilterOption cachedOption = new FilterOption("Cached PDB Entries",
 +              "-", VIEWS_LOCAL_PDB, false);
 +      cmb_filterOption.addItem(cachedOption);
 +      cmb_filterOption.setSelectedItem(cachedOption);
 +    }
 +
 +    cmb_filterOption.addItem(new FilterOption(
 +            "Predict 3D Model with Phyre2", "-", VIEWS_PHYRE2_PREDICTION,
 +            false));
 +    cmb_filterOption.addItemListener(this);
    }
  
    /**
     * Updates the displayed view based on the selected filter option
     */
 -  @Override
    protected void updateCurrentView()
    {
      FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
        chk_invertFilter.setVisible(true);
        filterResultSet(selectedFilterOpt.getValue());
      }
 +    else if (selectedFilterOpt.getView() == VIEWS_PHYRE2_PREDICTION)
 +    {
 +      mainFrame.setTitle(MessageManager
 +              .getString("label.phyre2_model_prediction"));
 +      phyre2InputAssSeqPanel.loadCmbAssSeq();
 +    }
      else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
              || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
      {
      {
        validateAssociationFromFile();
      }
 +    else if (currentView == VIEWS_PHYRE2_PREDICTION)
 +    {
 +      validateAssociationFromPhyre2();
 +      if (getPhyreResultTable().getSelectedRows().length > 0)
 +      {
 +        btn_view.setEnabled(true);
 +      }
 +    }
    }
  
    /**
                      "-Select Associated Seq-")))
      {
        btn_pdbFromFile.setEnabled(true);
 -      if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
 +      if (selectedStructureFileName != null && selectedStructureFileName.length() > 0)
        {
          btn_view.setEnabled(true);
          lbl_fromFileStatus.setIcon(goodImage);
      }
    }
  
 +  /**
 +   * Validates inputs for Phyre2 3D Model prediction
 +   */
 +  public void validateAssociationFromPhyre2()
 +  {
 +    AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) phyre2InputAssSeqPanel
 +            .getCmb_assSeq().getSelectedItem();
 +    if (selectedSequences.length == 1
 +            || (assSeqOpt != null && !assSeqOpt.getName().equalsIgnoreCase(
 +                    "-Select Associated Seq-")))
 +    {
 +      btn_runPhyre2Prediction.setEnabled(true);
 +    }
 +    else
 +    {
 +      btn_runPhyre2Prediction.setEnabled(false);
 +    }
 +  }
 +
    @Override
    public void cmbAssSeqStateChanged()
    {
    @Override
    public void ok_ActionPerformed()
    {
-     final long progressSessionId = System.currentTimeMillis();
      final StructureSelectionManager ssm = ap.getStructureSelectionManager();
 +    final int preferredHeight = pnl_filter.getHeight();
-     ssm.setProgressIndicator(this);
-     ssm.setProgressSessionId(progressSessionId);
 +    ssm.setMappingForPhyre2Model(false);
      new Thread(new Runnable()
      {
        @Override
        public void run()
        {
 -    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
 -            .getSelectedItem());
 -    String currentView = selectedFilterOpt.getView();
 -    if (currentView == VIEWS_FILTER)
 -    {
 +        FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
 +                .getSelectedItem());
 +        String currentView = selectedFilterOpt.getView();
 +        if (currentView == VIEWS_FILTER)
 +        {
            int pdbIdColIndex = getResultTable().getColumn("PDB Id")
                    .getModelIndex();
            int refSeqColIndex = getResultTable().getColumn("Ref Sequence")
 -              .getModelIndex();
 +                  .getModelIndex();
            int[] selectedRows = getResultTable().getSelectedRows();
 -      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
 -      int count = 0;
 -      ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
 -      for (int row : selectedRows)
 -      {
 +          PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
 +          int count = 0;
 +          List<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
 +          for (int row : selectedRows)
 +          {
              String pdbIdStr = getResultTable().getValueAt(row,
 -                    pdbIdColIndex)
 -                .toString();
 +                    pdbIdColIndex).toString();
              SequenceI selectedSeq = (SequenceI) getResultTable()
 -                    .getValueAt(row,
 -                refSeqColIndex);
 -        selectedSeqsToView.add(selectedSeq);
 +                    .getValueAt(row, refSeqColIndex);
 +            selectedSeqsToView.add(selectedSeq);
              PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
              if (pdbEntry == null)
              {
                pdbEntry = getFindEntry(pdbIdStr,
                        selectedSeq.getAllPDBEntries());
              }
 -        if (pdbEntry == null)
 -        {
 -          pdbEntry = new PDBEntry();
 -          pdbEntry.setId(pdbIdStr);
 -          pdbEntry.setType(PDBEntry.Type.PDB);
 -          selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
 +            if (pdbEntry == null)
 +            {
 +              pdbEntry = new PDBEntry();
 +              pdbEntry.setId(pdbIdStr);
 +              pdbEntry.setType(PDBEntry.Type.PDB);
 +              selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
 +            }
 +            pdbEntriesToView[count++] = pdbEntry;
 +          }
 +          SequenceI[] selectedSeqs = selectedSeqsToView
 +                  .toArray(new SequenceI[selectedSeqsToView.size()]);
 +          launchStructureViewer(ssm, pdbEntriesToView, ap, selectedSeqs);
          }
 -        pdbEntriesToView[count++] = pdbEntry;
 -      }
 -      SequenceI[] selectedSeqs = selectedSeqsToView
 -              .toArray(new SequenceI[selectedSeqsToView.size()]);
 -          launchStructureViewer(ssm, pdbEntriesToView, ap,
 -                  selectedSeqs);
 -    }
 -    else if (currentView == VIEWS_LOCAL_PDB)
 -    {
 -      int[] selectedRows = tbl_local_pdb.getSelectedRows();
 -      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
 -      int count = 0;
 +        else if (currentView == VIEWS_LOCAL_PDB)
 +        {
 +          int[] selectedRows = tbl_local_pdb.getSelectedRows();
 +          PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
 +          int count = 0;
            int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
                    .getModelIndex();
 -      int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
 -              .getModelIndex();
 -      ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
 -      for (int row : selectedRows)
 -      {
 -        PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
 -                pdbIdColIndex);
 -        pdbEntriesToView[count++] = pdbEntry;
 -        SequenceI selectedSeq = (SequenceI) tbl_local_pdb.getValueAt(row,
 -                refSeqColIndex);
 -        selectedSeqsToView.add(selectedSeq);
 -      }
 -      SequenceI[] selectedSeqs = selectedSeqsToView
 -              .toArray(new SequenceI[selectedSeqsToView.size()]);
 -          launchStructureViewer(ssm, pdbEntriesToView, ap,
 -                  selectedSeqs);
 -    }
 -    else if (currentView == VIEWS_ENTER_ID)
 -    {
 -      SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
 -              .getCmb_assSeq().getSelectedItem()).getSequence();
 -      if (userSelectedSeq != null)
 -      {
 -        selectedSequence = userSelectedSeq;
 -      }
 +          int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
 +                  .getModelIndex();
 +          List<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
 +          for (int row : selectedRows)
 +          {
 +            PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
 +                    pdbIdColIndex);
 +            pdbEntriesToView[count++] = pdbEntry;
 +            SequenceI selectedSeq = (SequenceI) tbl_local_pdb.getValueAt(
 +                    row, refSeqColIndex);
 +            selectedSeqsToView.add(selectedSeq);
 +          }
 +          SequenceI[] selectedSeqs = selectedSeqsToView
 +                  .toArray(new SequenceI[selectedSeqsToView.size()]);
 +          launchStructureViewer(ssm, pdbEntriesToView, ap, selectedSeqs);
 +        }
 +        else if (currentView == VIEWS_ENTER_ID)
 +        {
 +          SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
 +                  .getCmb_assSeq().getSelectedItem()).getSequence();
 +          if (userSelectedSeq != null)
 +          {
 +            selectedSequence = userSelectedSeq;
 +          }
  
 -      String pdbIdStr = txt_search.getText();
 -      PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
 -      if (pdbEntry == null)
 -      {
 -        pdbEntry = new PDBEntry();
++
 +          String pdbIdStr = txt_search.getText();
 +          PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
 +          if (pdbEntry == null)
 +          {
 +            pdbEntry = new PDBEntry();
              if (pdbIdStr.split(":").length > 1)
              {
 -              pdbEntry.setChainCode(pdbIdStr.split(":")[1]);
 +              pdbEntry.setId(pdbIdStr.split(":")[0]);
 +              pdbEntry.setChainCode(pdbIdStr.split(":")[1].toUpperCase());
              }
 -        pdbEntry.setId(pdbIdStr);
 -        pdbEntry.setType(PDBEntry.Type.PDB);
 -        selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
 -      }
 +            else
 +            {
 +              pdbEntry.setId(pdbIdStr);
 +            }
 +            pdbEntry.setType(PDBEntry.Type.PDB);
 +            selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
 +          }
  
 -      PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
 +          PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
            launchStructureViewer(ssm, pdbEntriesToView, ap,
                    new SequenceI[] { selectedSequence });
      }
        {
          selectedSequence = userSelectedSeq;
        }
 -      PDBEntry fileEntry = new AssociatePdbFileWithSeq()
 -              .associatePdbWithSeq(selectedPdbFileName,
 -                      jalview.io.AppletFormatAdapter.FILE,
 +      PDBEntry fileEntry = new AssociateStructureFileWithSeq()
 +              .associateStructureWithSeq(selectedStructureFileName,
 +                          DataSourceType.FILE,
                        selectedSequence, true, Desktop.instance);
  
-           launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap,
+           launchStructureViewer(ssm,
+                   new PDBEntry[] { fileEntry }, ap,
                    new SequenceI[] { selectedSequence });
 -    }
 -    mainFrame.dispose();
 +        }
 +        else if (currentView == VIEWS_PHYRE2_PREDICTION)
 +        {
 +          SequenceI userSelectedSeq = ((AssociateSeqOptions) phyre2InputAssSeqPanel
 +                  .getCmb_assSeq().getSelectedItem()).getSequence();
 +          if (userSelectedSeq != null)
 +          {
 +            selectedSequence = userSelectedSeq;
 +          }
 +          int templateColIndex = getPhyreResultTable()
 +                  .getColumn("Template")
 +                  .getModelIndex();
 +          int[] selectedRows = getPhyreResultTable().getSelectedRows();
 +          PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
 +          int count = 0;
 +          for (int row : selectedRows)
 +          {
 +            String templateId = getPhyreResultTable().getValueAt(row,
 +                    templateColIndex).toString();
 +            String structureFile = phyre2ResultDirectory + templateId
 +                    + ".pdb";
 +            pdbEntriesToView[count++] = new AssociateStructureFileWithSeq()
 +                    .associateStructureWithSeq(structureFile,
 +                            DataSourceType.FILE, selectedSequence, true,
 +                            Desktop.instance);
 +          }
 +
 +          final StructureSelectionManager ssm = ap
 +                  .getStructureSelectionManager();
 +          ssm.setMappingForPhyre2Model(true);
 +          final long progressSessionId = System.currentTimeMillis();
 +          ssm.setProgressSessionId(progressSessionId);
 +
 +          SequenceI[] sequences = new SequenceI[] { selectedSequence };
 +
 +          ssm.setProgressBar(MessageManager
 +                  .getString("status.launching_3d_structure_viewer"));
 +          final StructureViewer sViewer = new StructureViewer(ssm);
 +          if (pdbEntriesToView.length > 1)
 +          {
 +            ArrayList<SequenceI[]> seqsMap = new ArrayList<SequenceI[]>();
 +            for (SequenceI seq : sequences)
 +            {
 +              seqsMap.add(new SequenceI[] { seq });
 +            }
 +            SequenceI[][] collatedSeqs = seqsMap
 +                    .toArray(new SequenceI[0][0]);
 +            ssm.setProgressBar(null);
 +            ssm.setProgressBar(MessageManager
 +                    .getString("status.fetching_3d_structures_for_selected_entries"));
 +            sViewer.viewStructures(pdbEntriesToView, collatedSeqs, ap);
 +          }
 +          else
 +          {
 +            ssm.setProgressBar(null);
 +            ssm.setProgressBar(MessageManager.formatMessage(
 +                    "status.fetching_3d_structures_for",
 +                    pdbEntriesToView[0].getId()));
 +            sViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
 +          }
 +        }
 +        closeAction(preferredHeight);
        }
      }).start();
    }
  
 +  private String phyre2ResultDirectory;
 +
 +  @Override
 +  public void predict3DModelWithPhyre2()
 +  {
 +    // TODO implement code for submitting sequence to Phyre2 service, and code
 +    // for getting the result directory when the job completes, this is
 +    // currently hard-wired to the directory of result for FER_CAPAN/1-144
 +    phyre2ResultDirectory = "examples/testdata/phyre2results/56da5616b4559c93/";
 +    String summaryhtml = phyre2ResultDirectory + "summary.html";
 +    // TODO ditch HTML parsing once appropriated data file (i.e. JSON) for
 +    // Phyre2 result summary is made available
 +    List<Phyre2SummaryPojo> phyreResults = Phyre2Client
 +            .parsePhyre2ResultSummaryTable(summaryhtml);
 +    getPhyreResultTable()
 +            .setModel(Phyre2Client.getTableModel(phyreResults));
 +    Phyre2Client.configurePhyreResultTable(getPhyreResultTable());
 +  }
 +
    private PDBEntry getFindEntry(String id, Vector<PDBEntry> pdbEntries)
    {
      Objects.requireNonNull(id);
            final PDBEntry[] pdbEntriesToView,
            final AlignmentPanel alignPanel, SequenceI[] sequences)
    {
-     ssm.setProgressBar(MessageManager
-             .getString("status.launching_3d_structure_viewer"));
+     long progressId = sequences.hashCode();
+     setProgressBar(MessageManager
+             .getString("status.launching_3d_structure_viewer"), progressId);
      final StructureViewer sViewer = new StructureViewer(ssm);
+     setProgressBar(null, progressId);
  
      if (SiftsSettings.isMapWithSifts())
      {
 -      // TODO: prompt user if there are lots of sequences without dbrefs.
 -      // It can take a long time if we need to fetch all dbrefs for all
 -      // sequences!
 -      ArrayList<SequenceI> seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
 +      List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
 +      int p = 0;
 +      // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
 +      // real PDB ID. For moment, we can also safely do this if there is already
 +      // a known mapping between the PDBEntry and the sequence.
++
        for (SequenceI seq : sequences)
        {
 -        if (seq.getSourceDBRef() == null && seq.getDBRefs() == null)
 +        PDBEntry pdbe = pdbEntriesToView[p++];
 +        if (pdbe != null && pdbe.getFile() != null)
          {
 -            seqsWithoutSourceDBRef.add(seq);
 -            continue;
 +          StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
 +          if (smm != null && smm.length > 0)
 +          {
 +            for (StructureMapping sm : smm)
 +            {
 +              if (sm.getSequence() == seq)
 +              {
 +                continue;
 +              }
 +            }
            }
 +        }
 +        if (seq.getPrimaryDBRefs().size() == 0)
 +        {
 +          seqsWithoutSourceDBRef.add(seq);
 +          continue;
 +        }
        }
        if (!seqsWithoutSourceDBRef.isEmpty())
        {
          int y = seqsWithoutSourceDBRef.size();
-         ssm.setProgressBar(null);
-         ssm.setProgressBar(MessageManager.formatMessage(
+         setProgressBar(MessageManager.formatMessage(
                  "status.fetching_dbrefs_for_sequences_without_valid_refs",
-                 y));
+                 y), progressId);
          SequenceI[] seqWithoutSrcDBRef = new SequenceI[y];
          int x = 0;
          for (SequenceI fSeq : seqsWithoutSourceDBRef)
          {
            seqWithoutSrcDBRef[x++] = fSeq;
          }
-         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
-         dbRefFetcher.fetchDBRefs(true);
+         new DBRefFetcher(seqWithoutSrcDBRef).fetchDBRefs(true);
+         setProgressBar("Fetch complete.", progressId); // todo i18n
        }
      }
      if (pdbEntriesToView.length > 1)
          seqsMap.add(new SequenceI[] { seq });
        }
        SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]);
-       ssm.setProgressBar(null);
-       ssm.setProgressBar(MessageManager
-               .getString("status.fetching_3d_structures_for_selected_entries"));
+       setProgressBar(MessageManager
+                    .getString("status.fetching_3d_structures_for_selected_entries"), progressId);
        sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel);
      }
      else
      {
-       ssm.setProgressBar(null);
-       ssm.setProgressBar(MessageManager.formatMessage(
+       setProgressBar(MessageManager.formatMessage(
                "status.fetching_3d_structures_for",
-               pdbEntriesToView[0].getId()));
+               pdbEntriesToView[0].getId()),progressId);
        sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
      }
+     setProgressBar(null, progressId);
    }
  
    /**
  
    public boolean isStructuresDiscovered()
    {
 -    return structuresDiscovered;
 -  }
 -
 -  public void setStructuresDiscovered(boolean structuresDiscovered)
 -  {
 -    this.structuresDiscovered = structuresDiscovered;
 +    return discoveredStructuresSet != null
 +            && !discoveredStructuresSet.isEmpty();
    }
  
    public Collection<FTSData> getDiscoveredStructuresSet()
            pdbRequest.setResponseSize(1);
            pdbRequest.setFieldToSearchBy("(pdb_id:");
            pdbRequest.setWantedFields(wantedFields);
 -          pdbRequest
 -.setSearchTerm(searchTerm + ")");
 +          pdbRequest.setSearchTerm(searchTerm + ")");
            pdbRequest.setAssociatedSequence(selectedSequence);
            pdbRestCleint = PDBFTSRestClient.getInstance();
            wantedFields.add(pdbRestCleint.getPrimaryKeyColumn());
@@@ -24,23 -24,18 +24,22 @@@ import jalview.datamodel.PDBEntry
  import jalview.datamodel.SequenceI;
  import jalview.gui.StructureViewer.ViewerType;
  import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 -import jalview.io.AppletFormatAdapter;
 +import jalview.io.DataSourceType;
  import jalview.jbgui.GStructureViewer;
  import jalview.structures.models.AAStructureBindingModel;
  import jalview.util.MessageManager;
  
  import java.awt.Component;
 +import java.awt.event.ActionEvent;
 +import java.awt.event.ActionListener;
 +import java.awt.event.ItemEvent;
 +import java.awt.event.ItemListener;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Vector;
  
 +import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JMenuItem;
--import javax.swing.JOptionPane;
  
  /**
   * Base class with common functionality for JMol, Chimera or other structure
@@@ -81,8 -76,6 +80,8 @@@ public abstract class StructureViewerBa
  
    protected Thread worker = null;
  
 +  protected boolean allChainsSelected = false;
 +
    /**
     * 
     * @param ap2
  
    protected abstract AAStructureBindingModel getBindingModel();
  
+   protected abstract IProgressIndicator getIProgressIndicator();
    /**
     * add a new structure (with associated sequences and chains) to this viewer,
     * retrieving it if necessary first.
     * 
     * @param pdbId
     * @param view
 -   * @return YES, NO or CANCEL JOptionPane code
 +   * @return YES, NO or CANCEL JvOptionPane code
     */
    protected int chooseAlignStructureToViewer(String pdbId,
            StructureViewerBase view)
    {
 -    int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
 +    int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
              MessageManager.formatMessage("label.add_pdbentry_to_view",
                      new Object[] { pdbId, view.getTitle() }),
              MessageManager
                      .getString("label.align_to_existing_structure_view"),
 -            JOptionPane.YES_NO_CANCEL_OPTION);
 +            JvOptionPane.YES_NO_CANCEL_OPTION);
      return option;
    }
  
          continue;
        }
        int option = chooseAlignStructureToViewer(pdbId, view);
 -      if (option == JOptionPane.CANCEL_OPTION)
 +      if (option == JvOptionPane.CANCEL_OPTION)
        {
          return true;
        }
 -      else if (option == JOptionPane.YES_OPTION)
 +      else if (option == JvOptionPane.YES_OPTION)
        {
          view.useAlignmentPanelForSuperposition(apanel);
          view.addStructure(pdbentry, seq, chains, true, apanel.alignFrame);
       * create the mappings
       */
      apanel.getStructureSelectionManager().setMapping(seq, chains,
-             pdbFilename, DataSourceType.FILE);
 -            pdbFilename, AppletFormatAdapter.FILE, getIProgressIndicator());
++            pdbFilename, DataSourceType.FILE, getIProgressIndicator());
  
      /*
       * alert the FeatureRenderer to show new (PDB RESNUM) features
        /*
         * the PDB file is already loaded
         */
 -      int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
 +      int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
                MessageManager.formatMessage(
                        "label.pdb_entry_is_already_displayed",
                        new Object[] { pdbId }), MessageManager
                        .formatMessage(
                                "label.map_sequences_to_visible_window",
                                new Object[] { pdbId }),
 -              JOptionPane.YES_NO_CANCEL_OPTION);
 -      if (option == JOptionPane.CANCEL_OPTION)
 +              JvOptionPane.YES_NO_CANCEL_OPTION);
 +      if (option == JvOptionPane.CANCEL_OPTION)
        {
          finished = true;
        }
 -      else if (option == JOptionPane.YES_OPTION)
 +      else if (option == JvOptionPane.YES_OPTION)
        {
          addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
          finished = true;
      }
      return finished;
    }
 +
 +  void setChainMenuItems(List<String> chainNames)
 +  {
 +    chainMenu.removeAll();
 +    if (chainNames == null || chainNames.isEmpty())
 +    {
 +      return;
 +    }
 +    JMenuItem menuItem = new JMenuItem(
 +            MessageManager.getString("label.all"));
 +    menuItem.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent evt)
 +      {
 +        allChainsSelected = true;
 +        for (int i = 0; i < chainMenu.getItemCount(); i++)
 +        {
 +          if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
 +          {
 +            ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
 +          }
 +        }
 +        showSelectedChains();
 +        allChainsSelected = false;
 +      }
 +    });
 +
 +    chainMenu.add(menuItem);
 +
 +    for (String chain : chainNames)
 +    {
 +      menuItem = new JCheckBoxMenuItem(chain, true);
 +      menuItem.addItemListener(new ItemListener()
 +      {
 +        @Override
 +        public void itemStateChanged(ItemEvent evt)
 +        {
 +          if (!allChainsSelected)
 +          {
 +            showSelectedChains();
 +          }
 +        }
 +      });
 +
 +      chainMenu.add(menuItem);
 +    }
 +  }
 +
 +  abstract void showSelectedChains();
 +
  }
@@@ -31,15 -31,12 +31,15 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.Annotation;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SearchResults;
 +import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.SequenceI;
 +import jalview.ext.jmol.JmolParser;
  import jalview.gui.IProgressIndicator;
 -import jalview.io.AppletFormatAdapter;
 +import jalview.io.DataSourceType;
  import jalview.io.StructureFile;
  import jalview.util.MappingUtils;
  import jalview.util.MessageManager;
 +import jalview.ws.phyre2.Phyre2Client;
  import jalview.ws.sifts.SiftsClient;
  import jalview.ws.sifts.SiftsException;
  import jalview.ws.sifts.SiftsSettings;
@@@ -73,14 -70,8 +73,14 @@@ public class StructureSelectionManage
  
    private boolean addTempFacAnnot = false;
  
 +  private IProgressIndicator progressIndicator;
 +
    private SiftsClient siftsClient = null;
  
 +  private long progressSessionId;
 +
 +  private boolean mappingForPhyre2Model;
 +
    /*
     * Set of any registered mappings between (dataset) sequences.
     */
     * @return null or the structure data parsed as a pdb file
     */
    synchronized public StructureFile setMapping(SequenceI[] sequence,
-           String[] targetChains, String pdbFile, DataSourceType protocol)
 -          String[] targetChains, String pdbFile, String protocol,
++          String[] targetChains, String pdbFile, DataSourceType protocol,
+           IProgressIndicator progress)
    {
-     return setMapping(true, sequence, targetChains, pdbFile, protocol);
+     return computeMapping(true, sequence, targetChains, pdbFile, protocol,
+             progress);
    }
  
    /**
     * create sequence structure mappings between each sequence and the given
     * pdbFile (retrieved via the given protocol).
     *          - one or more sequences to be mapped to pdbFile
     * @param targetChainIds
     *          - optional chain specification for mapping each sequence to pdb
-    *          (may be nill, individual elements may be nill)
+    *          (may be null, individual elements may be null)
     * @param pdbFile
     *          - structure data resource
-    * @param sourceType
+    * @param protocol
     *          - how to resolve data from resource
     * @return null or the structure data parsed as a pdb file
     */
    synchronized public StructureFile setMapping(boolean forStructureView,
            SequenceI[] sequenceArray, String[] targetChainIds,
-           String pdbFile, DataSourceType sourceType)
 -          String pdbFile,
 -          String protocol)
++          String pdbFile, DataSourceType protocol)
    {
+     return computeMapping(forStructureView, sequenceArray, targetChainIds,
+             pdbFile, protocol, null);
+   }
+   synchronized public StructureFile computeMapping(
+           boolean forStructureView, SequenceI[] sequenceArray,
 -          String[] targetChainIds, String pdbFile, String protocol,
++          String[] targetChainIds, String pdbFile, DataSourceType protocol,
+           IProgressIndicator progress)
+   {
+     long progressSessionId = System.currentTimeMillis() * 3;
      /*
       * There will be better ways of doing this in the future, for now we'll use
       * the tried and tested MCview pdb mapping
      boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
      try
      {
-       pdb = new JmolParser(pdbFile, sourceType);
 -
 -      if (pdbFile != null && isCIFFile(pdbFile))
 -      {
 -        pdb = new jalview.ext.jmol.JmolParser(addTempFacAnnot, parseSecStr,
 -                secStructServices, pdbFile, protocol);
 -      }
 -      else
 -      {
 -        pdb = new PDBfile(addTempFacAnnot, parseSecStr, secStructServices,
 -                pdbFile, protocol);
 -      }
++      pdb = new JmolParser(pdbFile, protocol);
  
        if (pdb.getId() != null && pdb.getId().trim().length() > 0
-               && DataSourceType.FILE == sourceType)
 -              && AppletFormatAdapter.FILE.equals(protocol))
++              && DataSourceType.FILE == protocol)
        {
          registerPDBFile(pdb.getId().trim(), pdbFile);
        }
 +      // if PDBId is unavailable then skip SIFTS mapping execution path
 +      isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable();
 +
      } catch (Exception ex)
      {
        ex.printStackTrace();
      {
        boolean infChain = true;
        final SequenceI seq = sequenceArray[s];
 +      SequenceI ds = seq;
 +      while (ds.getDatasetSequence() != null)
 +      {
 +        ds = ds.getDatasetSequence();
 +      }
 +
        if (targetChainIds != null && targetChainIds[s] != null)
        {
          infChain = false;
          continue;
        }
  
-       if (sourceType == DataSourceType.PASTE)
 -      if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
++      if (protocol.equals(DataSourceType.PASTE))
        {
          pdbFile = "INLINE" + pdb.getId();
        }
 -      ArrayList<StructureMapping> seqToStrucMapping = new ArrayList<StructureMapping>();
 -      if (isMapUsingSIFTs)
 +
 +      List<StructureMapping> seqToStrucMapping = new ArrayList<StructureMapping>();
 +      if (!isMappingForPhyre2Model() && isMapUsingSIFTs && seq.isProtein())
        {
-         setProgressBar(null);
-         setProgressBar(MessageManager
-                 .getString("status.obtaining_mapping_with_sifts"));
+         if (progress!=null) {
+           progress.setProgressBar(MessageManager
+                 .getString("status.obtaining_mapping_with_sifts"),
+                   progressSessionId);
+         }
          jalview.datamodel.Mapping sqmpping = maxAlignseq
                  .getMappingFromS1(false);
          if (targetChainId != null && !targetChainId.trim().isEmpty())
                      pdb, maxChain, sqmpping, maxAlignseq);
              seqToStrucMapping.add(siftsMapping);
              maxChain.makeExactMapping(maxAlignseq, seq);
 -            maxChain.transferRESNUMFeatures(seq, null);
 +            maxChain.transferRESNUMFeatures(seq, null);// FIXME: is this
 +                                                       // "IEA:SIFTS" ?
              maxChain.transferResidueAnnotation(siftsMapping, sqmpping);
 +            ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
 +
            } catch (SiftsException e)
            {
              // fall back to NW alignment
              StructureMapping nwMapping = getNWMappings(seq, pdbFile,
                      targetChainId, maxChain, pdb, maxAlignseq);
              seqToStrucMapping.add(nwMapping);
 +            maxChain.makeExactMapping(maxAlignseq, seq);
 +            maxChain.transferRESNUMFeatures(seq, null); // FIXME: is this
 +                                                        // "IEA:Jalview" ?
 +            maxChain.transferResidueAnnotation(nwMapping, sqmpping);
 +            ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
 +          } catch (Exception e)
 +          {
 +            e.printStackTrace();
            }
          }
          else
          {
 -          ArrayList<StructureMapping> foundSiftsMappings = new ArrayList<StructureMapping>();
 +          List<StructureMapping> foundSiftsMappings = new ArrayList<StructureMapping>();
            for (PDBChain chain : pdb.getChains())
            {
              try
              {
                StructureMapping siftsMapping = getStructureMapping(seq,
 -                      pdbFile,
 -                      chain.id, pdb, chain, sqmpping, maxAlignseq);
 +                      pdbFile, chain.id, pdb, chain, sqmpping, maxAlignseq);
                foundSiftsMappings.add(siftsMapping);
              } catch (SiftsException e)
              {
                System.err.println(e.getMessage());
 +            } catch (Exception e)
 +            {
 +              e.printStackTrace();
              }
            }
            if (!foundSiftsMappings.isEmpty())
            {
              seqToStrucMapping.addAll(foundSiftsMappings);
              maxChain.makeExactMapping(maxAlignseq, seq);
 -            maxChain.transferRESNUMFeatures(seq, null);
 +            maxChain.transferRESNUMFeatures(seq, null);// FIXME: is this
 +                                                       // "IEA:SIFTS" ?
              maxChain.transferResidueAnnotation(foundSiftsMappings.get(0),
                      sqmpping);
 +            ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
            }
            else
            {
              StructureMapping nwMapping = getNWMappings(seq, pdbFile,
                      maxChainId, maxChain, pdb, maxAlignseq);
              seqToStrucMapping.add(nwMapping);
 +            maxChain.transferRESNUMFeatures(seq, null); // FIXME: is this
 +                                                        // "IEA:Jalview" ?
 +            maxChain.transferResidueAnnotation(nwMapping, sqmpping);
 +            ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
            }
          }
        }
 +      else if (isMappingForPhyre2Model())
 +      {
 +        setProgressBar(null);
 +        setProgressBar(MessageManager
 +                .getString("status.obtaining_mapping_with_phyre2_template_alignment"));
 +        StructureMapping phyre2ModelMapping = new Phyre2Client(pdb)
 +                .getStructureMapping(seq, pdbFile, " ");
 +
 +        seqToStrucMapping.add(phyre2ModelMapping);
 +        ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
 +      }
        else
        {
-         setProgressBar(null);
-         setProgressBar(MessageManager
-                 .getString("status.obtaining_mapping_with_nw_alignment"));
+         if (progress != null)
+         {
+           progress.setProgressBar(MessageManager
 -                                .getString("status.obtaining_mapping_with_nw_alignment"),
++                                  .getString("status.obtaining_mapping_with_nw_alignment"),
+                   progressSessionId);
+         }
 -        seqToStrucMapping.add(getNWMappings(seq, pdbFile, maxChainId,
 -                maxChain, pdb, maxAlignseq));
 +        StructureMapping nwMapping = getNWMappings(seq, pdbFile,
 +                maxChainId, maxChain, pdb, maxAlignseq);
 +        seqToStrucMapping.add(nwMapping);
 +        ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
 +
        }
 +
        if (forStructureView)
        {
          mappings.addAll(seqToStrucMapping);
        }
 -      if (progress != null)
 -      {
 -        progress.setProgressBar(null, progressSessionId);
 -      }
      }
      return pdb;
    }
      return "cif".equalsIgnoreCase(fileExt);
    }
  
 +  /**
 +   * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
 +   * uniprot or PDB
 +   * 
 +   * @param seq
 +   * @param pdbFile
 +   * @param targetChainId
 +   * @param pdb
 +   * @param maxChain
 +   * @param sqmpping
 +   * @param maxAlignseq
 +   * @return
 +   * @throws SiftsException
 +   */
    private StructureMapping getStructureMapping(SequenceI seq,
            String pdbFile, String targetChainId, StructureFile pdb,
            PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
 -          AlignSeq maxAlignseq) throws SiftsException
 +          AlignSeq maxAlignseq) throws Exception
    {
 -      StructureMapping curChainMapping = siftsClient
 -              .getSiftsStructureMapping(seq, pdbFile, targetChainId);
 -      try
 -      {
 +    StructureMapping curChainMapping = siftsClient
 +            .getStructureMapping(seq, pdbFile, targetChainId);
 +    try
 +    {
        PDBChain chain = pdb.findChain(targetChainId);
        if (chain != null)
        {
          chain.transferResidueAnnotation(curChainMapping, sqmpping);
        }
 -      } catch (Exception e)
 -      {
 -        e.printStackTrace();
 -      }
 -      return curChainMapping;
 +    } catch (Exception e)
 +    {
 +      e.printStackTrace();
 +    }
 +    return curChainMapping;
    }
  
 -  private StructureMapping getNWMappings(SequenceI seq,
 -          String pdbFile,
 +  private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
            String maxChainId, PDBChain maxChain, StructureFile pdb,
            AlignSeq maxAlignseq)
    {
        return;
      }
  
 -    SearchResults results = new SearchResults();
 +    SearchResultsI results = new SearchResults();
      for (AtomSpec atom : atoms)
      {
        SequenceI lastseq = null;
    {
      boolean hasSequenceListeners = handlingVamsasMo
              || !seqmappings.isEmpty();
 -    SearchResults results = null;
 +    SearchResultsI results = null;
      if (seqPos == -1)
      {
        seqPos = seq.findPosition(indexpos);
      return null;
    }
  
 +  public IProgressIndicator getProgressIndicator()
 +  {
 +    return progressIndicator;
 +  }
 +
 +  public void setProgressIndicator(IProgressIndicator progressIndicator)
 +  {
 +    this.progressIndicator = progressIndicator;
 +  }
 +
 +  public long getProgressSessionId()
 +  {
 +    return progressSessionId;
 +  }
 +
 +  public void setProgressSessionId(long progressSessionId)
 +  {
 +    this.progressSessionId = progressSessionId;
 +  }
 +
 +  public void setProgressBar(String message)
 +  {
 +    if (progressIndicator == null)
 +    {
 +      return;
 +    }
 +    progressIndicator.setProgressBar(message, progressSessionId);
 +  }
 +
    public List<AlignedCodonFrame> getSequenceMappings()
    {
      return seqmappings;
    }
  
 +  public boolean isMappingForPhyre2Model()
 +  {
 +    return mappingForPhyre2Model;
 +  }
 +
 +  public void setMappingForPhyre2Model(boolean mappingForPhyre2Model)
 +  {
 +    this.mappingForPhyre2Model = mappingForPhyre2Model;
 +  }
 +
 +  public static StructureSelectionManager getStructureSelectionManager()
 +  {
 +    return instances.values().iterator().next();
 +  }
 +
  }
@@@ -30,8 -30,7 +30,8 @@@ import jalview.datamodel.PDBEntry
  import jalview.datamodel.PDBEntry.Type;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceI;
 -import jalview.io.AppletFormatAdapter;
 +import jalview.gui.JvOptionPane;
 +import jalview.io.DataSourceType;
  import jalview.structure.AtomSpec;
  import jalview.structure.StructureSelectionManager;
  import jalview.structures.models.AAStructureBindingModel.SuperposeData;
@@@ -39,7 -38,6 +39,7 @@@
  import java.util.Arrays;
  import java.util.List;
  
 +import org.testng.annotations.BeforeClass;
  import org.testng.annotations.BeforeMethod;
  import org.testng.annotations.Test;
  
   */
  public class AAStructureBindingModelTest
  {
 +
 +  @BeforeClass(alwaysRun = true)
 +  public void setUpJvOptionPane()
 +  {
 +    JvOptionPane.setInteractiveMode(false);
 +    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
 +  }
 +
 +  /*
 +   * Scenario: Jalview has 4 sequences, corresponding to 1YCS (chains A and B), 3A6S|B, 1OOT|A
 +   */
    private static final String PDB_1 = "HEADER    COMPLEX (ANTI-ONCOGENE/ANKYRIN REPEATS) 30-SEP-96   1YCS              \n"
            + "ATOM      2  CA  VAL A  97      24.134   4.926  45.821  1.00 47.43           C  \n"
            + "ATOM      9  CA  PRO A  98      25.135   8.584  46.217  1.00 41.60           C  \n"
            + "ATOM     16  CA  SER A  99      28.243   9.596  44.271  1.00 39.63           C  \n"
            + "ATOM     22  CA  GLN A 100      31.488  10.133  46.156  1.00 35.60           C  \n"
 -          + "ATOM     31  CA  LYS A 101      33.323  11.587  43.115  1.00 41.69           C  \n";
 +          // artificial jump in residue numbering to prove it is correctly
 +          // mapped:
 +          + "ATOM     31  CA  LYS A 102      33.323  11.587  43.115  1.00 41.69           C  \n"
 +          + "ATOM   1857  CA  GLU B 374       9.193 -16.005  95.870  1.00 54.22           C  \n"
 +          + "ATOM   1866  CA  ILE B 375       7.101 -14.921  92.847  1.00 46.82           C  \n"
 +          + "ATOM   1874  CA  VAL B 376      10.251 -13.625  91.155  1.00 47.80           C  \n"
 +          + "ATOM   1881  CA  LYS B 377      11.767 -17.068  91.763  1.00 50.21           C  \n"
 +          + "ATOM   1890  CA  PHE B 378       8.665 -18.948  90.632  1.00 44.85           C  \n";
  
    private static final String PDB_2 = "HEADER    HYDROLASE                               09-SEP-09   3A6S              \n"
 -          + "ATOM      2  CA  MET A   1      15.366 -11.648  24.854  1.00 32.05           C  \n"
 -          + "ATOM     10  CA  LYS A   2      16.846  -9.215  22.340  1.00 25.68           C  \n"
 -          + "ATOM     19  CA  LYS A   3      15.412  -6.335  20.343  1.00 19.42           C  \n"
 -          + "ATOM     28  CA  LEU A   4      15.629  -5.719  16.616  1.00 15.49           C  \n"
 -          + "ATOM     36  CA  GLN A   5      14.412  -2.295  15.567  1.00 12.19           C  \n";
 +          + "ATOM      2  CA  MET B   1      15.366 -11.648  24.854  1.00 32.05           C  \n"
 +          + "ATOM     10  CA  LYS B   2      16.846  -9.215  22.340  1.00 25.68           C  \n"
 +          + "ATOM     19  CA  LYS B   3      15.412  -6.335  20.343  1.00 19.42           C  \n"
 +          + "ATOM     28  CA  LEU B   4      15.629  -5.719  16.616  1.00 15.49           C  \n"
 +          + "ATOM     36  CA  GLN B   5      14.412  -2.295  15.567  1.00 12.19           C  \n";
  
    private static final String PDB_3 = "HEADER    STRUCTURAL GENOMICS                     04-MAR-03   1OOT              \n"
 -          + "ATOM      2  CA  SER A   1      29.427   3.330  -6.578  1.00 32.50           C  \n"
 -          + "ATOM      8  CA  PRO A   2      29.975   3.340  -2.797  1.00 17.62           C  \n"
 -          + "ATOM     16  CA ALYS A   3      26.958   3.024  -0.410  0.50  8.78           C  \n"
 -          + "ATOM     33  CA  ALA A   4      26.790   4.320   3.172  1.00 11.98           C  \n"
 -          + "ATOM     39  CA AVAL A   5      24.424   3.853   6.106  0.50 13.83           C  \n";
 +          + "ATOM      2  CA  SER A   7      29.427   3.330  -6.578  1.00 32.50           C  \n"
 +          + "ATOM      8  CA  PRO A   8      29.975   3.340  -2.797  1.00 17.62           C  \n"
 +          + "ATOM     16  CA ALYS A   9      26.958   3.024  -0.410  0.50  8.78           C  \n"
 +          + "ATOM     33  CA  ALA A  10      26.790   4.320   3.172  1.00 11.98           C  \n"
 +          + "ATOM     39  CA AVAL A  12      24.424   3.853   6.106  0.50 13.83           C  \n";
  
    AAStructureBindingModel testee;
  
    @BeforeMethod(alwaysRun = true)
    public void setUp()
    {
 -    SequenceI seq1 = new Sequence("1YCS", "-VPSQK");
 +    SequenceI seq1a = new Sequence("1YCS|A", "-VPSQK");
 +    SequenceI seq1b = new Sequence("1YCS|B", "EIVKF-");
      SequenceI seq2 = new Sequence("3A6S", "MK-KLQ");
      SequenceI seq3 = new Sequence("1OOT", "SPK-AV");
 -    al = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
 +    al = new Alignment(new SequenceI[] { seq1a, seq1b, seq2, seq3 });
      al.setDataset(null);
  
 +    /*
 +     * give pdb files the name generated by Jalview for PASTE source
 +     */
      PDBEntry[] pdbFiles = new PDBEntry[3];
 -    pdbFiles[0] = new PDBEntry("1YCS", "A", Type.PDB, "1YCS.pdb");
 -    pdbFiles[1] = new PDBEntry("3A6S", "B", Type.PDB, "3A6S.pdb");
 -    pdbFiles[2] = new PDBEntry("1OOT", "A", Type.PDB, "1OOT.pdb");
 -    String[][] chains = new String[3][];
 +    pdbFiles[0] = new PDBEntry("1YCS", "A", Type.PDB, "INLINE1YCS");
 +    pdbFiles[1] = new PDBEntry("3A6S", "B", Type.PDB, "INLINE3A6S");
 +    pdbFiles[2] = new PDBEntry("1OOT", "A", Type.PDB, "INLINE1OOT");
      SequenceI[][] seqs = new SequenceI[3][];
 -    seqs[0] = new SequenceI[] { seq1 };
 +    seqs[0] = new SequenceI[] { seq1a, seq1b };
      seqs[1] = new SequenceI[] { seq2 };
      seqs[2] = new SequenceI[] { seq3 };
      StructureSelectionManager ssm = new StructureSelectionManager();
  
 -    ssm.setMapping(new SequenceI[] { seq1 }, null, PDB_1,
 -            AppletFormatAdapter.PASTE, null);
 +    ssm.setMapping(new SequenceI[] { seq1a, seq1b }, null, PDB_1,
-             DataSourceType.PASTE);
++            DataSourceType.PASTE, null);
      ssm.setMapping(new SequenceI[] { seq2 }, null, PDB_2,
-             DataSourceType.PASTE);
 -            AppletFormatAdapter.PASTE, null);
++            DataSourceType.PASTE, null);
      ssm.setMapping(new SequenceI[] { seq3 }, null, PDB_3,
-             DataSourceType.PASTE);
 -            AppletFormatAdapter.PASTE, null);
++            DataSourceType.PASTE, null);
++
  
 -    testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, chains, null)
 +    testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null)
      {
        @Override
        public String[] getPdbFile()
        {
 -        /*
 -         * fudge 'filenames' to match those generated when PDBFile parses PASTE
 -         * data
 -         */
          return new String[] { "INLINE1YCS", "INLINE3A6S", "INLINE1OOT" };
        }
  
        public void highlightAtoms(List<AtomSpec> atoms)
        {
        }
 +
 +      @Override
 +      public List<String> getChainNames()
 +      {
 +        return null;
 +      }
      };
    }
  
    @Test(groups = { "Functional" })
    public void testFindSuperposableResidues()
    {
 -    SuperposeData[] structs = new SuperposeData[al.getHeight()];
 +    /*
 +     * create a data bean to hold data per structure file
 +     */
 +    SuperposeData[] structs = new SuperposeData[testee.getPdbFile().length];
      for (int i = 0; i < structs.length; i++)
      {
        structs[i] = testee.new SuperposeData(al.getWidth());
       */
      assertFalse(matched[0]); // gap in first sequence
      assertTrue(matched[1]);
 -    assertFalse(matched[2]); // gap in second sequence
 -    assertFalse(matched[3]); // gap in third sequence
 +    assertFalse(matched[2]); // gap in third sequence
 +    assertFalse(matched[3]); // gap in fourth sequence
      assertTrue(matched[4]);
 -    assertTrue(matched[5]);
 +    assertTrue(matched[5]); // gap in second sequence
 +
 +    assertEquals("1YCS", structs[0].pdbId);
 +    assertEquals("3A6S", structs[1].pdbId);
 +    assertEquals("1OOT", structs[2].pdbId);
 +    assertEquals("A", structs[0].chain); // ? struct has chains A _and_ B
 +    assertEquals("B", structs[1].chain);
 +    assertEquals("A", structs[2].chain);
 +    // the 0's for unsuperposable positions propagate down the columns:
 +    assertEquals("[0, 97, 98, 99, 100, 102]",
 +            Arrays.toString(structs[0].pdbResNo));
 +    assertEquals("[0, 2, 0, 3, 4, 5]", Arrays.toString(structs[1].pdbResNo));
 +    assertEquals("[0, 8, 0, 0, 10, 12]",
 +            Arrays.toString(structs[2].pdbResNo));
    }
  
    @Test(groups = { "Functional" })