Merge remote-tracking branch 'origin/develop' into
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 9 Feb 2015 10:13:16 +0000 (10:13 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 9 Feb 2015 10:13:16 +0000 (10:13 +0000)
features/JAL-845splitPaneMergeDevelop

Conflicts:
resources/lang/Messages.properties
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/Sequence.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/ext/varna/JalviewVarnaBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/Desktop.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/TreePanel.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/AWSThread.java
test/jalview/ws/jabaws/JpredJabaStructExportImport.java
test/jalview/ws/jabaws/RNAStructExportImport.java

47 files changed:
1  2 
.classpath
resources/lang/Messages.properties
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/SeqPanel.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewLite.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AppVarnaBinding.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/TreePanel.java
src/jalview/gui/VamsasApplication.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/FileLoader.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/schemes/ResidueProperties.java
src/jalview/structure/AtomSpec.java
src/jalview/structure/SequenceListener.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/structures/models/AAStructureBindingModel.java
src/jalview/util/MappingUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/AWSThread.java
test/jalview/datamodel/SequenceTest.java

diff --cc .classpath
@@@ -51,9 -52,7 +52,8 @@@
        <classpathentry kind="lib" path="lib/xml-apis.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Plugin.jar"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin"/>
+       <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
++      <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin.jar"/>
        <classpathentry kind="output" path="classes"/>
  </classpath>
@@@ -1192,9 -1178,16 +1194,23 @@@ label.show_logo = Show Log
  label.normalise_logo = Normalise Logo
  label.no_colour_selection_in_scheme = Please, make a colour selection before to apply colour scheme
  label.no_colour_selection_warn = Error saving colour scheme
 +label.open_linked_alignment? = Would you like to open as a separate alignment, with cDNA and protein linked?
 +label.open_linked_alignment = Open linked alignment
 +label.no_mappings = No mappings found
 +label.mapping_failed = No sequence mapping could be made between the alignments.<br>A mapping requires sequence names to match, and equivalent sequence lengths.
 +action.no = No
 +label.for = for
+ label.select_by_annotation = Select By Annotation
+ action.select_by_annotation = Select by Annotation...
+ label.threshold_filter =  Threshold Filter
+ action.hide = Hide
+ action.select = Select
+ label.alpha_helix = Alpha Helix
+ label.beta_strand = Beta Strand
+ label.turn = Turn
+ label.select_all = Select All
+ label.structures_filter = Structures Filter
+ label.search_filter = Search Filter
+ label.display_name = Display Label
+ label.description = Description
++
Simple merge
Simple merge
@@@ -200,20 -265,93 +265,116 @@@ public interface AlignViewport
    List<AlignmentAnnotation> getVisibleAlignmentAnnotation(
            boolean selectedOnly);
  
+   FeaturesDisplayedI getFeaturesDisplayed();
+   String getSequenceSetId();
+   boolean isShowSequenceFeatures();
+   void setShowSequenceFeatures(boolean b);
+   /**
+    * 
+    * @param flag
+    *          indicating if annotation panel shown below alignment
+    * 
+    */
+   void setShowAnnotation(boolean b);
+   /**
+    * flag indicating if annotation panel shown below alignment
+    * 
+    * @return
+    */
+   boolean isShowAnnotation();
+   boolean isRightAlignIds();
+   void setRightAlignIds(boolean rightAlignIds);
+   boolean areFeaturesDisplayed();
+   void setShowSequenceFeaturesHeight(boolean selected);
+   boolean isShowSequenceFeaturesHeight();
+   void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI);
+   void alignmentChanged(AlignmentViewPanel ap);
+   /**
+    * @return the padGaps
+    */
+   boolean isPadGaps();
+   /**
+    * @param padGaps
+    *          the padGaps to set
+    */
+   void setPadGaps(boolean padGaps);
+   /**
+    * return visible region boundaries within given column range
+    * 
+    * @param min
+    *          first column (inclusive, from 0)
+    * @param max
+    *          last column (exclusive)
+    * @return int[][] range of {start,end} visible positions TODO: change to list
+    *         of int ranges
+    */
+   int[][] getVisibleRegionBoundaries(int min, int max);
+   /**
+    * This method returns an array of new SequenceI objects derived from the
+    * whole alignment or just the current selection with start and end points
+    * adjusted
+    * 
+    * @note if you need references to the actual SequenceI objects in the
+    *       alignment or currently selected then use getSequenceSelection()
+    * @return selection as new sequenceI objects
+    */
+   SequenceI[] getSelectionAsNewSequence();
+   void invertColumnSelection();
+   /**
+    * broadcast selection to any interested parties
+    */
+   void sendSelection();
+   /**
+    * calculate the row position for alignmentIndex if all hidden sequences were
+    * shown
+    * 
+    * @param alignmentIndex
+    * @return adjusted row position
+    */
+   int adjustForHiddenSeqs(int alignmentIndex);
+   boolean hasHiddenRows();
 +  /**
 +   * Returns a viewport which holds the cDna for this (protein), or vice versa,
 +   * or null if none is set.
 +   * 
 +   * @return
 +   */
 +  AlignViewportI getCodingComplement();
 +
++
++  /**
++   * Sets the viewport which holds the cDna for this (protein), or vice versa.
++   * Implementation should guarantee that the reciprocal relationship is always
++   * set, i.e. each viewport is the complement of the other.
++   */
 +  void setCodingComplement(AlignViewportI sl);
 +
 +  /**
-    * Answers true if viewport hosts DNA/RAN, false if peptide.
++   * Answers true if viewport hosts DNA/RNA, else false.
 +   * 
 +   * @return
 +   */
 +  boolean isNucleotide();
++
  }
@@@ -1687,12 -1690,9 +1690,8 @@@ public class AlignFrame extends Embmenu
      {
        copiedHiddenColumns = new Vector();
        int hiddenOffset = viewport.getSelectionGroup().getStartRes();
-       for (int i = 0; i < viewport.getColumnSelection().getHiddenColumns()
-               .size(); i++)
+       for (int[] region : viewport.getColumnSelection().getHiddenColumns())
        {
-         int[] region = viewport.getColumnSelection()
-                 .getHiddenColumns().get(i);
--
          copiedHiddenColumns.addElement(new int[]
          { region[0] - hiddenOffset, region[1] - hiddenOffset });
        }
Simple merge
Simple merge
Simple merge
Simple merge
  package jalview.datamodel;
  
  import jalview.util.ShiftList;
+ import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
+ import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
  
  import java.util.ArrayList;
 -import java.util.Arrays;
++import java.util.Collections;
+ import java.util.Enumeration;
  import java.util.List;
+ import java.util.Vector;
  
  /**
   * NOTE: Columns are zero based.
@@@ -324,17 -324,16 +324,16 @@@ public class ColumnSelectio
     * removes intersection of position,length ranges in deletions from the
     * start,end regions marked in intervals.
     * 
 -   * @param deletions
 +   * @param shifts
-    * @param hiddenColumns2
+    * @param intervals
     * @return
     */
-   private boolean pruneIntervalVector(List<int[]> shifts,
-           List<int[]> hiddenColumns2)
 -  private boolean pruneIntervalVector(Vector deletions, Vector intervals)
++  private boolean pruneIntervalVector(List<int[]> shifts, Vector intervals)
    {
      boolean pruned = false;
-     int i = 0, j = hiddenColumns2.size() - 1, s = 0, t = shifts.size() - 1;
-     int hr[] = hiddenColumns2.get(i);
 -    int i = 0, j = intervals.size() - 1, s = 0, t = deletions.size() - 1;
++    int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
+     int hr[] = (int[]) intervals.elementAt(i);
 -    int sr[] = (int[]) deletions.elementAt(s);
 +    int sr[] = shifts.get(s);
      while (i <= j && s <= t)
      {
        boolean trailinghn = hr[1] >= sr[0];
      // operations.
    }
  
-   private boolean pruneColumnList(List<int[]> shifts, List<Integer> list)
 -  private boolean pruneColumnList(Vector deletion, Vector list)
++  private boolean pruneColumnList(List<int[]> shifts, Vector list)
    {
 -    int s = 0, t = deletion.size();
 -    int[] sr = (int[]) list.elementAt(s++);
 +    int s = 0, t = shifts.size();
 +    int[] sr = shifts.get(s++);
      boolean pruned = false;
      int i = 0, j = list.size();
      while (i < j && s <= t)
     */
    public List<int[]> getHiddenColumns()
    {
-     return hiddenColumns;
 -    return hiddenColumns == null ? Arrays.asList(new int[]
 -    {}) : hiddenColumns;
++    return hiddenColumns == null ? Collections.<int[]> emptyList()
++            : hiddenColumns;
    }
  
    /**
@@@ -694,48 -681,70 +681,71 @@@ public class Sequence implements Sequen
      return map;
    }
  
-   /**
-    * Delete sequence characters from position 'from' (inclusive) to 'to'
-    * (exclusive). The end range is limited to the length of the sequence. If the
-    * start position is out of range, do nothing.
-    * 
-    * @see jalview.datamodel.SequenceI#deleteChars(int, int)
-    */
    @Override
-   public void deleteChars(int from, int to)
+   public List<int[]> getInsertions()
    {
-     if (from >= sequence.length)
+     ArrayList<int[]> map = new ArrayList<int[]>();
+     int lastj = -1, j = 0;
+     int pos = start;
+     int seqlen = sequence.length;
+     while ((j < seqlen))
      {
-       return;
+       if (jalview.util.Comparison.isGap(sequence[j]))
+       {
+         if (lastj == -1)
+         {
+           lastj = j;
+         }
+       }
+       else
+       {
+         if (lastj != -1)
+         {
+           map.add(new int[]
+           { lastj, j - 1 });
+           lastj = -1;
+         }
+       }
+       j++;
      }
-     if (to > sequence.length)
+     if (lastj != -1)
      {
-       to = sequence.length;
+       map.add(new int[]
+       { lastj, j - 1 });
+       lastj = -1;
      }
-     char[] tmp = StringUtils.deleteChars(sequence, from, to);
-     recreateDatasetAfterDeletions(from, to);
-     sequence = tmp;
+     return map;
    }
  
-   /**
-    * Reconstruct the dataset sequence, if necessary, after deletion of columns
-    * in the aligned sequence.
-    * 
-    * @param from
-    *          start column of deletions (zero-based, inclusive)
-    * @param to
-    *          end column of deletions (exclusive)
-    */
-   protected boolean recreateDatasetAfterDeletions(int from, int to)
+   @Override
+   public void deleteChars(int i, int j)
    {
-     boolean created = false;
-     boolean createNewDs = false;
      int newstart = start, newend = end;
+     if (i >= sequence.length)
+     {
+       return;
+     }
++    // TODO use StringUtils.deleteChars
+     char[] tmp;
+     if (j >= sequence.length)
+     {
+       tmp = new char[i];
+       System.arraycopy(sequence, 0, tmp, 0, i);
+       j = sequence.length;
+     }
+     else
+     {
+       tmp = new char[sequence.length - j + i];
+       System.arraycopy(sequence, 0, tmp, 0, i);
+       System.arraycopy(sequence, j, tmp, i, sequence.length - j);
+     }
+     boolean createNewDs = false;
      // TODO: take a look at the new dataset creation validation method below -
-     // this could become time consuming for large sequences - consider making it
+     // this could become time comsuming for large sequences - consider making it
      // more efficient
-     for (int s = from; s < to; s++)
+     for (int s = i; s < j; s++)
      {
        if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
        {
Simple merge
@@@ -46,8 -43,8 +43,7 @@@ import java.awt.event.ComponentListener
  import java.io.File;
  import java.net.URL;
  import java.security.AccessControlException;
 -import java.util.Enumeration;
  import java.util.Hashtable;
- import java.util.List;
  import java.util.Map;
  import java.util.Vector;
  
@@@ -1,23 -1,23 +1,3 @@@
--/*
-- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
-- * Copyright (C) 2014 The Jalview Authors
-- * 
-- * This file is part of Jalview.
-- * 
-- * Jalview is free software: you can redistribute it and/or
-- * modify it under the terms of the GNU General Public License 
-- * as published by the Free Software Foundation, either version 3
-- * of the License, or (at your option) any later version.
-- *  
-- * Jalview is distributed in the hope that it will be useful, but 
-- * WITHOUT ANY WARRANTY; without even the implied warranty 
-- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
-- * PURPOSE.  See the GNU General Public License for more details.
-- * 
-- * You should have received a copy of the GNU General Public License
-- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
-- * The Jalview Authors are detailed in the 'AUTHORS' file.
-- */
  package jalview.ext.rbvi.chimera;
  
  import jalview.api.AlignmentViewPanel;
@@@ -40,9 -38,8 +18,7 @@@ import jalview.util.Comparison
  import jalview.util.MessageManager;
  
  import java.awt.Color;
- import java.awt.event.ComponentEvent;
- import java.io.File;
  import java.util.ArrayList;
 -import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.LinkedHashMap;
  import java.util.List;
@@@ -767,19 -693,42 +672,41 @@@ public abstract class JalviewChimeraBin
      }
      AlignmentI alignment = alignmentv.getAlignment();
  
-     for (jalview.structure.StructureMappingcommandSet cpdbbyseq : ChimeraCommands
-             .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
-                     alignment))
 -    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(files, sr, fr, alignment))
++    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
++            files, sr, fr, alignment))
      {
-       for (String cbyseq : cpdbbyseq.commands)
+       for (String command : cpdbbyseq.commands)
        {
-         waitForChimera();
-         evalStateCommand(cbyseq, false);
-         waitForChimera();
+         executeWhenReady(command);
        }
      }
    }
  
+   /**
+    * @param files
+    * @param sr
+    * @param fr
+    * @param alignment
+    * @return
+    */
+   protected StructureMappingcommandSet[] getColourBySequenceCommands(
+           String[] files, SequenceRenderer sr, FeatureRenderer fr,
+           AlignmentI alignment)
+   {
 -    return ChimeraCommands
 -            .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
 -                    fr,
 -                    alignment);
++    return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
++            getSequence(), sr, fr, alignment);
+   }
+   /**
+    * @param command
+    */
+   protected void executeWhenReady(String command)
+   {
+     waitForChimera();
+     evalStateCommand(command, false);
+     waitForChimera();
+   }
    private void waitForChimera()
    {
      while (viewer != null && viewer.isBusy())
      }
    }
  
-   public boolean isColourBySequence()
-   {
-     return colourBySequence;
-   }
 -  
 +
-   public void setColourBySequence(boolean colourBySequence)
-   {
-     this.colourBySequence = colourBySequence;
-   }
  
    // End StructureListener
    // //////////////////////////
    public abstract SequenceRenderer getSequenceRenderer(
            AlignmentViewPanel alignment);
  
-   /**
-    * Highlight the specified atom positions in the structure.
-    * 
-    * @param atomIndex
-    * @param pdbResNum
-    * @param chain
-    * @param pdbfile
-    */
-   @Override
-   public void highlightAtoms(List<AtomSpec> atoms)
+   // jmol/ssm only
+   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
+           String pdbfile)
    {
-     for (AtomSpec atom : atoms)
+     List<ChimeraModel> cms = chimeraMaps.get(pdbfile);
+     if (cms != null)
      {
-       int pdbResNum = atom.getPdbResNum();
-       String chain = atom.getChain();
-       List<ChimeraModel> cms = chimmaps.get(atom.getPdbId());
-       if (cms != null)
-       {
-         int mdlNum = cms.get(0).getModelNumber();
-         viewerCommandHistory(false);
-         // viewer.stopListening();
-         if (resetLastRes.length() > 0)
-         {
-           eval.setLength(0);
-           eval.append(resetLastRes.toString() + ";");
-         }
+       int mdlNum = cms.get(0).getModelNumber();
  
-         eval.append("display "); // +modelNum
+       viewerCommandHistory(false);
+       // viewer.stopListening();
+       if (resetLastRes.length() > 0)
+       {
+         eval.setLength(0);
+         eval.append(resetLastRes.toString() + ";");
+       }
  
-         resetLastRes.setLength(0);
-         resetLastRes.append("~display ");
-         {
-           eval.append(" #" + (mdlNum));
-           resetLastRes.append(" #" + (mdlNum));
-         }
-         // complete select string
+       eval.append("display "); // +modelNum
  
-         eval.append(":" + pdbResNum);
-         resetLastRes.append(":" + pdbResNum);
-         if (!chain.equals(" "))
-         {
-           eval.append("." + chain);
-           resetLastRes.append("." + chain);
-         }
+       resetLastRes.setLength(0);
+       resetLastRes.append("~display ");
+       {
+         eval.append(" #" + (mdlNum));
+         resetLastRes.append(" #" + (mdlNum));
+       }
+       // complete select string
  
-         viewer.sendChimeraCommand(eval.toString(), false);
-         viewerCommandHistory(true);
-         // viewer.startListening();
+       eval.append(":" + pdbResNum);
+       resetLastRes.append(":" + pdbResNum);
+       if (!chain.equals(" "))
+       {
+         eval.append("." + chain);
+         resetLastRes.append("." + chain);
        }
 -      
++
+       viewer.sendChimeraCommand(eval.toString(), false);
+       viewerCommandHistory(true);
+       // viewer.startListening();
      }
    }
  
    }
  
    /**
-    * Adds sequences to the pe'th pdbentry's sequence set.
+    * Ask Chimera to open a session file. Returns true if successful, else false.
+    * The filename must have a .py extension for this command to work.
     * 
-    * @param pe
-    * @param seq
+    * @param filepath
+    * @return
     */
-   public void addSequence(int pe, SequenceI[] seq)
+   public boolean openSession(String filepath)
    {
-     addSequenceAndChain(pe, seq, null);
+     evalStateCommand("open " + filepath, true);
+     // todo: test for failure - how?
+     return true;
    }
  
-   private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
+   public boolean isFinishedInit()
    {
-     if (pe < 0 || pe >= pdbentry.length)
-     {
-       throw new Error(MessageManager.formatMessage(
-               "error.implementation_error_no_pdbentry_from_index",
-               new Object[]
-               { Integer.valueOf(pe).toString() }));
-     }
-     final String nullChain = "TheNullChain";
-     List<SequenceI> s = new ArrayList<SequenceI>();
-     List<String> c = new ArrayList<String>();
-     if (chains == null)
-     {
-       chains = new String[pdbentry.length][];
-     }
-     if (sequence[pe] != null)
-     {
-       for (int i = 0; i < sequence[pe].length; i++)
-       {
-         s.add(sequence[pe][i]);
-         if (chains[pe] != null)
-         {
-           if (i < chains[pe].length)
-           {
-             c.add(chains[pe][i]);
-           }
-           else
-           {
-             c.add(nullChain);
-           }
-         }
-         else
-         {
-           if (tchain != null && tchain.length > 0)
-           {
-             c.add(nullChain);
-           }
-         }
-       }
-     }
-     for (int i = 0; i < seq.length; i++)
-     {
-       if (!s.contains(seq[i]))
-       {
-         s.add(seq[i]);
-         if (tchain != null && i < tchain.length)
-         {
-           c.add(tchain[i] == null ? nullChain : tchain[i]);
-         }
-       }
-     }
-     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
-     sequence[pe] = tmp;
-     if (c.size() > 0)
-     {
-       String[] tch = c.toArray(new String[c.size()]);
-       for (int i = 0; i < tch.length; i++)
-       {
-         if (tch[i] == nullChain)
-         {
-           tch[i] = null;
-         }
-       }
-       chains[pe] = tch;
-     }
-     else
-     {
-       chains[pe] = null;
-     }
+     return finishedInit;
    }
  
-   /**
-    * 
-    * @param pdbfile
-    * @return text report of alignment between pdbfile and any associated
-    *         alignment sequences
-    */
-   public String printMapping(String pdbfile)
+   public void setFinishedInit(boolean finishedInit)
+   {
+     this.finishedInit = finishedInit;
+   }
+   public List<String> getChainNames()
    {
-     return ssm.printMapping(pdbfile);
+     return chainNames;
    }
  
--}
++}
@@@ -121,6 -118,6 +122,7 @@@ import java.io.File
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
++import java.util.Deque;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.List;
@@@ -952,7 -862,7 +954,7 @@@ public class AlignFrame extends GAlignF
          public void actionPerformed(ActionEvent e)
          {
            handler.cancelActivity(id);
--          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
++          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new Object[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
          }
        });
        progressPanel.add(cancel, BorderLayout.EAST);
            this.setTitle(file);
            statusBar.setText(MessageManager.formatMessage(
                    "label.successfully_saved_to_file_in_format",
--                  new String[]
++                  new Object[]
                    { fileName, format }));
          } catch (Exception ex)
          {
      if (!success)
      {
        JOptionPane.showInternalMessageDialog(this, MessageManager
--              .formatMessage("label.couldnt_save_file", new String[]
++              .formatMessage("label.couldnt_save_file", new Object[]
                { fileName }), MessageManager
                .getString("label.error_saving_file"),
                JOptionPane.WARNING_MESSAGE);
                viewport.getAlignment(), omitHidden,
                viewport.getColumnSelection()));
        Desktop.addInternalFrame(cap, MessageManager.formatMessage(
--              "label.alignment_output_command", new String[]
++              "label.alignment_output_command", new Object[]
                { e.getActionCommand() }), 600, 500);
      } catch (OutOfMemoryError oom)
      {
    void updateEditMenuBar()
    {
  
 -    if (viewport.historyList.size() > 0)
 +    if (viewport.getHistoryList().size() > 0)
      {
        undoMenuItem.setEnabled(true);
 -      CommandI command = viewport.historyList.peek();
 +      CommandI command = viewport.getHistoryList().peek();
        undoMenuItem.setText(MessageManager.formatMessage(
--              "label.undo_command", new String[]
++              "label.undo_command", new Object[]
                { command.getDescription() }));
      }
      else
      {
        redoMenuItem.setEnabled(true);
  
 -      CommandI command = viewport.redoList.peek();
 +      CommandI command = viewport.getRedoList().peek();
        redoMenuItem.setText(MessageManager.formatMessage(
--              "label.redo_command", new String[]
++              "label.redo_command", new Object[]
                { command.getDescription() }));
      }
      else
      {
        return;
      }
 -    CommandI command = viewport.historyList.pop();
 -    viewport.redoList.push(command);
 +    CommandI command = viewport.getHistoryList().pop();
 +    viewport.addToRedoList(command);
-     // TODO: execute command before adding to redo list / broadcasting?
      command.undoCommand(getViewAlignments());
  
 -    AlignViewport originalSource = getOriginatingSource(command);
 +    AlignmentViewport originalSource = getOriginatingSource(command);
      updateEditMenuBar();
  
      if (originalSource != null)
      }
  
      boolean appendHistoryItem = false;
-     if (viewport.getHistoryList() != null
-             && viewport.getHistoryList().size() > 0
-             && viewport.getHistoryList().peek() instanceof SlideSequencesCommand)
 -    if (viewport.historyList != null && viewport.historyList.size() > 0
 -            && viewport.historyList.peek() instanceof SlideSequencesCommand)
++    Deque<CommandI> historyList = viewport.getHistoryList();
++    if (historyList != null
++            && historyList.size() > 0
++            && historyList.peek() instanceof SlideSequencesCommand)
      {
        appendHistoryItem = ssc
-               .appendSlideCommand((SlideSequencesCommand) viewport
-                       .getHistoryList()
 -              .appendSlideCommand((SlideSequencesCommand) viewport.historyList
++              .appendSlideCommand((SlideSequencesCommand) historyList
                        .peek());
      }
  
      addHistoryItem(removeGapCols);
  
      statusBar.setText(MessageManager.formatMessage(
--            "label.removed_empty_columns", new String[]
++            "label.removed_empty_columns", new Object[]
              { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
      // This is to maintain viewport position on first residue
      StringBuffer contents = new AlignmentProperties(viewport.getAlignment())
              .formatAsHtml();
      editPane.setText(MessageManager.formatMessage("label.html_content",
--            new String[]
++            new Object[]
              { contents.toString() }));
      JInternalFrame frame = new JInternalFrame();
      frame.getContentPane().add(new JScrollPane(editPane));
  
--    Desktop.instance.addInternalFrame(frame, MessageManager.formatMessage(
--            "label.alignment_properties", new String[]
++    Desktop.addInternalFrame(frame, MessageManager.formatMessage(
++            "label.alignment_properties", new Object[]
              { getTitle() }), 500, 400);
    }
  
      OverviewPanel overview = new OverviewPanel(alignPanel);
      frame.setContentPane(overview);
      Desktop.addInternalFrame(frame, MessageManager.formatMessage(
--            "label.overview_params", new String[]
++            "label.overview_params", new Object[]
              { this.getTitle() }), frame.getWidth(), frame.getHeight());
      frame.pack();
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
    public void addSortByOrderMenuItem(String title,
            final AlignmentOrder order)
    {
--    final JMenuItem item = new JMenuItem(MessageManager.formatMessage("action.by_title_param", new String[]{title}));
++    final JMenuItem item = new JMenuItem(MessageManager.formatMessage("action.by_title_param", new Object[]{title}));
      sort.add(item);
      item.addActionListener(new java.awt.event.ActionListener()
      {
        } catch (Exception e)
        {
        }
--      ;
      }
      final AlignFrame me = this;
      buildingMenu = true;
                          .debug("Exception during web service menu building process.",
                                  e);
                }
--              ;
              }
            });
          } catch (Exception e)
          {
          }
--        ;
--
          buildingMenu = false;
        }
      }).start();
        public void run()
        {
          final long sttime = System.currentTimeMillis();
--        ths.setProgressBar(MessageManager.formatMessage("status.searching_for_sequences_from", new String[]{fsrc}), sttime);
++        ths.setProgressBar(MessageManager.formatMessage("status.searching_for_sequences_from", new Object[]{fsrc}), sttime);
          try
          {
            Alignment ds = ths.getViewport().getAlignment().getDataset(); // update
            jalview.bin.Cache.log.error("Error when finding crossreferences",
                    e);
          }
--        ths.setProgressBar(MessageManager.formatMessage("status.finished_searching_for_sequences_from", new String[]{fsrc}),
++        ths.setProgressBar(MessageManager.formatMessage("status.finished_searching_for_sequences_from", new Object[]{fsrc}),
                  sttime);
        }
  
      {
        AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
        Desktop.addInternalFrame(af, MessageManager.formatMessage(
--              "label.translation_of_params", new String[]
++              "label.translation_of_params", new Object[]
                { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT);
 +      // enable next line for linked editing
 +      // viewport.getStructureSelectionManager().addCommandListener(viewport);
      }
    }
  
                                    MessageManager
                                            .formatMessage(
                                                    "label.automatically_associate_pdb_files_with_sequences_same_name",
--                                                  new String[]
++                                                  new Object[]
                                                    { Integer.valueOf(
                                                            filesmatched
                                                                    .size())
                                    "<html>"+MessageManager
                                            .formatMessage(
                                                    "label.ignore_unmatched_dropped_files_info",
--                                                  new String[]
++                                                  new Object[]
                                                    { Integer.valueOf(
                                                            filesnotmatched
                                                                    .size())
            {
              jalview.io.JPredFile predictions = new jalview.io.JPredFile(
                      file, protocol);
--            new JnetAnnotationMaker().add_annotation(predictions,
++            new JnetAnnotationMaker();
++            JnetAnnotationMaker.add_annotation(predictions,
                      viewport.getAlignment(), 0, false);
              isAnnotation = true;
            }
                    }
  
                  });
--                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from", new String[]{src.getDbName()})));
++                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from", new Object[]{src.getDbName()})));
                  dfetch.add(fetchr);
                  comp++;
                }
                  // fetch all entry
                  DbSourceProxy src = otherdb.get(0);
                  fetchr = new JMenuItem(MessageManager.formatMessage(
--                        "label.fetch_all_param", new String[]
++                        "label.fetch_all_param", new Object[]
                          { src.getDbSource() }));
                  fetchr.addActionListener(new ActionListener()
                  {
                    }
                  });
  
--                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from_all_sources", new String[]{Integer.valueOf(otherdb.size()).toString(), src.getDbSource(), src.getDbName()})));
++                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from_all_sources", new Object[]{Integer.valueOf(otherdb.size()).toString(), src.getDbSource(), src.getDbName()})));
                  dfetch.add(fetchr);
                  comp++;
                  // and then build the rest of the individual menus
--                ifetch = new JMenu(MessageManager.formatMessage("label.source_from_db_source", new String[]{src.getDbSource()}));
++                ifetch = new JMenu(MessageManager.formatMessage("label.source_from_db_source", new Object[]{src.getDbSource()}));
                  icomp = 0;
                  String imname = null;
                  int i = 0;
                            0, 10) + "..." : dbname;
                    if (imname == null)
                    {
--                    imname = MessageManager.formatMessage("label.from_msname", new String[]{sname});
++                    imname = MessageManager.formatMessage("label.from_msname", new Object[]{sname});
                    }
                    fetchr = new JMenuItem(msname);
                    final DbSourceProxy[] dassrc =
  
                    });
                    fetchr.setToolTipText("<html>"
--                          + MessageManager.formatMessage("label.fetch_retrieve_from", new String[]{dbname}));
++                          + MessageManager.formatMessage("label.fetch_retrieve_from", new Object[]{dbname}));
                    ifetch.add(fetchr);
                    ++i;
                    if (++icomp >= mcomp || i == (otherdb.size()))
@@@ -67,16 -61,11 +67,17 @@@ import java.awt.Color
  import java.awt.Container;
  import java.awt.Font;
  import java.awt.Rectangle;
++import java.io.File;
 +import java.util.ArrayDeque;
  import java.util.ArrayList;
 +import java.util.Deque;
  import java.util.Hashtable;
 -import java.util.Stack;
 +import java.util.Set;
  import java.util.Vector;
  
 +import javax.swing.JInternalFrame;
 +import javax.swing.JOptionPane;
 +
  /**
   * DOCUMENT ME!
   * 
@@@ -1112,11 -1018,6 +1030,9 @@@ public class AlignViewport extends Alig
      return followSelection;
    }
  
-   boolean showSeqFeaturesHeight;
 +  /**
 +   * Send the current selection to be broadcast to any selection listeners.
 +   */
    public void sendSelection()
    {
      jalview.structure.StructureSelectionManager
      this.showAutocalculatedAbove = showAutocalculatedAbove;
    }
  
 +  /**
 +   * Method called when another alignment's edit (or possibly other) command is
 +   * broadcast to here.
 +   *
 +   * To allow for sequence mappings (e.g. protein to cDNA), we have to first
 +   * 'unwind' the command on the source sequences (in simulation, not in fact),
 +   * and then for each edit in turn:
 +   * <ul>
 +   * <li>compute the equivalent edit on the mapped sequences</li>
 +   * <li>apply the mapped edit</li>
 +   * <li>'apply' the source edit to the working copy of the source sequences</li>
 +   * </ul>
 +   * 
 +   * @param command
 +   * @param undo
 +   * @param ssm
 +   */
 +  @Override
 +  public void mirrorCommand(CommandI command, boolean undo,
 +          StructureSelectionManager ssm, VamsasSource source)
 +  {
 +    /*
 +     * ...work in progress... do nothing unless we are a 'complement' of the
 +     * source May replace this with direct calls not via SSM.
 +     */
 +    if (source instanceof AlignViewportI
 +            && ((AlignViewportI) source).getCodingComplement() == this)
 +    {
 +      // ok to continue;
 +    }
 +    else
 +    {
 +      return;
 +    }
 +
 +    CommandI mappedCommand = ssm.mapCommand(command, undo, getAlignment(),
 +            getGapCharacter());
 +    if (mappedCommand != null)
 +    {
 +      AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
 +      mappedCommand.doCommand(views);
 +      getAlignPanel().alignmentChanged();
 +    }
 +  }
 +
 +  @Override
 +  public VamsasSource getVamsasSource()
 +  {
 +    return this;
 +  }
 +
 +  /**
 +   * Add one command to the command history list.
 +   * 
 +   * @param command
 +   */
 +  public void addToHistoryList(CommandI command)
 +  {
 +    if (this.historyList != null)
 +    {
 +      this.historyList.push(command);
 +      broadcastCommand(command, false);
 +    }
 +  }
 +
 +  protected void broadcastCommand(CommandI command, boolean undo)
 +  {
 +    getStructureSelectionManager().commandPerformed(command, undo, getVamsasSource());
 +  }
 +
 +  /**
 +   * Add one command to the command redo list.
 +   * 
 +   * @param command
 +   */
 +  public void addToRedoList(CommandI command)
 +  {
 +    if (this.redoList != null)
 +    {
 +      this.redoList.push(command);
 +    }
 +    broadcastCommand(command, true);
 +  }
 +
 +  /**
 +   * Clear the command redo list.
 +   */
 +  public void clearRedoList()
 +  {
 +    if (this.redoList != null)
 +    {
 +      this.redoList.clear();
 +    }
 +  }
 +
 +  public void setHistoryList(Deque<CommandI> list)
 +  {
 +    this.historyList = list;
 +  }
 +
 +  public Deque<CommandI> getHistoryList()
 +  {
 +    return this.historyList;
 +  }
 +
 +  public void setRedoList(Deque<CommandI> list)
 +  {
 +    this.redoList = list;
 +  }
 +
 +  public Deque<CommandI> getRedoList()
 +  {
 +    return this.redoList;
 +  }
 +
 +  /**
 +   * Add the sequences from the given alignment to this viewport. Optionally,
 +   * may give the user the option to open a new frame, or split panel, with cDNA
 +   * and protein linked.
 +   * 
 +   * @param al
 +   * @param title
 +   */
 +  public void addAlignment(AlignmentI al, String title)
 +  {
 +    // TODO: promote to AlignViewportI? applet CutAndPasteTransfer is different
 +
 +    // refactored from FileLoader / CutAndPasteTransfer / SequenceFetcher with
 +    // this comment:
 +    // TODO: create undo object for this JAL-1101
 +
 +    /*
 +     * If one alignment is protein and one nucleotide, with at least one
 +     * sequence name in common, offer to open a linked alignment.
 +     */
 +    if (getAlignment().isNucleotide() != al.isNucleotide())
 +    {
 +      final Set<String> sequenceNames = getAlignment().getSequenceNames();
 +      sequenceNames.retainAll(al.getSequenceNames());
 +      if (!sequenceNames.isEmpty()) // at least one sequence name in both
 +      {
 +        if (openLinkedAlignment(al, title))
 +        {
 +          return;
 +        }
 +      }
 +    }
 +
 +    for (int i = 0; i < al.getHeight(); i++)
 +    {
 +      getAlignment().addSequence(al.getSequenceAt(i));
 +    }
 +    // TODO this call was done by SequenceFetcher but not FileLoader or
 +    // CutAndPasteTransfer. Is it needed?
 +    setEndSeq(getAlignment().getHeight());
 +    firePropertyChange("alignment", null, getAlignment().getSequences());
 +  }
 +
 +  /**
 +   * Show a dialog with the option to open and link (cDNA <-> protein) as a new
 +   * alignment. Returns true if the new alignment was opened, false if not,
 +   * because the user declined the offer.
 +   * 
 +   * @param title
 +   */
 +  protected boolean openLinkedAlignment(AlignmentI al, String title)
 +  {
 +    String[] options = new String[]
 +    { MessageManager.getString("action.no"),
 +        MessageManager.getString("label.split_window"),
 +        MessageManager.getString("label.new_window"), };
 +    final String question = JvSwingUtils.wrapTooltip(true,
 +            MessageManager.getString("label.open_linked_alignment?"));
 +    int response = JOptionPane.showOptionDialog(Desktop.desktop, question,
 +            MessageManager.getString("label.open_linked_alignment"),
 +            JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
 +            options, options[0]);
 +
 +    if (response != 1 && response != 2)
 +    {
 +      return false;
 +    }
 +    final boolean openSplitPane = (response == 1);
 +    final boolean openInNewWindow = (response == 2);
 +
 +    /*
 +     * Create the AlignFrame first (which creates the new alignment's datasets),
 +     * before attempting sequence mapping.
 +     */
 +    AlignFrame newAlignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
 +            AlignFrame.DEFAULT_HEIGHT);
 +    newAlignFrame.setTitle(title);
 +
 +    /*
 +     * Identify protein and dna alignments. Make a copy of this one if opening
 +     * in a new split pane.
 +     */
 +    AlignmentI thisAlignment = openSplitPane ? new Alignment(getAlignment())
 +            : getAlignment();
-     final AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
++    AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
 +    final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
 +
 +    newAlignFrame.statusBar.setText(MessageManager.formatMessage(
 +            "label.successfully_loaded_file", new Object[]
 +            { title }));
 +
 +    // TODO if we want this (e.g. to enable reload of the alignment from file),
 +    // we will need to add parameters to the stack.
 +    // if (!protocol.equals(AppletFormatAdapter.PASTE))
 +    // {
 +    // alignFrame.setFileName(file, format);
 +    // }
 +
 +    if (openInNewWindow)
 +    {
 +      Desktop.addInternalFrame(newAlignFrame, title,
 +              AlignFrame.DEFAULT_WIDTH,
 +              AlignFrame.DEFAULT_HEIGHT);
 +    }
 +
 +    /*
-      * Try to find mappings for at least one sequence.
++     * Try to find mappings for at least one sequence. Any mappings made will be
++     * added to the protein alignment.
 +     */
 +    MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna);
 +    final StructureSelectionManager ssm = StructureSelectionManager
 +            .getStructureSelectionManager(Desktop.instance);
-     if (mapped == MappingResult.Mapped)
++    if (mapped != MappingResult.Mapped)
 +    {
-       /*
-        * Register the mappings (held on the protein alignment) with the
-        * StructureSelectionManager (for mouseover linking).
-        */
-       ssm.addMappings(protein.getCodonFrames());
-     }
-     else
-     {
 +      /*
 +       * No mapping possible - warn the user, but leave window open.
 +       */
 +      final String msg = JvSwingUtils.wrapTooltip(true,
 +              MessageManager.getString("label.mapping_failed"));
 +      JOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
 +              MessageManager.getString("label.no_mappings"),
 +              JOptionPane.WARNING_MESSAGE);
 +    }
 +
 +    try
 +    {
 +      newAlignFrame.setMaximum(jalview.bin.Cache.getDefault(
 +              "SHOW_FULLSCREEN",
 +              false));
 +    } catch (java.beans.PropertyVetoException ex)
 +    {
 +    }
 +
 +    if (openSplitPane)
 +    {
 +      /*
 +       * Open in split pane. DNA sequence above, protein below.
 +       */
 +      AlignFrame copyMe = new AlignFrame(thisAlignment,
 +              AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
 +      copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
 +      final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
 +              : newAlignFrame;
 +      final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame
 +              : copyMe;
++      protein = proteinFrame.viewport.getAlignment();
 +
 +      cdnaFrame.setVisible(true);
 +      proteinFrame.setVisible(true);
++      String sep = String.valueOf(File.separatorChar);
 +      String proteinShortName = StringUtils.getLastToken(
-               proteinFrame.getTitle(), "/");
++              proteinFrame.getTitle(), sep);
 +      String dnaShortName = StringUtils.getLastToken(cdnaFrame.getTitle(),
-               "/");
++              sep);
 +      String linkedTitle = MessageManager.formatMessage(
 +              "label.linked_view_title", dnaShortName, proteinShortName);
 +      JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame);
 +      Desktop.addInternalFrame(splitFrame, linkedTitle,
 +              AlignFrame.DEFAULT_WIDTH,
 +              AlignFrame.DEFAULT_HEIGHT);
 +
 +      /*
 +       * Set the frames to listen for each other's edit and sort commands.
 +       */
 +      ssm.addCommandListener(cdnaFrame.getViewport());
 +      ssm.addCommandListener(proteinFrame.getViewport());
 +
 +      /*
 +       * 'Coding complement' (dna/protein) views will mirror each others' edits,
 +       * selections, sorting etc as decided from time to time by the relevant
 +       * authorities.
 +       */
 +      proteinFrame.getViewport().setCodingComplement(cdnaFrame.getViewport());
 +    }
 +
++    /*
++     * Register the mappings (held on the protein alignment) with the
++     * StructureSelectionManager (for mouseover linking).
++     */
++    ssm.addMappings(protein.getCodonFrames());
++
 +    return true;
 +  }
++
+   public AnnotationColumnChooser getAnnotationColumnSelectionState()
+   {
+     return annotationColumnSelectionState;
+   }
+   public void setAnnotationColumnSelectionState(
+           AnnotationColumnChooser currentAnnotationColumnSelectionState)
+   {
+     this.annotationColumnSelectionState = currentAnnotationColumnSelectionState;
+   }
  }
@@@ -1458,9 -1472,8 +1468,11 @@@ public class AlignmentPanel extends GAl
      {
        jalview.structure.StructureSelectionManager ssm = av
                .getStructureSelectionManager();
-       ssm.removeStructureViewerListener(seqPanel, null);
-       ssm.removeSelectionListener(seqPanel);
+       ssm.removeStructureViewerListener(getSeqPanel(), null);
+       ssm.removeSelectionListener(getSeqPanel());
 +      ssm.removeEditListener(av);
++      ssm.removeStructureViewerListener(getSeqPanel(), null);
++      ssm.removeSelectionListener(getSeqPanel());
        av.setAlignment(null);
        av = null;
      }
@@@ -683,7 -683,7 +683,7 @@@ public class AnnotationLabels extends J
          pop.addSeparator();
          // av and sequencegroup need to implement same interface for
          final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
--                      MessageManager.getString("label.ignore_gaps_consensus"),
++                        MessageManager.getString("label.ignore_gaps_consensus"),
                  (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
                          .getIgnoreGapsConsensus() : ap.av
                          .getIgnoreGapsConsensus());
          if (aaa.groupRef != null)
          {
            final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
--                        MessageManager.getString("label.show_group_histogram"),
++                          MessageManager.getString("label.show_group_histogram"),
                    aa[selectedRow].groupRef.isShowConsensusHistogram());
            chist.addActionListener(new ActionListener()
            {
            });
            pop.add(chist);
            final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
--                        MessageManager.getString("label.show_group_logo"),
++                          MessageManager.getString("label.show_group_logo"),
                    aa[selectedRow].groupRef.isShowSequenceLogo());
            cprofl.addActionListener(new ActionListener()
            {
            });
            pop.add(cprofl);
            final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
--                        MessageManager.getString("label.normalise_group_logo"),
++                          MessageManager.getString("label.normalise_group_logo"),
                    aa[selectedRow].groupRef.isNormaliseSequenceLogo());
            cproflnorm.addActionListener(new ActionListener()
            {
          else
          {
            final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
--                        MessageManager.getString("label.show_histogram"), av.isShowConsensusHistogram());
++                          MessageManager.getString("label.show_histogram"), av.isShowConsensusHistogram());
            chist.addActionListener(new ActionListener()
            {
              public void actionPerformed(ActionEvent e)
            });
            pop.add(chist);
            final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
--                        MessageManager.getString("label.show_logo"), av.isShowSequenceLogo());
++                          MessageManager.getString("label.show_logo"), av.isShowSequenceLogo());
            cprof.addActionListener(new ActionListener()
            {
              public void actionPerformed(ActionEvent e)
            });
            pop.add(cprof);
            final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
--                        MessageManager.getString("label.normalise_logo"), av.isNormaliseSequenceLogo());
++                          MessageManager.getString("label.normalise_logo"), av.isNormaliseSequenceLogo());
            cprofnorm.addActionListener(new ActionListener()
            {
              public void actionPerformed(ActionEvent e)
@@@ -20,6 -20,6 +20,7 @@@
   */
  package jalview.gui;
  
++import jalview.structure.AtomSpec;
  import jalview.util.MessageManager;
  
  import java.awt.BorderLayout;
@@@ -827,8 -827,8 +828,6 @@@ public class AppVarnaBinding extends ja
  
    public void onWarningEmitted(String s)
    {
--    // TODO Auto-generated method stub
--
    }
  
    public void mouseClicked(MouseEvent e)
  
    public void mouseEntered(MouseEvent arg0)
    {
--    // TODO Auto-generated method stub
--
    }
  
    public void mouseExited(MouseEvent arg0)
    {
--    // TODO Auto-generated method stub
--
    }
  
    public void mousePressed(MouseEvent arg0)
    {
--    // TODO Auto-generated method stub
--
    }
  
    public void mouseReleased(MouseEvent arg0)
    {
--    // TODO Auto-generated method stub
--
 -  }
 -
 -  @Override
 -  public Color getColour(int atomIndex, int pdbResNum, String chain,
 -          String pdbId)
 -  {
 -    // TODO Auto-generated method stub
 -    return null;
    }
  
    @Override
    public String[] getPdbFile()
    {
--    // TODO Auto-generated method stub
      return null;
    }
  
    @Override
 -  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
 -          String pdbId)
 -  {
 -    // TODO Auto-generated method stub
 -
 -  }
 -
 -  @Override
 -  public void mouseOverStructure(int atomIndex, String strInfo)
 -  {
 -    // TODO Auto-generated method stub
 -
 -  }
 -
 -  @Override
    public void releaseReferences(Object svl)
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void updateColours(Object source)
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void componentHidden(ComponentEvent e)
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void componentMoved(ComponentEvent e)
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void componentResized(ComponentEvent e)
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void componentShown(ComponentEvent e)
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void onStructureRedrawn()
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void onZoomLevelChanged()
    {
--    // TODO Auto-generated method stub
--
    }
  
    @Override
    public void onTranslationChanged()
    {
--    // TODO Auto-generated method stub
++  }
  
++  @Override
++  public void highlightAtoms(List<AtomSpec> atoms)
++  {
    }
  }
--
--/*
-- * public static void main(String[] args) { JTextField str = new
-- * JTextField("ATGC");
-- * 
-- * AppVarnaBinding vab = new AppVarnaBinding(); vab.varnagui.set_seq(str);
-- * vab.varnagui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-- * vab.varnagui.pack(); vab.varnagui.setVisible(true); } }
-- */
@@@ -1088,7 -1087,7 +1091,10 @@@ public class Desktop extends jalview.jb
        if (format.equals("URL NOT FOUND"))
        {
          JOptionPane.showInternalMessageDialog(Desktop.desktop,
--                MessageManager.formatMessage("label.couldnt_locate", new String[]{url}), MessageManager.getString("label.url_not_found"),
++                MessageManager.formatMessage("label.couldnt_locate",
++                        new Object[]
++                        { url }), MessageManager
++                        .getString("label.url_not_found"),
                  JOptionPane.WARNING_MESSAGE);
  
          return;
          public void run()
          {
  
--          setProgressBar(MessageManager.formatMessage("label.saving_jalview_project", new String[]{choice.getName()}),
++          setProgressBar(MessageManager.formatMessage("label.saving_jalview_project", new Object[]{choice.getName()}),
                    choice.hashCode());
            jalview.bin.Cache.setProperty("LAST_DIRECTORY",
                    choice.getParent());
                      ex);
              JOptionPane.showMessageDialog(
                      me,
--                    MessageManager.formatMessage("label.error_whilst_saving_current_state_to", new String[]{ choice.getName()}),
++                    MessageManager.formatMessage("label.error_whilst_saving_current_state_to", new Object[]{ choice.getName()}),
                      MessageManager.getString("label.couldnt_save_project"),
                      JOptionPane.WARNING_MESSAGE);
            }
        {
          public void run()
          {
--          setProgressBar(MessageManager.formatMessage("label.loading_jalview_project", new String[]{choice}),
++          setProgressBar(MessageManager.formatMessage(
++                  "label.loading_jalview_project", new Object[]
++                  { choice }),
                    choice.hashCode());
            try
            {
              Cache.log.error("Problems whilst loading project from "
                      + choice, ex);
              JOptionPane.showMessageDialog(Desktop.desktop,
--                      MessageManager.formatMessage("label.error_whilst_loading_project_from", new String[]{choice}),
++ MessageManager
++                    .formatMessage(
++                            "label.error_whilst_loading_project_from",
++                            new Object[]
++                            { choice }),
                      MessageManager.getString("label.couldnt_load_project"), JOptionPane.WARNING_MESSAGE);
            }
            setProgressBar(null, choice.hashCode());
    {
      if (fileLoadingCount == 0)
      {
--      fileLoadingPanels.add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new String[]{fileName})));
++      fileLoadingPanels.add(addProgressPanel(MessageManager.formatMessage(
++              "label.loading_file", new Object[]
++              { fileName })));
      }
      fileLoadingCount++;
    }
                            Desktop.desktop,
                            MessageManager.formatMessage(
                                    "label.couldnt_import_as_vamsas_session",
--                                  new String[]
++                                  new Object[]
                                    { fle }),
                            MessageManager
                                    .getString("label.vamsas_document_import_failed"),
        return false;
      }
  
--    setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new String[]{file.getName()}),
++    setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new Object[]{file.getName()}),
              file.hashCode());
      try
      {
        v_client = new jalview.gui.VamsasApplication(this, file, null);
      } catch (Exception ex)
      {
--        setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new String[]{file.getName()}),
++        setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new Object[]{file.getName()}),
                  file.hashCode());
        jalview.bin.Cache.log.error(
                "New vamsas session from existing session file failed:", ex);
      }
      setupVamsasConnectedGui();
      v_client.initial_update(); // TODO: thread ?
--    setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new String[]{file.getName()}),
++    setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new Object[]{file.getName()}),
              file.hashCode());
      return v_client.inSession();
    }
            JMenuItem sessit = new JMenuItem();
            sessit.setText(sess[i]);
            sessit.setToolTipText(MessageManager.formatMessage(
--                  "label.connect_to_session", new String[]
++                  "label.connect_to_session", new Object[]
                    { sess[i] }));
            final Desktop dsktp = this;
            final String mysesid = sess[i];
        if (value == JalviewFileChooser.APPROVE_OPTION)
        {
          java.io.File choice = chooser.getSelectedFile();
--        JPanel progpanel = addProgressPanel(MessageManager.formatMessage("label.saving_vamsas_doc", new String[]{choice.getName()}));
++        JPanel progpanel = addProgressPanel(MessageManager.formatMessage("label.saving_vamsas_doc", new Object[]{choice.getName()}));
          jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice.getParent());
          String warnmsg = null;
          String warnttl = null;
    public class MyDesktopPane extends JDesktopPane implements Runnable
    {
  
++    private static final float ONE_MB = 1048576f;
++
      boolean showMemoryUsage = false;
  
      Runtime runtime;
        {
          try
          {
--          maxMemory = runtime.maxMemory() / 1048576f;
--          allocatedMemory = runtime.totalMemory() / 1048576f;
--          freeMemory = runtime.freeMemory() / 1048576f;
++          maxMemory = runtime.maxMemory() / ONE_MB;
++          allocatedMemory = runtime.totalMemory() / ONE_MB;
++          freeMemory = runtime.freeMemory() / ONE_MB;
            totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
  
            percentUsage = (totalFreeMemory / maxMemory) * 100;
          {
            g.drawString(MessageManager.formatMessage(
                    "label.memory_stats",
--                  new String[]
++                  new Object[]
                    { df.format(totalFreeMemory), df.format(maxMemory),
                        df.format(percentUsage) }), 10,
                    getHeight() - fm.getHeight());
      return afs;
    }
  
 +  /**
 +   * Returns an array of any AppJmol frames in the Desktop (or null if none).
 +   * 
 +   * @return
 +   */
-   public AppJmol[] getJmols()
+   public GStructureViewer[] getJmols()
    {
      JInternalFrame[] frames = Desktop.desktop.getAllFrames();
  
      {
        return null;
      }
-     List<AppJmol> avp = new ArrayList<AppJmol>();
 -    Vector avp = new Vector();
 -    try
++    List<GStructureViewer> avp = new ArrayList<GStructureViewer>();
 +    // REVERSE ORDER
 +    for (int i = frames.length - 1; i > -1; i--)
      {
 -      // REVERSE ORDER
 -      for (int i = frames.length - 1; i > -1; i--)
 +      if (frames[i] instanceof AppJmol)
        {
-         AppJmol af = (AppJmol) frames[i];
 -        if (frames[i] instanceof AppJmol)
 -        {
 -          GStructureViewer af = (GStructureViewer) frames[i];
 -          avp.addElement(af);
 -        }
++        GStructureViewer af = (GStructureViewer) frames[i];
 +        avp.add(af);
        }
 -    } catch (Exception ex)
 -    {
 -      ex.printStackTrace();
      }
      if (avp.size() == 0)
      {
        return null;
      }
-     AppJmol afs[] = avp.toArray(new AppJmol[avp.size()]);
 -    GStructureViewer afs[] = new GStructureViewer[avp.size()];
 -    for (int i = 0, j = avp.size(); i < j; i++)
 -    {
 -      afs[i] = (GStructureViewer) avp.elementAt(i);
 -    }
 -    avp.clear();
++    GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
      return afs;
    }
  
          public void actionPerformed(ActionEvent e)
          {
            handler.cancelActivity(id);
--          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
++          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new Object[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
          }
        });
        progressPanel.add(cancel, BorderLayout.EAST);
          {
            if (progress != null)
            {
--            progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new String[]{url}), this.hashCode());
++            progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[]{url}), this.hashCode());
            }
            jalview.util.BrowserLauncher.openURL(url);
          } catch (Exception ex)
Simple merge
Simple merge
Simple merge
@@@ -26,8 -25,12 +26,11 @@@ import jalview.datamodel.AlignedCodonFr
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
+ import jalview.datamodel.StructureViewerModel;
+ import jalview.datamodel.StructureViewerModel.StructureData;
  import jalview.schemabinding.version2.AlcodMap;
 -import jalview.schemabinding.version2.Alcodon;
  import jalview.schemabinding.version2.AlcodonFrame;
  import jalview.schemabinding.version2.Annotation;
  import jalview.schemabinding.version2.AnnotationColours;
@@@ -340,9 -357,9 +357,9 @@@ public class Jalview2XM
     * 
     * @param jout
     */
-   public void SaveState(JarOutputStream jout)
+   public void saveState(JarOutputStream jout)
    {
 -    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +    AlignFrame[] frames = Desktop.getAlignFrames(); // Desktop.desktop.getAllFrames();
  
      if (frames == null)
      {
        // REVERSE ORDER
        for (int i = frames.length - 1; i > -1; i--)
        {
 -        if (frames[i] instanceof AlignFrame)
 +        AlignFrame af = frames[i];
 +        // skip ?
 +        if (skipList != null
 +                && skipList
 +                        .containsKey(af.getViewport().getSequenceSetId()))
          {
 -          AlignFrame af = (AlignFrame) frames[i];
 -          // skip ?
 -          if (skipList != null
 -                  && skipList.containsKey(af.getViewport()
 -                          .getSequenceSetId()))
 -          {
 -            continue;
 -          }
 +          continue;
 +        }
 +
 +        String shortName = af.getTitle();
  
 -          String shortName = af.getTitle();
 +        if (shortName.indexOf(File.separatorChar) > -1)
 +        {
 +          shortName = shortName.substring(shortName
 +                  .lastIndexOf(File.separatorChar) + 1);
 +        }
 +
 +        int count = 1;
  
 -          if (shortName.indexOf(File.separatorChar) > -1)
 +        while (shortNames.contains(shortName))
 +        {
 +          if (shortName.endsWith("_" + (count - 1)))
            {
 -            shortName = shortName.substring(shortName
 -                    .lastIndexOf(File.separatorChar) + 1);
 +            shortName = shortName.substring(0, shortName.lastIndexOf("_"));
            }
  
 -          int count = 1;
 +          shortName = shortName.concat("_" + count);
 +          count++;
 +        }
  
-         shortNames.add(shortName);
 -          while (shortNames.contains(shortName))
 -          {
 -            if (shortName.endsWith("_" + (count - 1)))
 -            {
 -              shortName = shortName
 -                      .substring(0, shortName.lastIndexOf("_"));
 -            }
++        shortNames.addElement(shortName);
  
 -            shortName = shortName.concat("_" + count);
 -            count++;
 -          }
 +        if (!shortName.endsWith(".xml"))
 +        {
 +          shortName = shortName + ".xml";
 +        }
  
-         int apSize = af.alignPanels.size();
 -          shortNames.addElement(shortName);
++        int ap, apSize = af.alignPanels.size();
  
-         for (int ap = 0; ap < apSize; ap++)
 -          if (!shortName.endsWith(".xml"))
++        for (ap = 0; ap < apSize; ap++)
 +        {
 +          AlignmentPanel apanel = af.alignPanels.get(ap);
 +          String fileName = apSize == 1 ? shortName : ap + shortName;
 +          if (!fileName.endsWith(".xml"))
            {
 -            shortName = shortName + ".xml";
 +            fileName = fileName + ".xml";
            }
  
-           SaveState(apanel, fileName, jout);
 -          int ap, apSize = af.alignPanels.size();
++          saveState(apanel, fileName, jout);
  
 -          for (ap = 0; ap < apSize; ap++)
 +          String dssid = getDatasetIdRef(af.getViewport().getAlignment()
 +                  .getDataset());
 +          if (!dsses.containsKey(dssid))
            {
 -            AlignmentPanel apanel = (AlignmentPanel) af.alignPanels
 -                    .elementAt(ap);
 -            String fileName = apSize == 1 ? shortName : ap + shortName;
 -            if (!fileName.endsWith(".xml"))
 -            {
 -              fileName = fileName + ".xml";
 -            }
 -
 -            saveState(apanel, fileName, jout);
 -
 -            String dssid = getDatasetIdRef(af.getViewport().getAlignment()
 -                    .getDataset());
 -            if (!dsses.containsKey(dssid))
 -            {
 -              dsses.put(dssid, af);
 -            }
 -
 +            dsses.put(dssid, af);
            }
          }
        }
    {
      try
      {
--      int ap, apSize = af.alignPanels.size();
++      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>();
--      for (ap = 0; ap < apSize; ap++)
++      for (AlignmentPanel apanel : af.alignPanels)
        {
-         AlignmentPanel apanel = af.alignPanels
-                 .get(ap);
 -        AlignmentPanel apanel = (AlignmentPanel) af.alignPanels
 -                .elementAt(ap);
          String jfileName = apSize == 1 ? fileName : fileName + ap;
++        ap++;
          if (!jfileName.endsWith(".xml"))
          {
            jfileName = jfileName + ".xml";
                  {
                    byte[] data = new byte[(int) file.length()];
                    jout.putNextEntry(new JarEntry(entry.getId()));
-                   DataInputStream dis = new DataInputStream(
-                           new FileInputStream(file));
 -                  dis = new DataInputStream(
 -                          new FileInputStream(file));
++                  dis = new DataInputStream(new FileInputStream(file));
                    dis.readFully(data);
  
                    DataOutputStream dout = new DataOutputStream(jout);
        jal = av.getAlignment();
      }
      // SAVE MAPPINGS
-     Set<AlignedCodonFrame> jac = jal.getCodonFrames();
-     if (jac != null)
 -    if (jal.getCodonFrames() != null && jal.getCodonFrames().length > 0)
++    if (jal.getCodonFrames() != null)
      {
 -      jalview.datamodel.AlignedCodonFrame[] jac = jal.getCodonFrames();
 -      for (int i = 0; i < jac.length; i++)
++      Set<AlignedCodonFrame> jac = jal.getCodonFrames();
 +      for (AlignedCodonFrame acf : jac)
        {
          AlcodonFrame alc = new AlcodonFrame();
          vamsasSet.addAlcodonFrame(alc);
              alc.addAlcodMap(alcmap);
            }
          }
++
++//      {
++//        AlcodonFrame alc = new AlcodonFrame();
++//        vamsasSet.addAlcodonFrame(alc);
++//        for (int p = 0; p < acf.aaWidth; p++)
++//        {
++//          Alcodon cmap = new Alcodon();
++//          if (acf.codons[p] != null)
++//          {
++//            // Null codons indicate a gapped column in the translated peptide
++//            // alignment.
++//            cmap.setPos1(acf.codons[p][0]);
++//            cmap.setPos2(acf.codons[p][1]);
++//            cmap.setPos3(acf.codons[p][2]);
++//          }
++//          alc.addAlcodon(cmap);
++//        }
++//        if (acf.getProtMappings() != null
++//                && acf.getProtMappings().length > 0)
++//        {
++//          SequenceI[] dnas = acf.getdnaSeqs();
++//          jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
++//          for (int m = 0; m < pmaps.length; m++)
++//          {
++//            AlcodMap alcmap = new AlcodMap();
++//            alcmap.setDnasq(seqHash(dnas[m]));
++//            alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
++//                    false));
++//            alc.addAlcodMap(alcmap);
++//          }
++//        }
        }
      }
  
        {
          jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
  
-         String[] renderOrder = ap.seqPanel.seqCanvas.getFeatureRenderer().renderOrder;
 -        String[] renderOrder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
 -                .getRenderOrder().toArray(new String[0]);
++        String[] renderOrder = ap.getSeqPanel().seqCanvas
++                .getFeatureRenderer().getRenderOrder()
++                .toArray(new String[0]);
  
          Vector settingsAdded = new Vector();
          Object gstyle = null;
              }
              else
              {
-               setting.setColour(ap.seqPanel.seqCanvas.getFeatureRenderer()
 -              setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
++              setting.setColour(ap.getSeqPanel().seqCanvas
++                      .getFeatureRenderer()
                        .getColour(renderOrder[ro]).getRGB());
              }
  
            fs.addSetting(setting);
            settingsAdded.addElement(key);
          }
-         en = ap.seqPanel.seqCanvas.getFeatureRenderer().featureGroups
-                 .keySet().iterator();
+         // is groups actually supposed to be a map here ?
 -        en = ap.getSeqPanel().seqCanvas.getFeatureRenderer().getFeatureGroups()
 -                .iterator();
++        en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
++                .getFeatureGroups().iterator();
          Vector groupsAdded = new Vector();
          while (en.hasNext())
          {
      return object;
    }
  
+   /**
+    * Save the state of a structure viewer
+    * 
+    * @param ap
+    * @param jds
+    * @param pdb
+    *          the archive XML element under which to save the state
+    * @param entry
+    * @param viewIds
+    * @param matchedFile
+    * @param viewFrame
+    * @return
+    */
+   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
+           Pdbids pdb, PDBEntry entry, List<String> viewIds,
+           String matchedFile, StructureViewerBase viewFrame)
+   {
 -    final AAStructureBindingModel bindingModel = viewFrame
 -            .getBinding();
 -    for (int peid = 0; peid < bindingModel
 -            .getPdbCount(); peid++)
++    final AAStructureBindingModel bindingModel = viewFrame.getBinding();
++    for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
+     {
+       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
+       final String pdbId = pdbentry.getId();
+       if (!pdbId.equals(entry.getId())
+               && !(entry.getId().length() > 4 && entry.getId()
 -                      .toLowerCase()
 -                      .startsWith(pdbId.toLowerCase())))
++                      .toLowerCase().startsWith(pdbId.toLowerCase())))
+       {
+         continue;
+       }
+       if (matchedFile == null)
+       {
+         matchedFile = pdbentry.getFile();
+       }
 -      else if (!matchedFile.equals(pdbentry
 -              .getFile()))
++      else if (!matchedFile.equals(pdbentry.getFile()))
+       {
+         Cache.log
+                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
+                         + pdbentry.getFile());
+       }
+       // record the
+       // file so we
+       // can get at it if the ID
+       // match is ambiguous (e.g.
+       // 1QIP==1qipA)
+       String statestring = viewFrame.getStateInfo();
 -      for (int smap = 0; smap < viewFrame.getBinding()
 -              .getSequence()[peid].length; smap++)
++      for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
+       {
+         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
+         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
+         {
+           StructureState state = new StructureState();
+           state.setVisible(true);
+           state.setXpos(viewFrame.getX());
+           state.setYpos(viewFrame.getY());
+           state.setWidth(viewFrame.getWidth());
+           state.setHeight(viewFrame.getHeight());
+           final String viewId = viewFrame.getViewId();
+           state.setViewId(viewId);
+           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
 -          state.setColourwithAlignPanel(viewFrame
 -                  .isUsedforcolourby(ap));
++          state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
+           state.setColourByJmol(viewFrame.isColouredByViewer());
+           /*
+            * Only store each structure viewer's state once in each XML document.
+            */
+           if (!viewIds.contains(viewId))
+           {
+             viewIds.add(viewId);
+             state.setContent(statestring.replaceAll("\n", ""));
+           }
+           else
+           {
+             state.setContent("# duplicate state");
+           }
+           pdb.addStructureState(state);
+         }
+       }
+     }
+     return matchedFile;
+   }
    private AnnotationColours constructAnnotationColours(
-           AnnotationColourGradient acg, Vector userColours,
+           AnnotationColourGradient acg, List<UserColourScheme> userColours,
            JalviewModelSequence jms)
    {
      AnnotationColours ac = new AnnotationColours();
          return false;
        }
      }
--    throw new Error(MessageManager.formatMessage("error.unsupported_version_calcIdparam", new String[]{calcIdParam.toString()}));
++    throw new Error(MessageManager.formatMessage(
++            "error.unsupported_version_calcIdparam", new String[]
++            { calcIdParam.toString() }));
    }
  
    /**
                }
              }
              StructureSelectionManager.getStructureSelectionManager(
--                    Desktop.instance)
--                    .registerPDBEntry(entry);
++                    Desktop.instance).registerPDBEntry(entry);
              al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
            }
          }
            AlcodMap[] maps = alc[i].getAlcodMap();
            for (int m = 0; m < maps.length; m++)
            {
--            SequenceI dnaseq = seqRefIds
--                    .get(maps[m].getDnasq());
++            SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
              // Load Mapping
              jalview.datamodel.Mapping mapping = null;
              // attach to dna sequence reference.
          }
          al.addCodonFrame(cf);
        }
--
      }
  
      // ////////////////////////////////
          for (int s = 0; s < groups[i].getSeqCount(); s++)
          {
            String seqId = groups[i].getSeq(s) + "";
--          jalview.datamodel.SequenceI ts = seqRefIds
--                  .get(seqId);
++          jalview.datamodel.SequenceI ts = seqRefIds.get(seqId);
  
            if (ts != null)
            {
      // //LOAD STRUCTURES
      if (loadTreesAndStructures)
      {
-       // run through all PDB ids on the alignment, and collect mappings between
-       // jmol view ids and all sequences referring to it
-       Hashtable<String, Object[]> jmolViewIds = new Hashtable();
+       loadStructures(jprovider, jseqs, af, ap);
+     }
+     // and finally return.
+     return af;
+   }
+   /**
+    * Load and link any saved structure viewers.
+    * 
+    * @param jprovider
+    * @param jseqs
+    * @param af
+    * @param ap
+    */
+   protected void loadStructures(jarInputStreamProvider jprovider,
+           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
+   {
+     /*
+      * Run through all PDB ids on the alignment, and collect mappings between
+      * distinct view ids and all sequences referring to that view.
+      */
+     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
  
-       for (int i = 0; i < JSEQ.length; i++)
+     for (int i = 0; i < jseqs.length; i++)
+     {
+       if (jseqs[i].getPdbidsCount() > 0)
        {
-         if (JSEQ[i].getPdbidsCount() > 0)
+         Pdbids[] ids = jseqs[i].getPdbids();
+         for (int p = 0; p < ids.length; p++)
          {
-           Pdbids[] ids = JSEQ[i].getPdbids();
-           for (int p = 0; p < ids.length; p++)
+           final int structureStateCount = ids[p].getStructureStateCount();
+           for (int s = 0; s < structureStateCount; s++)
            {
-             for (int s = 0; s < ids[p].getStructureStateCount(); s++)
+             // check to see if we haven't already created this structure view
 -            final StructureState structureState = ids[p].getStructureState(s);
++            final StructureState structureState = ids[p]
++                    .getStructureState(s);
+             String sviewid = (structureState.getViewId() == null) ? null
 -                    : structureState.getViewId()
 -                            + uniqueSetSuffix;
++                    : structureState.getViewId() + uniqueSetSuffix;
+             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
+             // 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.setId(ids[p].getId());
+             int x = structureState.getXpos();
+             int y = structureState.getYpos();
+             int width = structureState.getWidth();
+             int height = structureState.getHeight();
+             // 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());
 -            jalview.datamodel.SequenceI seq = seqRefIds
 -                    .get(jseqs[i].getId() + "");
++            jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
++                    .getId() + "");
+             if (sviewid == null)
              {
-               // check to see if we haven't already created this structure view
-               String sviewid = (ids[p].getStructureState(s).getViewId() == null) ? null
-                       : ids[p].getStructureState(s).getViewId()
-                               + uniqueSetSuffix;
-               jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
-               // 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.setId(ids[p].getId());
-               int x = ids[p].getStructureState(s).getXpos();
-               int y = ids[p].getStructureState(s).getYpos();
-               int width = ids[p].getStructureState(s).getWidth();
-               int height = ids[p].getStructureState(s).getHeight();
-               // 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());
-               jalview.datamodel.SequenceI seq = seqRefIds
-                       .get(JSEQ[i].getId() + "");
-               if (sviewid == null)
-               {
-                 sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
-                         + "," + height;
-               }
-               if (!jmolViewIds.containsKey(sviewid))
-               {
-                 jmolViewIds.put(sviewid, new Object[]
-                 { new int[]
-                 { x, y, width, height }, "",
-                     new Hashtable<String, Object[]>(), new boolean[]
-                     { false, false, true } });
-                 // Legacy pre-2.7 conversion JAL-823 :
-                 // do not assume any view has to be linked for colour by
-                 // sequence
-               }
+               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
+                       + "," + height;
+             }
+             if (!structureViewers.containsKey(sviewid))
+             {
 -              structureViewers.put(sviewid, new StructureViewerModel(x, y, width, height,
 -                      false, false, true));
++              structureViewers.put(sviewid, new StructureViewerModel(x, y,
++                      width, height, false, false, true));
+               // Legacy pre-2.7 conversion JAL-823 :
+               // do not assume any view has to be linked for colour by
+               // sequence
+             }
  
-               // assemble String[] { pdb files }, String[] { id for each
-               // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
-               // seqs_file 2}, boolean[] {
-               // linkAlignPanel,superposeWithAlignpanel}} from hash
-               Object[] jmoldat = jmolViewIds.get(sviewid);
-               ((boolean[]) jmoldat[3])[0] |= ids[p].getStructureState(s)
-                       .hasAlignwithAlignPanel() ? ids[p].getStructureState(
-                       s).getAlignwithAlignPanel() : false;
-               // never colour by linked panel if not specified
-               ((boolean[]) jmoldat[3])[1] |= ids[p].getStructureState(s)
-                       .hasColourwithAlignPanel() ? ids[p]
-                       .getStructureState(s).getColourwithAlignPanel()
-                       : false;
-               // default for pre-2.7 projects is that Jmol colouring is enabled
-               ((boolean[]) jmoldat[3])[2] &= ids[p].getStructureState(s)
-                       .hasColourByJmol() ? ids[p].getStructureState(s)
-                       .getColourByJmol() : true;
-               if (((String) jmoldat[1]).length() < ids[p]
-                       .getStructureState(s).getContent().length())
+             // assemble String[] { pdb files }, String[] { id for each
+             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
+             // seqs_file 2}, boolean[] {
+             // linkAlignPanel,superposeWithAlignpanel}} from hash
+             StructureViewerModel jmoldat = structureViewers.get(sviewid);
+             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
+                     | (structureState.hasAlignwithAlignPanel() ? structureState
+                             .getAlignwithAlignPanel() : false));
+             /*
+              * Default colour by linked panel to false if not specified (e.g.
+              * for pre-2.7 projects)
+              */
+             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
+             colourWithAlignPanel |= (structureState
+                     .hasColourwithAlignPanel() ? structureState
+                     .getColourwithAlignPanel() : false);
+             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
+             /*
+              * Default colour by viewer to true if not specified (e.g. for
+              * pre-2.7 projects)
+              */
+             boolean colourByViewer = jmoldat.isColourByViewer();
 -            colourByViewer &= structureState
 -                    .hasColourByJmol() ? structureState
++            colourByViewer &= structureState.hasColourByJmol() ? structureState
+                     .getColourByJmol() : true;
+             jmoldat.setColourByViewer(colourByViewer);
+             if (jmoldat.getStateData().length() < structureState
+                     .getContent().length())
+             {
                {
-                 {
-                   jmoldat[1] = ids[p].getStructureState(s).getContent();
-                 }
+                 jmoldat.setStateData(structureState.getContent());
                }
-               if (ids[p].getFile() != null)
+             }
+             if (ids[p].getFile() != null)
+             {
+               File mapkey = new File(ids[p].getFile());
+               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
+               if (seqstrmaps == null)
                {
-                 File mapkey = new File(ids[p].getFile());
-                 Object[] seqstrmaps = (Object[]) ((Hashtable) jmoldat[2])
-                         .get(mapkey);
-                 if (seqstrmaps == null)
-                 {
-                   ((Hashtable) jmoldat[2]).put(mapkey,
-                           seqstrmaps = new Object[]
-                           { pdbFile, ids[p].getId(), new Vector(),
-                               new Vector() });
-                 }
-                 if (!((Vector) seqstrmaps[2]).contains(seq))
-                 {
-                   ((Vector) seqstrmaps[2]).addElement(seq);
-                   // ((Vector)seqstrmaps[3]).addElement(n) :
-                   // in principle, chains
-                   // should be stored here : do we need to
-                   // TODO: store and recover seq/pdb_id :
-                   // chain mappings
-                 }
+                 jmoldat.getFileData().put(
+                         mapkey,
+                         seqstrmaps = jmoldat.new StructureData(pdbFile,
+                                 ids[p].getId()));
                }
-               else
+               if (!seqstrmaps.getSeqList().contains(seq))
                {
-                 errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
-                 warn(errorMessage);
+                 seqstrmaps.getSeqList().add(seq);
+                 // TODO and chains?
                }
              }
+             else
+             {
+               errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
+               warn(errorMessage);
+             }
            }
          }
        }
+     }
 -      // Instantiate the associated structure views
 -      for (Entry<String, StructureViewerModel> entry : structureViewers.entrySet())
++    // Instantiate the associated structure views
++    for (Entry<String, StructureViewerModel> entry : structureViewers
++            .entrySet())
        {
 -        createOrLinkStructureViewer(entry, af, ap);
 -      }
++      createOrLinkStructureViewer(entry, af, ap);
++    }
+   }
+   /**
+    * 
+    * @param viewerData
+    * @param af
+    * @param ap
+    */
+   protected void createOrLinkStructureViewer(
+           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+           AlignmentPanel ap)
+   {
+     final StructureViewerModel svattrib = viewerData.getValue();
+     /*
+      * Search for any viewer windows already open from other alignment views
+      * that exactly match the stored structure state
+      */
+     StructureViewerBase comp = findMatchingViewer(viewerData);
+     if (comp != null)
+     {
+       linkStructureViewer(ap, comp, svattrib);
+       return;
+     }
+     /*
+      * Pending an XML element for ViewerType, just check if stateData contains
+      * "chimera" (part of the chimera session filename).
+      */
+     if (svattrib.getStateData().indexOf("chimera") > -1)
+     {
+       createChimeraViewer(viewerData, af);
+     }
+     else
+     {
+       createJmolViewer(viewerData, af);
+     }
+   }
+   /**
+    * Create a new Chimera viewer.
+    * 
+    * @param viewerData
+    * @param af
+    */
 -  protected void createChimeraViewer(Entry<String, StructureViewerModel> viewerData,
 -          AlignFrame af)
++  protected void createChimeraViewer(
++          Entry<String, StructureViewerModel> viewerData, AlignFrame af)
+   {
+     final StructureViewerModel data = viewerData.getValue();
+     String chimeraSession = data.getStateData();
  
-         // Instantiate the associated Jmol views
-         for (Entry<String, Object[]> entry : jmolViewIds.entrySet())
+     if (new File(chimeraSession).exists())
+     {
+       Set<Entry<File, StructureData>> fileData = data.getFileData()
+               .entrySet();
+       List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
+       List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
+       for (Entry<File, StructureData> pdb : fileData)
+       {
+         String filePath = pdb.getValue().getFilePath();
+         String pdbId = pdb.getValue().getPdbId();
+         pdbs.add(new PDBEntry(filePath, pdbId));
+         final List<SequenceI> seqList = pdb.getValue().getSeqList();
+         SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
+         allseqs.add(seqs);
+       }
+       boolean colourByChimera = data.isColourByViewer();
+       boolean colourBySequence = data.isColourWithAlignPanel();
+       // TODO can/should this be done via StructureViewer (like Jmol)?
 -      final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs
 -              .size()]);
 -      final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs.size()][]);
++      final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
++      final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
++              .size()][]);
+       new ChimeraViewFrame(chimeraSession, af.alignPanel, pdbArray,
 -              seqsArray,
 -              colourByChimera, colourBySequence);
++              seqsArray, colourByChimera, colourBySequence);
+     }
+     else
+     {
+       Cache.log.error("Chimera session file " + chimeraSession
+               + " not found");
+     }
+   }
+   /**
+    * Create a new Jmol window. First parse the Jmol state to translate filenames
+    * loaded into the view, and record the order in which files are shown in the
+    * Jmol view, so we can add the sequence mappings in same order.
+    * 
+    * @param viewerData
+    * @param af
+    */
+   protected void createJmolViewer(
 -          final Entry<String, StructureViewerModel> viewerData, AlignFrame af)
++          final Entry<String, StructureViewerModel> viewerData,
++          AlignFrame af)
+   {
+     final StructureViewerModel svattrib = viewerData.getValue();
+     String state = svattrib.getStateData();
+     List<String> pdbfilenames = new ArrayList<String>();
+     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
+     List<String> pdbids = new ArrayList<String>();
+     StringBuilder newFileLoc = new StringBuilder(64);
+     int cp = 0, ncp, ecp;
+     Map<File, StructureData> oldFiles = svattrib.getFileData();
+     while ((ncp = state.indexOf("load ", cp)) > -1)
+     {
+       do
+       {
+         // look for next filename in load statement
+         newFileLoc.append(state.substring(cp,
+                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
+         String oldfilenam = state.substring(ncp,
+                 ecp = state.indexOf("\"", ncp));
+         // recover the new mapping data for this old filename
+         // have to normalize filename - since Jmol and jalview do
+         // filename
+         // translation differently.
+         StructureData filedat = oldFiles.get(new File(oldfilenam));
+         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
+         pdbfilenames.add(filedat.getFilePath());
+         pdbids.add(filedat.getPdbId());
 -        seqmaps.add(filedat.getSeqList()
 -                .toArray(new SequenceI[0]));
++        seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
+         newFileLoc.append("\"");
+         cp = ecp + 1; // advance beyond last \" and set cursor so we can
+                       // look for next file statement.
+       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
+     }
+     if (cp > 0)
+     {
+       // just append rest of state
+       newFileLoc.append(state.substring(cp));
+     }
+     else
+     {
+       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
+       newFileLoc = new StringBuilder(state);
+       newFileLoc.append("; load append ");
+       for (File id : oldFiles.keySet())
+       {
+         // add this and any other pdb files that should be present in
+         // the viewer
+         StructureData filedat = oldFiles.get(id);
+         newFileLoc.append(filedat.getFilePath());
+         pdbfilenames.add(filedat.getFilePath());
+         pdbids.add(filedat.getPdbId());
 -        seqmaps.add(filedat.getSeqList()
 -                .toArray(new SequenceI[0]));
++        seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
+         newFileLoc.append(" \"");
+         newFileLoc.append(filedat.getFilePath());
+         newFileLoc.append("\"");
+       }
+       newFileLoc.append(";");
+     }
+     if (newFileLoc.length() > 0)
+     {
+       int histbug = newFileLoc.indexOf("history = ");
+       histbug += 10;
+       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
+       String val = (diff == -1) ? null : newFileLoc
+               .substring(histbug, diff);
+       if (val != null && val.length() >= 4)
+       {
+         if (val.contains("e"))
          {
-           String sviewid = entry.getKey();
-           Object[] svattrib = entry.getValue();
-           int[] geom = (int[]) svattrib[0];
-           String state = (String) svattrib[1];
-           Hashtable<File, Object[]> oldFiles = (Hashtable<File, Object[]>) svattrib[2];
-           final boolean useinJmolsuperpos = ((boolean[]) svattrib[3])[0], usetoColourbyseq = ((boolean[]) svattrib[3])[1], jmolColouring = ((boolean[]) svattrib[3])[2];
-           int x = geom[0], y = geom[1], width = geom[2], height = geom[3];
-           // collate the pdbfile -> sequence mappings from this view
-           Vector<String> pdbfilenames = new Vector<String>();
-           Vector<SequenceI[]> seqmaps = new Vector<SequenceI[]>();
-           Vector<String> pdbids = new Vector<String>();
-           // Search to see if we've already created this Jmol view
-           AppJmol comp = null;
-           JInternalFrame[] frames = null;
-           do
+           if (val.trim().equals("true"))
            {
-             try
-             {
-               frames = Desktop.desktop.getAllFrames();
-             } catch (ArrayIndexOutOfBoundsException e)
-             {
-               // occasional No such child exceptions are thrown here...
-               frames = null;
-               try
-               {
-                 Thread.sleep(10);
-               } catch (Exception f)
-               {
-               }
-               ;
-             }
-           } while (frames == null);
-           // search for any Jmol windows already open from other
-           // alignment views that exactly match the stored structure state
-           for (int f = 0; comp == null && f < frames.length; f++)
+             val = "1";
+           }
+           else
            {
-             if (frames[f] instanceof AppJmol)
-             {
-               if (sviewid != null
-                       && ((AppJmol) frames[f]).getViewId().equals(sviewid))
-               {
-                 // post jalview 2.4 schema includes structure view id
-                 comp = (AppJmol) frames[f];
-               }
-               else if (frames[f].getX() == x && frames[f].getY() == y
-                       && frames[f].getHeight() == height
-                       && frames[f].getWidth() == width)
-               {
-                 comp = (AppJmol) frames[f];
-               }
-             }
+             val = "0";
            }
+           newFileLoc.replace(histbug, diff, val);
+         }
+       }
  
-           if (comp == null)
+       final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
+               .size()]);
+       final String[] id = pdbids.toArray(new String[pdbids.size()]);
+       final SequenceI[][] sq = seqmaps
+               .toArray(new SequenceI[seqmaps.size()][]);
+       final String fileloc = newFileLoc.toString();
+       final String sviewid = viewerData.getKey();
+       final AlignFrame alf = af;
+       final Rectangle rect = new Rectangle(svattrib.getX(),
+               svattrib.getY(), svattrib.getWidth(), svattrib.getHeight());
+       try
+       {
+         javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+         {
+           @Override
+           public void run()
            {
-             // create a new Jmol window.
-             // First parse the Jmol state to translate filenames loaded into the
-             // view, and record the order in which files are shown in the Jmol
-             // view, so we can add the sequence mappings in same order.
-             StringBuffer newFileLoc = null;
-             int cp = 0, ncp, ecp;
-             while ((ncp = state.indexOf("load ", cp)) > -1)
-             {
-               if (newFileLoc == null)
-               {
-                 newFileLoc = new StringBuffer();
-               }
-               do
-               {
-                 // look for next filename in load statement
-                 newFileLoc.append(state.substring(cp,
-                         ncp = (state.indexOf("\"", ncp + 1) + 1)));
-                 String oldfilenam = state.substring(ncp,
-                         ecp = state.indexOf("\"", ncp));
-                 // recover the new mapping data for this old filename
-                 // have to normalize filename - since Jmol and jalview do
-                 // filename
-                 // translation differently.
-                 Object[] filedat = oldFiles.get(new File(oldfilenam));
-                 newFileLoc.append(Platform
-                         .escapeString((String) filedat[0]));
-                 pdbfilenames.addElement((String) filedat[0]);
-                 pdbids.addElement((String) filedat[1]);
-                 seqmaps.addElement(((Vector<SequenceI>) filedat[2])
-                         .toArray(new SequenceI[0]));
-                 newFileLoc.append("\"");
-                 cp = ecp + 1; // advance beyond last \" and set cursor so we can
-                               // look for next file statement.
-               } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
-             }
-             if (cp > 0)
+             JalviewStructureDisplayI sview = null;
+             try
              {
-               // just append rest of state
-               newFileLoc.append(state.substring(cp));
-             }
-             else
+               // JAL-1333 note - we probably can't migrate Jmol views to UCSF
+               // Chimera!
+               sview = new StructureViewer(alf.alignPanel
+                       .getStructureSelectionManager()).createView(
+                       StructureViewer.ViewerType.JMOL, pdbf, id, sq,
+                       alf.alignPanel, svattrib, fileloc, rect, sviewid);
+               addNewStructureViewer(sview);
+             } catch (OutOfMemoryError ex)
              {
-               System.err
-                       .print("Ignoring incomplete Jmol state for PDB ids: ");
-               newFileLoc = new StringBuffer(state);
-               newFileLoc.append("; load append ");
-               for (File id : oldFiles.keySet())
+               new OOMWarning("restoring structure view for PDB id " + id,
+                       (OutOfMemoryError) ex.getCause());
+               if (sview != null && sview.isVisible())
                {
-                 // add this and any other pdb files that should be present in
-                 // the viewer
-                 Object[] filedat = oldFiles.get(id);
-                 String nfilename;
-                 newFileLoc.append(((String) filedat[0]));
-                 pdbfilenames.addElement((String) filedat[0]);
-                 pdbids.addElement((String) filedat[1]);
-                 seqmaps.addElement(((Vector<SequenceI>) filedat[2])
-                         .toArray(new SequenceI[0]));
-                 newFileLoc.append(" \"");
-                 newFileLoc.append((String) filedat[0]);
-                 newFileLoc.append("\"");
+                 sview.closeViewer();
+                 sview.setVisible(false);
+                 sview.dispose();
                }
-               newFileLoc.append(";");
              }
+           }
+         });
+       } catch (InvocationTargetException ex)
+       {
+         warn("Unexpected error when opening Jmol view.", ex);
  
-             if (newFileLoc != null)
-             {
-               int histbug = newFileLoc.indexOf("history = ");
-               histbug += 10;
-               int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";",
-                       histbug);
-               String val = (diff == -1) ? null : newFileLoc.substring(
-                       histbug, diff);
-               if (val != null && val.length() >= 4)
-               {
-                 if (val.contains("e"))
-                 {
-                   if (val.trim().equals("true"))
-                   {
-                     val = "1";
-                   }
-                   else
-                   {
-                     val = "0";
-                   }
-                   newFileLoc.replace(histbug, diff, val);
-                 }
-               }
-               // TODO: assemble String[] { pdb files }, String[] { id for each
-               // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
-               // seqs_file 2}} from hash
-               final String[] pdbf = pdbfilenames
-                       .toArray(new String[pdbfilenames.size()]), id = pdbids
-                       .toArray(new String[pdbids.size()]);
-               final SequenceI[][] sq = seqmaps
-                       .toArray(new SequenceI[seqmaps.size()][]);
-               final String fileloc = newFileLoc.toString(), vid = sviewid;
-               final AlignFrame alf = af;
-               final java.awt.Rectangle rect = new java.awt.Rectangle(x, y,
-                       width, height);
-               try
-               {
-                 javax.swing.SwingUtilities.invokeAndWait(new Runnable()
-                 {
-                   @Override
-                   public void run()
-                   {
-                     JalviewStructureDisplayI sview = null;
-                     try
-                     {
-                       // JAL-1333 note - we probably can't migrate Jmol views to UCSF Chimera!
-                       sview = new StructureViewer(alf.alignPanel.getStructureSelectionManager()).createView(StructureViewer.Viewer.JMOL, pdbf, id, sq, alf.alignPanel,
-                               useinJmolsuperpos, usetoColourbyseq,
-                               jmolColouring, fileloc, rect, vid);
-                       addNewStructureViewer(sview);
-                     } catch (OutOfMemoryError ex)
-                     {
-                       new OOMWarning("restoring structure view for PDB id "
-                               + id, (OutOfMemoryError) ex.getCause());
-                       if (sview != null && sview.isVisible())
-                       {
-                         sview.closeViewer();
-                         sview.setVisible(false);
-                         sview.dispose();
-                       }
-                     }
-                   }
-                 });
-               } catch (InvocationTargetException ex)
-               {
-                 warn("Unexpected error when opening Jmol view.", ex);
+       } catch (InterruptedException e)
+       {
+         // e.printStackTrace();
+       }
+     }
+   }
  
-               } catch (InterruptedException e)
-               {
-                 // e.printStackTrace();
-               }
-             }
+   /**
+    * Returns any open frame that matches given structure viewer data. The match
+    * is based on the unique viewId, or (for older project versions) the frame's
+    * geometry.
+    * 
+    * @param viewerData
+    * @return
+    */
+   protected StructureViewerBase findMatchingViewer(
+           Entry<String, StructureViewerModel> viewerData)
+   {
+     final String sviewid = viewerData.getKey();
+     final StructureViewerModel svattrib = viewerData.getValue();
+     StructureViewerBase comp = null;
+     JInternalFrame[] frames = getAllFrames();
+     for (JInternalFrame frame : frames)
+     {
+       if (frame instanceof StructureViewerBase)
+       {
+         /*
+          * Post jalview 2.4 schema includes structure view id
+          */
+         if (sviewid != null
 -                && ((StructureViewerBase) frame).getViewId().equals(
 -                        sviewid))
++                && ((StructureViewerBase) frame).getViewId()
++                        .equals(sviewid))
+         {
+           comp = (AppJmol) frame;
+           // todo: break?
+         }
+         /*
+          * Otherwise test for matching position and size of viewer frame
+          */
+         else if (frame.getX() == svattrib.getX()
+                 && frame.getY() == svattrib.getY()
+                 && frame.getHeight() == svattrib.getHeight()
+                 && frame.getWidth() == svattrib.getWidth())
+         {
+           comp = (AppJmol) frame;
+           // todo: break?
+         }
+       }
+     }
+     return comp;
+   }
  
-           }
-           else
-           // if (comp != null)
-           {
-             // NOTE: if the jalview project is part of a shared session then
-             // view synchronization should/could be done here.
+   /**
+    * Link an AlignmentPanel to an existing structure viewer.
+    * 
+    * @param ap
+    * @param viewer
+    * @param oldFiles
+    * @param useinViewerSuperpos
+    * @param usetoColourbyseq
+    * @param viewerColouring
+    */
+   protected void linkStructureViewer(AlignmentPanel ap,
+           StructureViewerBase viewer, StructureViewerModel svattrib)
+   {
+     // NOTE: if the jalview project is part of a shared session then
+     // view synchronization should/could be done here.
  
-             // add mapping for sequences in this view to an already open Jmol
-             // instance
-             for (File id : oldFiles.keySet())
-             {
-               // add this and any other pdb files that should be present in the
-               // viewer
-               Object[] filedat = oldFiles.get(id);
-               String pdbFile = (String) filedat[0];
-               SequenceI[] seq = ((Vector<SequenceI>) filedat[2])
-                       .toArray(new SequenceI[0]);
-               comp.jmb.ssm.setMapping(seq, null, pdbFile,
-                       jalview.io.AppletFormatAdapter.FILE);
-               comp.jmb.addSequenceForStructFile(pdbFile, seq);
-             }
-             // and add the AlignmentPanel's reference to the Jmol view
-             comp.addAlignmentPanel(ap);
-             if (useinJmolsuperpos)
-             {
-               comp.useAlignmentPanelForSuperposition(ap);
-             }
-             else
-             {
-               comp.excludeAlignmentPanelForSuperposition(ap);
-             }
-             if (usetoColourbyseq)
-             {
-               comp.useAlignmentPanelForColourbyseq(ap, !jmolColouring);
-             }
-             else
-             {
-               comp.excludeAlignmentPanelForColourbyseq(ap);
-             }
-           }
+     final boolean useinViewerSuperpos = svattrib.isAlignWithPanel();
+     final boolean usetoColourbyseq = svattrib.isColourWithAlignPanel();
+     final boolean viewerColouring = svattrib.isColourByViewer();
+     Map<File, StructureData> oldFiles = svattrib.getFileData();
+     /*
+      * Add mapping for sequences in this view to an already open viewer
+      */
+     final AAStructureBindingModel binding = viewer.getBinding();
+     for (File id : oldFiles.keySet())
+     {
+       // add this and any other pdb files that should be present in the
+       // viewer
+       StructureData filedat = oldFiles.get(id);
+       String pdbFile = filedat.getFilePath();
+       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
+       binding.getSsm().setMapping(seq, null, pdbFile,
+               jalview.io.AppletFormatAdapter.FILE);
+       binding.addSequenceForStructFile(pdbFile, seq);
+     }
+     // and add the AlignmentPanel's reference to the view panel
+     viewer.addAlignmentPanel(ap);
+     if (useinViewerSuperpos)
+     {
+       viewer.useAlignmentPanelForSuperposition(ap);
+     }
+     else
+     {
+       viewer.excludeAlignmentPanelForSuperposition(ap);
+     }
+     if (usetoColourbyseq)
+     {
+       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
+     }
+     else
+     {
+       viewer.excludeAlignmentPanelForColourbyseq(ap);
+     }
+   }
+   /**
+    * Get all frames within the Desktop.
+    * 
+    * @return
+    */
+   protected JInternalFrame[] getAllFrames()
+   {
+     JInternalFrame[] frames = null;
+     // TODO is this necessary - is it safe - risk of hanging?
+     do
+     {
+       try
+       {
+         frames = Desktop.desktop.getAllFrames();
+       } catch (ArrayIndexOutOfBoundsException e)
+       {
+         // occasional No such child exceptions are thrown here...
+         try
+         {
+           Thread.sleep(10);
+         } catch (InterruptedException f)
+         {
          }
        }
-     }
-     // and finally return.
-     return af;
+     } while (frames == null);
+     return frames;
    }
  
    /**
@@@ -210,51 -212,24 +213,71 @@@ public final class JvSwingUtil
      }
    }
  
 +  /**
 +   * Returns the proportion of its range that a scrollbar's position represents,
 +   * as a value between 0 and 1. For example if the whole range is from 0 to
 +   * 200, then a position of 40 gives proportion = 0.2.
 +   * 
 +   * @see http://www.javalobby.org/java/forums/t33050.html#91885334
 +   * 
 +   * @param scroll
 +   * @return
 +   */
 +  public static float getScrollBarProportion(JScrollBar scroll)
 +  {
 +    /*
 +     * The extent (scroll handle width) deduction gives the true operating range
 +     * of possible positions.
 +     */
 +    int possibleRange = scroll.getMaximum() - scroll.getMinimum()
 +            - scroll.getModel().getExtent();
 +    float valueInRange = scroll.getValue()
 +            - (scroll.getModel().getExtent() / 2f);
 +    float proportion = valueInRange / possibleRange;
 +    return proportion;
 +  }
 +
 +  /**
 +   * Returns the scroll bar position in its range that would match the given
 +   * proportion (between 0 and 1) of the whole. For example if the whole range
 +   * is from 0 to 200, then a proportion of 0.25 gives position 50.
 +   * 
 +   * @param scrollbar
 +   * @param proportion
 +   * @return
 +   */
 +  public static int getScrollValueForProportion(JScrollBar scrollbar,
 +          float proportion)
 +  {
 +    /*
 +     * The extent (scroll handle width) deduction gives the true operating range
 +     * of possible positions.
 +     */
 +    float fraction = proportion
 +            * (scrollbar.getMaximum() - scrollbar.getMinimum() - scrollbar
 +                    .getModel().getExtent())
 +            + (scrollbar.getModel().getExtent() / 2f);
 +    return Math.min(Math.round(fraction), scrollbar.getMaximum());
 +  }
 +
+   public static void jvInitComponent(AbstractButton comp, String i18nString)
+   {
+     setColorAndFont(comp);
+     if (i18nString != null && !i18nString.isEmpty())
+     {
+       comp.setText(MessageManager.getString(i18nString));
+     }
+   }
+   public static void jvInitComponent(JComponent comp)
+   {
+     setColorAndFont(comp);
+   }
+   private static void setColorAndFont(JComponent comp)
+   {
+     comp.setBackground(Color.white);
+     comp.setFont(JvSwingUtils.getLabelFont());
+   }
  }
Simple merge
Simple merge
@@@ -478,20 -478,20 +478,16 @@@ public class ScalePanel extends JPanel 
            maxX = (i - startx + 1) * av.charWidth + fm.stringWidth(string);
          }
  
--        gg.drawLine(
--                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
--                y + 2,
--                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
--                y + (fm.getDescent() * 2));
++        gg.drawLine(((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
++                y + 2, ((i - startx - 1) * av.charWidth)
++                        + (av.charWidth / 2), y + (fm.getDescent() * 2));
  
        }
        else
        {
--        gg.drawLine(
--                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
--                y + fm.getDescent(),
--                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
--                y + (fm.getDescent() * 2));
++        gg.drawLine(((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
++                y + fm.getDescent(), ((i - startx - 1) * av.charWidth)
++                        + (av.charWidth / 2), y + (fm.getDescent() * 2));
        }
      }
  
Simple merge
@@@ -249,71 -243,22 +249,33 @@@ public class SeqPanel extends JPanel im
      return seq;
    }
  
-   SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
-   {
-     Vector tmp = new Vector();
-     SequenceFeature[] features = sequence.getSequenceFeatures();
-     if (features != null)
-     {
-       for (int i = 0; i < features.length; i++)
-       {
-         if (av.featuresDisplayed == null
-                 || !av.featuresDisplayed.containsKey(features[i].getType()))
-         {
-           continue;
-         }
-         if (features[i].featureGroup != null
-                 && seqCanvas.fr.featureGroups != null
-                 && seqCanvas.fr.featureGroups
-                         .containsKey(features[i].featureGroup)
-                 && !((Boolean) seqCanvas.fr.featureGroups
-                         .get(features[i].featureGroup)).booleanValue())
-         {
-           continue;
-         }
-         if ((features[i].getBegin() <= res)
-                 && (features[i].getEnd() >= res))
-         {
-           tmp.addElement(features[i]);
-         }
-       }
-     }
-     features = new SequenceFeature[tmp.size()];
-     tmp.copyInto(features);
-     return features;
-   }
 +  /**
 +   * When all of a sequence of edits are complete, put the resulting edit list
 +   * on the history stack (undo list), and reset flags for editing in progress.
 +   */
    void endEditing()
    {
 -    if (editCommand != null && editCommand.getSize() > 0)
 +    try
 +    {
 +      if (editCommand != null && editCommand.getSize() > 0)
 +      {
 +        ap.alignFrame.addHistoryItem(editCommand);
 +        av.firePropertyChange("alignment", null, av.getAlignment()
 +                .getSequences());
 +      }
 +    } finally
      {
 -      ap.alignFrame.addHistoryItem(editCommand);
 -      av.firePropertyChange("alignment", null, av.getAlignment()
 -              .getSequences());
 +      /*
 +       * Tidy up come what may...
 +       */
 +      startseq = -1;
 +      lastres = -1;
 +      editingSeqs = false;
 +      groupEditing = false;
 +      keyboardNo1 = null;
 +      keyboardNo2 = null;
 +      editCommand = null;
      }
 -
 -    startseq = -1;
 -    lastres = -1;
 -    editingSeqs = false;
 -    groupEditing = false;
 -    keyboardNo1 = null;
 -    keyboardNo2 = null;
 -    editCommand = null;
    }
  
    void setCursorRow()
    }
  
    @Override
 +  public VamsasSource getVamsasSource()
 +  {
 +    return this.ap == null ? null : this.ap.av;
 +  }
++  @Override
    public void updateColours(SequenceI seq, int index)
    {
      System.out.println("update the seqPanel colours");
@@@ -239,8 -242,7 +242,8 @@@ public class TreePanel extends GTreePan
        associateLeavesMenu.add(item);
      }
  
 -    final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
 +    final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
-             MessageManager.getString("label.all_views"));
++            "label.all_views");
      buttonGroup.add(itemf);
      itemf.setSelected(treeCanvas.applyToAllViews);
      itemf.addActionListener(new ActionListener()
          // msaorder);
  
          Desktop.addInternalFrame(af, MessageManager.formatMessage(
--                "label.original_data_for_params", new String[]
++                "label.original_data_for_params", new Object[]
                  { this.title }), AlignFrame.DEFAULT_WIDTH,
                  AlignFrame.DEFAULT_HEIGHT);
        }
@@@ -1055,8 -1050,10 +1055,9 @@@ public class VamsasApplication implemen
                    {
                      // gather selected columns outwith the sequence positions
                      // too
-                     for (int ival : colsel.getSelected())
 -                    Enumeration cols = colsel.getSelected().elements();
 -                    while (cols.hasMoreElements())
++                    for (Object obj : colsel.getSelected())
                      {
 -                      int ival = ((Integer) cols.nextElement()).intValue();
++                      int ival = ((Integer) obj).intValue();
                        Pos p = new Pos();
                        p.setI(ival + 1);
                        range.addPos(p);
@@@ -46,24 -46,10 +46,24 @@@ public class AppletFormatAdapte
     * List of valid format strings used in the isValidFormat method
     */
    public static final String[] READABLE_FORMATS = new String[]
--          { "BLC", "CLUSTAL", "FASTA", "MSF", "PileUp", "PIR", "PFAM", "STH",
-     "PDB", "JnetFile", "RNAML", PhylipFile.FILE_DESC }; // , "SimpleBLAST" };
 -      "PDB", "JnetFile", "RNAML", PhylipFile.FILE_DESC, "HTML" }; // ,
 -                                                                              // "SimpleBLAST"
 -                                                                              // };
++  { "BLC", "CLUSTAL", "FASTA", "MSF", "PileUp", "PIR", "PFAM", "STH",
++      "PDB", "JnetFile", "RNAML", PhylipFile.FILE_DESC, "HTML" };
 +
 +  /**
 +   * List of readable format file extensions by application in order
 +   * corresponding to READABLE_FNAMES
 +   */
 +  public static final String[] READABLE_EXTENSIONS = new String[]
 +  { "fa, fasta, mfa, fastq", "aln", "pfam", "msf", "pir", "blc", "amsa",
-       "sto,stk", "xml,rnaml", PhylipFile.FILE_EXT, "jar,jvp" };
++      "sto,stk", "xml,rnaml", PhylipFile.FILE_EXT, "jar,jvp", "html" };
 +
 +  /**
 +   * List of readable formats by application in order corresponding to
 +   * READABLE_EXTENSIONS
 +   */
 +  public static final String[] READABLE_FNAMES = new String[]
 +  { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "Stockholm",
-       "RNAML", PhylipFile.FILE_DESC, "Jalview" };
++      "RNAML", PhylipFile.FILE_DESC, "Jalview", "HTML" };
  
    /**
     * List of valid format strings for use by callers of the formatSequences
     * WRITABLE_EXTENSIONS list of formats.
     */
    public static final String[] WRITABLE_FNAMES = new String[]
-           { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "Jalview",
-     "STH", "Jalview", PhylipFile.FILE_DESC };
-   // "SimpleBLAST"
-   // };
+   { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "STH",
+       PhylipFile.FILE_DESC, "Jalview" };
  
 -  /**
 -   * List of readable format file extensions by application in order
 -   * corresponding to READABLE_FNAMES
 -   */
 -  public static final String[] READABLE_EXTENSIONS = new String[]
 -          { "fa, fasta, mfa, fastq", "aln", "pfam", "msf", "pir", "blc", "amsa",
 -      "jar,jvp", "sto,stk", "xml,rnaml", PhylipFile.FILE_EXT,
 - "html" }; // ".blast"
 -
 -  /**
 -   * List of readable formats by application in order corresponding to
 -   * READABLE_EXTENSIONS
 -   */
 -  public static final String[] READABLE_FNAMES = new String[]
 -          { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "Jalview",
 -      "Stockholm", "RNAML", PhylipFile.FILE_DESC, "HTML" };// ,
 -
 -  // "SimpleBLAST"
 -  // };
 -
    public static String INVALID_CHARACTERS = "Contains invalid characters";
  
    // TODO: make these messages dynamic
Simple merge
@@@ -144,22 -144,24 +144,25 @@@ public class MouseOverStructureListene
    }
  
    @Override
 -  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
 -          String pdbId)
 +  public void highlightAtoms(List<AtomSpec> atoms)
    {
 -    String[] st = new String[0];
 -    try
 -    {
 -      executeJavascriptFunction(_listenerfn, st = new String[]
 -      { "mouseover", "" + pdbId, "" + chain, "" + (pdbResNum),
 -          "" + atomIndex });
 -    } catch (Exception ex)
 +    for (AtomSpec atom : atoms)
      {
 -      System.err.println("Couldn't execute callback with " + _listenerfn
 -              + " using args { " + st[0] + ", " + st[1] + ", " + st[2]
 -              + "," + st[3] + "\n");
 -      ex.printStackTrace();
 -
 +      try
 +      {
++        // TODO is this right? StructureSelectionManager passes pdbFile as the
++        // field that is interpreted (in 2.8.2) as pdbId?
 +        executeJavascriptFunction(_listenerfn, new String[]
-         { "mouseover", "" + atom.getPdbId(), "" + atom.getChain(),
++        { "mouseover", "" + atom.getPdbFile(),
++                    "" + atom.getChain(),
 +            "" + (atom.getPdbResNum()), "" + atom.getAtomIndex() });
 +      } catch (Exception ex)
 +      {
 +        System.err.println("Couldn't execute callback with " + _listenerfn
 +                + " for atomSpec: " + atom);
 +        ex.printStackTrace();
 +      }
      }
 -
    }
  
    @Override
@@@ -1196,9 -1201,22 +1200,22 @@@ public class GAlignFrame extends JInter
          htmlMenuItem_actionPerformed(e);
        }
      });
+     // TODO uncomment when supported by MassageManager
+     // createBioJS.setText(MessageManager.getString("label.biojs_html_export"));
+     createBioJS.setText("BioJS");
+     createBioJS.addActionListener(new java.awt.event.ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         bioJSMenuItem_actionPerformed(e);
+       }
+     });
      overviewMenuItem.setText(MessageManager
              .getString("label.overview_window"));
 -    overviewMenuItem.addActionListener(new java.awt.event.ActionListener()
 +    overviewMenuItem.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
index 271c893,0000000..d3e8d42
mode 100644,000000..100644
--- /dev/null
@@@ -1,61 -1,0 +1,64 @@@
 +package jalview.structure;
 +
 +/**
 + * Java bean representing an atom in a PDB (or similar) structure model.
 + * 
 + * @author gmcarstairs
 + *
 + */
 +public class AtomSpec
 +{
-   private String pdbId;
++  // TODO clarify do we want pdbFile here, or pdbId?
++  // compare highlightAtom in 2.8.2 for JalviewJmolBinding and
++  // javascript.MouseOverStructureListener
++  private String pdbFile;
 +
 +  private String chain;
 +
 +  private int pdbResNum;
 +
 +  private int atomIndex;
 +
 +  /**
 +   * Constructor
 +   * 
-    * @param id
++   * @param pdbFile
 +   * @param chain
 +   * @param resNo
 +   * @param atomNo
 +   */
-   public AtomSpec(String id, String chain, int resNo, int atomNo)
++  public AtomSpec(String pdbFile, String chain, int resNo, int atomNo)
 +  {
-     this.pdbId = id;
++    this.pdbFile = pdbFile;
 +    this.chain = chain;
 +    this.pdbResNum = resNo;
 +    this.atomIndex = atomNo;
 +  }
 +
-   public String getPdbId()
++  public String getPdbFile()
 +  {
-     return pdbId;
++    return pdbFile;
 +  }
 +
 +  public String getChain()
 +  {
 +    return chain;
 +  }
 +
 +  public int getPdbResNum()
 +  {
 +    return pdbResNum;
 +  }
 +
 +  public int getAtomIndex()
 +  {
 +    return atomIndex;
 +  }
 +
 +  @Override
 +  public String toString()
 +  {
-     return "pdbId: " + pdbId + ", chain: " + chain + ", res: " + pdbResNum
-             + ", atom: " + atomIndex;
++    return "pdbFile: " + pdbFile + ", chain: " + chain + ", res: "
++            + pdbResNum + ", atom: " + atomIndex;
 +  }
 +}
   */
  package jalview.structure;
  
- import jalview.datamodel.SearchResults;
 -import jalview.datamodel.*;
++import jalview.datamodel.SequenceI;
 +
  
  public interface SequenceListener
  {
-   public void highlightSequence(SearchResults results);
+   public void mouseOverSequence(SequenceI sequence, int index, int pos);
+   public void highlightSequence(jalview.datamodel.SearchResults results);
+   public void updateColours(SequenceI sequence, int index);
 +
-   // Required so we can check for (and skip if) target == source
 +  public VamsasSource getVamsasSource();
++
  }
@@@ -41,9 -35,6 +41,10 @@@ import java.util.ArrayList
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.IdentityHashMap;
 +import java.util.LinkedHashSet;
 +import java.util.List;
++import java.util.Map;
 +import java.util.Set;
  import java.util.Vector;
  
  import MCview.Atom;
@@@ -53,19 -44,10 +54,28 @@@ public class StructureSelectionManage
  {
    static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
  
 -  StructureMapping[] mappings;
 +  private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
  
 -  private boolean processSecondaryStructure = false,
 -          secStructServices = false, addTempFacAnnot = false;
 +  private boolean processSecondaryStructure = false;
 +
 +  private boolean secStructServices = false;
 +
 +  private boolean addTempFacAnnot = false;
 +
-   private Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
++  /*
++   * Set of any registered mappings between (dataset) sequences.
++   */
++  Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
++
++  /*
++   * Reference counters for the above mappings. Remove mappings when ref count
++   * goes to zero.
++   */
++  Map<AlignedCodonFrame, Integer> seqMappingRefCounts = new HashMap<AlignedCodonFrame, Integer>();
 +
 +  private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
 +
 +  private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
  
    /**
     * @return true if will try to use external services for processing secondary
      return sb.toString();
    }
  
 -  private int[] seqmappingrefs = null; // refcount for seqmappings elements
 -
 -  private synchronized void modifySeqMappingList(boolean add,
 -          AlignedCodonFrame[] codonFrames)
 +  /**
-    * Remove each of the given codonFrames from the stored set (if present).
++   * Decrement the reference counter for each of the given mappings, and remove
++   * it entirely if its reference counter reduces to zero.
 +   * 
 +   * @param set
 +   */
 +  public void removeMappings(Set<AlignedCodonFrame> set)
    {
 -    if (!add && (seqmappings == null || seqmappings.size() == 0))
 -    {
 -      return;
 -    }
 -    if (seqmappings == null)
 +    if (set != null)
      {
-       seqmappings.removeAll(set);
 -      seqmappings = new Vector();
++      for (AlignedCodonFrame acf : set)
++      {
++        removeMapping(acf);
++      }
      }
 -    if (codonFrames != null && codonFrames.length > 0)
 +  }
 +
 +  /**
-    * Add each of the given codonFrames to the stored set (if not aready
-    * present).
++   * Decrement the reference counter for the given mapping, and remove it
++   * entirely if its reference counter reduces to zero.
++   * 
++   * @param acf
++   */
++  public void removeMapping(AlignedCodonFrame acf)
++  {
++    if (acf != null && seqmappings.contains(acf))
+     {
 -      for (int cf = 0; cf < codonFrames.length; cf++)
++      int count = seqMappingRefCounts.get(acf);
++      count--;
++      if (count > 0)
+       {
 -        if (seqmappings.contains(codonFrames[cf]))
 -        {
 -          if (add)
 -          {
 -            seqmappingrefs[seqmappings.indexOf(codonFrames[cf])]++;
 -          }
 -          else
 -          {
 -            if (--seqmappingrefs[seqmappings.indexOf(codonFrames[cf])] <= 0)
 -            {
 -              int pos = seqmappings.indexOf(codonFrames[cf]);
 -              int[] nr = new int[seqmappingrefs.length - 1];
 -              if (pos > 0)
 -              {
 -                System.arraycopy(seqmappingrefs, 0, nr, 0, pos);
 -              }
 -              if (pos < seqmappingrefs.length - 1)
 -              {
 -                System.arraycopy(seqmappingrefs, pos + 1, nr, 0,
 -                        seqmappingrefs.length - pos - 2);
 -              }
 -            }
 -          }
 -        }
 -        else
 -        {
 -          if (add)
 -          {
 -            seqmappings.addElement(codonFrames[cf]);
 -
 -            int[] nsr = new int[(seqmappingrefs == null) ? 1
 -                    : seqmappingrefs.length + 1];
 -            if (seqmappingrefs != null && seqmappingrefs.length > 0)
 -            {
 -              System.arraycopy(seqmappingrefs, 0, nsr, 0,
 -                      seqmappingrefs.length);
 -            }
 -            nsr[(seqmappingrefs == null) ? 0 : seqmappingrefs.length] = 1;
 -            seqmappingrefs = nsr;
 -          }
 -        }
++        seqMappingRefCounts.put(acf, count);
++      }
++      else
++      {
++        seqmappings.remove(acf);
++        seqMappingRefCounts.remove(acf);
+       }
+     }
+   }
 -  public void removeMappings(AlignedCodonFrame[] codonFrames)
++  /**
++   * Add each of the given codonFrames to the stored set. If not aready present,
++   * increments its reference count instead.
 +   * 
 +   * @param set
 +   */
 +  public void addMappings(Set<AlignedCodonFrame> set)
    {
 -    modifySeqMappingList(false, codonFrames);
 +    if (set != null)
 +    {
-       seqmappings.addAll(set);
++      for (AlignedCodonFrame acf : set)
++      {
++        addMapping(acf);
++      }
 +    }
    }
  
 -  public void addMappings(AlignedCodonFrame[] codonFrames)
++  /**
++   * Add the given mapping to the stored set, or if already stored, increment
++   * its reference counter.
++   */
 +  public void addMapping(AlignedCodonFrame acf)
    {
 -    modifySeqMappingList(true, codonFrames);
 +    if (acf != null)
 +    {
-       seqmappings.add(acf);
++      if (seqmappings.contains(acf))
++      {
++        seqMappingRefCounts.put(acf, seqMappingRefCounts.get(acf) + 1);
++      }
++      else
++      {
++        seqmappings.add(acf);
++        seqMappingRefCounts.put(acf, 1);
++      }
 +    }
    }
  
 -  Vector<SelectionListener> sel_listeners = new Vector<SelectionListener>();
 -
    public void addSelectionListener(SelectionListener selecter)
    {
      if (!sel_listeners.contains(selecter))
index 0000000,664c903..653ec2d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,364 +1,382 @@@
+ package jalview.structures.models;
+ import jalview.api.StructureSelectionManagerProvider;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
++import jalview.structure.AtomSpec;
+ import jalview.structure.StructureListener;
+ import jalview.structure.StructureSelectionManager;
+ import jalview.util.MessageManager;
 -import java.awt.event.ComponentEvent;
+ import java.util.ArrayList;
+ import java.util.List;
+ /**
++ * 
+  * A base class to hold common function for protein structure model binding.
+  * Initial version created by refactoring JMol and Chimera binding models, but
+  * other structure viewers could in principle be accommodated in future.
+  * 
+  * @author gmcarstairs
+  *
+  */
+ public abstract class AAStructureBindingModel extends
+         SequenceStructureBindingModel implements StructureListener,
+         StructureSelectionManagerProvider
+ {
+   private StructureSelectionManager ssm;
+   private PDBEntry[] pdbEntry;
+   /*
+    * sequences mapped to each pdbentry
+    */
+   private SequenceI[][] sequence;
+   /*
+    * array of target chains for sequences - tied to pdbentry and sequence[]
+    */
+   private String[][] chains;
+   /*
+    * datasource protocol for access to PDBEntrylatest
+    */
+   String protocol = null;
+   protected boolean colourBySequence = true;
+   /**
+    * Constructor
+    * 
+    * @param ssm
+    * @param seqs
+    */
+   public AAStructureBindingModel(StructureSelectionManager ssm,
+           SequenceI[][] seqs)
+   {
+     this.ssm = ssm;
+     this.sequence = seqs;
+   }
+   /**
+    * Constructor
+    * 
+    * @param ssm
+    * @param pdbentry
+    * @param sequenceIs
+    * @param chains
+    * @param protocol
+    */
+   public AAStructureBindingModel(StructureSelectionManager ssm,
+           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
+           String protocol)
+   {
+     this.ssm = ssm;
+     this.sequence = sequenceIs;
+     this.chains = chains;
+     this.pdbEntry = pdbentry;
+     this.protocol = protocol;
+     if (chains == null)
+     {
+       this.chains = new String[pdbentry.length][];
+     }
+   }
+   public StructureSelectionManager getSsm()
+   {
+     return ssm;
+   }
+   /**
+    * Returns the i'th PDBEntry (or null)
+    * 
+    * @param i
+    * @return
+    */
+   public PDBEntry getPdbEntry(int i)
+   {
+     return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
+   }
+   /**
+    * Returns the number of modelled PDB file entries.
+    * 
+    * @return
+    */
+   public int getPdbCount()
+   {
+     return pdbEntry == null ? 0 : pdbEntry.length;
+   }
+   public SequenceI[][] getSequence()
+   {
+     return sequence;
+   }
+   public String[][] getChains()
+   {
+     return chains;
+   }
+   public String getProtocol()
+   {
+     return protocol;
+   }
+   // TODO may remove this if calling methods can be pulled up here
+   protected void setPdbentry(PDBEntry[] pdbentry)
+   {
+     this.pdbEntry = pdbentry;
+   }
+   protected void setSequence(SequenceI[][] sequence)
+   {
+     this.sequence = sequence;
+   }
+   protected void setChains(String[][] chains)
+   {
+     this.chains = chains;
+   }
+   /**
+    * Construct a title string for the viewer window based on the data Jalview
+    * knows about
+    * @param viewerName TODO
+    * @param verbose
+    * 
+    * @return
+    */
+   public String getViewerTitle(String viewerName, boolean verbose)
+   {
+     if (getSequence() == null || getSequence().length < 1
+             || getPdbCount() < 1
+             || getSequence()[0].length < 1)
+     {
+       return ("Jalview " + viewerName + " Window");
+     }
+     // TODO: give a more informative title when multiple structures are
+     // displayed.
+     StringBuilder title = new StringBuilder(64);
+     final PDBEntry pdbEntry = getPdbEntry(0);
+     title.append(viewerName + " view for " + getSequence()[0][0].getName()
+             + ":"
+             + pdbEntry.getId());
+   
+     if (verbose)
+     {
+       if (pdbEntry.getProperty() != null)
+       {
+         if (pdbEntry.getProperty().get("method") != null)
+         {
+           title.append(" Method: ");
+           title.append(pdbEntry.getProperty().get("method"));
+         }
+         if (pdbEntry.getProperty().get("chains") != null)
+         {
+           title.append(" Chain:");
+           title.append(pdbEntry.getProperty().get("chains"));
+         }
+       }
+     }
+     return title.toString();
+   }
+   /**
+    * Called by after closeViewer is called, to release any resources and
+    * references so they can be garbage collected. Override if needed.
+    */
+   protected void releaseUIResources()
+   {
+   }
+   public boolean isColourBySequence()
+   {
+     return colourBySequence;
+   }
+   public void setColourBySequence(boolean colourBySequence)
+   {
+     this.colourBySequence = colourBySequence;
+   }
+   protected void addSequenceAndChain(int pe, SequenceI[] seq,
+           String[] tchain)
+   {
+     if (pe < 0 || pe >= getPdbCount())
+     {
+       throw new Error(MessageManager.formatMessage(
+               "error.implementation_error_no_pdbentry_from_index",
+               new Object[]
+               { Integer.valueOf(pe).toString() }));
+     }
+     final String nullChain = "TheNullChain";
+     List<SequenceI> s = new ArrayList<SequenceI>();
+     List<String> c = new ArrayList<String>();
+     if (getChains() == null)
+     {
+       setChains(new String[getPdbCount()][]);
+     }
+     if (getSequence()[pe] != null)
+     {
+       for (int i = 0; i < getSequence()[pe].length; i++)
+       {
+         s.add(getSequence()[pe][i]);
+         if (getChains()[pe] != null)
+         {
+           if (i < getChains()[pe].length)
+           {
+             c.add(getChains()[pe][i]);
+           }
+           else
+           {
+             c.add(nullChain);
+           }
+         }
+         else
+         {
+           if (tchain != null && tchain.length > 0)
+           {
+             c.add(nullChain);
+           }
+         }
+       }
+     }
+     for (int i = 0; i < seq.length; i++)
+     {
+       if (!s.contains(seq[i]))
+       {
+         s.add(seq[i]);
+         if (tchain != null && i < tchain.length)
+         {
+           c.add(tchain[i] == null ? nullChain : tchain[i]);
+         }
+       }
+     }
+     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
+     getSequence()[pe] = tmp;
+     if (c.size() > 0)
+     {
+       String[] tch = c.toArray(new String[c.size()]);
+       for (int i = 0; i < tch.length; i++)
+       {
+         if (tch[i] == nullChain)
+         {
+           tch[i] = null;
+         }
+       }
+       getChains()[pe] = tch;
+     }
+     else
+     {
+       getChains()[pe] = null;
+     }
+   }
+   /**
+    * add structures and any known sequence associations
+    * 
+    * @returns the pdb entries added to the current set.
+    */
+   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe, SequenceI[][] seq,
+           String[][] chns)
+   {
+     List<PDBEntry> v = new ArrayList<PDBEntry>();
+     List<int[]> rtn = new ArrayList<int[]>();
+     for (int i = 0; i < getPdbCount(); i++)
+     {
+       v.add(getPdbEntry(i));
+     }
+     for (int i = 0; i < pdbe.length; i++)
+     {
+       int r = v.indexOf(pdbe[i]);
+       if (r == -1 || r >= getPdbCount())
+       {
+         rtn.add(new int[]
+         { v.size(), i });
+         v.add(pdbe[i]);
+       }
+       else
+       {
+         // just make sure the sequence/chain entries are all up to date
+         addSequenceAndChain(r, seq[i], chns[i]);
+       }
+     }
+     pdbe = v.toArray(new PDBEntry[v.size()]);
+     setPdbentry(pdbe);
+     if (rtn.size() > 0)
+     {
+       // expand the tied sequence[] and string[] arrays
+       SequenceI[][] sqs = new SequenceI[getPdbCount()][];
+       String[][] sch = new String[getPdbCount()][];
+       System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
+       System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
+       setSequence(sqs);
+       setChains(sch);
+       pdbe = new PDBEntry[rtn.size()];
+       for (int r = 0; r < pdbe.length; r++)
+       {
+         int[] stri = (rtn.get(r));
+         // record the pdb file as a new addition
+         pdbe[r] = getPdbEntry(stri[0]);
+         // and add the new sequence/chain entries
+         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
+       }
+     }
+     else
+     {
+       pdbe = null;
+     }
+     return pdbe;
+   }
+   /**
+    * Add sequences to the pe'th pdbentry's sequence set.
+    * 
+    * @param pe
+    * @param seq
+    */
+   public void addSequence(int pe, SequenceI[] seq)
+   {
+     addSequenceAndChain(pe, seq, null);
+   }
+   /**
+    * add the given sequences to the mapping scope for the given pdb file handle
+    * 
+    * @param pdbFile
+    *          - pdbFile identifier
+    * @param seq
+    *          - set of sequences it can be mapped to
+    */
+   public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
+   {
+     for (int pe = 0; pe < getPdbCount(); pe++)
+     {
+       if (getPdbEntry(pe).getFile().equals(pdbFile))
+       {
+         addSequence(pe, seq);
+       }
+     }
+   }
++  @Override
++  public void highlightAtoms(List<AtomSpec> atoms)
++  {
++    if (atoms != null)
++    {
++      for (AtomSpec atom : atoms)
++      {
++        highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
++                atom.getChain(), atom.getPdbFile());
++      }
++    }
++  }
++
++  // TODO Jmol and Chimera seem to expect pdbFile, javascript listener pdbId
++  protected abstract void highlightAtom(int atomIndex, int pdbResNum,
++          String chain, String pdbFile);
++
+ }
index 0b931a2,0000000..6dfebfe
mode 100644,000000..100644
--- /dev/null
@@@ -1,480 -1,0 +1,481 @@@
 +package jalview.util;
 +
 +import jalview.analysis.AlignmentSorter;
 +import jalview.api.AlignViewportI;
 +import jalview.commands.CommandI;
 +import jalview.commands.EditCommand;
 +import jalview.commands.EditCommand.Action;
 +import jalview.commands.EditCommand.Edit;
 +import jalview.commands.OrderCommand;
 +import jalview.datamodel.AlignedCodonFrame;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.AlignmentOrder;
 +import jalview.datamodel.ColumnSelection;
 +import jalview.datamodel.SearchResults;
 +import jalview.datamodel.SearchResults.Match;
 +import jalview.datamodel.Sequence;
 +import jalview.datamodel.SequenceGroup;
 +import jalview.datamodel.SequenceI;
 +import jalview.gui.AlignViewport;
 +
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +
 +/**
 + * Helper methods for manipulations involving sequence mappings.
 + * 
 + * @author gmcarstairs
 + *
 + */
 +public final class MappingUtils
 +{
 +
 +  /**
 +   * Helper method to map a CUT or PASTE command.
 +   * 
 +   * @param edit
 +   *          the original command
 +   * @param undo
 +   *          if true, the command is to be undone
 +   * @param targetSeqs
 +   *          the mapped sequences to apply the mapped command to
 +   * @param result
 +   *          the mapped EditCommand to add to
 +   * @param mappings
 +   */
 +  protected static void mapCutOrPaste(Edit edit, boolean undo,
 +          List<SequenceI> targetSeqs, EditCommand result,
 +          Set<AlignedCodonFrame> mappings)
 +  {
 +    Action action = edit.getAction();
 +    if (undo)
 +    {
 +      action = action.getUndoAction();
 +    }
 +    // TODO write this
 +    System.err.println("MappingUtils.mapCutOrPaste not yet implemented");
 +  }
 +
 +  /**
 +   * Returns a new EditCommand representing the given command as mapped to the
 +   * given sequences. If there is no mapping, returns null.
 +   * 
 +   * @param command
 +   * @param undo
 +   * @param mapTo
 +   * @param gapChar
 +   * @param mappings
 +   * @return
 +   */
 +  public static EditCommand mapEditCommand(EditCommand command,
 +          boolean undo, final AlignmentI mapTo, char gapChar,
 +          Set<AlignedCodonFrame> mappings)
 +  {
 +    /*
 +     * For now, only support mapping from protein edits to cDna
 +     */
 +    if (!mapTo.isNucleotide())
 +    {
 +      return null;
 +    }
 +
 +    /*
 +     * Cache a copy of the target sequences so we can mimic successive edits on
 +     * them. This lets us compute mappings for all edits in the set.
 +     */
 +    Map<SequenceI, SequenceI> targetCopies = new HashMap<SequenceI, SequenceI>();
 +    for (SequenceI seq : mapTo.getSequences())
 +    {
 +      SequenceI ds = seq.getDatasetSequence();
 +      if (ds != null)
 +      {
 +        final SequenceI copy = new Sequence("", new String(
 +                seq.getSequence()));
 +        copy.setDatasetSequence(ds);
 +        targetCopies.put(ds, copy);
 +      }
 +    }
 +
 +    /*
 +     * Compute 'source' sequences as they were before applying edits:
 +     */
 +    Map<SequenceI, SequenceI> originalSequences = command.priorState(undo);
 +
 +    EditCommand result = new EditCommand();
 +    Iterator<Edit> edits = command.getEditIterator(!undo);
 +    while (edits.hasNext())
 +    {
 +      Edit edit = edits.next();
 +      if (edit.getAction() == Action.CUT
 +              || edit.getAction() == Action.PASTE)
 +      {
 +        mapCutOrPaste(edit, undo, mapTo.getSequences(), result, mappings);
 +      }
 +      else if (edit.getAction() == Action.INSERT_GAP
 +              || edit.getAction() == Action.DELETE_GAP)
 +      {
 +        mapInsertOrDelete(edit, undo, originalSequences,
 +                mapTo.getSequences(), targetCopies, gapChar, result,
 +                mappings);
 +      }
 +    }
 +    return result.getSize() > 0 ? result : null;
 +  }
 +
 +  /**
 +   * Helper method to map an edit command to insert or delete gaps.
 +   * 
 +   * @param edit
 +   *          the original command
 +   * @param undo
 +   *          if true, the action is to undo the command
 +   * @param originalSequences
 +   *          the sequences the command acted on
 +   * @param targetSeqs
 +   * @param targetCopies
 +   * @param gapChar
 +   * @param result
 +   *          the new EditCommand to add mapped commands to
 +   * @param mappings
 +   */
 +  protected static void mapInsertOrDelete(Edit edit, boolean undo,
 +          Map<SequenceI, SequenceI> originalSequences,
 +          final List<SequenceI> targetSeqs,
 +          Map<SequenceI, SequenceI> targetCopies, char gapChar,
 +          EditCommand result, Set<AlignedCodonFrame> mappings)
 +  {
 +    Action action = edit.getAction();
 +
 +    /*
 +     * Invert sense of action if an Undo.
 +     */
 +    if (undo)
 +    {
 +      action = action.getUndoAction();
 +    }
 +    final int count = edit.getNumber();
 +    final int editPos = edit.getPosition();
 +    for (SequenceI seq : edit.getSequences())
 +    {
 +      /*
 +       * Get residue position at (or to right of) edit location. Note we use our
 +       * 'copy' of the sequence before editing for this.
 +       */
 +      SequenceI ds = seq.getDatasetSequence();
 +      if (ds == null)
 +      {
 +        continue;
 +      }
 +      final SequenceI actedOn = originalSequences.get(ds);
 +      final int seqpos = actedOn.findPosition(editPos);
 +
 +      /*
 +       * Determine all mappings from this position to mapped sequences.
 +       */
 +      SearchResults sr = buildSearchResults(seq, seqpos, mappings);
 +
 +      if (!sr.isEmpty())
 +      {
 +        for (SequenceI targetSeq : targetSeqs)
 +        {
 +          ds = targetSeq.getDatasetSequence();
 +          if (ds == null)
 +          {
 +            continue;
 +          }
 +          SequenceI copyTarget = targetCopies.get(ds);
 +          final int[] match = sr.getResults(copyTarget, 0,
 +                  copyTarget.getLength());
 +          if (match != null)
 +          {
 +            final int ratio = 3; // TODO: compute this - how?
 +            final int mappedCount = count * ratio;
 +
 +            /*
 +             * Shift Delete start position left, as it acts on positions to its
 +             * right.
 +             */
 +            int mappedEditPos = action == Action.DELETE_GAP ? match[0]
 +                    - mappedCount : match[0];
 +            Edit e = result.new Edit(action, new SequenceI[]
 +            { targetSeq }, mappedEditPos, mappedCount, gapChar);
 +            result.addEdit(e);
 +
 +            /*
 +             * and 'apply' the edit to our copy of its target sequence
 +             */
 +            if (action == Action.INSERT_GAP)
 +            {
 +              copyTarget.setSequence(new String(StringUtils.insertCharAt(
 +                      copyTarget.getSequence(), mappedEditPos, mappedCount,
 +                      gapChar)));
 +            }
 +            else if (action == Action.DELETE_GAP)
 +            {
 +              copyTarget.setSequence(new String(StringUtils.deleteChars(
 +                      copyTarget.getSequence(), mappedEditPos,
 +                      mappedEditPos + mappedCount)));
 +            }
 +          }
 +        }
 +      }
 +      /*
 +       * and 'apply' the edit to our copy of its source sequence
 +       */
 +      if (action == Action.INSERT_GAP)
 +      {
 +        actedOn.setSequence(new String(StringUtils.insertCharAt(
 +                actedOn.getSequence(), editPos, count, gapChar)));
 +      }
 +      else if (action == Action.DELETE_GAP)
 +      {
 +        actedOn.setSequence(new String(StringUtils.deleteChars(
 +                actedOn.getSequence(), editPos, editPos + count)));
 +      }
 +    }
 +  }
 +
 +  /**
 +   * Returns a SearchResults object describing the mapped region corresponding
 +   * to the specified sequence position.
 +   * 
 +   * @param seq
 +   * @param index
 +   * @param seqmappings
 +   * @return
 +   */
 +  public static SearchResults buildSearchResults(SequenceI seq, int index,
 +          Set<AlignedCodonFrame> seqmappings)
 +  {
 +    SearchResults results;
 +    results = new SearchResults();
 +    if (index >= seq.getStart() && index <= seq.getEnd())
 +    {
 +      for (AlignedCodonFrame acf : seqmappings)
 +      {
 +        acf.markMappedRegion(seq, index, results);
 +      }
 +    }
 +    return results;
 +  }
 +
 +  /**
 +   * Returns a (possibly empty) SequenceGroup containing any sequences in the
 +   * mapped viewport corresponding to the given group in the source viewport.
 +   * 
 +   * @param sg
 +   * @param mapFrom
 +   * @param mapTo
 +   * @return
 +   */
 +  public static SequenceGroup mapSequenceGroup(SequenceGroup sg,
 +          AlignViewportI mapFrom, AlignViewportI mapTo)
 +  {
 +    /*
 +     * Note the SequenceGroup holds aligned sequences, the mappings hold dataset
 +     * sequences.
 +     */
 +    boolean targetIsNucleotide = mapTo.isNucleotide();
 +    AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
 +    Set<AlignedCodonFrame> codonFrames = protein.getAlignment()
 +            .getCodonFrames();
 +
 +    /*
 +     * Copy group name, colours, but not sequences
 +     */
 +    SequenceGroup mappedGroup = new SequenceGroup(sg);
 +    mappedGroup.clear();
 +    // TODO set width of mapped group
 +
 +    for (SequenceI selected : sg.getSequences())
 +    {
 +      for (AlignedCodonFrame acf : codonFrames)
 +      {
 +        SequenceI mappedSequence = targetIsNucleotide ? acf
 +                .getDnaForAaSeq(selected) : acf.getAaForDnaSeq(selected);
 +        if (mappedSequence != null)
 +        {
 +          for (SequenceI seq : mapTo.getAlignment().getSequences())
 +          {
 +            if (seq.getDatasetSequence() == mappedSequence)
 +            {
 +              mappedGroup.addSequence(seq, false);
 +              break;
 +            }
 +          }
 +        }
 +      }
 +    }
 +    return mappedGroup;
 +  }
 +
 +  /**
 +   * Returns an OrderCommand equivalent to the given one, but acting on mapped
 +   * sequences as described by the mappings, or null if no mapping can be made.
 +   * 
 +   * @param command
 +   *          the original order command
 +   * @param undo
 +   *          if true, the action is to undo the sort
 +   * @param mapTo
 +   *          the alignment we are mapping to
 +   * @param mappings
 +   *          the mappings available
 +   * @return
 +   */
 +  public static CommandI mapOrderCommand(OrderCommand command,
 +          boolean undo, AlignmentI mapTo, Set<AlignedCodonFrame> mappings)
 +  {
 +    SequenceI[] sortOrder = command.getSequenceOrder(undo);
 +    List<SequenceI> mappedOrder = new ArrayList<SequenceI>();
 +    int j = 0;
 +    for (SequenceI seq : sortOrder)
 +    {
 +      for (AlignedCodonFrame acf : mappings)
 +      {
 +        /*
 +         * Try protein-to-Dna, failing that try dna-to-protein
 +         */
 +        SequenceI mappedSeq = acf.getDnaForAaSeq(seq);
 +        if (mappedSeq == null)
 +        {
 +          mappedSeq = acf.getAaForDnaSeq(seq);
 +        }
 +        if (mappedSeq != null)
 +        {
 +          for (SequenceI seq2 : mapTo.getSequences())
 +          {
 +            if (seq2.getDatasetSequence() == mappedSeq)
 +            {
 +              mappedOrder.add(seq2);
 +              j++;
 +              break;
 +            }
 +          }
 +        }
 +      }
 +    }
 +
 +    /*
 +     * Return null if no mappings made.
 +     */
 +    if (j == 0)
 +    {
 +      return null;
 +    }
 +
 +    /*
 +     * Add any unmapped sequences on the end of the sort in their original
 +     * ordering.
 +     */
 +    if (j < mapTo.getHeight())
 +    {
 +      for (SequenceI seq : mapTo.getSequences())
 +      {
 +        if (!mappedOrder.contains(seq))
 +        {
 +          mappedOrder.add(seq);
 +        }
 +      }
 +    }
 +
 +    /*
 +     * Have to align the sequences before constructing the OrderCommand - which
 +     * then realigns them?!?
 +     */
 +    final SequenceI[] mappedOrderArray = mappedOrder
 +            .toArray(new SequenceI[mappedOrder.size()]);
 +    SequenceI[] oldOrder = mapTo.getSequencesArray();
 +    AlignmentSorter.sortBy(mapTo, new AlignmentOrder(mappedOrderArray));
 +    final OrderCommand result = new OrderCommand(command.getDescription(),
 +            oldOrder, mapTo);
 +    return result;
 +  }
 +
 +  /**
 +   * Returns a ColumnSelection in the 'mapTo' view which corresponds to the
 +   * given selection in the 'mapFrom' view. We assume one is nucleotide, the
 +   * other is protein (and holds the mappings from codons to protein residues).
 +   * 
 +   * @param colsel
 +   * @param mapFrom
 +   * @param mapTo
 +   * @return
 +   */
 +  public static ColumnSelection mapColumnSelection(ColumnSelection colsel,
 +          AlignViewportI mapFrom, AlignViewport mapTo)
 +  {
 +    boolean targetIsNucleotide = mapTo.isNucleotide();
 +    AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
 +    Set<AlignedCodonFrame> codonFrames = protein.getAlignment()
 +            .getCodonFrames();
 +    ColumnSelection mappedColumns = new ColumnSelection();
 +    char fromGapChar = mapFrom.getAlignment().getGapCharacter();
 +
-     for (int col : colsel.getSelected())
++    for (Object obj : colsel.getSelected())
 +    {
++      int col = ((Integer) obj).intValue();
 +      int mappedToMin = Integer.MAX_VALUE;
 +      int mappedToMax = Integer.MIN_VALUE;
 +
 +      /*
 +       * For each sequence in the 'from' alignment
 +       */
 +      for (SequenceI fromSeq : mapFrom.getAlignment().getSequences())
 +      {
 +        /*
 +         * Ignore gaps (unmapped anyway)
 +         */
 +        if (fromSeq.getCharAt(col) == fromGapChar)
 +        {
 +          continue;
 +        }
 +
 +        /*
 +         * Get the residue position and find the mapped position.
 +         */
 +        int residuePos = fromSeq.findPosition(col);
 +        SearchResults sr = buildSearchResults(fromSeq, residuePos,
 +                codonFrames);
 +        for (Match m : sr.getResults())
 +        {
 +          int mappedStartResidue = m.getStart();
 +          int mappedEndResidue = m.getEnd();
 +          SequenceI mappedSeq = m.getSequence();
 +
 +          /*
 +           * Locate the aligned sequence whose dataset is mappedSeq. TODO a
 +           * datamodel that can do this efficiently.
 +           */
 +          for (SequenceI toSeq : mapTo.getAlignment().getSequences())
 +          {
 +            if (toSeq.getDatasetSequence() == mappedSeq)
 +            {
 +              int mappedStartCol = toSeq.findIndex(mappedStartResidue);
 +              int mappedEndCol = toSeq.findIndex(mappedEndResidue);
 +              mappedToMin = Math.min(mappedToMin, mappedStartCol);
 +              mappedToMax = Math.max(mappedToMax, mappedEndCol);
 +              // System.out.println(fromSeq.getName() + " mapped to cols "
 +              // + mappedStartCol + ":" + mappedEndCol);
 +              break;
 +              // TODO remove break if we ever want to map one to many sequences
 +            }
 +          }
 +        }
 +      }
 +      /*
 +       * Add mapped columns to mapped selection (converting base 1 to base 0)
 +       */
 +      for (int i = mappedToMin; i <= mappedToMax; i++)
 +      {
 +        mappedColumns.addElement(i - 1);
 +      }
 +    }
 +    return mappedColumns;
 +  }
 +
 +}
@@@ -28,6 -29,6 +29,7 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.Annotation;
++import jalview.datamodel.CigarArray;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceCollectionI;
@@@ -1064,12 -1049,10 +1057,7 @@@ public abstract class AlignmentViewpor
              alignmentIndex);
    }
  
-   // Selection manipulation
-   /**
-    * broadcast selection to any interested parties
-    */
-   public abstract void sendSelection();
+   @Override
 -  public abstract void sendSelection();
 -
 -  @Override
    public void invertColumnSelection()
    {
      colSel.invertColumnSelection(0, alignment.getWidth());
      return sequences;
    }
  
-   /**
-    * This method returns the visible alignment as text, as seen on the GUI, ie
-    * if columns are hidden they will not be returned in the result. Use this for
-    * calculating trees, PCA, redundancy etc on views which contain hidden
-    * columns.
-    * 
-    * @return String[]
-    */
    @Override
--  public jalview.datamodel.CigarArray getViewAsCigars(
++  public CigarArray getViewAsCigars(
            boolean selectedRegionOnly)
    {
-     return new jalview.datamodel.CigarArray(alignment,
-             (hasHiddenColumns ? colSel : null),
 -    return new jalview.datamodel.CigarArray(alignment, colSel,
++    return new CigarArray(alignment, colSel,
              (selectedRegionOnly ? selectionGroup : null));
    }
  
      }
      oldrfs.clear();
    }
+   /**
+    * show the reference sequence in the alignment view
+    */
+   private boolean displayReferenceSeq=false;
+   /**
+    * colour according to the reference sequence defined on the alignment
+    */
+   private boolean colourByReferenceSeq=false;
+   @Override
+   public boolean isDisplayReferenceSeq()
+   {
+     return alignment.hasSeqrep() && displayReferenceSeq;
+   }
+   @Override
+   public void setDisplayReferenceSeq(boolean displayReferenceSeq)
+   {
+     this.displayReferenceSeq = displayReferenceSeq;
+   }
++  @Override
+   public boolean isColourByReferenceSeq()
+   {
+     return alignment.hasSeqrep() && colourByReferenceSeq;
+   }
++  @Override
+   public void setColourByReferenceSeq(boolean colourByReferenceSeq)
+   {
+     this.colourByReferenceSeq = colourByReferenceSeq;
+   }
  
    @Override
    public Color getSequenceColour(SequenceI seq)
    @Override
    public void clearSequenceColours()
    {
 -    sequenceColours = null;
 +    sequenceColours.clear();
    };
  
 +  @Override
 +  public AlignViewportI getCodingComplement()
 +  {
 +    return this.codingComplement;
 +  }
 +
 +  /**
 +   * Set this as the (cDna/protein) complement of the given viewport. Also
 +   * ensures the reverse relationship is set on the given viewport.
 +   */
 +  @Override
 +  public void setCodingComplement(AlignViewportI av)
 +  {
 +    if (this == av)
 +    {
 +      System.err.println("Ignoring recursive setCodingComplement request");
 +    }
 +    else
 +    {
 +      this.codingComplement = av;
 +      // avoid infinite recursion!
 +      if (av.getCodingComplement() != this)
 +      {
 +        av.setCodingComplement(this);
 +      }
 +    }
 +  }
 +
 +  @Override
 +  public boolean isNucleotide()
 +  {
 +    return getAlignment() == null ? false : getAlignment().isNucleotide();
 +  }
++
+   FeaturesDisplayedI featuresDisplayed = null;
+   @Override
+   public FeaturesDisplayedI getFeaturesDisplayed()
+   {
+     return featuresDisplayed;
+   }
+   @Override
+   public void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI)
+   {
+     featuresDisplayed = featuresDisplayedI;
+   }
+   @Override
+   public boolean areFeaturesDisplayed()
+   {
+     return featuresDisplayed != null && featuresDisplayed.getRegisterdFeaturesCount()>0;
+   }
+   /**
+    * display setting for showing/hiding sequence features on alignment view
+    */
+   boolean showSequenceFeatures = false;
+   /**
+    * set the flag
+    * 
+    * @param b
+    *          features are displayed if true
+    */
+   @Override
+   public void setShowSequenceFeatures(boolean b)
+   {
+     showSequenceFeatures = b;
+   }
+   @Override
+   public boolean isShowSequenceFeatures()
+   {
+     return showSequenceFeatures;
+   }
+   boolean showSeqFeaturesHeight;
+   @Override
+   public void setShowSequenceFeaturesHeight(boolean selected)
+   {
+     showSeqFeaturesHeight = selected;
+   }
+   @Override
+   public boolean isShowSequenceFeaturesHeight()
+   {
+     return showSeqFeaturesHeight;
+   }
+   private boolean showAnnotation = true;
+   private boolean rightAlignIds = false;
+   @Override
+   public void setShowAnnotation(boolean b)
+   {
+     showAnnotation = b;
+   }
+   @Override
+   public boolean isShowAnnotation()
+   {
+     return showAnnotation;
+   }
+   @Override
+   public boolean isRightAlignIds()
+   {
+     return rightAlignIds;
+   }
+   @Override
+   public void setRightAlignIds(boolean rightAlignIds)
+   {
+     this.rightAlignIds = rightAlignIds;
+   }
 -
  }
@@@ -27,12 -27,9 +27,12 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
- import jalview.gui.FeatureRenderer.FeatureRendererSettings;
  import jalview.gui.WebserviceInfo;
 -import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
  import jalview.util.MessageManager;
++import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 +
 +import java.util.LinkedHashSet;
 +import java.util.Set;
  
  public abstract class AWSThread extends Thread
  {
            } catch (Exception ex)
            {
              // Deal with Transaction exceptions
--            wsInfo.appendProgressText(jobs[j].jobnum, 
--                      MessageManager.formatMessage("info.server_exception", new String[]{WebServiceName,ex.getMessage()}));
++            wsInfo.appendProgressText(jobs[j].jobnum, MessageManager
++                    .formatMessage("info.server_exception", new Object[]
++                    { WebServiceName, ex.getMessage() }));
              // always output the exception's stack trace to the log
              Cache.log.warn(WebServiceName + " job(" + jobs[j].jobnum
                      + ") Server exception.");
      {
        Cache.log
                .debug("WebServiceJob poll loop finished with no jobs created.");
--      wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
++      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
        wsInfo.appendProgressText(MessageManager.getString("info.no_jobs_ran"));
        wsInfo.setFinishedNoResults();
      }