Merge branch 'features/JAL-845splitPaneMergeDevelop' into merge_JAL-845_JAL-1640
authorJim Procter <jprocter@dundee.ac.uk>
Mon, 9 Feb 2015 16:06:40 +0000 (16:06 +0000)
committerJim Procter <jprocter@dundee.ac.uk>
Mon, 9 Feb 2015 16:06:40 +0000 (16:06 +0000)
minimal conflicts where list accessors were used in methods where logic
also modified on Desktop

Conflicts:
.classpath
resources/lang/Messages.properties
src/jalview/api/AlignViewportI.java
src/jalview/gui/Desktop.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/TreeCanvas.java
src/jalview/viewmodel/AlignmentViewport.java

103 files changed:
.classpath
resources/lang/Messages.properties
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/analysis/AlignSeq.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/CodonComparator.java [new file with mode: 0644]
src/jalview/analysis/CrossRef.java
src/jalview/analysis/Dna.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/commands/EditCommand.java
src/jalview/commands/OrderCommand.java
src/jalview/datamodel/AlignedCodon.java [new file with mode: 0644]
src/jalview/datamodel/AlignedCodonFrame.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/AlignmentOrder.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/Mapping.java
src/jalview/datamodel/SearchResults.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/xdb/embl/EmblEntry.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/CutAndPasteHtmlTransfer.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PaintRefresher.java
src/jalview/gui/PairwiseAlignPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/RotatableCanvas.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/SplitFrame.java [new file with mode: 0644]
src/jalview/gui/TreeCanvas.java
src/jalview/gui/TreePanel.java
src/jalview/gui/VamsasApplication.java
src/jalview/gui/WsJobParameters.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/FileLoader.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/io/vamsas/Sequencemapping.java
src/jalview/io/vamsas/Tree.java
src/jalview/javascript/MouseOverListener.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GSplitFrame.java [new file with mode: 0644]
src/jalview/jbgui/GTreePanel.java
src/jalview/schemes/ResidueProperties.java
src/jalview/structure/AtomSpec.java [new file with mode: 0644]
src/jalview/structure/CommandListener.java [new file with mode: 0644]
src/jalview/structure/SequenceListener.java
src/jalview/structure/StructureListener.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/structure/VamsasListener.java
src/jalview/structures/models/AAStructureBindingModel.java
src/jalview/util/Comparison.java
src/jalview/util/MapList.java
src/jalview/util/MappingUtils.java [new file with mode: 0644]
src/jalview/util/ReverseListIterator.java [new file with mode: 0644]
src/jalview/util/ShiftList.java
src/jalview/util/StringUtils.java [new file with mode: 0644]
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/AWSThread.java
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/analysis/DnaAlignmentGenerator.java [new file with mode: 0644]
test/jalview/analysis/DnaTest.java [new file with mode: 0644]
test/jalview/analysis/DnaTranslation.java [deleted file]
test/jalview/analysis/TestAlignSeq.java
test/jalview/commands/EditCommandTest.java
test/jalview/datamodel/AlignedCodonFrameTest.java [new file with mode: 0644]
test/jalview/datamodel/AlignedCodonTest.java [new file with mode: 0644]
test/jalview/datamodel/AlignmentTest.java
test/jalview/datamodel/ColumnSelectionTest.java [new file with mode: 0644]
test/jalview/datamodel/SequenceTest.java
test/jalview/gui/JvSwingUtilsTest.java [new file with mode: 0644]
test/jalview/gui/PaintRefresherTest.java [new file with mode: 0644]
test/jalview/io/Jalview2xmlTests.java
test/jalview/util/ComparisonTest.java [new file with mode: 0644]
test/jalview/util/MapListTest.java [new file with mode: 0644]
test/jalview/util/ShiftListTest.java [new file with mode: 0644]
test/jalview/util/StringUtilsTest.java [new file with mode: 0644]

index c110217..6abc315 100644 (file)
@@ -51,7 +51,7 @@
        <classpathentry kind="lib" path="lib/VARNAv3-91.jar"/>
        <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
        <classpathentry kind="lib" path="lib/quaqua-filechooser-only-8.0.jar"/>
-       <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/xml-apis.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
index 2e37e06..bdf75df 100644 (file)
@@ -382,6 +382,7 @@ label.automatically_associate_pdb_files_with_sequences_same_name = Do you want t
 label.automatically_associate_pdb_files_by_name = Automatically Associate PDB files by name
 label.ignore_unmatched_dropped_files_info = <html>Do you want to <em>ignore</em> the {0} files whose names did not match any sequence IDs ?</html>
 label.ignore_unmatched_dropped_files = Ignore unmatched dropped files?
+label.view_name_original = Original
 label.enter_view_name = Enter View Name
 label.enter_label = Enter label
 label.enter_label_for_the_structure = Enter a label for the structure?
@@ -690,11 +691,25 @@ label.load_tree_for_sequence_set = Load a tree for this sequence set
 label.export_image = Export Image
 label.vamsas_store = VAMSAS store
 label.translate_cDNA = Translate cDNA
+label.cdna = cDNA
+label.link_cdna = Link cDNA
+label.link_cdna_tip = Link to any compatible cDNA alignments.<br>Sequences are linked that have the same name and compatible lengths.
+label.no_cdna = No compatible cDNA was found
+label.linked_cdna = {0} cDNA alignments linked
+label.cdna_all_linked = All {0} compatible cDNA alignments are already linked
+label.align_cdna = Align linked cDNA
+label.align_cdna_tip = Any linked cDNA sequences will be realigned to match this alignment.
+label.cdna_aligned = {0} sequences in {1} alignments were realigned
+label.view_as_cdna = Show aligned cDNA
+label.view_as_cdna_tip = Open a new alignment of the related cDNA sequences
+label.linked_view_title = {0} and {1}
+label.align = Align
 label.extract_scores = Extract Scores
 label.get_cross_refs = Get Cross References
 label.sort_alignment_new_tree = Sort Alignment With New Tree
 label.add_sequences = Add Sequences
 label.new_window = New Window
+label.split_window = Split Window
 label.refresh_available_sources = Refresh Available Sources
 label.use_registry = Use Registry
 label.add_local_source = Add Local Source
@@ -734,6 +749,7 @@ label.paste_new_window = Paste To New Window
 label.settings_for_param = Settings for {0}
 label.view_params = View {0}
 label.select_all_views = Select all views
+label.all_views = All Views
 label.align_sequences_to_existing_alignment = Align sequences to an existing alignment
 label.realign_with_params = Realign with {0}
 label.calcname_with_default_settings = {0} with Defaults
@@ -1178,6 +1194,12 @@ label.show_logo = Show Logo
 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
@@ -1192,3 +1214,4 @@ label.search_filter = Search Filter
 label.display_name = Display Label
 label.description = Description
 label.include_description= Include Description
+
index 9b0412a..1938d9a 100644 (file)
  */
 package MCview;
 
-import java.io.*;
-import java.util.*;
+import jalview.analysis.AlignSeq;
+import jalview.appletgui.AlignmentPanel;
+import jalview.appletgui.FeatureRenderer;
+import jalview.appletgui.SequenceRenderer;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.structure.AtomSpec;
+import jalview.structure.StructureListener;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.MessageManager;
 
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Event;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Image;
 // JBPNote TODO: This class is quite noisy - needs proper log.info/log.debug
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.analysis.*;
-import jalview.datamodel.*;
-
-import jalview.appletgui.*;
-import jalview.structure.*;
-import jalview.util.MessageManager;
+import java.awt.Panel;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Vector;
 
 public class AppletPDBCanvas extends Panel implements MouseListener,
         MouseMotionListener, StructureListener
@@ -144,7 +159,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol);
 
       if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
+      {
         pdbentry.setFile("INLINE" + pdb.id);
+      }
 
     } catch (Exception ex)
     {
@@ -172,10 +189,10 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
     {
 
       mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
-              + ((PDBChain) pdb.chains.elementAt(i)).sequence
+              + pdb.chains.elementAt(i).sequence
                       .getSequenceAsString());
       mappingDetails.append("\nNo of residues = "
-              + ((PDBChain) pdb.chains.elementAt(i)).residues.size()
+              + pdb.chains.elementAt(i).residues.size()
               + "\n\n");
 
       // Now lets compare the sequences to get
@@ -183,8 +200,8 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       // Align the sequence to the pdb
       // TODO: DNa/Pep switch
       AlignSeq as = new AlignSeq(sequence,
-              ((PDBChain) pdb.chains.elementAt(i)).sequence,
-              ((PDBChain) pdb.chains.elementAt(i)).isNa ? AlignSeq.DNA
+              pdb.chains.elementAt(i).sequence,
+              pdb.chains.elementAt(i).isNa ? AlignSeq.DNA
                       : AlignSeq.PEP);
       as.calcScoreMatrix();
       as.traceAlignment();
@@ -218,7 +235,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       mappingDetails.append("\nSEQ start/end " + seqstart + " " + seqend);
     }
 
-    mainchain = (PDBChain) pdb.chains.elementAt(maxchain);
+    mainchain = pdb.chains.elementAt(maxchain);
 
     mainchain.pdbstart = pdbstart;
     mainchain.pdbend = pdbend;
@@ -277,9 +294,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      if (((PDBChain) pdb.chains.elementAt(ii)).isVisible)
+      if (pdb.chains.elementAt(ii).isVisible)
       {
-        Vector tmp = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector tmp = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < tmp.size(); i++)
         {
@@ -308,9 +325,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      if (((PDBChain) pdb.chains.elementAt(ii)).isVisible)
+      if (pdb.chains.elementAt(ii).isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < bonds.size(); i++)
         {
@@ -379,9 +396,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       }
     }
 
-    width[0] = (float) Math.abs(max[0] - min[0]);
-    width[1] = (float) Math.abs(max[1] - min[1]);
-    width[2] = (float) Math.abs(max[2] - min[2]);
+    width[0] = Math.abs(max[0] - min[0]);
+    width[1] = Math.abs(max[1] - min[1]);
+    width[2] = Math.abs(max[2] - min[2]);
 
     maxwidth = width[0];
 
@@ -438,9 +455,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
     // Find centre coordinate
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      if (((PDBChain) pdb.chains.elementAt(ii)).isVisible)
+      if (pdb.chains.elementAt(ii).isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         bsize += bonds.size();
 
@@ -567,7 +584,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
     {
       for (int ii = 0; ii < pdb.chains.size(); ii++)
       {
-        chain = (PDBChain) pdb.chains.elementAt(ii);
+        chain = pdb.chains.elementAt(ii);
 
         for (int i = 0; i < chain.bonds.size(); i++)
         {
@@ -779,7 +796,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       repaint();
       if (foundchain != -1)
       {
-        PDBChain chain = (PDBChain) pdb.chains.elementAt(foundchain);
+        PDBChain chain = pdb.chains.elementAt(foundchain);
         if (chain == mainchain)
         {
           if (fatom.alignmentMapping != -1)
@@ -825,7 +842,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
     PDBChain chain = null;
     if (foundchain != -1)
     {
-      chain = (PDBChain) pdb.chains.elementAt(foundchain);
+      chain = pdb.chains.elementAt(foundchain);
       if (chain == mainchain)
       {
         mouseOverStructure(fatom.resNumber, chain.id);
@@ -875,18 +892,18 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
     if ((evt.getModifiers() & Event.META_MASK) != 0)
     {
-      objmat.rotatez((float) ((mx - omx)));
+      objmat.rotatez(((mx - omx)));
     }
     else
     {
-      objmat.rotatex((float) ((omy - my)));
-      objmat.rotatey((float) ((omx - mx)));
+      objmat.rotatex(((omy - my)));
+      objmat.rotatey(((omx - mx)));
     }
 
     // Alter the bonds
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+      Vector bonds = pdb.chains.elementAt(ii).bonds;
 
       for (int i = 0; i < bonds.size(); i++)
       {
@@ -927,11 +944,11 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      PDBChain chain = (PDBChain) pdb.chains.elementAt(ii);
+      PDBChain chain = pdb.chains.elementAt(ii);
 
       if (chain.isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < bonds.size(); i++)
         {
@@ -985,13 +1002,13 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      PDBChain chain = (PDBChain) pdb.chains.elementAt(ii);
+      PDBChain chain = pdb.chains.elementAt(ii);
       int truex;
       Bond tmpBond = null;
 
       if (chain.isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < bonds.size(); i++)
         {
@@ -1032,7 +1049,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
       if (fatom != null) // )&& chain.ds != null)
       {
-        chain = (PDBChain) pdb.chains.elementAt(foundchain);
+        chain = pdb.chains.elementAt(foundchain);
       }
     }
 
@@ -1100,7 +1117,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
   {
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      PDBChain chain = (PDBChain) pdb.chains.elementAt(ii);
+      PDBChain chain = pdb.chains.elementAt(ii);
       chain.isVisible = b;
     }
     mainchain.isVisible = true;
@@ -1121,7 +1138,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
   public void mouseOverStructure(int pdbResNum, String chain)
   {
     if (lastMessage == null || !lastMessage.equals(pdbResNum + chain))
+    {
       ssm.mouseOverStructure(pdbResNum, chain, pdbentry.getFile());
+    }
 
     lastMessage = pdbResNum + chain;
   }
@@ -1130,19 +1149,40 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
   StringBuffer eval = new StringBuffer();
 
-  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
-          String pdbfile)
+  /**
+   * Highlight the specified atoms in the structure.
+   * 
+   * @param atoms
+   */
+  @Override
+  public void highlightAtoms(List<AtomSpec> atoms)
   {
     if (!seqColoursReady)
     {
       return;
     }
-
-    if (highlightRes != null && highlightRes.contains((atomIndex - 1) + ""))
+    for (AtomSpec atom : atoms)
     {
-      return;
+      int atomIndex = atom.getAtomIndex();
+
+      if (highlightRes != null
+              && highlightRes.contains((atomIndex - 1) + ""))
+      {
+        continue;
+      }
+
+      highlightAtom(atomIndex);
     }
 
+    redrawneeded = true;
+    repaint();
+  }
+
+  /**
+   * @param atomIndex
+   */
+  protected void highlightAtom(int atomIndex)
+  {
     int index = -1;
     Bond tmpBond;
     for (index = 0; index < mainchain.bonds.size(); index++)
@@ -1178,9 +1218,6 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
         break;
       }
     }
-
-    redrawneeded = true;
-    repaint();
   }
 
   public Color getColour(int atomIndex, int pdbResNum, String chain,
index 4fd7a35..f189f0a 100644 (file)
  */
 package MCview;
 
-import java.io.*;
-import java.util.*;
-
+import jalview.analysis.AlignSeq;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.FeatureRenderer;
+import jalview.gui.SequenceRenderer;
+import jalview.structure.AtomSpec;
+import jalview.structure.StructureListener;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureSelectionManager;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Event;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
 // JBPNote TODO: This class is quite noisy - needs proper log.info/log.debug
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-
-import jalview.analysis.*;
-import jalview.datamodel.*;
-import jalview.gui.*;
-import jalview.structure.*;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+import javax.swing.ToolTipManager;
 
 public class PDBCanvas extends JPanel implements MouseListener,
         MouseMotionListener, StructureListener
@@ -134,7 +153,9 @@ public class PDBCanvas extends JPanel implements MouseListener,
       pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol);
 
       if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
+      {
         pdbentry.setFile("INLINE" + pdb.id);
+      }
 
     } catch (Exception ex)
     {
@@ -167,17 +188,17 @@ public class PDBCanvas extends JPanel implements MouseListener,
     {
 
       mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
-              + ((PDBChain) pdb.chains.elementAt(i)).sequence
+              + pdb.chains.elementAt(i).sequence
                       .getSequenceAsString());
       mappingDetails.append("\nNo of residues = "
-              + ((PDBChain) pdb.chains.elementAt(i)).residues.size()
+              + pdb.chains.elementAt(i).residues.size()
               + "\n\n");
 
       // Now lets compare the sequences to get
       // the start and end points.
       // Align the sequence to the pdb
       AlignSeq as = new AlignSeq(sequence,
-              ((PDBChain) pdb.chains.elementAt(i)).sequence, "pep");
+              pdb.chains.elementAt(i).sequence, "pep");
       as.calcScoreMatrix();
       as.traceAlignment();
       PrintStream ps = new PrintStream(System.out)
@@ -210,7 +231,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
       mappingDetails.append("\nSEQ start/end " + seqstart + " " + seqend);
     }
 
-    mainchain = (PDBChain) pdb.chains.elementAt(maxchain);
+    mainchain = pdb.chains.elementAt(maxchain);
 
     mainchain.pdbstart = pdbstart;
     mainchain.pdbend = pdbend;
@@ -254,9 +275,9 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      if (((PDBChain) pdb.chains.elementAt(ii)).isVisible)
+      if (pdb.chains.elementAt(ii).isVisible)
       {
-        Vector tmp = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector tmp = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < tmp.size(); i++)
         {
@@ -286,9 +307,9 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      if (((PDBChain) pdb.chains.elementAt(ii)).isVisible)
+      if (pdb.chains.elementAt(ii).isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < bonds.size(); i++)
         {
@@ -362,9 +383,9 @@ public class PDBCanvas extends JPanel implements MouseListener,
      * System.out.println("zmax " + max[2] + " min " + min[2]);
      */
 
-    width[0] = (float) Math.abs(max[0] - min[0]);
-    width[1] = (float) Math.abs(max[1] - min[1]);
-    width[2] = (float) Math.abs(max[2] - min[2]);
+    width[0] = Math.abs(max[0] - min[0]);
+    width[1] = Math.abs(max[1] - min[1]);
+    width[2] = Math.abs(max[2] - min[2]);
 
     maxwidth = width[0];
 
@@ -421,9 +442,9 @@ public class PDBCanvas extends JPanel implements MouseListener,
     // Find centre coordinate
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      if (((PDBChain) pdb.chains.elementAt(ii)).isVisible)
+      if (pdb.chains.elementAt(ii).isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         bsize += bonds.size();
 
@@ -537,7 +558,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
     {
       for (int ii = 0; ii < pdb.chains.size(); ii++)
       {
-        chain = (PDBChain) pdb.chains.elementAt(ii);
+        chain = pdb.chains.elementAt(ii);
 
         for (int i = 0; i < chain.bonds.size(); i++)
         {
@@ -751,7 +772,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
       repaint();
       if (foundchain != -1)
       {
-        PDBChain chain = (PDBChain) pdb.chains.elementAt(foundchain);
+        PDBChain chain = pdb.chains.elementAt(foundchain);
         if (chain == mainchain)
         {
           if (fatom.alignmentMapping != -1)
@@ -797,7 +818,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
     PDBChain chain = null;
     if (foundchain != -1)
     {
-      chain = (PDBChain) pdb.chains.elementAt(foundchain);
+      chain = pdb.chains.elementAt(foundchain);
       if (chain == mainchain)
       {
         mouseOverStructure(fatom.resNumber, chain.id);
@@ -840,18 +861,18 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
     if ((evt.getModifiers() & Event.META_MASK) != 0)
     {
-      objmat.rotatez((float) ((mx - omx)));
+      objmat.rotatez(((mx - omx)));
     }
     else
     {
-      objmat.rotatex((float) ((my - omy)));
-      objmat.rotatey((float) ((omx - mx)));
+      objmat.rotatex(((my - omy)));
+      objmat.rotatey(((omx - mx)));
     }
 
     // Alter the bonds
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+      Vector bonds = pdb.chains.elementAt(ii).bonds;
 
       for (int i = 0; i < bonds.size(); i++)
       {
@@ -892,11 +913,11 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      PDBChain chain = (PDBChain) pdb.chains.elementAt(ii);
+      PDBChain chain = pdb.chains.elementAt(ii);
 
       if (chain.isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < bonds.size(); i++)
         {
@@ -948,13 +969,13 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      PDBChain chain = (PDBChain) pdb.chains.elementAt(ii);
+      PDBChain chain = pdb.chains.elementAt(ii);
       int truex;
       Bond tmpBond = null;
 
       if (chain.isVisible)
       {
-        Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds;
+        Vector bonds = pdb.chains.elementAt(ii).bonds;
 
         for (int i = 0; i < bonds.size(); i++)
         {
@@ -995,7 +1016,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
       if (fatom != null) // )&& chain.ds != null)
       {
-        chain = (PDBChain) pdb.chains.elementAt(foundchain);
+        chain = pdb.chains.elementAt(foundchain);
       }
     }
 
@@ -1060,7 +1081,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
   {
     for (int ii = 0; ii < pdb.chains.size(); ii++)
     {
-      PDBChain chain = (PDBChain) pdb.chains.elementAt(ii);
+      PDBChain chain = pdb.chains.elementAt(ii);
       chain.isVisible = b;
     }
     mainchain.isVisible = true;
@@ -1081,7 +1102,9 @@ public class PDBCanvas extends JPanel implements MouseListener,
   public void mouseOverStructure(int pdbResNum, String chain)
   {
     if (lastMessage == null || !lastMessage.equals(pdbResNum + chain))
+    {
       ssm.mouseOverStructure(pdbResNum, chain, pdbentry.getFile());
+    }
 
     lastMessage = pdbResNum + chain;
   }
@@ -1090,19 +1113,42 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
   StringBuffer eval = new StringBuffer();
 
-  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
-          String pdbfile)
+  /**
+   * Highlight the specified atoms in the structure.
+   * 
+   * @param atoms
+   */
+  @Override
+  public void highlightAtoms(List<AtomSpec> atoms)
   {
     if (!seqColoursReady)
     {
       return;
     }
 
-    if (highlightRes != null && highlightRes.contains((atomIndex - 1) + ""))
+    for (AtomSpec atom : atoms)
     {
-      return;
+      int atomIndex = atom.getAtomIndex();
+      if (highlightRes != null
+              && highlightRes.contains((atomIndex - 1) + ""))
+      {
+        continue;
+      }
+
+      highlightAtom(atomIndex);
     }
 
+    redrawneeded = true;
+    repaint();
+  }
+
+  /**
+   * Highlight the atom at the specified index.
+   * 
+   * @param atomIndex
+   */
+  protected void highlightAtom(int atomIndex)
+  {
     int index = -1;
     Bond tmpBond;
     for (index = 0; index < mainchain.bonds.size(); index++)
@@ -1138,9 +1184,6 @@ public class PDBCanvas extends JPanel implements MouseListener,
         break;
       }
     }
-
-    redrawneeded = true;
-    repaint();
   }
 
   public Color getColour(int atomIndex, int pdbResNum, String chain,
index 2ef1c76..cd548b3 100755 (executable)
@@ -804,19 +804,23 @@ public class AlignSeq
   }
 
   /**
-   * DOCUMENT ME!
+   * Returns the given sequence with all of the given gap characters removed.
    * 
-   * @param gapChar
-   *          DOCUMENT ME!
+   * @param gapChars
+   *          a string of characters to be treated as gaps
    * @param seq
-   *          DOCUMENT ME!
+   *          the input sequence
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
-  public static String extractGaps(String gapChar, String seq)
+  public static String extractGaps(String gapChars, String seq)
   {
-    StringTokenizer str = new StringTokenizer(seq, gapChar);
-    StringBuffer newString = new StringBuffer();
+    if (gapChars == null || seq == null)
+    {
+      return null;
+    }
+    StringTokenizer str = new StringTokenizer(seq, gapChars);
+    StringBuilder newString = new StringBuilder(seq.length());
 
     while (str.hasMoreTokens())
     {
index b7cfbbd..4f52741 100755 (executable)
  */
 package jalview.analysis;
 
-import java.util.*;
-
-import jalview.datamodel.*;
-import jalview.util.*;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceNode;
+import jalview.util.Comparison;
+import jalview.util.MessageManager;
+import jalview.util.QuickSort;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Routines for manipulating the order of a multiple sequence alignment TODO:
@@ -169,7 +178,7 @@ public class AlignmentSorter
    * @param tmp
    *          sequences as a vector
    */
-  private static void setOrder(AlignmentI align, Vector tmp)
+  private static void setOrder(AlignmentI align, List<SequenceI> tmp)
   {
     setOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
   }
@@ -285,7 +294,7 @@ public class AlignmentSorter
   {
     // MAINTAINS ORIGNAL SEQUENCE ORDER,
     // ORDERS BY GROUP SIZE
-    Vector groups = new Vector();
+    List<SequenceGroup> groups = new ArrayList<SequenceGroup>();
 
     if (groups.hashCode() != lastGroupHash)
     {
@@ -303,11 +312,11 @@ public class AlignmentSorter
     {
       for (int j = 0; j < groups.size(); j++)
       {
-        SequenceGroup sg2 = (SequenceGroup) groups.elementAt(j);
+        SequenceGroup sg2 = groups.get(j);
 
         if (sg.getSize() > sg2.getSize())
         {
-          groups.insertElementAt(sg, j);
+          groups.add(j, sg);
 
           break;
         }
@@ -315,22 +324,22 @@ public class AlignmentSorter
 
       if (!groups.contains(sg))
       {
-        groups.addElement(sg);
+        groups.add(sg);
       }
     }
 
     // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
     // /////////////////////////////////////////////
-    Vector seqs = new Vector();
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
 
     for (int i = 0; i < groups.size(); i++)
     {
-      SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
+      SequenceGroup sg = groups.get(i);
       SequenceI[] orderedseqs = sg.getSequencesInOrder(align);
 
       for (int j = 0; j < orderedseqs.length; j++)
       {
-        seqs.addElement(orderedseqs[j]);
+        seqs.add(orderedseqs[j]);
       }
     }
 
@@ -346,28 +355,8 @@ public class AlignmentSorter
   }
 
   /**
-   * Converts Vector to array. java 1.18 does not have Vector.toArray()
-   * 
-   * @param tmp
-   *          Vector of SequenceI objects
-   * 
-   * @return array of Sequence[]
-   */
-  private static SequenceI[] vectorToArray(Vector tmp)
-  {
-    SequenceI[] seqs = new SequenceI[tmp.size()];
-
-    for (int i = 0; i < tmp.size(); i++)
-    {
-      seqs[i] = (SequenceI) tmp.elementAt(i);
-    }
-
-    return seqs;
-  }
-
-  /**
    * Select sequences in order from tmp that is present in mask, and any
-   * remaining seqeunces in mask not in tmp
+   * remaining sequences in mask not in tmp
    * 
    * @param tmp
    *          thread safe collection of sequences
@@ -379,6 +368,10 @@ public class AlignmentSorter
   private static SequenceI[] vectorSubsetToArray(List<SequenceI> tmp,
           List<SequenceI> mask)
   {
+    // or?
+    // tmp2 = tmp.retainAll(mask);
+    // return tmp2.addAll(mask.removeAll(tmp2))
+
     ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
     int i, idx;
     boolean[] tmask = new boolean[mask.size()];
@@ -421,7 +414,7 @@ public class AlignmentSorter
   public static void sortBy(AlignmentI align, AlignmentOrder order)
   {
     // Get an ordered vector of sequences which may also be present in align
-    Vector tmp = order.getOrder();
+    List<SequenceI> tmp = order.getOrder();
 
     if (lastOrder == order)
     {
@@ -452,11 +445,12 @@ public class AlignmentSorter
    * 
    * @return DOCUMENT ME!
    */
-  private static Vector getOrderByTree(AlignmentI align, NJTree tree)
+  private static List<SequenceI> getOrderByTree(AlignmentI align,
+          NJTree tree)
   {
     int nSeq = align.getHeight();
 
-    Vector tmp = new Vector();
+    List<SequenceI> tmp = new ArrayList<SequenceI>();
 
     tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
 
@@ -494,7 +488,7 @@ public class AlignmentSorter
    */
   public static void sortByTree(AlignmentI align, NJTree tree)
   {
-    Vector tmp = getOrderByTree(align, tree);
+    List<SequenceI> tmp = getOrderByTree(align, tree);
 
     // tmp should properly permute align with tree.
     if (lastTree != tree)
@@ -522,22 +516,22 @@ public class AlignmentSorter
    * 
    * @param align
    *          DOCUMENT ME!
-   * @param seqs
+   * @param tmp
    *          DOCUMENT ME!
    */
-  private static void addStrays(AlignmentI align, Vector seqs)
+  private static void addStrays(AlignmentI align, List<SequenceI> tmp)
   {
     int nSeq = align.getHeight();
 
     for (int i = 0; i < nSeq; i++)
     {
-      if (!seqs.contains(align.getSequenceAt(i)))
+      if (!tmp.contains(align.getSequenceAt(i)))
       {
-        seqs.addElement(align.getSequenceAt(i));
+        tmp.add(align.getSequenceAt(i));
       }
     }
 
-    if (nSeq != seqs.size())
+    if (nSeq != tmp.size())
     {
       System.err
               .println("ERROR: Size still not right even after addStrays");
@@ -556,7 +550,8 @@ public class AlignmentSorter
    * 
    * @return DOCUMENT ME!
    */
-  private static Vector _sortByTree(SequenceNode node, Vector tmp,
+  private static List<SequenceI> _sortByTree(SequenceNode node,
+          List<SequenceI> tmp,
           List<SequenceI> seqset)
   {
     if (node == null)
@@ -577,7 +572,7 @@ public class AlignmentSorter
                                              // seqset.size()==0 ||
                                              // seqset.contains(tmp)))
           {
-            tmp.addElement(node.element());
+            tmp.add((SequenceI) node.element());
           }
         }
       }
index 6385fa7..7116af9 100644 (file)
  */
 package jalview.analysis;
 
+import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
+import jalview.schemes.ResidueProperties;
+import jalview.util.MapList;
 
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * grab bag of useful alignment manipulation operations Expect these to be
@@ -38,6 +44,15 @@ public class AlignmentUtils
 {
 
   /**
+   * Represents the 3 possible results of trying to map one alignment to
+   * another.
+   */
+  public enum MappingResult
+  {
+    Mapped, NotMapped, AlreadyMapped
+  }
+
+  /**
    * given an existing alignment, create a new alignment including all, or up to
    * flankSize additional symbols from each sequence's dataset sequence
    * 
@@ -159,4 +174,456 @@ public class AlignmentUtils
     }
     return result;
   }
+
+  /**
+   * Returns a map of lists of sequences in the alignment, keyed by sequence
+   * name. For use in mapping between different alignment views of the same
+   * sequences.
+   * 
+   * @see jalview.datamodel.AlignmentI#getSequencesByName()
+   */
+  public static Map<String, List<SequenceI>> getSequencesByName(
+          AlignmentI al)
+  {
+    Map<String, List<SequenceI>> theMap = new LinkedHashMap<String, List<SequenceI>>();
+    for (SequenceI seq : al.getSequences())
+    {
+      String name = seq.getName();
+      if (name != null)
+      {
+        List<SequenceI> seqs = theMap.get(name);
+        if (seqs == null)
+        {
+          seqs = new ArrayList<SequenceI>();
+          theMap.put(name, seqs);
+        }
+        seqs.add(seq);
+      }
+    }
+    return theMap;
+  }
+
+  /**
+   * Build mapping of protein to cDNA alignment. Mappings are made between
+   * sequences which have the same name and compatible lengths. Has a 3-valued
+   * result: either Mapped (at least one sequence mapping was created),
+   * AlreadyMapped (all possible sequence mappings already exist), or NotMapped
+   * (no possible sequence mappings exist).
+   * 
+   * @param proteinAlignment
+   * @param cdnaAlignment
+   * @return
+   */
+  public static MappingResult mapProteinToCdna(
+          final AlignmentI proteinAlignment,
+          final AlignmentI cdnaAlignment)
+  {
+    boolean mappingPossible = false;
+    boolean mappingPerformed = false;
+
+    List<SequenceI> thisSeqs = proteinAlignment.getSequences();
+  
+    /*
+     * Build a look-up of cDNA sequences by name, for matching purposes.
+     */
+    Map<String, List<SequenceI>> cdnaSeqs = cdnaAlignment
+            .getSequencesByName();
+  
+    for (SequenceI aaSeq : thisSeqs)
+    {
+      AlignedCodonFrame acf = new AlignedCodonFrame();
+      List<SequenceI> candidates = cdnaSeqs.get(aaSeq.getName());
+      if (candidates == null)
+      {
+        /*
+         * No cDNA sequence with matching name, so no mapping possible for this
+         * protein sequence
+         */
+        continue;
+      }
+      mappingPossible = true;
+      for (SequenceI cdnaSeq : candidates)
+      {
+        if (!mappingExists(proteinAlignment.getCodonFrames(),
+                aaSeq.getDatasetSequence(), cdnaSeq.getDatasetSequence()))
+        {
+          MapList map = mapProteinToCdna(aaSeq, cdnaSeq);
+          if (map != null)
+          {
+            acf.addMap(cdnaSeq, aaSeq, map);
+            mappingPerformed = true;
+          }
+        }
+      }
+      proteinAlignment.addCodonFrame(acf);
+    }
+
+    /*
+     * If at least one mapping was possible but none was done, then the
+     * alignments are already as mapped as they can be.
+     */
+    if (mappingPossible && !mappingPerformed)
+    {
+      return MappingResult.AlreadyMapped;
+    }
+    else
+    {
+      return mappingPerformed ? MappingResult.Mapped
+              : MappingResult.NotMapped;
+    }
+  }
+
+  /**
+   * Answers true if the mappings include one between the given (dataset)
+   * sequences.
+   */
+  public static boolean mappingExists(Set<AlignedCodonFrame> set,
+          SequenceI aaSeq, SequenceI cdnaSeq)
+  {
+    if (set != null)
+    {
+      for (AlignedCodonFrame acf : set)
+      {
+        if (cdnaSeq == acf.getDnaForAaSeq(aaSeq))
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Build a mapping (if possible) of a protein to a cDNA sequence. The cDNA
+   * must be three times the length of the protein, possibly after ignoring
+   * start and/or stop codons. Returns null if no mapping is determined.
+   * 
+   * @param proteinSeqs
+   * @param cdnaSeq
+   * @return
+   */
+  public static MapList mapProteinToCdna(SequenceI proteinSeq,
+          SequenceI cdnaSeq)
+  {
+    String aaSeqString = proteinSeq.getDatasetSequence()
+            .getSequenceAsString();
+    String cdnaSeqString = cdnaSeq.getDatasetSequence()
+            .getSequenceAsString();
+    if (aaSeqString == null || cdnaSeqString == null)
+    {
+      return null;
+    }
+
+    final int mappedLength = 3 * aaSeqString.length();
+    int cdnaLength = cdnaSeqString.length();
+    int cdnaStart = 1;
+    int cdnaEnd = cdnaLength;
+    final int proteinStart = 1;
+    final int proteinEnd = aaSeqString.length();
+
+    /*
+     * If lengths don't match, try ignoring stop codon.
+     */
+    if (cdnaLength != mappedLength)
+    {
+      for (Object stop : ResidueProperties.STOP)
+      {
+        if (cdnaSeqString.toUpperCase().endsWith((String) stop))
+        {
+          cdnaEnd -= 3;
+          cdnaLength -= 3;
+          break;
+        }
+      }
+    }
+
+    /*
+     * If lengths still don't match, try ignoring start codon.
+     */
+    if (cdnaLength != mappedLength
+            && cdnaSeqString.toUpperCase().startsWith(
+                    ResidueProperties.START))
+    {
+      cdnaStart += 3;
+      cdnaLength -= 3;
+    }
+
+    if (cdnaLength == mappedLength)
+    {
+      MapList map = new MapList(new int[]
+      { cdnaStart, cdnaEnd }, new int[]
+      { proteinStart, proteinEnd }, 3, 1);
+      return map;
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+  /**
+   * Align sequence 'seq' to match the alignment of a mapped sequence. Note this
+   * currently assumes that we are aligning cDNA to match protein.
+   * 
+   * @param seq
+   *          the sequence to be realigned
+   * @param al
+   *          the alignment whose sequence alignment is to be 'copied'
+   * @param gap
+   *          character string represent a gap in the realigned sequence
+   * @param preserveUnmappedGaps
+   * @param preserveMappedGaps
+   * @return true if the sequence was realigned, false if it could not be
+   */
+  public static boolean alignSequenceAs(SequenceI seq, AlignmentI al,
+          String gap, boolean preserveMappedGaps,
+          boolean preserveUnmappedGaps)
+  {
+    /*
+     * Get any mappings from the source alignment to the target (dataset) sequence.
+     */
+    // TODO there may be one AlignedCodonFrame per dataset sequence, or one with
+    // all mappings. Would it help to constrain this?
+    List<AlignedCodonFrame> mappings = al.getCodonFrame(seq);
+    if (mappings == null)
+    {
+      return false;
+    }
+  
+    /*
+     * Locate the aligned source sequence whose dataset sequence is mapped. We
+     * just take the first match here (as we can't align cDNA like more than one
+     * protein sequence).
+     */
+    SequenceI alignFrom = null;
+    AlignedCodonFrame mapping = null;
+    for (AlignedCodonFrame mp : mappings)
+    {
+      alignFrom = mp.findAlignedSequence(seq.getDatasetSequence(), al);
+      if (alignFrom != null)
+      {
+        mapping = mp;
+        break;
+      }
+    }
+  
+    if (alignFrom == null)
+    {
+      return false;
+    }
+    alignSequenceAs(seq, alignFrom, mapping, gap, al.getGapCharacter(),
+            preserveMappedGaps, preserveUnmappedGaps);
+    return true;
+  }
+
+  /**
+   * Align sequence 'alignTo' the same way as 'alignFrom', using the mapping to
+   * match residues and codons. Flags control whether existing gaps in unmapped
+   * (intron) and mapped (exon) regions are preserved or not. Gaps linking intro
+   * and exon are only retained if both flags are set.
+   * 
+   * @param alignTo
+   * @param alignFrom
+   * @param mapping
+   * @param myGap
+   * @param sourceGap
+   * @param preserveUnmappedGaps
+   * @param preserveMappedGaps
+   */
+  public static void alignSequenceAs(SequenceI alignTo,
+          SequenceI alignFrom,
+          AlignedCodonFrame mapping, String myGap, char sourceGap,
+          boolean preserveMappedGaps, boolean preserveUnmappedGaps)
+  {
+    // TODO generalise to work for Protein-Protein, dna-dna, dna-protein
+    final char[] thisSeq = alignTo.getSequence();
+    final char[] thatAligned = alignFrom.getSequence();
+    StringBuilder thisAligned = new StringBuilder(2 * thisSeq.length);
+  
+    // aligned and dataset sequence positions, all base zero
+    int thisSeqPos = 0;
+    int sourceDsPos = 0;
+
+    int basesWritten = 0;
+    char myGapChar = myGap.charAt(0);
+    int ratio = myGap.length();
+
+    /*
+     * Traverse the aligned protein sequence.
+     */
+    int sourceGapMappedLength = 0;
+    boolean inExon = false;
+    for (char sourceChar : thatAligned)
+    {
+      if (sourceChar == sourceGap)
+      {
+        sourceGapMappedLength += ratio;
+        continue;
+      }
+
+      /*
+       * Found a residue. Locate its mapped codon (start) position.
+       */
+      sourceDsPos++;
+      // Note mapping positions are base 1, our sequence positions base 0
+      int[] mappedPos = mapping.getMappedRegion(alignTo, alignFrom,
+              sourceDsPos);
+      if (mappedPos == null)
+      {
+        /*
+         * Abort realignment if unmapped protein. Or could ignore it??
+         */
+        System.err.println("Can't align: no codon mapping to residue "
+                + sourceDsPos + "(" + sourceChar + ")");
+        return;
+      }
+
+      int mappedCodonStart = mappedPos[0]; // position (1...) of codon start
+      int mappedCodonEnd = mappedPos[mappedPos.length - 1]; // codon end pos
+      StringBuilder trailingCopiedGap = new StringBuilder();
+
+      /*
+       * Copy dna sequence up to and including this codon. Optionally, include
+       * gaps before the codon starts (in introns) and/or after the codon starts
+       * (in exons).
+       * 
+       * Note this only works for 'linear' splicing, not reverse or interleaved.
+       * But then 'align dna as protein' doesn't make much sense otherwise.
+       */
+      int intronLength = 0;
+      while (basesWritten < mappedCodonEnd && thisSeqPos < thisSeq.length)
+      {
+        final char c = thisSeq[thisSeqPos++];
+        if (c != myGapChar)
+        {
+          basesWritten++;
+
+          if (basesWritten < mappedCodonStart)
+          {
+            /*
+             * Found an unmapped (intron) base. First add in any preceding gaps
+             * (if wanted).
+             */
+            if (preserveUnmappedGaps && trailingCopiedGap.length() > 0)
+            {
+              thisAligned.append(trailingCopiedGap.toString());
+              intronLength += trailingCopiedGap.length();
+              trailingCopiedGap = new StringBuilder();
+            }
+            intronLength++;
+            inExon = false;
+          }
+          else
+          {
+            final boolean startOfCodon = basesWritten == mappedCodonStart;
+            int gapsToAdd = calculateGapsToInsert(preserveMappedGaps,
+                    preserveUnmappedGaps, sourceGapMappedLength, inExon,
+                    trailingCopiedGap.length(), intronLength, startOfCodon);
+            for (int i = 0; i < gapsToAdd; i++)
+            {
+              thisAligned.append(myGapChar);
+            }
+            sourceGapMappedLength = 0;
+            inExon = true;
+          }
+          thisAligned.append(c);
+          trailingCopiedGap = new StringBuilder();
+        }
+        else
+        {
+          if (inExon && preserveMappedGaps)
+          {
+            trailingCopiedGap.append(myGapChar);
+          }
+          else if (!inExon && preserveUnmappedGaps)
+          {
+            trailingCopiedGap.append(myGapChar);
+          }
+        }
+      }
+    }
+
+    /*
+     * At end of protein sequence. Copy any remaining dna sequence, optionally
+     * including (intron) gaps. We do not copy trailing gaps in protein.
+     */
+    while (thisSeqPos < thisSeq.length)
+    {
+      final char c = thisSeq[thisSeqPos++];
+      if (c != myGapChar || preserveUnmappedGaps)
+      {
+        thisAligned.append(c);
+      }
+    }
+
+    /*
+     * All done aligning, set the aligned sequence.
+     */
+    alignTo.setSequence(new String(thisAligned));
+  }
+
+  /**
+   * Helper method to work out how many gaps to insert when realigning.
+   * 
+   * @param preserveMappedGaps
+   * @param preserveUnmappedGaps
+   * @param sourceGapMappedLength
+   * @param inExon
+   * @param trailingCopiedGap
+   * @param intronLength
+   * @param startOfCodon
+   * @return
+   */
+  protected static int calculateGapsToInsert(boolean preserveMappedGaps,
+          boolean preserveUnmappedGaps, int sourceGapMappedLength,
+          boolean inExon, int trailingGapLength,
+          int intronLength, final boolean startOfCodon)
+  {
+    int gapsToAdd = 0;
+    if (startOfCodon)
+    {
+      /*
+       * Reached start of codon. Ignore trailing gaps in intron unless we are
+       * preserving gaps in both exon and intron. Ignore them anyway if the
+       * protein alignment introduces a gap at least as large as the intronic
+       * region.
+       */
+      if (inExon && !preserveMappedGaps)
+      {
+        trailingGapLength = 0;
+      }
+      if (!inExon && !(preserveMappedGaps && preserveUnmappedGaps))
+      {
+        trailingGapLength = 0;
+      }
+      if (inExon)
+      {
+        gapsToAdd = Math.max(sourceGapMappedLength, trailingGapLength);
+      }
+      else
+      {
+        if (intronLength + trailingGapLength <= sourceGapMappedLength)
+        {
+          gapsToAdd = sourceGapMappedLength - intronLength;
+        }
+        else
+        {
+          gapsToAdd = Math.min(intronLength + trailingGapLength
+                  - sourceGapMappedLength, trailingGapLength);
+        }
+      }
+    }
+    else
+    {
+      /*
+       * second or third base of codon; check for any gaps in dna
+       */
+      if (!preserveMappedGaps)
+      {
+        trailingGapLength = 0;
+      }
+      gapsToAdd = Math.max(sourceGapMappedLength, trailingGapLength);
+    }
+    return gapsToAdd;
+  }
 }
diff --git a/src/jalview/analysis/CodonComparator.java b/src/jalview/analysis/CodonComparator.java
new file mode 100644 (file)
index 0000000..fc196de
--- /dev/null
@@ -0,0 +1,91 @@
+package jalview.analysis;
+
+import jalview.datamodel.AlignedCodon;
+
+import java.util.Comparator;
+
+/**
+ * Implements rules for comparing two aligned codons, i.e. determining whether
+ * they should occupy the same position in a translated protein alignment, or
+ * one or the other should 'follow' (by preceded by a gap).
+ * 
+ * @author gmcarstairs
+ *
+ */
+public final class CodonComparator implements Comparator<AlignedCodon>
+{
+
+  @Override
+  public int compare(AlignedCodon ac1, AlignedCodon ac2)
+  {
+    if (ac1 == null || ac2 == null || ac1.equals(ac2))
+    {
+      return 0;
+    }
+
+    /**
+     * <pre>
+     * Case 1: if one starts before the other, and doesn't end after it, then it
+     * precedes. We ignore the middle base position here.
+     * A--GT
+     * -CT-G
+     * </pre>
+     */
+    if (ac1.pos1 < ac2.pos1 && ac1.pos3 <= ac2.pos3)
+    {
+      return -1;
+    }
+    if (ac2.pos1 < ac1.pos1 && ac2.pos3 <= ac1.pos3)
+    {
+      return 1;
+    }
+
+    /**
+     * <pre>
+     * Case 2: if one ends after the other, and doesn't start before it, then it
+     * follows. We ignore the middle base position here.
+     * -TG-A
+     * G-TC
+     * </pre>
+     */
+    if (ac1.pos3 > ac2.pos3 && ac1.pos1 >= ac2.pos1)
+    {
+      return 1;
+    }
+    if (ac2.pos3 > ac1.pos3 && ac2.pos1 >= ac1.pos1)
+    {
+      return -1;
+    }
+
+    /*
+     * Case 3: if start and end match, compare middle base positions.
+     */
+    if (ac1.pos1 == ac2.pos1 && ac1.pos3 == ac2.pos3)
+    {
+      return Integer.compare(ac1.pos2, ac2.pos2);
+    }
+
+    /*
+     * That just leaves the 'enclosing' case - one codon starts after but ends
+     * before the other. If the middle bases don't match, use their comparison
+     * (majority vote).
+     */
+    int compareMiddles = Integer.compare(ac1.pos2, ac2.pos2);
+    if (compareMiddles != 0)
+    {
+      return compareMiddles;
+    }
+
+    /**
+     * <pre>
+     * Finally just leaves overlap with matching middle base, e.g. 
+     * -A-A-A
+     * G--GG 
+     * In this case the choice is arbitrary whether to compare based on
+     * first or last base position. We pick the first. Note this preserves
+     * symmetricality of the comparison.
+     * </pre>
+     */
+    return Integer.compare(ac1.pos1, ac2.pos1);
+  }
+}
index fa0fe2f..435a477 100644 (file)
  */
 package jalview.analysis;
 
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Vector;
-import java.util.Hashtable;
-
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.DBRefSource;
 import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.DBRefSource;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.ws.SequenceFetcher;
 import jalview.ws.seqfetcher.ASequenceFetcher;
 
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
 /**
  * Functions for cross-referencing sequence databases. user must first specify
  * if cross-referencing from protein or dna (set dna==true)
@@ -230,7 +230,7 @@ public class CrossRef
   {
     Vector rseqs = new Vector();
     Alignment ral = null;
-    AlignedCodonFrame cf = new AlignedCodonFrame(0); // nominal width
+    AlignedCodonFrame cf = new AlignedCodonFrame(); // nominal width
     for (int s = 0; s < seqs.length; s++)
     {
       SequenceI dss = seqs[s];
@@ -258,7 +258,9 @@ public class CrossRef
       for (int r = 0; xrfs != null && r < xrfs.length; r++)
       {
         if (source != null && !source.equals(xrfs[r].getSource()))
+        {
           continue;
+        }
         if (xrfs[r].hasMap())
         {
           if (xrfs[r].getMap().getTo() != null)
@@ -291,7 +293,9 @@ public class CrossRef
           {
             found |= searchDataset(dss, xrfs[r], dataset, rseqs, cf); // ,false,!dna);
             if (found)
+             {
               xrfs[r] = null; // we've recovered seqs for this one.
+            }
           }
         }
       }
@@ -328,7 +332,9 @@ public class CrossRef
             for (int r = 0; r < xrfs.length; r++)
             {
               if (xrfs[r] != null)
+              {
                 t[l++] = xrfs[r];
+              }
             }
             xrfs = t;
             try
@@ -432,7 +438,9 @@ public class CrossRef
   {
     boolean found = false;
     if (lrfs == null)
+    {
       return false;
+    }
     for (int i = 0; i < lrfs.length; i++)
     {
       DBRefEntry xref = new DBRefEntry(lrfs[i]);
@@ -484,7 +492,9 @@ public class CrossRef
     boolean found = false;
     SequenceI[] typer = new SequenceI[1];
     if (dataset == null)
+    {
       return false;
+    }
     if (dataset.getSequences() == null)
     {
       System.err.println("Empty dataset sequence set - NO VECTOR");
@@ -494,6 +504,7 @@ public class CrossRef
     synchronized (ds = dataset.getSequences())
     {
       for (SequenceI nxt : ds)
+      {
         if (nxt != null)
         {
           if (nxt.getDatasetSequence() != null)
@@ -566,6 +577,7 @@ public class CrossRef
 
           }
         }
+      }
     }
     return found;
   }
index 2e56e67..2ef63d4 100644 (file)
  */
 package jalview.analysis;
 
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.Vector;
-
+import jalview.api.AlignViewportI;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignedCodon;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.DBRefSource;
 import jalview.datamodel.FeatureProperties;
+import jalview.datamodel.GraphLine;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ResidueProperties;
+import jalview.util.Comparison;
+import jalview.util.DBRefUtils;
 import jalview.util.MapList;
 import jalview.util.ShiftList;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
 public class Dna
 {
-  /**
+  private static final String STOP_X = "X";
+
+  private static final Comparator<AlignedCodon> comparator = new CodonComparator();
+
+  /*
+   * 'final' variables describe the inputs to the translation, which should not
+   * be modified.
+   */
+  final private List<SequenceI> selection;
+
+  final private String[] seqstring;
+
+  final private int[] contigs;
+
+  final private char gapChar;
+
+  final private AlignmentAnnotation[] annotations;
+
+  final private int dnaWidth;
+
+  final private Alignment dataset;
+
+  /*
+   * Working variables for the translation.
    * 
-   * @param cdp1
-   * @param cdp2
-   * @return -1 if cdp1 aligns before cdp2, 0 if in the same column or cdp2 is
-   *         null, +1 if after cdp2
+   * The width of the translation-in-progress protein alignment.
    */
-  private static int compare_codonpos(int[] cdp1, int[] cdp2)
-  {
-    if (cdp2 == null
-            || (cdp1[0] == cdp2[0] && cdp1[1] == cdp2[1] && cdp1[2] == cdp2[2]))
-      return 0;
-    if (cdp1[0] < cdp2[0] || cdp1[1] < cdp2[1] || cdp1[2] < cdp2[2])
-      return -1; // one base in cdp1 precedes the corresponding base in the
-    // other codon
-    return 1; // one base in cdp1 appears after the corresponding base in the
-    // other codon.
-  }
+  private int aaWidth = 0;
 
-  /**
-   * DNA->mapped protein sequence alignment translation given set of sequences
-   * 1. id distinct coding regions within selected region for each sequence 2.
-   * generate peptides based on inframe (or given) translation or (optionally
-   * and where specified) out of frame translations (annotated appropriately) 3.
-   * align peptides based on codon alignment
+  /*
+   * This array will be built up so that position i holds the codon positions
+   * e.g. [7, 9, 10] that match column i (base 0) in the aligned translation.
+   * Note this implies a contract that if two codons do not align exactly, their
+   * translated products must occupy different column positions.
    */
+  private AlignedCodon[] alignedCodons;
+
   /**
-   * id potential products from dna 1. search for distinct products within
-   * selected region for each selected sequence 2. group by associated DB type.
-   * 3. return as form for input into above function
+   * Constructor given a viewport and the visible contigs.
+   * 
+   * @param viewport
+   * @param visibleContigs
    */
+  public Dna(AlignViewportI viewport, int[] visibleContigs)
+  {
+    this.selection = Arrays.asList(viewport.getSequenceSelection());
+    this.seqstring = viewport.getViewAsString(true);
+    this.contigs = visibleContigs;
+    this.gapChar = viewport.getGapCharacter();
+    this.annotations = viewport.getAlignment().getAlignmentAnnotation();
+    this.dnaWidth = viewport.getAlignment().getWidth();
+    this.dataset = viewport.getAlignment().getDataset();
+  }
+
   /**
+   * Test whether codon positions cdp1 should align before, with, or after cdp2.
+   * Returns zero if all positions match (or either argument is null). Returns
+   * -1 if any position in the first codon precedes the corresponding position
+   * in the second codon. Else returns +1 (some position in the second codon
+   * precedes the corresponding position in the first).
+   *
+   * Note this is not necessarily symmetric, for example:
+   * <ul>
+   * <li>compareCodonPos([2,5,6], [3,4,5]) returns -1</li>
+   * <li>compareCodonPos([3,4,5], [2,5,6]) also returns -1</li>
+   * </ul>
    * 
+   * @param ac1
+   * @param ac2
+   * @return
    */
+  public static final int compareCodonPos(AlignedCodon ac1, AlignedCodon ac2)
+  {
+    return comparator.compare(ac1, ac2);
+    // return jalview_2_8_2compare(ac1, ac2);
+  }
+
   /**
-   * create a new alignment of protein sequences by an inframe translation of
-   * the provided NA sequences
+   * Codon comparison up to Jalview 2.8.2. This rule is sequence order dependent
+   * - see http://issues.jalview.org/browse/JAL-1635
    * 
-   * @param selection
-   * @param seqstring
-   * @param viscontigs
-   * @param gapCharacter
-   * @param annotations
-   * @param aWidth
-   * @param dataset
-   *          destination dataset for translated sequences and mappings
+   * @param ac1
+   * @param ac2
    * @return
    */
-  public static AlignmentI CdnaTranslate(SequenceI[] selection,
-          String[] seqstring, int viscontigs[], char gapCharacter,
-          AlignmentAnnotation[] annotations, int aWidth, Alignment dataset)
+  private static int jalview_2_8_2compare(AlignedCodon ac1, AlignedCodon ac2)
   {
-    return CdnaTranslate(selection, seqstring, null, viscontigs,
-            gapCharacter, annotations, aWidth, dataset);
+    if (ac1 == null || ac2 == null || (ac1.equals(ac2)))
+    {
+      return 0;
+    }
+    if (ac1.pos1 < ac2.pos1 || ac1.pos2 < ac2.pos2 || ac1.pos3 < ac2.pos3)
+    {
+      // one base in cdp1 precedes the corresponding base in the other codon
+      return -1;
+    }
+    // one base in cdp1 appears after the corresponding base in the other codon.
+    return 1;
   }
 
   /**
    * 
-   * @param selection
-   * @param seqstring
-   * @param product
-   *          - array of DbRefEntry objects from which exon map in seqstring is
-   *          derived
-   * @param viscontigs
-   * @param gapCharacter
-   * @param annotations
-   * @param aWidth
-   * @param dataset
    * @return
    */
-  public static AlignmentI CdnaTranslate(SequenceI[] selection,
-          String[] seqstring, DBRefEntry[] product, int viscontigs[],
-          char gapCharacter, AlignmentAnnotation[] annotations, int aWidth,
-          Alignment dataset)
+  public AlignmentI translateCdna()
   {
-    AlignedCodonFrame codons = new AlignedCodonFrame(aWidth); // stores hash of
-    // subsequent
-    // positions for
-    // each codon
-    // start position
-    // in alignment
-    int s, sSize = selection.length;
-    Vector pepseqs = new Vector();
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+
+    alignedCodons = new AlignedCodon[dnaWidth];
+
+    int s;
+    int sSize = selection.size();
+    List<SequenceI> pepseqs = new ArrayList<SequenceI>();
     for (s = 0; s < sSize; s++)
     {
-      SequenceI newseq = translateCodingRegion(selection[s], seqstring[s],
-              viscontigs, codons, gapCharacter,
-              (product != null) ? product[s] : null, false); // possibly
-                                                             // anonymous
-      // product
+      SequenceI newseq = translateCodingRegion(selection.get(s),
+              seqstring[s], acf, pepseqs);
+
       if (newseq != null)
       {
-        pepseqs.addElement(newseq);
+        pepseqs.add(newseq);
         SequenceI ds = newseq;
         if (dataset != null)
         {
@@ -145,15 +182,15 @@ public class Dna
         }
       }
     }
-    if (codons.aaWidth == 0)
-      return null;
-    SequenceI[] newseqs = new SequenceI[pepseqs.size()];
-    pepseqs.copyInto(newseqs);
+
+    SequenceI[] newseqs = pepseqs.toArray(new SequenceI[pepseqs.size()]);
     AlignmentI al = new Alignment(newseqs);
-    al.padGaps(); // ensure we look aligned.
+    // ensure we look aligned.
+    al.padGaps();
+    // link the protein translation to the DNA dataset
     al.setDataset(dataset);
-    translateAlignedAnnotations(annotations, al, codons);
-    al.addCodonFrame(codons);
+    translateAlignedAnnotations(al, acf);
+    al.addCodonFrame(acf);
     return al;
   }
 
@@ -172,14 +209,13 @@ public class Dna
     for (int gd = 0; gd < selection.length; gd++)
     {
       SequenceI dna = selection[gd];
-      jalview.datamodel.DBRefEntry[] dnarefs = jalview.util.DBRefUtils
+      DBRefEntry[] dnarefs = DBRefUtils
               .selectRefs(dna.getDBRef(),
                       jalview.datamodel.DBRefSource.DNACODINGDBS);
       if (dnarefs != null)
       {
         // intersect with pep
-        // intersect with pep
-        Vector mappedrefs = new Vector();
+        List<DBRefEntry> mappedrefs = new ArrayList<DBRefEntry>();
         DBRefEntry[] refs = dna.getDBRef();
         for (int d = 0; d < refs.length; d++)
         {
@@ -187,11 +223,10 @@ public class Dna
                   && refs[d].getMap().getMap().getFromRatio() == 3
                   && refs[d].getMap().getMap().getToRatio() == 1)
           {
-            mappedrefs.addElement(refs[d]); // add translated protein maps
+            mappedrefs.add(refs[d]); // add translated protein maps
           }
         }
-        dnarefs = new DBRefEntry[mappedrefs.size()];
-        mappedrefs.copyInto(dnarefs);
+        dnarefs = mappedrefs.toArray(new DBRefEntry[mappedrefs.size()]);
         for (int d = 0; d < dnarefs.length; d++)
         {
           Mapping mp = dnarefs[d].getMap();
@@ -214,176 +249,107 @@ public class Dna
   }
 
   /**
-   * generate a set of translated protein products from annotated sequenceI
+   * Translate nucleotide alignment annotations onto translated amino acid
+   * alignment using codon mapping codons
    * 
-   * @param selection
-   * @param viscontigs
-   * @param gapCharacter
-   * @param dataset
-   *          destination dataset for translated sequences
-   * @param annotations
-   * @param aWidth
-   * @return
-   */
-  public static AlignmentI CdnaTranslate(SequenceI[] selection,
-          int viscontigs[], char gapCharacter, Alignment dataset)
-  {
-    int alwidth = 0;
-    Vector cdnasqs = new Vector();
-    Vector cdnasqi = new Vector();
-    Vector cdnaprod = new Vector();
-    for (int gd = 0; gd < selection.length; gd++)
-    {
-      SequenceI dna = selection[gd];
-      jalview.datamodel.DBRefEntry[] dnarefs = jalview.util.DBRefUtils
-              .selectRefs(dna.getDBRef(),
-                      jalview.datamodel.DBRefSource.DNACODINGDBS);
-      if (dnarefs != null)
-      {
-        // intersect with pep
-        Vector mappedrefs = new Vector();
-        DBRefEntry[] refs = dna.getDBRef();
-        for (int d = 0; d < refs.length; d++)
-        {
-          if (refs[d].getMap() != null && refs[d].getMap().getMap() != null
-                  && refs[d].getMap().getMap().getFromRatio() == 3
-                  && refs[d].getMap().getMap().getToRatio() == 1)
-          {
-            mappedrefs.addElement(refs[d]); // add translated protein maps
-          }
-        }
-        dnarefs = new DBRefEntry[mappedrefs.size()];
-        mappedrefs.copyInto(dnarefs);
-        for (int d = 0; d < dnarefs.length; d++)
-        {
-          Mapping mp = dnarefs[d].getMap();
-          StringBuffer sqstr = new StringBuffer();
-          if (mp != null)
-          {
-            Mapping intersect = mp.intersectVisContigs(viscontigs);
-            // generate seqstring for this sequence based on mapping
-
-            if (sqstr.length() > alwidth)
-              alwidth = sqstr.length();
-            cdnasqs.addElement(sqstr.toString());
-            cdnasqi.addElement(dna);
-            cdnaprod.addElement(intersect);
-          }
-        }
-      }
-      SequenceI[] cdna = new SequenceI[cdnasqs.size()];
-      DBRefEntry[] prods = new DBRefEntry[cdnaprod.size()];
-      String[] xons = new String[cdnasqs.size()];
-      cdnasqs.copyInto(xons);
-      cdnaprod.copyInto(prods);
-      cdnasqi.copyInto(cdna);
-      return CdnaTranslate(cdna, xons, prods, viscontigs, gapCharacter,
-              null, alwidth, dataset);
-    }
-    return null;
-  }
-
-  /**
-   * translate na alignment annotations onto translated amino acid alignment al
-   * using codon mapping codons
-   * 
-   * @param annotations
    * @param al
-   * @param codons
+   *          the translated protein alignment
    */
-  public static void translateAlignedAnnotations(
-          AlignmentAnnotation[] annotations, AlignmentI al,
-          AlignedCodonFrame codons)
+  protected void translateAlignedAnnotations(AlignmentI al,
+          AlignedCodonFrame acf)
   {
-    // //////////////////////////////
-    // Copy annotations across
-    //
     // Can only do this for columns with consecutive codons, or where
     // annotation is sequence associated.
 
-    int pos, a, aSize;
     if (annotations != null)
     {
-      for (int i = 0; i < annotations.length; i++)
+      for (AlignmentAnnotation annotation : annotations)
       {
-        // Skip any autogenerated annotation
-        if (annotations[i].autoCalculated)
+        /*
+         * Skip hidden or autogenerated annotation. Also (for now), RNA
+         * secondary structure annotation. If we want to show this against
+         * protein we need a smarter way to 'translate' without generating
+         * invalid (unbalanced) structure annotation.
+         */
+        if (annotation.autoCalculated || !annotation.visible
+                || annotation.isRNA())
         {
           continue;
         }
 
-        aSize = codons.getaaWidth(); // aa alignment width.
-        jalview.datamodel.Annotation[] anots = (annotations[i].annotations == null) ? null
-                : new jalview.datamodel.Annotation[aSize];
+        int aSize = aaWidth;
+        Annotation[] anots = (annotation.annotations == null) ? null
+                : new Annotation[aSize];
         if (anots != null)
         {
-          for (a = 0; a < aSize; a++)
+          for (int a = 0; a < aSize; a++)
           {
             // process through codon map.
-            if (codons.codons[a] != null
-                    && codons.codons[a][0] == (codons.codons[a][2] - 2))
+            if (a < alignedCodons.length && alignedCodons[a] != null
+                    && alignedCodons[a].pos1 == (alignedCodons[a].pos3 - 2))
             {
-              anots[a] = getCodonAnnotation(codons.codons[a],
-                      annotations[i].annotations);
+              anots[a] = getCodonAnnotation(alignedCodons[a],
+                      annotation.annotations);
             }
           }
         }
 
-        jalview.datamodel.AlignmentAnnotation aa = new jalview.datamodel.AlignmentAnnotation(
-                annotations[i].label, annotations[i].description, anots);
-        aa.graph = annotations[i].graph;
-        aa.graphGroup = annotations[i].graphGroup;
-        aa.graphHeight = annotations[i].graphHeight;
-        if (annotations[i].getThreshold() != null)
+        AlignmentAnnotation aa = new AlignmentAnnotation(annotation.label,
+                annotation.description, anots);
+        aa.graph = annotation.graph;
+        aa.graphGroup = annotation.graphGroup;
+        aa.graphHeight = annotation.graphHeight;
+        if (annotation.getThreshold() != null)
         {
-          aa.setThreshold(new jalview.datamodel.GraphLine(annotations[i]
+          aa.setThreshold(new GraphLine(annotation
                   .getThreshold()));
         }
-        if (annotations[i].hasScore)
+        if (annotation.hasScore)
         {
-          aa.setScore(annotations[i].getScore());
+          aa.setScore(annotation.getScore());
         }
-        if (annotations[i].sequenceRef != null)
+
+        final SequenceI seqRef = annotation.sequenceRef;
+        if (seqRef != null)
         {
-          SequenceI aaSeq = codons
-                  .getAaForDnaSeq(annotations[i].sequenceRef);
+          SequenceI aaSeq = acf.getAaForDnaSeq(seqRef);
           if (aaSeq != null)
           {
             // aa.compactAnnotationArray(); // throw away alignment annotation
             // positioning
             aa.setSequenceRef(aaSeq);
-            aa.createSequenceMapping(aaSeq, aaSeq.getStart(), true); // rebuild
-            // mapping
+            // rebuild mapping
+            aa.createSequenceMapping(aaSeq, aaSeq.getStart(), true);
             aa.adjustForAlignment();
             aaSeq.addAlignmentAnnotation(aa);
           }
-
         }
         al.addAnnotation(aa);
       }
     }
   }
 
-  private static Annotation getCodonAnnotation(int[] is,
+  private static Annotation getCodonAnnotation(AlignedCodon is,
           Annotation[] annotations)
   {
     // Have a look at all the codon positions for annotation and put the first
     // one found into the translated annotation pos.
     int contrib = 0;
     Annotation annot = null;
-    for (int p = 0; p < 3; p++)
+    for (int p = 1; p <= 3; p++)
     {
-      if (annotations[is[p]] != null)
+      int dnaCol = is.getBaseColumn(p);
+      if (annotations[dnaCol] != null)
       {
         if (annot == null)
         {
-          annot = new Annotation(annotations[is[p]]);
+          annot = new Annotation(annotations[dnaCol]);
           contrib = 1;
         }
         else
         {
           // merge with last
-          Annotation cpy = new Annotation(annotations[is[p]]);
+          Annotation cpy = new Annotation(annotations[dnaCol]);
           if (annot.colour == null)
           {
             annot.colour = cpy.colour;
@@ -407,7 +373,7 @@ public class Dna
     }
     if (contrib > 1)
     {
-      annot.value /= (float) contrib;
+      annot.value /= contrib;
     }
     return annot;
   }
@@ -419,92 +385,72 @@ public class Dna
    *          sequence displayed under viscontigs visible columns
    * @param seqstring
    *          ORF read in some global alignment reference frame
-   * @param viscontigs
-   *          mapping from global reference frame to visible seqstring ORF read
-   * @param codons
-   *          Definition of global ORF alignment reference frame
-   * @param gapCharacter
-   * @return sequence ready to be added to alignment.
-   * @deprecated Use
-   *             {@link #translateCodingRegion(SequenceI,String,int[],AlignedCodonFrame,char,DBRefEntry,boolean)}
-   *             instead
-   */
-  public static SequenceI translateCodingRegion(SequenceI selection,
-          String seqstring, int[] viscontigs, AlignedCodonFrame codons,
-          char gapCharacter, DBRefEntry product)
-  {
-    return translateCodingRegion(selection, seqstring, viscontigs, codons,
-            gapCharacter, product, false);
-  }
-
-  /**
-   * Translate a na sequence
-   * 
-   * @param selection
-   *          sequence displayed under viscontigs visible columns
-   * @param seqstring
-   *          ORF read in some global alignment reference frame
-   * @param viscontigs
-   *          mapping from global reference frame to visible seqstring ORF read
-   * @param codons
+   * @param acf
    *          Definition of global ORF alignment reference frame
-   * @param gapCharacter
-   * @param starForStop
-   *          when true stop codons will translate as '*', otherwise as 'X'
+   * @param proteinSeqs
    * @return sequence ready to be added to alignment.
    */
-  public static SequenceI translateCodingRegion(SequenceI selection,
-          String seqstring, int[] viscontigs, AlignedCodonFrame codons,
-          char gapCharacter, DBRefEntry product, final boolean starForStop)
+  protected SequenceI translateCodingRegion(SequenceI selection,
+          String seqstring, AlignedCodonFrame acf,
+          List<SequenceI> proteinSeqs)
   {
-    java.util.List skip = new ArrayList();
+    List<int[]> skip = new ArrayList<int[]>();
     int skipint[] = null;
     ShiftList vismapping = new ShiftList(); // map from viscontigs to seqstring
     // intervals
-    int vc, scontigs[] = new int[viscontigs.length];
+    int vc;
+    int[] scontigs = new int[contigs.length];
     int npos = 0;
-    for (vc = 0; vc < viscontigs.length; vc += 2)
+    for (vc = 0; vc < contigs.length; vc += 2)
     {
       if (vc == 0)
       {
-        vismapping.addShift(npos, viscontigs[vc]);
+        vismapping.addShift(npos, contigs[vc]);
       }
       else
       {
         // hidden region
-        vismapping.addShift(npos, viscontigs[vc] - viscontigs[vc - 1] + 1);
+        vismapping.addShift(npos, contigs[vc] - contigs[vc - 1] + 1);
       }
-      scontigs[vc] = viscontigs[vc];
-      scontigs[vc + 1] = viscontigs[vc + 1];
+      scontigs[vc] = contigs[vc];
+      scontigs[vc + 1] = contigs[vc + 1];
     }
 
-    StringBuffer protein = new StringBuffer();
-    String seq = seqstring.replace('U', 'T');
+    // allocate a roughly sized buffer for the protein sequence
+    StringBuilder protein = new StringBuilder(seqstring.length() / 2);
+    String seq = seqstring.replace('U', 'T').replace('u', 'T');
     char codon[] = new char[3];
-    int cdp[] = new int[3], rf = 0, lastnpos = 0, nend;
+    int cdp[] = new int[3];
+    int rf = 0;
+    int lastnpos = 0;
+    int nend;
     int aspos = 0;
     int resSize = 0;
     for (npos = 0, nend = seq.length(); npos < nend; npos++)
     {
-      if (!jalview.util.Comparison.isGap(seq.charAt(npos)))
+      if (!Comparison.isGap(seq.charAt(npos)))
       {
         cdp[rf] = npos; // store position
         codon[rf++] = seq.charAt(npos); // store base
       }
-      // filled an RF yet ?
       if (rf == 3)
       {
+        /*
+         * Filled up a reading frame...
+         */
+        AlignedCodon alignedCodon = new AlignedCodon(cdp[0], cdp[1], cdp[2]);
         String aa = ResidueProperties.codonTranslate(new String(codon));
         rf = 0;
+        final String gapString = String.valueOf(gapChar);
         if (aa == null)
         {
-          aa = String.valueOf(gapCharacter);
+          aa = gapString;
           if (skipint == null)
           {
             skipint = new int[]
-            { cdp[0], cdp[2] };
+            { alignedCodon.pos1, alignedCodon.pos3 /* cdp[0], cdp[2] */};
           }
-          skipint[1] = cdp[2];
+          skipint[1] = alignedCodon.pos3; // cdp[2];
         }
         else
         {
@@ -599,52 +545,66 @@ public class Dna
           }
           if (aa.equals("STOP"))
           {
-            aa = starForStop ? "*" : "X";
+            aa = STOP_X;
           }
           resSize++;
         }
-        // insert/delete gaps prior to this codon - if necessary
         boolean findpos = true;
         while (findpos)
         {
-          // first ensure that the codons array is long enough.
-          codons.checkCodonFrameWidth(aspos);
-          // now check to see if we place the aa at the current aspos in the
-          // protein alignment
-          switch (Dna.compare_codonpos(cdp, codons.codons[aspos]))
+          /*
+           * Compare this codon's base positions with those currently aligned to
+           * this column in the translation.
+           */
+          final int compareCodonPos = compareCodonPos(alignedCodon,
+                  alignedCodons[aspos]);
+          switch (compareCodonPos)
           {
           case -1:
-            codons.insertAAGap(aspos, gapCharacter);
+
+            /*
+             * This codon should precede the mapped positions - need to insert a
+             * gap in all prior sequences.
+             */
+            insertAAGap(aspos, proteinSeqs);
             findpos = false;
             break;
+
           case +1:
-            // this aa appears after the aligned codons at aspos, so prefix it
-            // with a gap
-            aa = "" + gapCharacter + aa;
+
+            /*
+             * This codon belongs after the aligned codons at aspos. Prefix it
+             * with a gap and try the next position.
+             */
+            aa = gapString + aa;
             aspos++;
-            // if (aspos >= codons.aaWidth)
-            // codons.aaWidth = aspos + 1;
-            break; // check the next position for alignment
+            break;
+
           case 0:
-            // codon aligns at aspos position.
+
+            /*
+             * Exact match - codon 'belongs' at this translated position.
+             */
             findpos = false;
           }
         }
-        // codon aligns with all other sequence residues found at aspos
         protein.append(aa);
         lastnpos = npos;
-        if (codons.codons[aspos] == null)
+        if (alignedCodons[aspos] == null)
         {
           // mark this column as aligning to this aligned reading frame
-          codons.codons[aspos] = new int[]
-          { cdp[0], cdp[1], cdp[2] };
+          alignedCodons[aspos] = alignedCodon;
+        }
+        else if (!alignedCodons[aspos].equals(alignedCodon))
+        {
+          throw new IllegalStateException("Tried to coalign "
+                  + alignedCodons[aspos].toString() + " with "
+                  + alignedCodon.toString());
         }
-        if (aspos >= codons.aaWidth)
+        if (aspos >= aaWidth)
         {
           // update maximum alignment width
-          // (we can do this without calling checkCodonFrameWidth because it was
-          // already done above)
-          codons.setAaWidth(aspos);
+          aaWidth = aspos;
         }
         // ready for next translated reading frame alignment position (if any)
         aspos++;
@@ -656,15 +616,14 @@ public class Dna
               protein.toString());
       if (rf != 0)
       {
-        if (jalview.bin.Cache.log != null)
+        final String errMsg = "trimming contigs for incomplete terminal codon.";
+        if (Cache.log != null)
         {
-          jalview.bin.Cache.log
-                  .debug("trimming contigs for incomplete terminal codon.");
+          Cache.log.debug(errMsg);
         }
         else
         {
-          System.err
-                  .println("trimming contigs for incomplete terminal codon.");
+          System.err.println(errMsg);
         }
         // map and trim contigs to ORF region
         vc = scontigs.length - 1;
@@ -694,7 +653,9 @@ public class Dna
           scontigs = t;
         }
         if (vc <= 0)
+        {
           scontigs = null;
+        }
       }
       if (scontigs != null)
       {
@@ -705,7 +666,9 @@ public class Dna
           scontigs[vc] = selection.findPosition(scontigs[vc]); // not from 1!
           scontigs[vc + 1] = selection.findPosition(scontigs[vc + 1]); // exclusive
           if (scontigs[vc + 1] == selection.getEnd())
+          {
             break;
+          }
         }
         // trim trailing empty intervals.
         if ((vc + 2) < scontigs.length)
@@ -731,27 +694,19 @@ public class Dna
         MapList map = new MapList(scontigs, new int[]
         { 1, resSize }, 3, 1);
 
-        // update newseq as if it was generated as mapping from product
-
-        if (product != null)
-        {
-          newseq.setName(product.getSource() + "|"
-                  + product.getAccessionId());
-          if (product.getMap() != null)
-          {
-            // Mapping mp = product.getMap();
-            // newseq.setStart(mp.getPosition(scontigs[0]));
-            // newseq.setEnd(mp
-            // .getPosition(scontigs[scontigs.length - 1]));
-          }
-        }
         transferCodedFeatures(selection, newseq, map, null, null);
-        SequenceI rseq = newseq.deriveSequence(); // construct a dataset
-        // sequence for our new
-        // peptide, regardless.
-        // store a mapping (this actually stores a mapping between the dataset
-        // sequences for the two sequences
-        codons.addMap(selection, rseq, map);
+
+        /*
+         * Construct a dataset sequence for our new peptide.
+         */
+        SequenceI rseq = newseq.deriveSequence();
+
+        /*
+         * Store a mapping (between the dataset sequences for the two
+         * sequences).
+         */
+        // SIDE-EFFECT: acf stores the aligned sequence reseq; to remove!
+        acf.addMap(selection, rseq, map);
         return rseq;
       }
     }
@@ -761,6 +716,53 @@ public class Dna
   }
 
   /**
+   * Insert a gap into the aligned proteins and the codon mapping array.
+   * 
+   * @param pos
+   * @param proteinSeqs
+   * @return
+   */
+  protected void insertAAGap(int pos,
+          List<SequenceI> proteinSeqs)
+  {
+    aaWidth++;
+    for (SequenceI seq : proteinSeqs)
+    {
+      seq.insertCharAt(pos, gapChar);
+    }
+
+    checkCodonFrameWidth();
+    if (pos < aaWidth)
+    {
+      aaWidth++;
+
+      /*
+       * Shift from [pos] to the end one to the right, and null out [pos]
+       */
+      System.arraycopy(alignedCodons, pos, alignedCodons, pos + 1,
+              alignedCodons.length - pos - 1);
+      alignedCodons[pos] = null;
+    }
+  }
+
+  /**
+   * Check the codons array can accommodate a single insertion, if not resize
+   * it.
+   */
+  protected void checkCodonFrameWidth()
+  {
+    if (alignedCodons[alignedCodons.length - 1] != null)
+    {
+      /*
+       * arraycopy insertion would bump a filled slot off the end, so expand.
+       */
+      AlignedCodon[] c = new AlignedCodon[alignedCodons.length + 10];
+      System.arraycopy(alignedCodons, 0, c, 0, alignedCodons.length);
+      alignedCodons = c;
+    }
+  }
+
+  /**
    * Given a peptide newly translated from a dna sequence, copy over and set any
    * features on the peptide from the DNA. If featureTypes is null, all features
    * on the dna sequence are searched (rather than just the displayed ones), and
@@ -770,20 +772,20 @@ public class Dna
    * @param pep
    * @param map
    * @param featureTypes
-   *          hash who's keys are the displayed feature type strings
+   *          hash whose keys are the displayed feature type strings
    * @param featureGroups
    *          hash where keys are feature groups and values are Boolean objects
    *          indicating if they are displayed.
    */
   private static void transferCodedFeatures(SequenceI dna, SequenceI pep,
-          MapList map, Hashtable featureTypes, Hashtable featureGroups)
+          MapList map, Map<String, Object> featureTypes,
+          Map<String, Boolean> featureGroups)
   {
-    SequenceFeature[] sf = (dna.getDatasetSequence() != null ? dna
+    SequenceFeature[] sfs = (dna.getDatasetSequence() != null ? dna
             .getDatasetSequence() : dna).getSequenceFeatures();
     Boolean fgstate;
-    jalview.datamodel.DBRefEntry[] dnarefs = jalview.util.DBRefUtils
-            .selectRefs(dna.getDBRef(),
-                    jalview.datamodel.DBRefSource.DNACODINGDBS);
+    DBRefEntry[] dnarefs = DBRefUtils.selectRefs(dna.getDBRef(),
+            DBRefSource.DNACODINGDBS);
     if (dnarefs != null)
     {
       // intersect with pep
@@ -795,16 +797,16 @@ public class Dna
         }
       }
     }
-    if (sf != null)
+    if (sfs != null)
     {
-      for (int f = 0; f < sf.length; f++)
+      for (SequenceFeature sf : sfs)
       {
-        fgstate = (featureGroups == null) ? null : ((Boolean) featureGroups
-                .get(sf[f].featureGroup));
-        if ((featureTypes == null || featureTypes.containsKey(sf[f]
-                .getType())) && (fgstate == null || fgstate.booleanValue()))
+        fgstate = (featureGroups == null) ? null : featureGroups
+                .get(sf.featureGroup);
+        if ((featureTypes == null || featureTypes.containsKey(sf.getType()))
+                && (fgstate == null || fgstate.booleanValue()))
         {
-          if (FeatureProperties.isCodingFeature(null, sf[f].getType()))
+          if (FeatureProperties.isCodingFeature(null, sf.getType()))
           {
             // if (map.intersectsFrom(sf[f].begin, sf[f].end))
             {
index b2fc4c2..d23fc75 100644 (file)
@@ -365,4 +365,25 @@ public interface AlignViewportI extends ViewStyleI
    */
   public void setViewStyle(ViewStyleI settingsForView);
 
+  /**
+   * 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/RNA, else false.
+   * 
+   * @return
+   */
+  boolean isNucleotide();
 }
index 334ee5d..5599695 100644 (file)
@@ -1692,7 +1692,6 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
       for (int[] region : viewport.getColumnSelection().getHiddenColumns())
       {
-
         copiedHiddenColumns.addElement(new int[]
         { region[0] - hiddenOffset, region[1] - hiddenOffset });
       }
index 9e0e237..cf0a047 100644 (file)
@@ -32,6 +32,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.SelectionSource;
 import jalview.structure.SequenceListener;
 import jalview.structure.StructureSelectionManager;
+import jalview.structure.VamsasSource;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -159,8 +160,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
   void setCursorPosition()
   {
-    SequenceI sequence = av.getAlignment().getSequenceAt(
-            seqCanvas.cursorY);
+    SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
 
     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
     scrollToVisible();
@@ -252,8 +252,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
   void setSelectionAreaAtCursor(boolean topLeft)
   {
-    SequenceI sequence = av.getAlignment().getSequenceAt(
-            seqCanvas.cursorY);
+    SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
 
     if (av.getSelectionGroup() != null)
     {
@@ -682,6 +681,12 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
   }
 
+  @Override
+  public VamsasSource getVamsasSource()
+  {
+    return this.ap == null ? null : this.ap.av;
+  }
+
   public void updateColours(SequenceI seq, int index)
   {
     System.out.println("update the seqPanel colours");
@@ -718,39 +723,38 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       mouseOverSequence(sequence, res, respos);
     }
 
-    StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
-            + sequence.getName());
+    StringBuilder text = new StringBuilder();
+    text.append("Sequence ").append(Integer.toString(seq + 1))
+            .append(" ID: ").append(sequence.getName());
 
-    Object obj = null;
+    String obj = null;
+    final String ch = String.valueOf(sequence.getCharAt(res));
     if (av.getAlignment().isNucleotide())
     {
-      obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
-              + "");
+      obj = ResidueProperties.nucleotideName.get(ch);
       if (obj != null)
       {
-        text.append(" Nucleotide: ");
+        text.append(" Nucleotide: ").append(obj);
       }
     }
     else
     {
-      obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
+      obj = "X".equalsIgnoreCase(ch) ? "STOP"
+              : ResidueProperties.aa2Triplet.get(ch);
       if (obj != null)
       {
-        text.append("  Residue: ");
+        text.append(" Residue: ").append(obj);
       }
     }
 
     if (obj != null)
     {
-      if (obj != "")
-      {
-        text.append(obj + " (" + respos + ")");
-      }
+      text.append(" (").append(Integer.toString(respos)).append(")");
     }
 
     ap.alignFrame.statusBar.setText(text.toString());
 
-    StringBuffer tooltipText = new StringBuffer();
+    StringBuilder tooltipText = new StringBuilder();
     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
     if (groups != null)
     {
@@ -761,7 +765,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
           if (!groups[g].getName().startsWith("JTreeGroup")
                   && !groups[g].getName().startsWith("JGroup"))
           {
-            tooltipText.append(groups[g].getName() + " ");
+            tooltipText.append(groups[g].getName()).append(" ");
           }
           if (groups[g].getDescription() != null)
           {
@@ -962,15 +966,18 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     StringBuffer message = new StringBuffer();
     if (groupEditing)
     {
-      message.append(MessageManager.getString("action.edit_group")).append(":");
+      message.append(MessageManager.getString("action.edit_group")).append(
+              ":");
       if (editCommand == null)
       {
-        editCommand = new EditCommand(MessageManager.getString("action.edit_group"));
+        editCommand = new EditCommand(
+                MessageManager.getString("action.edit_group"));
       }
     }
     else
     {
-      message.append(MessageManager.getString("label.edit_sequence")).append(" " + seq.getName());
+      message.append(MessageManager.getString("label.edit_sequence"))
+              .append(" " + seq.getName());
       String label = seq.getName();
       if (label.length() > 10)
       {
@@ -978,7 +985,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
       if (editCommand == null)
       {
-        editCommand = new EditCommand(MessageManager.formatMessage("label.edit_params", new String[]{label}));
+        editCommand = new EditCommand(MessageManager.formatMessage(
+                "label.edit_params", new String[]
+                { label }));
       }
     }
 
@@ -1180,8 +1189,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         }
         else
         {
-          editCommand.appendEdit(Action.INSERT_GAP, groupSeqs,
-                  startres, startres - lastres, av.getAlignment(), true);
+          editCommand.appendEdit(Action.INSERT_GAP, groupSeqs, startres,
+                  startres - lastres, av.getAlignment(), true);
         }
       }
       else
@@ -1196,8 +1205,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         }
         else
         {
-          editCommand.appendEdit(Action.DELETE_GAP, groupSeqs,
-                  startres, lastres - startres, av.getAlignment(), true);
+          editCommand.appendEdit(Action.DELETE_GAP, groupSeqs, startres,
+                  lastres - startres, av.getAlignment(), true);
         }
 
       }
@@ -1292,16 +1301,16 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
             av.getAlignment(), true);
 
-    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1, av.getAlignment(),
+            true);
 
   }
 
   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
   {
 
-    editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, av.getAlignment(),
+            true);
 
     editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
             av.getAlignment(), true);
index eede62e..51d83fa 100755 (executable)
@@ -966,7 +966,7 @@ public class Jalview
   private static FeatureFetcher startFeatureFetching(final Vector dasSources)
   {
     FeatureFetcher ff = new FeatureFetcher();
-    AlignFrame afs[] = Desktop.getAlignframes();
+    AlignFrame afs[] = Desktop.getAlignFrames();
     if (afs == null || afs.length == 0)
     {
       return null;
index e022c73..ada49cc 100644 (file)
@@ -60,6 +60,7 @@ import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.net.URL;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
@@ -447,12 +448,11 @@ public class JalviewLite extends Applet implements
         end = rs.findIndex(end);
         if (csel != null)
         {
-          Vector cs = csel.getSelected();
+          List<Integer> cs = csel.getSelected();
           csel.clear();
-          for (int csi = 0, csiS = cs.size(); csi < csiS; csi++)
+          for (Integer selectedCol : cs)
           {
-            csel.addElement(rs.findIndex(((Integer) cs.elementAt(csi))
-                    .intValue()));
+            csel.addElement(rs.findIndex(selectedCol));
           }
         }
       }
index 82de3b2..38a45ce 100644 (file)
@@ -26,11 +26,16 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.util.ReverseListIterator;
+import jalview.util.StringUtils;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Map;
 
 /**
  * 
@@ -58,7 +63,55 @@ public class EditCommand implements CommandI
 {
   public enum Action
   {
-    INSERT_GAP, DELETE_GAP, CUT, PASTE, REPLACE, INSERT_NUC
+    INSERT_GAP()
+    {
+      @Override
+      public Action getUndoAction()
+      {
+        return DELETE_GAP;
+      }
+    },
+    DELETE_GAP()
+    {
+      @Override
+      public Action getUndoAction()
+      {
+        return INSERT_GAP;
+      }
+    },
+    CUT()
+    {
+      @Override
+      public Action getUndoAction()
+      {
+        return PASTE;
+      }
+    },
+    PASTE()
+    {
+      @Override
+      public Action getUndoAction()
+      {
+        return CUT;
+      }
+    },
+    REPLACE
+    {
+      @Override
+      public Action getUndoAction()
+      {
+        return REPLACE;
+      }
+    },
+    INSERT_NUC
+    {
+      @Override
+      public Action getUndoAction()
+      {
+        return null;
+      }
+    };
+    public abstract Action getUndoAction();
   };
 
   private List<Edit> edits = new ArrayList<Edit>();
@@ -110,13 +163,88 @@ public class EditCommand implements CommandI
   }
 
   /**
-   * Add the given edit command to the stored list of commands.
+   * Add the given edit command to the stored list of commands. If simply
+   * expanding the range of the last command added, then modify it instead of
+   * adding a new command.
    * 
    * @param e
    */
-  protected void addEdit(Edit e)
+  public void addEdit(Edit e)
   {
-    edits.add(e);
+    if (!expandEdit(edits, e))
+    {
+      edits.add(e);
+    }
+  }
+
+  /**
+   * Returns true if the new edit is incorporated by updating (expanding the
+   * range of) the last edit on the list, else false. We can 'expand' the last
+   * edit if the new one is the same action, on the same sequences, and acts on
+   * a contiguous range. This is the case where a mouse drag generates a series
+   * of contiguous gap insertions or deletions.
+   * 
+   * @param edits
+   * @param e
+   * @return
+   */
+  protected static boolean expandEdit(List<Edit> edits, Edit e)
+  {
+    if (edits == null || edits.isEmpty())
+    {
+      return false;
+    }
+    Edit lastEdit = edits.get(edits.size() - 1);
+    Action action = e.command;
+    if (lastEdit.command != action)
+    {
+      return false;
+    }
+
+    /*
+     * Both commands must act on the same sequences - compare the underlying
+     * dataset sequences, rather than the aligned sequences, which change as
+     * they are edited.
+     */
+    if (lastEdit.seqs.length != e.seqs.length)
+    {
+      return false;
+    }
+    for (int i = 0; i < e.seqs.length; i++)
+    {
+      if (lastEdit.seqs[i].getDatasetSequence() != e.seqs[i]
+              .getDatasetSequence())
+      {
+        return false;
+      }
+    }
+
+    /**
+     * Check a contiguous edit; either
+     * <ul>
+     * <li>a new Insert <n> positions to the right of the last <insert n>, or</li>
+     * <li>a new Delete <n> gaps which is <n> positions to the left of the last
+     * delete.</li>
+     * </ul>
+     */
+    boolean contiguous = (action == Action.INSERT_GAP && e.position == lastEdit.position
+            + lastEdit.number)
+            || (action == Action.DELETE_GAP && e.position + e.number == lastEdit.position);
+    if (contiguous)
+    {
+      /*
+       * We are just expanding the range of the last edit. For delete gap, also
+       * moving the start position left.
+       */
+      lastEdit.number += e.number;
+      lastEdit.seqs = e.seqs;
+      if (action == Action.DELETE_GAP)
+      {
+        lastEdit.position--;
+      }
+      return true;
+    }
+    return false;
   }
 
   /**
@@ -209,7 +337,32 @@ public class EditCommand implements CommandI
       edit.fullAlignmentHeight = true;
     }
 
-    edits.add(edit);
+    addEdit(edit);
+
+    if (performEdit)
+    {
+      performEdit(edit, views);
+    }
+  }
+
+  /**
+   * Overloaded method that accepts an Edit object with additional parameters.
+   * 
+   * @param edit
+   * @param al
+   * @param performEdit
+   * @param views
+   */
+  final public void appendEdit(Edit edit, AlignmentI al,
+          boolean performEdit, AlignmentI[] views)
+  {
+    if (al.getHeight() == edit.seqs.length)
+    {
+      edit.al = al;
+      edit.fullAlignmentHeight = true;
+    }
+
+    addEdit(edit);
 
     if (performEdit)
     {
@@ -223,7 +376,7 @@ public class EditCommand implements CommandI
    * @param commandIndex
    * @param views
    */
-  final void performEdit(int commandIndex, AlignmentI[] views)
+  public final void performEdit(int commandIndex, AlignmentI[] views)
   {
     ListIterator<Edit> iterator = edits.listIterator(commandIndex);
     while (iterator.hasNext())
@@ -239,7 +392,7 @@ public class EditCommand implements CommandI
    * @param edit
    * @param views
    */
-  protected void performEdit(Edit edit, AlignmentI[] views)
+  protected static void performEdit(Edit edit, AlignmentI[] views)
   {
     switch (edit.command)
     {
@@ -316,13 +469,13 @@ public class EditCommand implements CommandI
    * 
    * @param command
    */
-  final private void insertGap(Edit command)
+  final private static void insertGap(Edit command)
   {
 
     for (int s = 0; s < command.seqs.length; s++)
     {
-      command.seqs[s].insertCharAt(command.position, command.number,
-              command.gapChar);
+      command.seqs[s].insertCharAt(command.position,
+              command.number, command.gapChar);
       // System.out.println("pos: "+command.position+" number: "+command.number);
     }
 
@@ -348,7 +501,7 @@ public class EditCommand implements CommandI
    * 
    * @param command
    */
-  final private void deleteGap(Edit command)
+  final static private void deleteGap(Edit command)
   {
     for (int s = 0; s < command.seqs.length; s++)
     {
@@ -366,7 +519,7 @@ public class EditCommand implements CommandI
    * @param command
    * @param views
    */
-  void cut(Edit command, AlignmentI[] views)
+  static void cut(Edit command, AlignmentI[] views)
   {
     boolean seqDeleted = false;
     command.string = new char[command.seqs.length][];
@@ -430,7 +583,7 @@ public class EditCommand implements CommandI
    * @param command
    * @param views
    */
-  void paste(Edit command, AlignmentI[] views)
+  static void paste(Edit command, AlignmentI[] views)
   {
     StringBuffer tmp;
     boolean newDSNeeded;
@@ -446,7 +599,7 @@ public class EditCommand implements CommandI
       if (command.seqs[i].getLength() < 1)
       {
         // ie this sequence was deleted, we need to
-        // read it to the alignment
+        // readd it to the alignment
         if (command.alIndex[i] < command.al.getHeight())
         {
           List<SequenceI> sequences;
@@ -548,7 +701,7 @@ public class EditCommand implements CommandI
     command.string = null;
   }
 
-  void replace(Edit command)
+  static void replace(Edit command)
   {
     StringBuffer tmp;
     String oldstring;
@@ -625,7 +778,7 @@ public class EditCommand implements CommandI
     }
   }
 
-  final void adjustAnnotations(Edit command, boolean insert,
+  final static void adjustAnnotations(Edit command, boolean insert,
           boolean modifyVisibility, AlignmentI[] views)
   {
     AlignmentAnnotation[] annotations = null;
@@ -949,7 +1102,7 @@ public class EditCommand implements CommandI
     }
   }
 
-  final void adjustFeatures(Edit command, int index, int i, int j,
+  final static void adjustFeatures(Edit command, int index, int i, int j,
           boolean insert)
   {
     SequenceI seq = command.seqs[index];
@@ -1027,7 +1180,112 @@ public class EditCommand implements CommandI
 
   }
 
-  class Edit
+  /**
+   * Returns the list of edit commands wrapped by this object.
+   * 
+   * @return
+   */
+  public List<Edit> getEdits()
+  {
+    return this.edits;
+  }
+
+  /**
+   * Returns a map whose keys are the dataset sequences, and values their
+   * aligned sequences before the command edit list was applied. The aligned
+   * sequences are copies, which may be updated without affecting the originals.
+   * 
+   * The command holds references to the aligned sequences (after editing). If
+   * the command is an 'undo',then the prior state is simply the aligned state.
+   * Otherwise, we have to derive the prior state by working backwards through
+   * the edit list to infer the aligned sequences before editing.
+   * 
+   * Note: an alternative solution would be to cache the 'before' state of each
+   * edit, but this would be expensive in space in the common case that the
+   * original is never needed (edits are not mirrored).
+   * 
+   * @return
+   * @throws IllegalStateException
+   *           on detecting an edit command of a type that can't be unwound
+   */
+  public Map<SequenceI, SequenceI> priorState(boolean forUndo)
+  {
+    Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
+    if (getEdits() == null)
+    {
+      return result;
+    }
+    if (forUndo)
+    {
+      for (Edit e : getEdits())
+      {
+        for (SequenceI seq : e.getSequences())
+        {
+          SequenceI ds = seq.getDatasetSequence();
+          SequenceI preEdit = result.get(ds);
+          if (preEdit == null)
+          {
+            preEdit = new Sequence("", seq.getSequenceAsString());
+            preEdit.setDatasetSequence(ds);
+            result.put(ds, preEdit);
+          }
+        }
+      }
+      return result;
+    }
+
+    /*
+     * Work backwards through the edit list, deriving the sequences before each
+     * was applied. The final result is the sequence set before any edits.
+     */
+    Iterator<Edit> edits = new ReverseListIterator<Edit>(getEdits());
+    while (edits.hasNext())
+    {
+      Edit oldEdit = edits.next();
+      Action action = oldEdit.getAction();
+      int position = oldEdit.getPosition();
+      int number = oldEdit.getNumber();
+      final char gap = oldEdit.getGapCharacter();
+      for (SequenceI seq : oldEdit.getSequences())
+      {
+        SequenceI ds = seq.getDatasetSequence();
+        SequenceI preEdit = result.get(ds);
+        if (preEdit == null)
+        {
+          preEdit = new Sequence("", seq.getSequenceAsString());
+          preEdit.setDatasetSequence(ds);
+          result.put(ds, preEdit);
+        }
+        /*
+         * 'Undo' this edit action on the sequence (updating the value in the
+         * map).
+         */
+        if (ds != null)
+        {
+          if (action == Action.DELETE_GAP)
+          {
+            preEdit.setSequence(new String(StringUtils.insertCharAt(
+                    preEdit.getSequence(), position,
+                    number, gap)));
+          }
+          else if (action == Action.INSERT_GAP)
+          {
+            preEdit.setSequence(new String(StringUtils.deleteChars(
+                    preEdit.getSequence(), position, position + number)));
+          }
+          else
+          {
+            System.err.println("Can't undo edit action " + action);
+            // throw new IllegalStateException("Can't undo edit action " +
+            // action);
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  public class Edit
   {
     public SequenceI[] oldds;
 
@@ -1053,7 +1311,7 @@ public class EditCommand implements CommandI
 
     char gapChar;
 
-    Edit(Action command, SequenceI[] seqs, int position, int number,
+    public Edit(Action command, SequenceI[] seqs, int position, int number,
             char gapChar)
     {
       this.command = command;
@@ -1099,5 +1357,49 @@ public class EditCommand implements CommandI
 
       fullAlignmentHeight = (al.getHeight() == seqs.length);
     }
+
+    public SequenceI[] getSequences()
+    {
+      return seqs;
+    }
+
+    public int getPosition()
+    {
+      return position;
+    }
+
+    public Action getAction()
+    {
+      return command;
+    }
+
+    public int getNumber()
+    {
+      return number;
+    }
+
+    public char getGapCharacter()
+    {
+      return gapChar;
+    }
+  }
+
+  /**
+   * Returns an iterator over the list of edit commands which traverses the list
+   * either forwards or backwards.
+   * 
+   * @param forwards
+   * @return
+   */
+  public Iterator<Edit> getEditIterator(boolean forwards)
+  {
+    if (forwards)
+    {
+      return getEdits().iterator();
+    }
+    else
+    {
+      return new ReverseListIterator<Edit>(getEdits());
+    }
   }
 }
index b610a54..f4010a1 100644 (file)
  */
 package jalview.commands;
 
-import jalview.analysis.*;
-import jalview.datamodel.*;
+import jalview.analysis.AlignmentSorter;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
 
+/**
+ * An undoable command to reorder the sequences in an alignment.
+ * 
+ * @author gmcarstairs
+ *
+ */
 public class OrderCommand implements CommandI
 {
   String description;
 
+  /*
+   * The sequence order before sorting (target order for an undo)
+   */
   SequenceI[] seqs;
 
+  /*
+   * The sequence order specified by this command
+   */
   SequenceI[] seqs2;
 
+  /*
+   * The alignment the command acts on
+   */
   AlignmentI al;
 
+  /**
+   * Constructor given the 'undo' sequence order, and the (already) sorted
+   * alignment.
+   * 
+   * @param description
+   *          a text label for the 'undo' menu option
+   * @param seqs
+   *          the sequence order for undo
+   * @param al
+   *          the alignment as ordered by this command
+   */
   public OrderCommand(String description, SequenceI[] seqs, AlignmentI al)
   {
     this.description = description;
@@ -61,4 +88,15 @@ public class OrderCommand implements CommandI
   {
     AlignmentSorter.setOrder(al, seqs);
   }
+
+  /**
+   * Returns the sequence order used to sort, or before sorting if undo=true.
+   * 
+   * @param undo
+   * @return
+   */
+  public SequenceI[] getSequenceOrder(boolean undo)
+  {
+    return undo ? seqs : seqs2;
+  }
 }
diff --git a/src/jalview/datamodel/AlignedCodon.java b/src/jalview/datamodel/AlignedCodon.java
new file mode 100644 (file)
index 0000000..d0e62a1
--- /dev/null
@@ -0,0 +1,71 @@
+package jalview.datamodel;
+
+/**
+ * Holds the aligned column positions (base 0) for one codon in a nucleotide
+ * sequence. The object is immutable once created.
+ * 
+ * Example: in "G-AT-C-GA" the aligned codons are (0, 2, 3) and (5, 7, 8).
+ * 
+ * @author gmcarstairs
+ *
+ */
+public final class AlignedCodon
+{
+  public final int pos1;
+
+  public final int pos2;
+
+  public final int pos3;
+
+  public AlignedCodon(int i, int j, int k)
+  {
+    pos1 = i;
+    pos2 = j;
+    pos3 = k;
+  }
+
+  /**
+   * Returns the column position for the given base (1, 2, 3).
+   * 
+   * @param base
+   * @return
+   * @throws IllegalArgumentException
+   *           if an argument value other than 1, 2 or 3 is supplied
+   */
+  public int getBaseColumn(int base)
+  {
+    if (base < 1 || base > 3)
+    {
+      throw new IllegalArgumentException(Integer.toString(base));
+    }
+    return base == 1 ? pos1 : (base == 2 ? pos2 : pos3);
+  }
+
+  /**
+   * Two aligned codons are equal if all their base positions are the same.
+   */
+  @Override
+  public boolean equals(Object o)
+  {
+    /*
+     * Equality with null value required for consistency with
+     * Dna.compareCodonPos
+     */
+    if (o == null)
+    {
+      return true;
+    }
+    if (!(o instanceof AlignedCodon))
+    {
+      return false;
+    }
+    AlignedCodon ac = (AlignedCodon) o;
+    return (pos1 == ac.pos1 && pos2 == ac.pos2 && pos3 == ac.pos3);
+  }
+
+  @Override
+  public String toString()
+  {
+    return "[" + pos1 + ", " + pos2 + ", " + pos3 + "]";
+  }
+}
index 3fc08d1..d3f6ad5 100644 (file)
  */
 package jalview.datamodel;
 
-import java.util.Enumeration;
-import java.util.Vector;
-
 import jalview.util.MapList;
 
 /**
  * Stores mapping between the columns of a protein alignment and a DNA alignment
  * and a list of individual codon to amino acid mappings between sequences.
  */
-
 public class AlignedCodonFrame
 {
-  /**
-   * array of nucleotide positions for aligned codons at column of aligned
-   * proteins.
+
+  /*
+   * tied array of na Sequence objects.
    */
-  public int[][] codons = null;
+  private SequenceI[] dnaSeqs = null;
 
-  /**
-   * width of protein sequence alignement implicit assertion that codons.length
-   * >= aaWidth
+  /*
+   * tied array of Mappings to protein sequence Objects and SequenceI[]
+   * aaSeqs=null; MapLists where each maps from the corresponding dnaSeqs
+   * element to corresponding aaSeqs element
    */
-  public int aaWidth = 0;
+  private Mapping[] dnaToProt = null;
 
   /**
    * initialise codon frame with a nominal alignment width
    * 
    * @param aWidth
    */
-  public AlignedCodonFrame(int aWidth)
-  {
-    if (aWidth <= 0)
-    {
-      codons = null;
-      return;
-    }
-    codons = new int[aWidth][];
-    for (int res = 0; res < aWidth; res++)
-      codons[res] = null;
-  }
-
-  /**
-   * ensure that codons array is at least as wide as aslen residues
-   * 
-   * @param aslen
-   * @return (possibly newly expanded) codon array
-   */
-  public int[][] checkCodonFrameWidth(int aslen)
-  {
-    if (codons.length <= aslen + 1)
-    {
-      // probably never have to do this ?
-      int[][] c = new int[codons.length + 10][];
-      for (int i = 0; i < codons.length; i++)
-      {
-        c[i] = codons[i];
-        codons[i] = null;
-      }
-      codons = c;
-    }
-    return codons;
-  }
-
-  /**
-   * @return width of aligned translated amino acid residues
-   */
-  public int getaaWidth()
-  {
-    return aaWidth;
-  }
-
-  /**
-   * TODO: not an ideal solution - we reference the aligned amino acid sequences
-   * in order to make insertions on them Better would be dnaAlignment and
-   * aaAlignment reference....
-   */
-  Vector a_aaSeqs = new Vector();
-
-  /**
-   * increase aaWidth by one and insert a new aligned codon position space at
-   * aspos.
-   * 
-   * @param aspos
-   */
-  public void insertAAGap(int aspos, char gapCharacter)
+  public AlignedCodonFrame()
   {
-    // this aa appears before the aligned codons at aspos - so shift them in
-    // each pair of mapped sequences
-    aaWidth++;
-    if (a_aaSeqs != null)
-    {
-      // we actually have to modify the aligned sequences here, so use the
-      // a_aaSeqs vector
-      Enumeration sq = a_aaSeqs.elements();
-      while (sq.hasMoreElements())
-      {
-        ((SequenceI) sq.nextElement()).insertCharAt(aspos, gapCharacter);
-      }
-    }
-    checkCodonFrameWidth(aspos);
-    if (aspos < aaWidth)
-    {
-      aaWidth++;
-      System.arraycopy(codons, aspos, codons, aspos + 1, codons.length
-              - aspos - 1);
-      codons[aspos] = null; // clear so new codon position can be marked.
-    }
   }
 
-  public void setAaWidth(int aapos)
-  {
-    aaWidth = aapos;
-  }
-
-  /**
-   * tied array of na Sequence objects.
-   */
-  SequenceI[] dnaSeqs = null;
-
-  /**
-   * tied array of Mappings to protein sequence Objects and SequenceI[]
-   * aaSeqs=null; MapLists where eac maps from the corresponding dnaSeqs element
-   * to corresponding aaSeqs element
-   */
-  Mapping[] dnaToProt = null;
-
   /**
    * add a mapping between the dataset sequences for the associated dna and
    * protein sequence objects
@@ -179,7 +83,6 @@ public class AlignedCodonFrame
     // aaseq.transferAnnotation(dnaseq, new Mapping(map.getInverse()));
     mp.to = (aaseq.getDatasetSequence() == null) ? aaseq : aaseq
             .getDatasetSequence();
-    a_aaSeqs.addElement(aaseq);
     dnaToProt[nlen] = mp;
   }
 
@@ -191,7 +94,9 @@ public class AlignedCodonFrame
   public SequenceI[] getAaSeqs()
   {
     if (dnaToProt == null)
+    {
       return null;
+    }
     SequenceI[] sqs = new SequenceI[dnaToProt.length];
     for (int sz = 0; sz < dnaToProt.length; sz++)
     {
@@ -203,7 +108,9 @@ public class AlignedCodonFrame
   public MapList[] getdnaToProt()
   {
     if (dnaToProt == null)
+    {
       return null;
+    }
     MapList[] sqs = new MapList[dnaToProt.length];
     for (int sz = 0; sz < dnaToProt.length; sz++)
     {
@@ -218,9 +125,11 @@ public class AlignedCodonFrame
   }
 
   /**
+   * Return the corresponding aligned or dataset aa sequence for given dna
+   * sequence, null if not found.
    * 
    * @param sequenceRef
-   * @return null or corresponding aaSeq entry for dnaSeq entry
+   * @return
    */
   public SequenceI getAaForDnaSeq(SequenceI dnaSeqRef)
   {
@@ -232,7 +141,9 @@ public class AlignedCodonFrame
     for (int ds = 0; ds < dnaSeqs.length; ds++)
     {
       if (dnaSeqs[ds] == dnaSeqRef || dnaSeqs[ds] == dnads)
+      {
         return dnaToProt[ds].to;
+      }
     }
     return null;
   }
@@ -252,7 +163,9 @@ public class AlignedCodonFrame
     for (int as = 0; as < dnaToProt.length; as++)
     {
       if (dnaToProt[as].to == aaSeqRef || dnaToProt[as].to == aads)
+      {
         return dnaSeqs[as];
+      }
     }
     return null;
   }
@@ -319,4 +232,124 @@ public class AlignedCodonFrame
       }
     }
   }
+
+  /**
+   * Returns the DNA codon positions (base 1) for the given position (base 1) in
+   * a mapped protein sequence, or null if no mapping is found.
+   * 
+   * Intended for use in aligning cDNA to match aligned protein. Only the first
+   * mapping found is returned, so not suitable for use if multiple protein
+   * sequences are mapped to the same cDNA (but aligning cDNA as protein is
+   * ill-defined for this case anyway).
+   * 
+   * @param seq
+   *          the DNA dataset sequence
+   * @param aaPos
+   *          residue position (base 1) in a protein sequence
+   * @return
+   */
+  public int[] getDnaPosition(SequenceI seq, int aaPos)
+  {
+    /*
+     * Adapted from markMappedRegion().
+     */
+    MapList ml = null;
+    for (int i = 0; i < dnaToProt.length; i++)
+    {
+      if (dnaSeqs[i] == seq)
+      {
+        ml = getdnaToProt()[i];
+        break;
+      }
+    }
+    return ml == null ? null : ml.locateInFrom(aaPos, aaPos);
+  }
+
+  /**
+   * Convenience method to return the first aligned sequence in the given
+   * alignment whose dataset has a mapping with the given dataset sequence.
+   * 
+   * @param seq
+   * 
+   * @param al
+   * @return
+   */
+  public SequenceI findAlignedSequence(SequenceI seq, AlignmentI al)
+  {
+    /*
+     * Search mapped protein ('to') sequences first.
+     */
+    if (this.dnaToProt != null)
+    {
+      for (int i = 0; i < dnaToProt.length; i++)
+      {
+        if (this.dnaSeqs[i] == seq)
+        {
+          for (SequenceI sourceAligned : al.getSequences())
+          {
+            if (this.dnaToProt[i].to == sourceAligned.getDatasetSequence())
+            {
+              return sourceAligned;
+            }
+          }
+        }
+      }
+    }
+
+    /*
+     * Then try mapped dna sequences.
+     */
+    if (this.dnaToProt != null)
+    {
+      for (int i = 0; i < dnaToProt.length; i++)
+      {
+        if (this.dnaToProt[i].to == seq)
+        {
+          for (SequenceI sourceAligned : al.getSequences())
+          {
+            if (this.dnaSeqs[i] == sourceAligned.getDatasetSequence())
+            {
+              return sourceAligned;
+            }
+          }
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns the region in the 'mappedFrom' sequence's dataset that is mapped to
+   * position 'pos' (base 1) in the 'mappedTo' sequence's dataset. The region is
+   * a set of start/end position pairs.
+   * 
+   * @param mappedFrom
+   * @param mappedTo
+   * @param pos
+   * @return
+   */
+  public int[] getMappedRegion(SequenceI mappedFrom, SequenceI mappedTo,
+          int pos)
+  {
+    SequenceI targetDs = mappedFrom.getDatasetSequence() == null ? mappedFrom
+            : mappedFrom.getDatasetSequence();
+    SequenceI sourceDs = mappedTo.getDatasetSequence() == null ? mappedTo
+            : mappedTo.getDatasetSequence();
+    if (targetDs == null || sourceDs == null || dnaToProt == null)
+    {
+      return null;
+    }
+    for (int mi = 0; mi < dnaToProt.length; mi++)
+    {
+      if (dnaSeqs[mi] == targetDs && dnaToProt[mi].to == sourceDs)
+      {
+        int[] codon = dnaToProt[mi].map.locateInFrom(pos, pos);
+        if (codon != null) {
+          return codon;
+        }
+      }
+    }
+    return null;
+  }
 }
index 01d3d8d..cb571ac 100755 (executable)
  */
 package jalview.datamodel;
 
+import jalview.analysis.AlignmentUtils;
+import jalview.io.FastaFile;
 import jalview.util.MessageManager;
 
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.Vector;
 
 /**
@@ -62,6 +67,8 @@ public class Alignment implements AlignmentI
 
   public Hashtable alignmentProperties;
 
+  private Set<AlignedCodonFrame> codonFrameList = new LinkedHashSet<AlignedCodonFrame>();
+
   private void initAlignment(SequenceI[] seqs)
   {
     int i = 0;
@@ -86,6 +93,27 @@ public class Alignment implements AlignmentI
   }
 
   /**
+   * Make a 'copy' alignment - sequences have new copies of features and
+   * annotations, but share the original dataset sequences.
+   */
+  public Alignment(AlignmentI al)
+  {
+    SequenceI[] seqs = al.getSequencesArray();
+    for (int i = 0; i < seqs.length; i++)
+    {
+      seqs[i] = new Sequence(seqs[i]);
+    }
+
+    /*
+     * Share the same dataset sequence mappings (if any). TODO: find a better
+     * place for these to live (alignment dataset?).
+     */
+    this.codonFrameList = ((Alignment) al).codonFrameList;
+
+    initAlignment(seqs);
+  }
+
+  /**
    * Make an alignment from an array of Sequences.
    * 
    * @param sequences
@@ -123,11 +151,6 @@ public class Alignment implements AlignmentI
     // this(compactAlignment.refCigars);
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
   @Override
   public List<SequenceI> getSequences()
   {
@@ -157,6 +180,17 @@ public class Alignment implements AlignmentI
   }
 
   /**
+   * Returns a map of lists of sequences keyed by sequence name.
+   * 
+   * @return
+   */
+  @Override
+  public Map<String, List<SequenceI>> getSequencesByName()
+  {
+    return AlignmentUtils.getSequencesByName(this);
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param i
@@ -226,10 +260,9 @@ public class Alignment implements AlignmentI
   @Override
   public void setSequenceAt(int i, SequenceI snew)
   {
-    SequenceI oldseq = getSequenceAt(i);
-    deleteSequence(i);
     synchronized (sequences)
     {
+      deleteSequence(i);
       sequences.set(i, snew);
     }
   }
@@ -298,8 +331,8 @@ public class Alignment implements AlignmentI
       synchronized (sequences)
       {
         sequences.remove(i);
+        hiddenSequences.adjustHeightSequenceDeleted(i);
       }
-      hiddenSequences.adjustHeightSequenceDeleted(i);
     }
   }
 
@@ -720,6 +753,28 @@ public class Alignment implements AlignmentI
     return true;
   }
 
+  /**
+   * Delete all annotations, including auto-calculated if the flag is set true.
+   * Returns true if at least one annotation was deleted, else false.
+   * 
+   * @param includingAutoCalculated
+   * @return
+   */
+  @Override
+  public boolean deleteAllAnnotations(boolean includingAutoCalculated)
+  {
+    boolean result = false;
+    for (AlignmentAnnotation alan : getAlignmentAnnotation())
+    {
+      if (!alan.autoCalculated || includingAutoCalculated)
+      {
+        deleteAnnotation(alan);
+        result = true;
+      }
+    }
+    return result;
+  }
+
   /*
    * (non-Javadoc)
    * 
@@ -1205,8 +1260,6 @@ public class Alignment implements AlignmentI
     return alignmentProperties;
   }
 
-  AlignedCodonFrame[] codonFrameList = null;
-
   /*
    * (non-Javadoc)
    * 
@@ -1217,31 +1270,10 @@ public class Alignment implements AlignmentI
   @Override
   public void addCodonFrame(AlignedCodonFrame codons)
   {
-    if (codons == null)
+    if (codons != null)
     {
-      return;
+      codonFrameList.add(codons);
     }
-    if (codonFrameList == null)
-    {
-      codonFrameList = new AlignedCodonFrame[]
-      { codons };
-      return;
-    }
-    AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
-    System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
-    t[codonFrameList.length] = codons;
-    codonFrameList = t;
-  }
-
-  /*
-   * (non-Javadoc)
-   * 
-   * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
-   */
-  @Override
-  public AlignedCodonFrame getCodonFrame(int index)
-  {
-    return codonFrameList[index];
   }
 
   /*
@@ -1251,36 +1283,42 @@ public class Alignment implements AlignmentI
    * jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
    */
   @Override
-  public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
+  public List<AlignedCodonFrame> getCodonFrame(SequenceI seq)
   {
-    if (seq == null || codonFrameList == null)
+    if (seq == null)
     {
       return null;
     }
-    Vector cframes = new Vector();
-    for (int f = 0; f < codonFrameList.length; f++)
+    List<AlignedCodonFrame> cframes = new ArrayList<AlignedCodonFrame>();
+    for (AlignedCodonFrame acf : codonFrameList)
     {
-      if (codonFrameList[f].involvesSequence(seq))
+      if (acf.involvesSequence(seq))
       {
-        cframes.addElement(codonFrameList[f]);
+        cframes.add(acf);
       }
     }
-    if (cframes.size() == 0)
-    {
-      return null;
-    }
-    AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
-    cframes.copyInto(cfr);
-    return cfr;
+    return cframes;
   }
 
-  /*
-   * (non-Javadoc)
+  /**
+   * Sets the codon frame mappings (replacing any existing mappings).
+   * 
+   * @see jalview.datamodel.AlignmentI#setCodonFrames()
+   */
+  @Override
+  public void setCodonFrames(Set<AlignedCodonFrame> acfs)
+  {
+    this.codonFrameList = acfs;
+  }
+
+  /**
+   * Returns the set of codon frame mappings. Any changes to the returned set
+   * will affect the alignment.
    * 
    * @see jalview.datamodel.AlignmentI#getCodonFrames()
    */
   @Override
-  public AlignedCodonFrame[] getCodonFrames()
+  public Set<AlignedCodonFrame> getCodonFrames()
   {
     return codonFrameList;
   }
@@ -1298,26 +1336,7 @@ public class Alignment implements AlignmentI
     {
       return false;
     }
-    boolean removed = false;
-    int i = 0, iSize = codonFrameList.length;
-    while (i < iSize)
-    {
-      if (codonFrameList[i] == codons)
-      {
-        removed = true;
-        if (i + 1 < iSize)
-        {
-          System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
-                  - i - 1);
-        }
-        iSize--;
-      }
-      else
-      {
-        i++;
-      }
-    }
-    return removed;
+    return codonFrameList.remove(codons);
   }
 
   @Override
@@ -1362,11 +1381,9 @@ public class Alignment implements AlignmentI
     {
       addAnnotation(alan[a]);
     }
-    AlignedCodonFrame[] acod = toappend.getCodonFrames();
-    for (int a = 0; acod != null && a < acod.length; a++)
-    {
-      this.addCodonFrame(acod[a]);
-    }
+
+    this.codonFrameList.addAll(toappend.getCodonFrames());
+
     List<SequenceGroup> sg = toappend.getGroups();
     if (sg != null)
     {
@@ -1627,4 +1644,94 @@ public class Alignment implements AlignmentI
   {
     return dataset;
   }
+
+  /**
+   * Align this alignment like the given (mapped) one.
+   */
+  @Override
+  public int alignAs(AlignmentI al)
+  {
+    /*
+     * Currently retains unmapped gaps (in introns), regaps mapped regions
+     * (exons)
+     */
+    return alignAs(al, false, true);
+  }
+
+  /**
+   * Align this alignment 'the same as' the given one. Mapped sequences only are
+   * realigned. If both of the same type (nucleotide/protein) then align both
+   * identically. If this is nucleotide and the other is protein, make 3 gaps
+   * for each gap in the protein sequences. If this is protein and the other is
+   * nucleotide, insert a gap for each 3 gaps (or part thereof) between
+   * nucleotide bases. Does nothing if alignment of protein from cDNA is
+   * requested (not yet implemented).
+   * 
+   * Parameters control whether gaps in exon (mapped) and intron (unmapped)
+   * regions are preserved. Gaps that connect introns to exons are treated
+   * conservatively, i.e. only preserved if both intron and exon gaps are
+   * preserved.
+   * 
+   * @param al
+   * @param preserveMappedGaps
+   *          if true, gaps within and between mapped codons are preserved
+   * @param preserveUnmappedGaps
+   *          if true, gaps within and between unmapped codons are preserved
+   */
+//  @Override
+  public int alignAs(AlignmentI al, boolean preserveMappedGaps,
+          boolean preserveUnmappedGaps)
+  {
+    // TODO should this method signature be the one in the interface?
+    int count = 0;
+    boolean thisIsNucleotide = this.isNucleotide();
+    boolean thatIsProtein = !al.isNucleotide();
+    if (!thatIsProtein && !thisIsNucleotide)
+    {
+      System.err
+              .println("Alignment of protein from cDNA not yet implemented");
+      return 0;
+      // todo: build it - a variant of Dna.CdnaTranslate()
+    }
+
+    char thisGapChar = this.getGapCharacter();
+    String gap = thisIsNucleotide && thatIsProtein ? String
+            .valueOf(new char[]
+            { thisGapChar, thisGapChar, thisGapChar }) : String
+            .valueOf(thisGapChar);
+
+    /*
+     * Get mappings from 'that' alignment's sequences to this.
+     */
+    for (SequenceI alignTo : getSequences())
+    {
+      count += AlignmentUtils.alignSequenceAs(alignTo, al, gap, preserveMappedGaps,
+              preserveUnmappedGaps) ? 1 : 0;
+    }
+    return count;
+  }
+
+  /**
+   * Returns the alignment in Fasta format. Behaviour of this method is not
+   * guaranteed between versions.
+   */
+  @Override
+  public String toString()
+  {
+    return new FastaFile().print(getSequencesArray());
+  }
+
+  /**
+   * Returns the set of distinct sequence names. No ordering is guaranteed.
+   */
+  @Override
+  public Set<String> getSequenceNames()
+  {
+    Set<String> names = new HashSet<String>();
+    for (SequenceI seq : getSequences())
+    {
+      names.add(seq.getName());
+    }
+    return names;
+  }
 }
index e137225..0d99155 100755 (executable)
@@ -27,9 +27,8 @@ import jalview.analysis.WUSSParseException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -155,7 +154,7 @@ public class AlignmentAnnotation
   /**
    * map of positions in the associated annotation
    */
-  public java.util.Hashtable<Integer, Annotation> sequenceMapping;
+  private Map<Integer, Annotation> sequenceMapping;
 
   /** DOCUMENT ME!! */
   public float graphMin;
@@ -499,6 +498,7 @@ public class AlignmentAnnotation
               : annotations[index + offset].secondaryStructure);
     }
 
+    @Override
     public String toString()
     {
       char[] string = new char[max - offset];
@@ -710,12 +710,13 @@ public class AlignmentAnnotation
       if (annotation.sequenceMapping != null)
       {
         Integer p = null;
-        sequenceMapping = new Hashtable();
-        Enumeration pos = annotation.sequenceMapping.keys();
-        while (pos.hasMoreElements())
+        sequenceMapping = new HashMap<Integer, Annotation>();
+        Iterator<Integer> pos = annotation.sequenceMapping.keySet()
+                .iterator();
+        while (pos.hasNext())
         {
           // could optimise this!
-          p = (Integer) pos.nextElement();
+          p = pos.next();
           Annotation a = annotation.sequenceMapping.get(p);
           if (a == null)
           {
@@ -789,11 +790,11 @@ public class AlignmentAnnotation
       int epos = sequenceRef.findPosition(endRes);
       if (sequenceMapping != null)
       {
-        Hashtable newmapping = new Hashtable();
-        Enumeration e = sequenceMapping.keys();
-        while (e.hasMoreElements())
+        Map<Integer, Annotation> newmapping = new HashMap<Integer, Annotation>();
+        Iterator<Integer> e = sequenceMapping.keySet().iterator();
+        while (e.hasNext())
         {
-          Integer pos = (Integer) e.nextElement();
+          Integer pos = e.next();
           if (pos.intValue() >= spos && pos.intValue() <= epos)
           {
             newmapping.put(pos, sequenceMapping.get(pos));
@@ -836,9 +837,10 @@ public class AlignmentAnnotation
    * 
    * @return DOCUMENT ME!
    */
+  @Override
   public String toString()
   {
-    StringBuffer buffer = new StringBuffer();
+    StringBuilder buffer = new StringBuilder(256);
 
     for (int i = 0; i < annotations.length; i++)
     {
@@ -911,7 +913,7 @@ public class AlignmentAnnotation
     {
       return;
     }
-    sequenceMapping = new java.util.Hashtable();
+    sequenceMapping = new HashMap<Integer, Annotation>();
 
     int seqPos;
 
@@ -1223,7 +1225,7 @@ public class AlignmentAnnotation
             .getTo() == sq.getDatasetSequence()) : false;
 
     // TODO build a better annotation element map and get rid of annotations[]
-    Hashtable<Integer, Annotation> mapForsq = new Hashtable();
+    Map<Integer, Annotation> mapForsq = new HashMap<Integer, Annotation>();
     if (sequenceMapping != null)
     {
       if (sp2sq != null)
@@ -1275,7 +1277,8 @@ public class AlignmentAnnotation
   {
     if (mapping != null)
     {
-      Hashtable<Integer, Annotation> old = sequenceMapping, remap = new Hashtable<Integer, Annotation>();
+      Map<Integer, Annotation> old = sequenceMapping;
+      Map<Integer, Annotation> remap = new HashMap<Integer, Annotation>();
       int index = -1;
       for (int mp[] : mapping)
       {
index c7e30a4..fe93683 100755 (executable)
@@ -5,16 +5,16 @@
  * 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 
+ * modify it under the terms of the GNU General 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.
+ * PURPOSE.  See the GNU General License for more details.
  * 
- * You should have received a copy of the GNU General Public License
+ * You should have received a copy of the GNU General License
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
@@ -23,6 +23,7 @@ package jalview.datamodel;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Data structure to hold and manipulate a multiple sequence alignment
@@ -34,15 +35,16 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return Number of sequences in alignment
    */
-  public int getHeight();
+  int getHeight();
 
   /**
+   * 
    * Calculates the maximum width of the alignment, including gaps.
    * 
    * @return Greatest sequence length within alignment.
    */
   @Override
-  public int getWidth();
+  int getWidth();
 
   /**
    * Calculates if this set of sequences (visible and invisible) are all the
@@ -50,7 +52,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return true if all sequences in alignment are the same length
    */
-  public boolean isAligned();
+  boolean isAligned();
 
   /**
    * Calculates if this set of sequences is all the same length
@@ -59,7 +61,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    *          optionally exclude hidden sequences from test
    * @return true if all (or just visible) sequences are the same length
    */
-  public boolean isAligned(boolean includeHidden);
+  boolean isAligned(boolean includeHidden);
 
   /**
    * Gets sequences as a Synchronized collection
@@ -67,14 +69,14 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @return All sequences in alignment.
    */
   @Override
-  public List<SequenceI> getSequences();
+  List<SequenceI> getSequences();
 
   /**
    * Gets sequences as a SequenceI[]
    * 
    * @return All sequences in alignment.
    */
-  public SequenceI[] getSequencesArray();
+  SequenceI[] getSequencesArray();
 
   /**
    * Find a specific sequence in this alignment.
@@ -84,7 +86,14 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return SequenceI at given index.
    */
-  public SequenceI getSequenceAt(int i);
+  SequenceI getSequenceAt(int i);
+
+  /**
+   * Returns a map of lists of sequences keyed by sequence name.
+   * 
+   * @return
+   */
+  Map<String, List<SequenceI>> getSequencesByName();
 
   /**
    * Add a new sequence to this alignment.
@@ -92,7 +101,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param seq
    *          New sequence will be added at end of alignment.
    */
-  public void addSequence(SequenceI seq);
+  void addSequence(SequenceI seq);
 
   /**
    * Used to set a particular index of the alignment with the given sequence.
@@ -102,7 +111,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param seq
    *          New sequence to be inserted.
    */
-  public void setSequenceAt(int i, SequenceI seq);
+  void setSequenceAt(int i, SequenceI seq);
 
   /**
    * Deletes a sequence from the alignment
@@ -110,7 +119,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param s
    *          Sequence to be deleted.
    */
-  public void deleteSequence(SequenceI s);
+  void deleteSequence(SequenceI s);
 
   /**
    * Deletes a sequence from the alignment.
@@ -118,7 +127,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param i
    *          Index of sequence to be deleted.
    */
-  public void deleteSequence(int i);
+  void deleteSequence(int i);
 
   /**
    * Finds sequence in alignment using sequence name as query.
@@ -128,9 +137,9 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return Sequence matching query, if found. If not found returns null.
    */
-  public SequenceI findName(String name);
+  SequenceI findName(String name);
 
-  public SequenceI[] findSequenceMatch(String name);
+  SequenceI[] findSequenceMatch(String name);
 
   /**
    * Finds index of a given sequence in the alignment.
@@ -140,7 +149,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return Index of sequence within the alignment or -1 if not found
    */
-  public int findIndex(SequenceI s);
+  int findIndex(SequenceI s);
 
   /**
    * Finds group that given sequence is part of.
@@ -151,7 +160,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @return First group found for sequence. WARNING : Sequences may be members
    *         of several groups. This method is incomplete.
    */
-  public SequenceGroup findGroup(SequenceI s);
+  SequenceGroup findGroup(SequenceI s);
 
   /**
    * Finds all groups that a given sequence is part of.
@@ -161,7 +170,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return All groups containing given sequence.
    */
-  public SequenceGroup[] findAllGroups(SequenceI s);
+  SequenceGroup[] findAllGroups(SequenceI s);
 
   /**
    * Adds a new SequenceGroup to this alignment.
@@ -169,7 +178,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param sg
    *          New group to be added.
    */
-  public void addGroup(SequenceGroup sg);
+  void addGroup(SequenceGroup sg);
 
   /**
    * Deletes a specific SequenceGroup
@@ -177,19 +186,19 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param g
    *          Group will be deleted from alignment.
    */
-  public void deleteGroup(SequenceGroup g);
+  void deleteGroup(SequenceGroup g);
 
   /**
    * Get all the groups associated with this alignment.
    * 
    * @return All groups as a list.
    */
-  public List<SequenceGroup> getGroups();
+  List<SequenceGroup> getGroups();
 
   /**
    * Deletes all groups from this alignment.
    */
-  public void deleteAllGroups();
+  void deleteAllGroups();
 
   /**
    * Adds a new AlignmentAnnotation to this alignment
@@ -197,7 +206,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @note Care should be taken to ensure that annotation is at least as wide as
    *       the longest sequence in the alignment for rendering purposes.
    */
-  public void addAnnotation(AlignmentAnnotation aa);
+  void addAnnotation(AlignmentAnnotation aa);
 
   /**
    * moves annotation to a specified index in alignment annotation display stack
@@ -207,7 +216,16 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param index
    *          the destination position
    */
-  public void setAnnotationIndex(AlignmentAnnotation aa, int index);
+  void setAnnotationIndex(AlignmentAnnotation aa, int index);
+
+  /**
+   * Delete all annotations, including auto-calculated if the flag is set true.
+   * Returns true if at least one annotation was deleted, else false.
+   * 
+   * @param includingAutoCalculated
+   * @return
+   */
+  boolean deleteAllAnnotations(boolean includingAutoCalculated);
 
   /**
    * Deletes a specific AlignmentAnnotation from the alignment, and removes its
@@ -219,7 +237,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    *          the annotation to delete
    * @return true if annotation was deleted from this alignment.
    */
-  public boolean deleteAnnotation(AlignmentAnnotation aa);
+  boolean deleteAnnotation(AlignmentAnnotation aa);
 
   /**
    * Deletes a specific AlignmentAnnotation from the alignment, and optionally
@@ -235,7 +253,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    *          into the alignment
    * @return true if annotation was deleted from this alignment.
    */
-  public boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook);
+  boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook);
 
   /**
    * Get the annotation associated with this alignment (this can be null if no
@@ -244,7 +262,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @return array of AlignmentAnnotation objects
    */
   @Override
-  public AlignmentAnnotation[] getAlignmentAnnotation();
+  AlignmentAnnotation[] getAlignmentAnnotation();
 
   /**
    * Change the gap character used in this alignment to 'gc'
@@ -252,34 +270,34 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param gc
    *          the new gap character.
    */
-  public void setGapCharacter(char gc);
+  void setGapCharacter(char gc);
 
   /**
    * Get the gap character used in this alignment
    * 
    * @return gap character
    */
-  public char getGapCharacter();
+  char getGapCharacter();
 
   /**
    * Test for all nucleotide alignment
    * 
    * @return true if alignment is nucleotide sequence
    */
-  public boolean isNucleotide();
+  boolean isNucleotide();
 
   /**
    * Test if alignment contains RNA structure
    * 
    * @return true if RNA structure AligmnentAnnotation was added to alignment
    */
-  public boolean hasRNAStructure();
+  boolean hasRNAStructure();
 
   /**
    * Set alignment to be a nucleotide sequence
    * 
    */
-  public void setNucleotide(boolean b);
+  void setNucleotide(boolean b);
 
   /**
    * Get the associated dataset for the alignment.
@@ -287,7 +305,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @return Alignment containing dataset sequences or null of this is a
    *         dataset.
    */
-  public Alignment getDataset();
+  Alignment getDataset();
 
   /**
    * Set the associated dataset for the alignment, or create one.
@@ -295,23 +313,23 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param dataset
    *          The dataset alignment or null to construct one.
    */
-  public void setDataset(Alignment dataset);
+  void setDataset(Alignment dataset);
 
   /**
    * pads sequences with gaps (to ensure the set looks like an alignment)
    * 
    * @return boolean true if alignment was modified
    */
-  public boolean padGaps();
+  boolean padGaps();
 
-  public HiddenSequences getHiddenSequences();
+  HiddenSequences getHiddenSequences();
 
   /**
    * Compact representation of alignment
    * 
    * @return CigarArray
    */
-  public CigarArray getCompactAlignment();
+  CigarArray getCompactAlignment();
 
   /**
    * Set an arbitrary key value pair for an alignment. Note: both key and value
@@ -320,7 +338,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param key
    * @param value
    */
-  public void setProperty(Object key, Object value);
+  void setProperty(Object key, Object value);
 
   /**
    * Get a named property from the alignment.
@@ -328,21 +346,21 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param key
    * @return value of property
    */
-  public Object getProperty(Object key);
+  Object getProperty(Object key);
 
   /**
    * Get the property hashtable.
    * 
    * @return hashtable of alignment properties (or null if none are defined)
    */
-  public Hashtable getProperties();
+  Hashtable getProperties();
 
   /**
    * add a reference to a frame of aligned codons for this alignment
    * 
    * @param codons
    */
-  public void addCodonFrame(AlignedCodonFrame codons);
+  void addCodonFrame(AlignedCodonFrame codons);
 
   /**
    * remove a particular codon frame reference from this alignment
@@ -350,27 +368,24 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param codons
    * @return true if codon frame was removed.
    */
-  public boolean removeCodonFrame(AlignedCodonFrame codons);
+  boolean removeCodonFrame(AlignedCodonFrame codons);
 
   /**
    * get all codon frames associated with this alignment
    * 
    * @return
    */
-  public AlignedCodonFrame[] getCodonFrames();
+  Set<AlignedCodonFrame> getCodonFrames();
 
   /**
-   * get a particular codon frame
-   * 
-   * @param index
-   * @return
+   * Set the codon frame mappings (replacing any existing set).
    */
-  public AlignedCodonFrame getCodonFrame(int index);
+  void setCodonFrames(Set<AlignedCodonFrame> acfs);
 
   /**
    * get codon frames involving sequenceI
    */
-  public AlignedCodonFrame[] getCodonFrame(SequenceI seq);
+  List<AlignedCodonFrame> getCodonFrame(SequenceI seq);
 
   /**
    * find sequence with given name in alignment
@@ -382,7 +397,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    *          tried
    * @return matched sequence or null
    */
-  public SequenceI findName(String token, boolean b);
+  SequenceI findName(String token, boolean b);
 
   /**
    * find next sequence with given name in alignment starting after a given
@@ -398,7 +413,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    *          tried
    * @return matched sequence or null
    */
-  public SequenceI findName(SequenceI startAfter, String token, boolean b);
+  SequenceI findName(SequenceI startAfter, String token, boolean b);
 
   /**
    * find first sequence in alignment which is involved in the given search
@@ -407,7 +422,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param results
    * @return -1 or index of sequence in alignment
    */
-  public int findIndex(SearchResults results);
+  int findIndex(SearchResults results);
 
   /**
    * append sequences and annotation from another alignment object to this one.
@@ -420,7 +435,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param toappend
    *          - the alignment to be appended.
    */
-  public void append(AlignmentI toappend);
+  void append(AlignmentI toappend);
 
   /**
    * Justify the sequences to the left or right by deleting and inserting gaps
@@ -430,7 +445,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    *          true if alignment padded to right, false to justify to left
    * @return true if alignment was changed TODO: return undo object
    */
-  public boolean justify(boolean right);
+  boolean justify(boolean right);
 
   /**
    * add given annotation row at given position (0 is start, -1 is end)
@@ -438,7 +453,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param consensus
    * @param i
    */
-  public void addAnnotation(AlignmentAnnotation consensus, int i);
+  void addAnnotation(AlignmentAnnotation consensus, int i);
 
   /**
    * search for or create a specific annotation row on the alignment
@@ -458,7 +473,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @return existing annotation matching the given attributes
    */
-  public AlignmentAnnotation findOrCreateAnnotation(String name,
+  AlignmentAnnotation findOrCreateAnnotation(String name,
           String calcId, boolean autoCalc, SequenceI seqRef,
           SequenceGroup groupRef);
 
@@ -472,7 +487,7 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @param up
    * @param i
    */
-  public void moveSelectedSequencesByOne(SequenceGroup sg,
+  void moveSelectedSequencesByOne(SequenceGroup sg,
           Map<SequenceI, SequenceCollectionI> map, boolean up);
 
   /**
@@ -481,5 +496,25 @@ public interface AlignmentI extends AnnotatedCollectionI
    * 
    * @param alignmentAnnotation
    */
-  public void validateAnnotation(AlignmentAnnotation alignmentAnnotation);
+  void validateAnnotation(AlignmentAnnotation alignmentAnnotation);
+
+  /**
+   * Align this alignment the same as the given one. If both of the same type
+   * (nucleotide/protein) then align both identically. If this is nucleotide and
+   * the other is protein, make 3 gaps for each gap in the protein sequences. If
+   * this is protein and the other is nucleotide, insert a gap for each 3 gaps
+   * (or part thereof) between nucleotide bases. Returns the number of mapped
+   * sequences that were realigned .
+   * 
+   * @param al
+   * @return
+   */
+  int alignAs(AlignmentI al);
+
+  /**
+   * Returns the set of distinct sequence names in the alignment.
+   * 
+   * @return
+   */
+  Set<String> getSequenceNames();
 }
index c0f9ab4..92d5f6c 100755 (executable)
@@ -20,7 +20,9 @@
  */
 package jalview.datamodel;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 public class AlignmentOrder
 {
@@ -51,7 +53,7 @@ public class AlignmentOrder
 
   private String Name;
 
-  private Vector Order = null;
+  private List<SequenceI> Order = null;
 
   /**
    * Creates a new AlignmentOrder object.
@@ -64,9 +66,8 @@ public class AlignmentOrder
    * AlignmentOrder
    * 
    * @param anOrder
-   *          Vector
    */
-  public AlignmentOrder(Vector anOrder)
+  public AlignmentOrder(List<SequenceI> anOrder)
   {
     Order = anOrder;
   }
@@ -79,11 +80,11 @@ public class AlignmentOrder
    */
   public AlignmentOrder(AlignmentI orderFrom)
   {
-    Order = new Vector();
+    Order = new ArrayList<SequenceI>();
 
-    for (int i = 0, ns = orderFrom.getHeight(); i < ns; i++)
+    for (SequenceI seq : orderFrom.getSequences())
     {
-      Order.addElement(orderFrom.getSequenceAt(i));
+      Order.add(seq);
     }
   }
 
@@ -95,12 +96,7 @@ public class AlignmentOrder
    */
   public AlignmentOrder(SequenceI[] orderFrom)
   {
-    Order = new Vector();
-
-    for (int i = 0, ns = orderFrom.length; i < ns; i++)
-    {
-      Order.addElement(orderFrom[i]);
-    }
+    Order = new ArrayList<SequenceI>(Arrays.asList(orderFrom));
   }
 
   /**
@@ -151,7 +147,7 @@ public class AlignmentOrder
    * @param Order
    *          DOCUMENT ME!
    */
-  public void setOrder(Vector Order)
+  public void setOrder(List<SequenceI> Order)
   {
     this.Order = Order;
   }
@@ -161,7 +157,7 @@ public class AlignmentOrder
    * 
    * @return DOCUMENT ME!
    */
-  public Vector getOrder()
+  public List<SequenceI> getOrder()
   {
     return Order;
   }
@@ -178,7 +174,7 @@ public class AlignmentOrder
     int found = Order.indexOf(oldref);
     if (found > -1)
     {
-      Order.setElementAt(newref, found);
+      Order.set(found, newref);
     }
     return found > -1;
   }
@@ -189,9 +185,14 @@ public class AlignmentOrder
    * @param o
    * @return true if o orders the same sequenceI objects in the same way
    */
-  public boolean equals(AlignmentOrder o)
+  @Override
+  public boolean equals(Object o)
   {
-    return equals(o, true);
+    if (o == null || !(o instanceof AlignmentOrder))
+    {
+      return false;
+    }
+    return equals((AlignmentOrder) o, true);
   }
 
   /**
@@ -223,7 +224,7 @@ public class AlignmentOrder
         {
           for (int i = 0, j = o.Order.size(); i < j; i++)
           {
-            if (Order.elementAt(i) != o.Order.elementAt(i))
+            if (Order.get(i) != o.Order.get(i))
             {
               return false;
             }
@@ -271,7 +272,7 @@ public class AlignmentOrder
       }
       if (Order != null && o.Order != null)
       {
-        Vector c, s;
+        List<SequenceI> c, s;
         if (o.Order.size() > Order.size())
         {
           c = o.Order;
@@ -292,7 +293,7 @@ public class AlignmentOrder
           int last = -1;
           for (int i = 0, j = s.size(); i < j; i++)
           {
-            int pos = c.indexOf(s.elementAt(i)); // JBPNote - optimize by
+            int pos = c.indexOf(s.get(i)); // JBPNote - optimize by
             // incremental position search
             if (pos > last)
             {
index 6acca32..c661f37 100644 (file)
@@ -25,7 +25,7 @@ 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;
@@ -303,13 +303,13 @@ public class ColumnSelection
   {
     if (shiftrecord != null)
     {
-      Vector shifts = shiftrecord.shifts;
+      List<int[]> shifts = shiftrecord.getShifts();
       if (shifts != null && shifts.size() > 0)
       {
         int shifted = 0;
         for (int i = 0, j = shifts.size(); i < j; i++)
         {
-          int[] sh = (int[]) shifts.elementAt(i);
+          int[] sh = shifts.get(i);
           // compensateForEdit(shifted+sh[0], sh[1]);
           compensateForDelEdits(shifted + sh[0], sh[1]);
           shifted -= sh[1];
@@ -324,16 +324,16 @@ public class ColumnSelection
    * removes intersection of position,length ranges in deletions from the
    * start,end regions marked in intervals.
    * 
-   * @param deletions
+   * @param shifts
    * @param intervals
    * @return
    */
-  private boolean pruneIntervalVector(Vector deletions, Vector intervals)
+  private boolean pruneIntervalVector(List<int[]> shifts, Vector intervals)
   {
     boolean pruned = false;
-    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];
@@ -354,7 +354,7 @@ public class ColumnSelection
       { // leadinghc disjoint or not a deletion
         if (s < t)
         {
-          sr = (int[]) deletions.elementAt(++s);
+          sr = shifts.get(++s);
         }
         else
         {
@@ -400,7 +400,7 @@ public class ColumnSelection
           // sr contained in hr
           if (s < t)
           {
-            sr = (int[]) deletions.elementAt(++s);
+            sr = shifts.get(++s);
           }
           else
           {
@@ -414,10 +414,10 @@ public class ColumnSelection
     // operations.
   }
 
-  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)
@@ -434,7 +434,7 @@ public class ColumnSelection
         {
           if (s < t)
           {
-            sr = (int[]) deletion.elementAt(s);
+            sr = shifts.get(s);
           }
           s++;
         }
@@ -453,7 +453,7 @@ public class ColumnSelection
   {
     if (deletions != null)
     {
-      Vector shifts = deletions.shifts;
+      List<int[]> shifts = deletions.getShifts();
       if (shifts != null && shifts.size() > 0)
       {
         // delete any intervals intersecting.
@@ -485,8 +485,8 @@ public class ColumnSelection
    */
   public List<int[]> getHiddenColumns()
   {
-    return hiddenColumns == null ? Arrays.asList(new int[]
-    {}) : hiddenColumns;
+    return hiddenColumns == null ? Collections.<int[]> emptyList()
+            : hiddenColumns;
   }
 
   /**
index cb87719..f2c16d0 100644 (file)
  */
 package jalview.datamodel;
 
-import java.util.Vector;
-
 import jalview.util.MapList;
 
+import java.util.Vector;
+
 public class Mapping
 {
   /**
    * Contains the start-end pairs mapping from the associated sequence to the
-   * sequence in the database coordinate system it also takes care of step
-   * difference between coordinate systems
+   * sequence in the database coordinate system. It also takes care of step
+   * difference between coordinate systems.
    */
   MapList map = null;
 
   /**
-   * The seuqence that map maps the associated seuqence to (if any).
+   * The sequence that map maps the associated sequence to (if any).
    */
   SequenceI to = null;
 
@@ -111,19 +111,31 @@ public class Mapping
    * @param other
    * @return
    */
-  public boolean equals(Mapping other)
+  @Override
+  public boolean equals(Object o)
   {
-    if (other == null)
+    if (o == null || !(o instanceof Mapping))
+    {
       return false;
+    }
+    Mapping other = (Mapping) o;
     if (other == this)
+    {
       return true;
+    }
     if (other.to != to)
+    {
       return false;
+    }
     if ((map != null && other.map == null)
             || (map == null && other.map != null))
+    {
       return false;
+    }
     if (map.equals(other.map))
+    {
       return true;
+    }
     return false;
   }
 
@@ -251,7 +263,9 @@ public class Mapping
           vf[v].setBegin(frange[i]);
           vf[v].setEnd(frange[i + 1]);
           if (frange.length > 2)
+          {
             vf[v].setDescription(f.getDescription() + "\nPart " + (v + 1));
+          }
         }
         return vf;
       }
@@ -300,14 +314,18 @@ public class Mapping
         from = (map.getToLowest() < from) ? from : map.getToLowest();
         to = (map.getToHighest() > to) ? to : map.getToHighest();
         if (from > to)
+        {
           return null;
+        }
       }
       else
       {
         from = (map.getToHighest() > from) ? from : map.getToHighest();
         to = (map.getToLowest() < to) ? to : map.getToLowest();
         if (from < to)
+        {
           return null;
+        }
       }
       return map.locateInFrom(from, to);
     }
@@ -333,14 +351,18 @@ public class Mapping
         from = (map.getFromLowest() < from) ? from : map.getFromLowest();
         to = (map.getFromHighest() > to) ? to : map.getFromHighest();
         if (from > to)
+        {
           return null;
+        }
       }
       else
       {
         from = (map.getFromHighest() > from) ? from : map.getFromHighest();
         to = (map.getFromLowest() < to) ? to : map.getFromLowest();
         if (from < to)
+        {
           return null;
+        }
       }
       return map.locateInTo(from, to);
     }
index 8434e81..7a241fd 100755 (executable)
  */
 package jalview.datamodel;
 
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a list of search result matches, where each match is a contiguous
+ * stretch of a single sequence.
+ * 
+ * @author gmcarstairs
+ *
+ */
 public class SearchResults
 {
 
-  Match[] matches;
+  private List<Match> matches = new ArrayList<Match>();
+
+  public class Match
+  {
+    SequenceI sequence;
+
+    int start;
+
+    int end;
+
+    public Match(SequenceI seq, int start, int end)
+    {
+      sequence = seq;
+      this.start = start;
+      this.end = end;
+    }
+
+    public SequenceI getSequence()
+    {
+      return sequence;
+    }
+
+    public int getStart()
+    {
+      return start;
+    }
+
+    public int getEnd()
+    {
+      return end;
+    }
+  }
 
   /**
    * This method replaces the old search results which merely held an alignment
@@ -39,25 +80,7 @@ public class SearchResults
    */
   public void addResult(SequenceI seq, int start, int end)
   {
-    if (matches == null)
-    {
-      matches = new Match[]
-      { new Match(seq, start, end) };
-      return;
-    }
-
-    int mSize = matches.length;
-
-    Match[] tmp = new Match[mSize + 1];
-    int m;
-    for (m = 0; m < mSize; m++)
-    {
-      tmp[m] = matches[m];
-    }
-
-    tmp[m] = new Match(seq, start, end);
-
-    matches = tmp;
+    matches.add(new Match(seq, start, end));
   }
 
   /**
@@ -69,15 +92,11 @@ public class SearchResults
    */
   public boolean involvesSequence(SequenceI sequence)
   {
-    if (matches == null || matches.length == 0)
-    {
-      return false;
-    }
     SequenceI ds = sequence.getDatasetSequence();
-    for (int m = 0; m < matches.length; m++)
+    for (Match m : matches)
     {
-      if (matches[m].sequence != null
-              && (matches[m].sequence == sequence || matches[m].sequence == ds))
+      if (m.sequence != null
+              && (m.sequence == sequence || m.sequence == ds))
       {
         return true;
       }
@@ -92,7 +111,7 @@ public class SearchResults
    */
   public int[] getResults(SequenceI sequence, int start, int end)
   {
-    if (matches == null)
+    if (matches.isEmpty())
     {
       return null;
     }
@@ -101,22 +120,22 @@ public class SearchResults
     int[] tmp = null;
     int resultLength, matchStart = 0, matchEnd = 0;
     boolean mfound;
-    for (int m = 0; m < matches.length; m++)
+    for (Match m : matches)
     {
       mfound = false;
-      if (matches[m].sequence == sequence)
+      if (m.sequence == sequence)
       {
         mfound = true;
         // locate aligned position
-        matchStart = sequence.findIndex(matches[m].start) - 1;
-        matchEnd = sequence.findIndex(matches[m].end) - 1;
+        matchStart = sequence.findIndex(m.start) - 1;
+        matchEnd = sequence.findIndex(m.end) - 1;
       }
-      else if (matches[m].sequence == sequence.getDatasetSequence())
+      else if (m.sequence == sequence.getDatasetSequence())
       {
         mfound = true;
         // locate region in local context
-        matchStart = sequence.findIndex(matches[m].start) - 1;
-        matchEnd = sequence.findIndex(matches[m].end) - 1;
+        matchStart = sequence.findIndex(m.start) - 1;
+        matchEnd = sequence.findIndex(m.end) - 1;
       }
       if (mfound)
       {
@@ -160,37 +179,53 @@ public class SearchResults
 
   public int getSize()
   {
-    return matches == null ? 0 : matches.length;
+    return matches.size();
   }
 
   public SequenceI getResultSequence(int index)
   {
-    return matches[index].sequence;
+    return matches.get(index).sequence;
   }
 
-  public int getResultStart(int index)
+  /**
+   * Returns the start position of the i'th match in the search results.
+   * 
+   * @param i
+   * @return
+   */
+  public int getResultStart(int i)
   {
-    return matches[index].start;
+    return matches.get(i).start;
   }
 
-  public int getResultEnd(int index)
+  /**
+   * Returns the end position of the i'th match in the search results.
+   * 
+   * @param i
+   * @return
+   */
+  public int getResultEnd(int i)
   {
-    return matches[index].end;
+    return matches.get(i).end;
   }
 
-  class Match
+  /**
+   * Returns true if no search result matches are held.
+   * 
+   * @return
+   */
+  public boolean isEmpty()
   {
-    SequenceI sequence;
-
-    int start;
-
-    int end;
+    return matches.isEmpty();
+  }
 
-    public Match(SequenceI seq, int start, int end)
-    {
-      sequence = seq;
-      this.start = start;
-      this.end = end;
-    }
+  /**
+   * Returns the list of matches.
+   * 
+   * @return
+   */
+  public List<Match> getResults()
+  {
+    return matches;
   }
 }
index 96e469a..963a7cf 100755 (executable)
@@ -725,6 +725,7 @@ public class Sequence implements SequenceI
     {
       return;
     }
+    // TODO use StringUtils.deleteChars
 
     char[] tmp;
 
@@ -1024,7 +1025,7 @@ public class Sequence implements SequenceI
           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
           _aa.sequenceRef = datasetSequence;
           _aa.adjustForAlignment(); // uses annotation's own record of
-                                   // sequence-column mapping
+                                    // sequence-column mapping
           datasetSequence.addAlignmentAnnotation(_aa);
         }
       }
@@ -1245,8 +1246,10 @@ public class Sequence implements SequenceI
           String label)
   {
     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
-    if (this.annotation != null) {
-      for (AlignmentAnnotation ann : annotation) {
+    if (this.annotation != null)
+    {
+      for (AlignmentAnnotation ann : annotation)
+      {
         if (ann.calcId != null && ann.calcId.equals(calcId)
                 && ann.label != null && ann.label.equals(label))
         {
index 752c6d4..9c046c6 100755 (executable)
@@ -27,7 +27,6 @@ import jalview.schemes.ResidueProperties;
 
 import java.awt.Color;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -160,13 +159,10 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     if (seqsel != null)
     {
-      sequences = new Vector();
-      Enumeration<SequenceI> sq = seqsel.sequences.elements();
-      while (sq.hasMoreElements())
+      for (SequenceI seq : seqsel.sequences)
       {
-        sequences.addElement(sq.nextElement());
+        sequences.addElement(seq);
       }
-      ;
       if (seqsel.groupName != null)
       {
         groupName = new String(seqsel.groupName);
@@ -989,12 +985,15 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     SequenceGroup sgroup = new SequenceGroup(this);
     SequenceI[] insect = getSequencesInOrder(alignment);
-    sgroup.sequences = new Vector();
-    for (int s = 0; insect != null && s < insect.length; s++)
+    sgroup.sequences = new Vector<SequenceI>();
+    if (insect != null)
     {
-      if (map == null || map.containsKey(insect[s]))
+      for (SequenceI seq : insect)
       {
-        sgroup.sequences.addElement(insect[s]);
+        if (map == null || map.containsKey(seq))
+        {
+          sgroup.sequences.addElement(seq);
+        }
       }
     }
     // Enumeration en =getSequences(hashtable).elements();
index e8c1d71..0c29d8c 100755 (executable)
@@ -172,8 +172,7 @@ public interface SequenceI
   public String getDescription();
 
   /**
-   * Return the alignment column for a sequence position * Return the alignment
-   * position for a sequence position
+   * Return the alignment column for a sequence position
    * 
    * @param pos
    *          lying from start to end
@@ -220,9 +219,9 @@ public interface SequenceI
    * if necessary and adjusting start and end positions accordingly.
    * 
    * @param i
-   *          first column in range to delete
+   *          first column in range to delete (inclusive)
    * @param j
-   *          last column in range to delete
+   *          last column in range to delete (exclusive)
    */
   public void deleteChars(int i, int j);
 
@@ -238,13 +237,12 @@ public interface SequenceI
 
   /**
    * DOCUMENT ME!
-   * 
-   * @param i
+   * @param position
    *          DOCUMENT ME!
-   * @param c
+   * @param ch
    *          DOCUMENT ME!
    */
-  public void insertCharAt(int i, int length, char c);
+  public void insertCharAt(int position, int count, char ch);
 
   /**
    * DOCUMENT ME!
index 3c88083..47c732f 100644 (file)
@@ -425,9 +425,13 @@ public class EmblEntry
       { 1, dna.getLength() }, 1, 1));
       // TODO: transform EMBL Database refs to canonical form
       if (dbRefs != null)
+      {
         for (Iterator i = dbRefs.iterator(); i.hasNext(); dna
                 .addDBRef((DBRefEntry) i.next()))
+        {
           ;
+        }
+      }
     }
     try
     {
@@ -440,7 +444,9 @@ public class EmblEntry
           {
             for (Iterator dbr = feature.dbRefs.iterator(); dbr.hasNext(); dna
                     .addDBRef((DBRefEntry) dbr.next()))
+            {
               ;
+            }
           }
         }
         if (FeatureProperties.isCodingFeature(sourceDb, feature.getName()))
@@ -456,7 +462,9 @@ public class EmblEntry
             {
               for (Iterator dbr = feature.dbRefs.iterator(); dbr.hasNext(); dna
                       .addDBRef((DBRefEntry) dbr.next()))
+              {
                 ;
+              }
             }
           }
         }
@@ -659,7 +667,9 @@ public class EmblEntry
           // { 1prstart, prstart + prseq.length() - 1 }, 3, 1);
           pcdnaref.setMap(new Mapping(mp));
           if (product != null)
+          {
             product.addDBRef(pcdnaref);
+          }
 
         }
       }
@@ -671,18 +681,20 @@ public class EmblEntry
         sf.setEnd(exon[xint + 1]);
         sf.setType(feature.getName());
         sf.setFeatureGroup(sourceDb);
-        sf.setDescription("Exon " + (1 + (int) (xint / 2))
+        sf.setDescription("Exon " + (1 + xint / 2)
                 + " for protein '" + prname + "' EMBLCDS:" + prid);
         sf.setValue(FeatureProperties.EXONPOS, new Integer(1 + xint));
         sf.setValue(FeatureProperties.EXONPRODUCT, prname);
         if (vals != null && vals.size() > 0)
         {
-          Enumeration kv = vals.elements();
+          Enumeration kv = vals.keys();
           while (kv.hasMoreElements())
           {
             Object key = kv.nextElement();
             if (key != null)
+            {
               sf.setValue(key.toString(), vals.get(key));
+            }
           }
         }
         dna.addSequenceFeature(sf);
index 619144e..9569bd0 100644 (file)
@@ -43,7 +43,6 @@ 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.Map;
 import java.util.Vector;
@@ -1328,17 +1327,15 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       return;
     }
 
-    String res;
     int index;
     Color col;
     jmolHistory(false);
     // TODO: Switch between nucleotide or aa selection expressions
-    Enumeration en = ResidueProperties.aa3Hash.keys();
-    StringBuffer command = new StringBuffer("select *;color white;");
-    while (en.hasMoreElements())
+    StringBuilder command = new StringBuilder(128);
+    command.append("select *;color white;");
+    for (String res : ResidueProperties.aa3Hash.keySet())
     {
-      res = en.nextElement().toString();
-      index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
+      index = ResidueProperties.aa3Hash.get(res).intValue();
       if (index > 20)
       {
         continue;
index b345c5e..f511a65 100644 (file)
@@ -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;
@@ -39,7 +19,6 @@ import jalview.util.MessageManager;
 
 import java.awt.Color;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -693,7 +672,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
     AlignmentI alignment = alignmentv.getAlignment();
 
-    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(files, sr, fr, alignment))
+    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
+            files, sr, fr, alignment))
     {
       for (String command : cpdbbyseq.commands)
       {
@@ -713,10 +693,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
           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);
   }
 
   /**
@@ -740,7 +718,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
   }
 
-  
+
 
   // End StructureListener
   // //////////////////////////
@@ -877,7 +855,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         eval.append("." + chain);
         resetLastRes.append("." + chain);
       }
-      
+
       viewer.sendChimeraCommand(eval.toString(), false);
       viewerCommandHistory(true);
       // viewer.startListening();
@@ -1140,20 +1118,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       return;
     }
 
-    String res;
     int index;
     Color col;
     // Chimera expects RBG values in the range 0-1
     final double normalise = 255D;
     viewerCommandHistory(false);
     // TODO: Switch between nucleotide or aa selection expressions
-    Enumeration en = ResidueProperties.aa3Hash.keys();
     StringBuilder command = new StringBuilder(128);
     command.append("color white;");
-    while (en.hasMoreElements())
+    for (String res : ResidueProperties.aa3Hash.keySet())
     {
-      res = en.nextElement().toString();
-      index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
+      index = ResidueProperties.aa3Hash.get(res).intValue();
       if (index > 20)
       {
         continue;
@@ -1287,4 +1262,4 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     return chainNames;
   }
 
-}
+}
\ No newline at end of file
index b9727e7..3ef56bf 100644 (file)
@@ -23,13 +23,15 @@ package jalview.gui;
 import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.AlignmentUtils;
+import jalview.analysis.AlignmentUtils.MappingResult;
 import jalview.analysis.Conservation;
 import jalview.analysis.CrossRef;
-import jalview.analysis.NJTree;
+import jalview.analysis.Dna;
 import jalview.analysis.ParseProperties;
 import jalview.analysis.SequenceIdMatcher;
 import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
+import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.analysis.ScoreModelI;
 import jalview.bin.Cache;
@@ -86,7 +88,9 @@ import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.UserColourScheme;
 import jalview.schemes.ZappoColourScheme;
+import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.jws1.Discoverer;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -121,9 +125,11 @@ 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;
+import java.util.Set;
 import java.util.Vector;
 
 import javax.swing.JButton;
@@ -151,19 +157,20 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         IProgressIndicator, AlignViewControllerGuiI
 {
 
-  /** DOCUMENT ME!! */
   public static final int DEFAULT_WIDTH = 700;
 
-  /** DOCUMENT ME!! */
   public static final int DEFAULT_HEIGHT = 500;
 
+  /*
+   * The currently displayed panel (selected tabbed view if more than one)
+   */
   public AlignmentPanel alignPanel;
 
   AlignViewport viewport;
 
   public AlignViewControllerI avc;
 
-  Vector alignPanels = new Vector();
+  List<AlignmentPanel> alignPanels = new ArrayList<AlignmentPanel>();
 
   /**
    * Last format used to load or save alignments in this window
@@ -406,6 +413,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     reload.setEnabled(true);
   }
 
+  /**
+   * Add a KeyListener with handlers for various KeyPressed and KeyReleased
+   * events
+   */
   void addKeyListener()
   {
     addKeyListener(new KeyAdapter()
@@ -634,7 +645,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     avc = new jalview.controller.AlignViewController(this, viewport,
             alignPanel);
 
-    alignPanels.addElement(ap);
+    alignPanels.add(ap);
 
     PaintRefresher.Register(ap, ap.av.getSequenceSetId());
 
@@ -677,7 +688,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     expandViews.setEnabled(true);
     gatherViews.setEnabled(true);
     tabbedPane.setVisible(true);
-    AlignmentPanel first = (AlignmentPanel) alignPanels.firstElement();
+    AlignmentPanel first = alignPanels.get(0);
     tabbedPane.addTab(first.av.viewName, first);
     this.getContentPane().add(tabbedPane, BorderLayout.CENTER);
   }
@@ -741,21 +752,102 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void setGUINucleotide(boolean nucleotide)
   {
     showTranslation.setVisible(nucleotide);
+    cdna.setVisible(!nucleotide);
     conservationMenuItem.setEnabled(!nucleotide);
     modifyConservation.setEnabled(!nucleotide);
     showGroupConservation.setEnabled(!nucleotide);
     rnahelicesColour.setEnabled(nucleotide);
     purinePyrimidineColour.setEnabled(nucleotide);
-    // Remember AlignFrame always starts as protein
-    // if (!nucleotide)
-    // {
-    // showTr
-    // calculateMenu.remove(calculateMenu.getItemCount() - 2);
-    // }
   }
 
   /**
-   * set up menus for the currently viewport. This may be called after any
+   * Builds codon mappings from this (protein) alignment to any compatible
+   * nucleotide alignments. Mappings are built between sequences with the same
+   * name and compatible lengths. Also makes the cDNA alignment a
+   * CommandListener for the protein alignment so that edits are mirrored.
+   */
+  @Override
+  protected void linkCdna_actionPerformed()
+  {
+    int linkedCount = 0;
+    int alreadyLinkedCount = 0;
+    final AlignmentI thisAlignment = this.alignPanel.getAlignment();
+
+    for (AlignFrame af : Desktop.getAlignFrames())
+    {
+      if (af.alignPanel != null)
+      {
+        final AlignmentI thatAlignment = af.alignPanel.getAlignment();
+        if (thatAlignment.isNucleotide())
+        {
+          MappingResult mapped = AlignmentUtils.mapProteinToCdna(
+                  thisAlignment, thatAlignment);
+          if (mapped == MappingResult.AlreadyMapped)
+          {
+            alreadyLinkedCount++;
+          }
+          else if (mapped == MappingResult.Mapped)
+          {
+            final StructureSelectionManager ssm = StructureSelectionManager
+                    .getStructureSelectionManager(Desktop.instance);
+            ssm.addMappings(thisAlignment.getCodonFrames());
+            // enable the next line to enable linked editing
+            // ssm.addCommandListener(af.getViewport());
+            linkedCount++;
+          }
+        }
+      }
+    }
+    String msg = "";
+    if (linkedCount == 0 && alreadyLinkedCount == 0)
+    {
+      msg = MessageManager.getString("label.no_cdna");
+    }
+    else if (linkedCount > 0)
+    {
+      msg = MessageManager.formatMessage("label.linked_cdna", linkedCount);
+    }
+    else
+    {
+      msg = MessageManager.formatMessage("label.cdna_all_linked",
+              alreadyLinkedCount);
+    }
+    setStatus(msg);
+  }
+
+  /**
+   * Align any linked cDNA to match the alignment of this (protein) alignment.
+   * Any mapped sequence regions will be realigned, unmapped sequences are not
+   * affected.
+   */
+  @Override
+  protected void alignCdna_actionPerformed()
+  {
+    int seqCount = 0;
+    int alignCount = 0;
+    final AlignmentI thisAlignment = this.alignPanel.getAlignment();
+    for (AlignFrame af : Desktop.getAlignFrames())
+    {
+      if (af.alignPanel != null)
+      {
+        final AlignmentI thatAlignment = af.alignPanel.getAlignment();
+        if (thatAlignment.isNucleotide())
+        {
+          int seqsAligned = thatAlignment.alignAs(thisAlignment);
+          seqCount += seqsAligned;
+          if (seqsAligned > 0)
+          {
+            af.alignPanel.alignmentChanged();
+            alignCount++;
+          }
+        }
+      }
+    }
+    setStatus(MessageManager.formatMessage("label.cdna_aligned", seqCount,
+            alignCount));
+  }
+  /**
+   * set up menus for the current viewport. This may be called after any
    * operation that affects the data in the current view (selection changed,
    * etc) to update the menus to reflect the new state.
    */
@@ -899,7 +991,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         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);
@@ -1070,7 +1162,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
       currentFileFormat = chooser.getSelectedFormat();
-      if (currentFileFormat == null)
+      while (currentFileFormat == null)
       {
         JOptionPane
                 .showInternalMessageDialog(
@@ -1080,8 +1172,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         MessageManager
                                 .getString("label.file_format_not_specified"),
                         JOptionPane.WARNING_MESSAGE);
+        currentFileFormat = chooser.getSelectedFormat();
         value = chooser.showSaveDialog(this);
-        return;
+        if (value != JalviewFileChooser.APPROVE_OPTION)
+        {
+          return;
+        }
       }
 
       fileName = chooser.getSelectedFile().getPath();
@@ -1178,7 +1274,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           this.setTitle(file);
           statusBar.setText(MessageManager.formatMessage(
                   "label.successfully_saved_to_file_in_format",
-                  new String[]
+                  new Object[]
                   { fileName, format }));
         } catch (Exception ex)
         {
@@ -1191,7 +1287,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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);
@@ -1253,7 +1349,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               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)
     {
@@ -1399,7 +1495,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             // setClosed(true) is called
             for (int i = 0; i < alignPanels.size(); i++)
             {
-              AlignmentPanel ap = (AlignmentPanel) alignPanels.elementAt(i);
+              AlignmentPanel ap = alignPanels.get(i);
               ap.closePanel();
             }
           }
@@ -1429,7 +1525,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     int index = tabbedPane.getSelectedIndex();
     int closedindex = tabbedPane.indexOfComponent(alignPanel2);
-    alignPanels.removeElement(alignPanel2);
+    alignPanels.remove(alignPanel2);
     // Unnecessary
     // if (viewport == alignPanel2.av)
     // {
@@ -1456,12 +1552,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   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
@@ -1470,13 +1566,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       undoMenuItem.setText(MessageManager.getString("action.undo"));
     }
 
-    if (viewport.redoList.size() > 0)
+    if (viewport.getRedoList().size() > 0)
     {
       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
@@ -1490,8 +1586,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     if (command.getSize() > 0)
     {
-      viewport.historyList.push(command);
-      viewport.redoList.clear();
+      viewport.addToHistoryList(command);
+      viewport.clearRedoList();
       updateEditMenuBar();
       viewport.updateHiddenColumns();
       // viewport.hasHiddenColumns = (viewport.getColumnSelection() != null
@@ -1509,11 +1605,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     if (alignPanels != null)
     {
-      Enumeration e = alignPanels.elements();
       AlignmentI[] als = new AlignmentI[alignPanels.size()];
-      for (int i = 0; e.hasMoreElements(); i++)
+      int i = 0;
+      for (AlignmentPanel ap : alignPanels)
       {
-        als[i] = ((AlignmentPanel) e.nextElement()).av.getAlignment();
+        als[i++] = ap.av.getAlignment();
       }
       return als;
     }
@@ -1534,15 +1630,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void undoMenuItem_actionPerformed(ActionEvent e)
   {
-    if (viewport.historyList.empty())
+    if (viewport.getHistoryList().isEmpty())
     {
       return;
     }
-    CommandI command = viewport.historyList.pop();
-    viewport.redoList.push(command);
+    CommandI command = viewport.getHistoryList().pop();
+    viewport.addToRedoList(command);
     command.undoCommand(getViewAlignments());
 
-    AlignViewport originalSource = getOriginatingSource(command);
+    AlignmentViewport originalSource = getOriginatingSource(command);
     updateEditMenuBar();
 
     if (originalSource != null)
@@ -1572,16 +1668,16 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void redoMenuItem_actionPerformed(ActionEvent e)
   {
-    if (viewport.redoList.size() < 1)
+    if (viewport.getRedoList().size() < 1)
     {
       return;
     }
 
-    CommandI command = viewport.redoList.pop();
-    viewport.historyList.push(command);
+    CommandI command = viewport.getRedoList().pop();
+    viewport.addToHistoryList(command);
     command.doCommand(getViewAlignments());
 
-    AlignViewport originalSource = getOriginatingSource(command);
+    AlignmentViewport originalSource = getOriginatingSource(command);
     updateEditMenuBar();
 
     if (originalSource != null)
@@ -1603,9 +1699,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
-  AlignViewport getOriginatingSource(CommandI command)
+  AlignmentViewport getOriginatingSource(CommandI command)
   {
-    AlignViewport originalSource = null;
+    AlignmentViewport originalSource = null;
     // For sequence removal and addition, we need to fire
     // the property change event FROM the viewport where the
     // original alignment was altered
@@ -1614,16 +1710,16 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       EditCommand editCommand = (EditCommand) command;
       al = editCommand.getAlignment();
-      Vector comps = (Vector) PaintRefresher.components.get(viewport
+      List<Component> comps = PaintRefresher.components.get(viewport
               .getSequenceSetId());
 
-      for (int i = 0; i < comps.size(); i++)
+      for (Component comp : comps)
       {
-        if (comps.elementAt(i) instanceof AlignmentPanel)
+        if (comp instanceof AlignmentPanel)
         {
-          if (al == ((AlignmentPanel) comps.elementAt(i)).av.getAlignment())
+          if (al == ((AlignmentPanel) comp).av.getAlignment())
           {
-            originalSource = ((AlignmentPanel) comps.elementAt(i)).av;
+            originalSource = ((AlignmentPanel) comp).av;
             break;
           }
         }
@@ -1662,11 +1758,23 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     viewport.getAlignment().moveSelectedSequencesByOne(sg,
             viewport.getHiddenRepSequences(), up);
     alignPanel.paintAlignment(true);
+
+    final AlignViewportI peer = viewport.getCodingComplement();
+    if (peer != null)
+    {
+      final SequenceGroup selectionGroup = peer.getSelectionGroup();
+      if (selectionGroup != null)
+      {
+        peer.getAlignment().moveSelectedSequencesByOne(
+                peer.getSelectionGroup(), peer.getHiddenRepSequences(), up);
+        ((AlignViewport) peer).getAlignPanel().paintAlignment(true);
+      }
+    }
   }
 
   synchronized void slideSequences(boolean right, int size)
   {
-    List<SequenceI> sg = new Vector();
+    List<SequenceI> sg = new ArrayList<SequenceI>();
     if (viewport.cursorMode)
     {
       sg.add(viewport.getAlignment().getSequenceAt(
@@ -1685,13 +1793,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Vector invertGroup = new Vector();
+    List<SequenceI> invertGroup = new ArrayList<SequenceI>();
 
-    for (int i = 0; i < viewport.getAlignment().getHeight(); i++)
+    for (SequenceI seq : viewport.getAlignment().getSequences())
     {
-      if (!sg.contains(viewport.getAlignment().getSequenceAt(i)))
+      if (!sg.contains(seq))
       {
-        invertGroup.add(viewport.getAlignment().getSequenceAt(i));
+        invertGroup.add(seq);
       }
     }
 
@@ -1700,7 +1808,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     SequenceI[] seqs2 = new SequenceI[invertGroup.size()];
     for (int i = 0; i < invertGroup.size(); i++)
     {
-      seqs2[i] = (SequenceI) invertGroup.elementAt(i);
+      seqs2[i] = invertGroup.get(i);
     }
 
     SlideSequencesCommand ssc;
@@ -1748,11 +1856,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     boolean appendHistoryItem = false;
-    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.historyList
+              .appendSlideCommand((SlideSequencesCommand) historyList
                       .peek());
     }
 
@@ -1828,7 +1938,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     Desktop.jalviewClipboard = new Object[]
     { seqs, viewport.getAlignment().getDataset(), hiddenColumns };
     statusBar.setText(MessageManager.formatMessage(
-            "label.copied_sequences_to_clipboard", new String[]
+            "label.copied_sequences_to_clipboard", new Object[]
             { Integer.valueOf(seqs.length).toString() }));
   }
 
@@ -2126,7 +2236,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 alignment.getSequences());
         if (alignPanels != null)
         {
-          for (AlignmentPanel ap : ((Vector<AlignmentPanel>) alignPanels))
+          for (AlignmentPanel ap : alignPanels)
           {
             ap.validateAnnotationDimensions(false);
           }
@@ -2548,7 +2658,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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
@@ -2619,16 +2729,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .getSequences());
   }
 
-  // else
-  {
-    // if (justifySeqs>0)
-    {
-      // alignment.justify(justifySeqs!=RIGHT_JUSTIFY);
-    }
-  }
-
-  // }
-
   /**
    * DOCUMENT ME!
    * 
@@ -2641,74 +2741,105 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     new Finder();
   }
 
+  /**
+   * Create a new view of the current alignment.
+   */
   @Override
   public void newView_actionPerformed(ActionEvent e)
   {
-    newView(true);
-  }
+    /*
+     * Note if the current view has a protein/cdna complementary view
+     */
+    AlignViewportI linkedView = this.viewport.getCodingComplement();
 
-  /**
-   * 
-   * @param copyAnnotation
-   *          if true then duplicate all annnotation, groups and settings
-   * @return new alignment panel, already displayed.
-   */
-  public AlignmentPanel newView(boolean copyAnnotation)
-  {
-    return newView(null, copyAnnotation);
-  }
+    AlignmentPanel newPanel = newView(null, true);
 
-  /**
-   * 
-   * @param viewTitle
-   *          title of newly created view
-   * @return new alignment panel, already displayed.
-   */
-  public AlignmentPanel newView(String viewTitle)
-  {
-    return newView(viewTitle, true);
+    /*
+     * If the original view has a protein/cdna linked view, make and link a new
+     * view there also.
+     */
+    // TODO refactor the hell out of this - move to a controller, lose the casts
+    // and direct member access, etc
+    if (linkedView != null)
+    {
+      AlignFrame linkedAlignFrame = ((AlignViewport) linkedView)
+              .getAlignPanel().alignFrame;
+      AlignmentPanel newLinkedPanel = linkedAlignFrame.newView(null, true);
+      newLinkedPanel.av.viewName = newPanel.av.viewName;
+      newPanel.av.setCodingComplement(newLinkedPanel.av);
+      final StructureSelectionManager ssm = StructureSelectionManager
+              .getStructureSelectionManager(Desktop.instance);
+      ssm.addCommandListener(newPanel.av);
+      ssm.addCommandListener(newLinkedPanel.av);
+
+    }
   }
 
   /**
+   * Creates and shows a new view of the current alignment.
    * 
    * @param viewTitle
-   *          title of newly created view
+   *          title of newly created view; if null, one will be generated
    * @param copyAnnotation
    *          if true then duplicate all annnotation, groups and settings
    * @return new alignment panel, already displayed.
    */
   public AlignmentPanel newView(String viewTitle, boolean copyAnnotation)
   {
+    /*
+     * Create a new AlignmentPanel (with its own, new Viewport)
+     */
     AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel,
             true);
     if (!copyAnnotation)
     {
-      // just remove all the current annotation except for the automatic stuff
+      /*
+       * remove all groups and annotation except for the automatic stuff
+       */
       newap.av.getAlignment().deleteAllGroups();
-      for (AlignmentAnnotation alan : newap.av.getAlignment()
-              .getAlignmentAnnotation())
-      {
-        if (!alan.autoCalculated)
-        {
-          newap.av.getAlignment().deleteAnnotation(alan);
-        }
-        ;
-      }
+      newap.av.getAlignment().deleteAllAnnotations(false);
     }
 
     newap.av.gatherViewsHere = false;
 
     if (viewport.viewName == null)
     {
-      viewport.viewName = "Original";
+      viewport.viewName = MessageManager
+              .getString("label.view_name_original");
     }
 
-    newap.av.historyList = viewport.historyList;
-    newap.av.redoList = viewport.redoList;
+    /*
+     * Views share the same edits, undo and redo stacks, mappings.
+     */
+    newap.av.setHistoryList(viewport.getHistoryList());
+    newap.av.setRedoList(viewport.getRedoList());
+    newap.av.getAlignment().setCodonFrames(
+            viewport.getAlignment().getCodonFrames());
+
+    newap.av.viewName = getNewViewName(viewTitle);
+
+    addAlignmentPanel(newap, true);
+    newap.alignmentChanged();
 
+    if (alignPanels.size() == 2)
+    {
+      viewport.gatherViewsHere = true;
+    }
+    tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
+    return newap;
+  }
+
+  /**
+   * Make a new name for the view, ensuring it is unique within the current
+   * sequenceSetId. (This used to be essential for Jalview Project archives, but
+   * these now use viewId. Unique view names are still desirable for usability.)
+   * 
+   * @param viewTitle
+   * @return
+   */
+  protected String getNewViewName(String viewTitle)
+  {
     int index = Desktop.getViewCount(viewport.getSequenceSetId());
-    // make sure the new view has a unique name - this is essential for Jalview
-    // 2 archives
     boolean addFirstIndex = false;
     if (viewTitle == null || viewTitle.trim().length() == 0)
     {
@@ -2720,45 +2851,55 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       index = 1;// we count from 1 if given a specific name
     }
     String newViewName = viewTitle + ((addFirstIndex) ? " " + index : "");
-    Vector comps = (Vector) PaintRefresher.components.get(viewport
+
+    List<Component> comps = PaintRefresher.components.get(viewport
             .getSequenceSetId());
-    Vector existingNames = new Vector();
-    for (int i = 0; i < comps.size(); i++)
-    {
-      if (comps.elementAt(i) instanceof AlignmentPanel)
-      {
-        AlignmentPanel ap = (AlignmentPanel) comps.elementAt(i);
-        if (!existingNames.contains(ap.av.viewName))
-        {
-          existingNames.addElement(ap.av.viewName);
-        }
-      }
-    }
+
+    List<String> existingNames = getExistingViewNames(comps);
 
     while (existingNames.contains(newViewName))
     {
       newViewName = viewTitle + " " + (++index);
     }
+    return newViewName;
+  }
 
-    newap.av.viewName = newViewName;
-
-    addAlignmentPanel(newap, true);
-    newap.alignmentChanged();
-
-    if (alignPanels.size() == 2)
+  /**
+   * Returns a list of distinct view names found in the given list of
+   * components. View names are held on the viewport of an AlignmentPanel.
+   * 
+   * @param comps
+   * @return
+   */
+  protected List<String> getExistingViewNames(List<Component> comps)
+  {
+    List<String> existingNames = new ArrayList<String>();
+    for (Component comp : comps)
     {
-      viewport.gatherViewsHere = true;
+      if (comp instanceof AlignmentPanel)
+      {
+        AlignmentPanel ap = (AlignmentPanel) comp;
+        if (!existingNames.contains(ap.av.viewName))
+        {
+          existingNames.add(ap.av.viewName);
+        }
+      }
     }
-    tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
-    return newap;
+    return existingNames;
   }
 
+  /**
+   * Explode tabbed views into separate windows.
+   */
   @Override
   public void expandViews_actionPerformed(ActionEvent e)
   {
     Desktop.instance.explodeViews(this);
   }
 
+  /**
+   * Gather views in separate windows back into a tabbed presentation.
+   */
   @Override
   public void gatherViews_actionPerformed(ActionEvent e)
   {
@@ -3167,13 +3308,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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);
   }
 
@@ -3195,7 +3336,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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);
@@ -3602,8 +3743,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
 
     Component[] menuItems = colourMenu.getMenuComponents();
-    int i, iSize = menuItems.length;
-    for (i = 0; i < iSize; i++)
+    int iSize = menuItems.length;
+    for (int i = 0; i < iSize; i++)
     {
       if (menuItems[i].getName() != null
               && menuItems[i].getName().equals("USER_DEFINED"))
@@ -3864,7 +4005,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void averageDistanceTreeMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("AV", "PID", "Average distance tree using PID");
+    newTreePanel("AV", "PID", "Average distance tree using PID");
   }
 
   /**
@@ -3876,7 +4017,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void neighbourTreeMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("NJ", "PID", "Neighbour joining tree using PID");
+    newTreePanel("NJ", "PID", "Neighbour joining tree using PID");
   }
 
   /**
@@ -3888,7 +4029,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void njTreeBlosumMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("NJ", "BL", "Neighbour joining tree using BLOSUM62");
+    newTreePanel("NJ", "BL", "Neighbour joining tree using BLOSUM62");
   }
 
   /**
@@ -3900,7 +4041,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void avTreeBlosumMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("AV", "BL", "Average distance tree using BLOSUM62");
+    newTreePanel("AV", "BL", "Average distance tree using BLOSUM62");
   }
 
   /**
@@ -3913,7 +4054,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param title
    *          DOCUMENT ME!
    */
-  void NewTreePanel(String type, String pwType, String title)
+  void newTreePanel(String type, String pwType, String title)
   {
     TreePanel tp;
 
@@ -4004,7 +4145,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   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()
     {
@@ -4128,7 +4269,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       String treecalcnm = MessageManager.getString("label.tree_calc_"
               + type.toLowerCase());
-      for (final Object pwtype : ResidueProperties.scoreMatrices.keySet())
+      for (final String pwtype : ResidueProperties.scoreMatrices.keySet())
       {
         JMenuItem tm = new JMenuItem();
         ScoreModelI sm = ResidueProperties.scoreMatrices.get(pwtype);
@@ -4144,7 +4285,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             @Override
             public void actionPerformed(ActionEvent e)
             {
-              NewTreePanel(type, (String) pwtype, title);
+              newTreePanel(type, pwtype, title);
             }
           });
           calculateTree.add(tm);
@@ -4154,21 +4295,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     sortByTreeMenu.removeAll();
 
-    Vector comps = (Vector) PaintRefresher.components.get(viewport
+    List<Component> comps = PaintRefresher.components.get(viewport
             .getSequenceSetId());
-    Vector treePanels = new Vector();
-    int i, iSize = comps.size();
-    for (i = 0; i < iSize; i++)
+    List<TreePanel> treePanels = new ArrayList<TreePanel>();
+    for (Component comp : comps)
     {
-      if (comps.elementAt(i) instanceof TreePanel)
+      if (comp instanceof TreePanel)
       {
-        treePanels.add(comps.elementAt(i));
+        treePanels.add((TreePanel) comp);
       }
     }
 
-    iSize = treePanels.size();
-
-    if (iSize < 1)
+    if (treePanels.size() < 1)
     {
       sortByTreeMenu.setVisible(false);
       return;
@@ -4176,17 +4314,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     sortByTreeMenu.setVisible(true);
 
-    for (i = 0; i < treePanels.size(); i++)
+    for (final TreePanel tp : treePanels)
     {
-      final TreePanel tp = (TreePanel) treePanels.elementAt(i);
       final JMenuItem item = new JMenuItem(tp.getTitle());
-      final NJTree tree = ((TreePanel) treePanels.elementAt(i)).getTree();
       item.addActionListener(new java.awt.event.ActionListener()
       {
         @Override
         public void actionPerformed(ActionEvent e)
         {
-          tp.sortByTree_actionPerformed(null);
+          tp.sortByTree_actionPerformed();
           addHistoryItem(tp.sortAlignmentIn(alignPanel));
 
         }
@@ -4426,7 +4562,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       } catch (Exception e)
       {
       }
-      ;
     }
     final AlignFrame me = this;
     buildingMenu = true;
@@ -4581,14 +4716,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         .debug("Exception during web service menu building process.",
                                 e);
               }
-              ;
             }
           });
         } catch (Exception e)
         {
         }
-        ;
-
         buildingMenu = false;
       }
     }).start();
@@ -4734,7 +4866,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       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
@@ -4758,12 +4890,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               sprods[s].updatePDBIds();
             }
             Alignment al = new Alignment(sprods);
-            AlignedCodonFrame[] cf = prods.getCodonFrames();
+            Set<AlignedCodonFrame> cf = prods.getCodonFrames();
             al.setDataset(ds);
-            for (int s = 0; cf != null && s < cf.length; s++)
+            for (AlignedCodonFrame acf : cf)
             {
-              al.addCodonFrame(cf[s]);
-              cf[s] = null;
+              al.addCodonFrame(acf);
             }
             AlignFrame naf = new AlignFrame(al, DEFAULT_WIDTH,
                     DEFAULT_HEIGHT);
@@ -4790,7 +4921,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           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);
       }
 
@@ -4816,92 +4947,49 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
-  @Override
-  public void showProducts_actionPerformed(ActionEvent e)
-  {
-    // /////////////////////////////
-    // Collect Data to be translated/transferred
-
-    SequenceI[] selection = viewport.getSequenceSelection();
-    AlignmentI al = null;
-    try
-    {
-      al = jalview.analysis.Dna.CdnaTranslate(selection, viewport
-              .getViewAsVisibleContigs(true), viewport.getGapCharacter(),
-              viewport.getAlignment().getDataset());
-    } catch (Exception ex)
-    {
-      al = null;
-      jalview.bin.Cache.log.debug("Exception during translation.", ex);
-    }
-    if (al == null)
-    {
-      JOptionPane
-              .showMessageDialog(
-                      Desktop.desktop,
-                      MessageManager
-                              .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation"),
-                      MessageManager.getString("label.translation_failed"),
-                      JOptionPane.WARNING_MESSAGE);
-    }
-    else
-    {
-      AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
-      Desktop.addInternalFrame(af, MessageManager.formatMessage(
-              "label.translation_of_params", new String[]
-              { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT);
-    }
-  }
-
+  /**
+   * Construct and display a new frame containing the translation of this
+   * frame's cDNA sequences to their aligned protein (amino acid) equivalents.
+   */
   @Override
   public void showTranslation_actionPerformed(ActionEvent e)
   {
-    // /////////////////////////////
-    // Collect Data to be translated/transferred
-
-    SequenceI[] selection = viewport.getSequenceSelection();
-    String[] seqstring = viewport.getViewAsString(true);
     AlignmentI al = null;
     try
     {
-      al = jalview.analysis.Dna.CdnaTranslate(selection, seqstring,
-              viewport.getViewAsVisibleContigs(true), viewport
-                      .getGapCharacter(), viewport.getAlignment()
-                      .getAlignmentAnnotation(), viewport.getAlignment()
-                      .getWidth(), viewport.getAlignment().getDataset());
+      Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
+
+      al = dna.translateCdna();
     } catch (Exception ex)
     {
-      al = null;
       jalview.bin.Cache.log.error(
               "Exception during translation. Please report this !", ex);
-      JOptionPane
-              .showMessageDialog(
-                      Desktop.desktop,
-                      MessageManager
-                              .getString("label.error_when_translating_sequences_submit_bug_report"),
-                      MessageManager
-                              .getString("label.implementation_error")
-                              + MessageManager
-                                      .getString("translation_failed"),
-                      JOptionPane.ERROR_MESSAGE);
+      final String msg = MessageManager
+              .getString("label.error_when_translating_sequences_submit_bug_report");
+      final String title = MessageManager
+              .getString("label.implementation_error")
+              + MessageManager.getString("translation_failed");
+      JOptionPane.showMessageDialog(Desktop.desktop, msg, title,
+              JOptionPane.ERROR_MESSAGE);
       return;
     }
-    if (al == null)
+    if (al == null || al.getHeight() == 0)
     {
-      JOptionPane
-              .showMessageDialog(
-                      Desktop.desktop,
-                      MessageManager
-                              .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation"),
-                      MessageManager.getString("label.translation_failed"),
-                      JOptionPane.WARNING_MESSAGE);
+      final String msg = MessageManager
+              .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
+      final String title = MessageManager
+              .getString("label.translation_failed");
+      JOptionPane.showMessageDialog(Desktop.desktop, msg, title,
+              JOptionPane.WARNING_MESSAGE);
     }
     else
     {
       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);
     }
   }
 
@@ -5097,7 +5185,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                                   MessageManager
                                           .formatMessage(
                                                   "label.automatically_associate_pdb_files_with_sequences_same_name",
-                                                  new String[]
+                                                  new Object[]
                                                   { Integer.valueOf(
                                                           filesmatched
                                                                   .size())
@@ -5140,7 +5228,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                                   "<html>"+MessageManager
                                           .formatMessage(
                                                   "label.ignore_unmatched_dropped_files_info",
-                                                  new String[]
+                                                  new Object[]
                                                   { Integer.valueOf(
                                                           filesnotmatched
                                                                   .size())
@@ -5248,7 +5336,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           {
             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;
           }
@@ -5320,31 +5409,38 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
+  /**
+   * Method invoked by the ChangeListener on the tabbed pane, in other words
+   * when a different tabbed pane is selected by the user or programmatically.
+   */
   @Override
   public void tabSelectionChanged(int index)
   {
     if (index > -1)
     {
-      alignPanel = (AlignmentPanel) alignPanels.elementAt(index);
+      alignPanel = alignPanels.get(index);
       viewport = alignPanel.av;
       avc.setViewportAndAlignmentPanel(viewport, alignPanel);
       setMenusFromViewport(viewport);
     }
   }
 
+  /**
+   * On right mouse click on view tab, prompt for and set new view name.
+   */
   @Override
   public void tabbedPane_mousePressed(MouseEvent e)
   {
     if (SwingUtilities.isRightMouseButton(e))
     {
-      String reply = JOptionPane.showInternalInputDialog(this,
-              MessageManager.getString("label.enter_view_name"),
-              MessageManager.getString("label.enter_view_name"),
+      String msg = MessageManager.getString("label.enter_view_name");
+      String reply = JOptionPane.showInternalInputDialog(this, msg, msg,
               JOptionPane.QUESTION_MESSAGE);
 
       if (reply != null)
       {
         viewport.viewName = reply;
+        // TODO warn if reply is in getExistingViewNames()?
         tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), reply);
       }
     }
@@ -5405,7 +5501,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param av
    */
-  public boolean closeView(AlignViewport av)
+  public boolean closeView(AlignmentViewport av)
   {
     if (viewport == av)
     {
@@ -5551,7 +5647,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   }
 
                 });
-                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++;
               }
@@ -5562,7 +5658,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 // 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()
                 {
@@ -5584,11 +5680,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   }
                 });
 
-                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;
@@ -5601,7 +5697,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                           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 =
@@ -5628,7 +5724,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
                   });
                   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()))
@@ -5882,11 +5978,60 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   /**
    * 
-   * @return alignment panels in this alignemnt frame
+   * @return alignment panels in this alignment frame
    */
-  public List<AlignmentViewPanel> getAlignPanels()
+  public List<? extends AlignmentViewPanel> getAlignPanels()
   {
-    return alignPanels == null ? Arrays.asList(alignPanel) : alignPanels;
+    return alignPanels == null ? Arrays.asList(alignPanel)
+            : alignPanels;
+  }
+
+  /**
+   * Open a new alignment window, with the cDNA associated with this (protein)
+   * alignment, aligned as is the protein.
+   */
+  @Override
+  protected void viewAsCdna_actionPerformed()
+  {
+    final AlignmentI alignment = getViewport().getAlignment();
+    Set<AlignedCodonFrame> mappings = alignment.getCodonFrames();
+    if (mappings == null)
+    {
+      return;
+    }
+    List<SequenceI> cdnaSeqs = new ArrayList<SequenceI>();
+    for (SequenceI aaSeq : alignment.getSequences()) {
+      for (AlignedCodonFrame acf : mappings) {
+        SequenceI dnaSeq = acf.getDnaForAaSeq(aaSeq.getDatasetSequence());
+        if (dnaSeq != null)
+        {
+          /*
+           * There is a cDNA mapping for this protein sequence - add to new
+           * alignment. It will share the same dataset sequence as other mapped
+           * cDNA (no new mappings need to be created).
+           */
+          final Sequence newSeq = new Sequence(dnaSeq);
+          newSeq.setDatasetSequence(dnaSeq);
+          cdnaSeqs.add(newSeq);
+        }
+      }
+    }
+    if (cdnaSeqs.size() == 0)
+    {
+      // show a warning dialog no mapped cDNA
+      return;
+    }
+    AlignmentI cdna = new Alignment(cdnaSeqs.toArray(new SequenceI[cdnaSeqs
+            .size()]));
+    AlignFrame alignFrame = new AlignFrame(cdna, AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
+    cdna.alignAs(alignment);
+    String newtitle = "cDNA " + MessageManager.getString("label.for") + " "
+            + this.title;
+    Desktop.addInternalFrame(alignFrame, newtitle,
+            AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
+
   }
 }
 
index 37fe1d2..6ea8df1 100644 (file)
  */
 package jalview.gui;
 
+import jalview.analysis.AlignmentUtils;
+import jalview.analysis.AlignmentUtils.MappingResult;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.NJTree;
 import jalview.api.AlignViewportI;
 import jalview.api.ViewStyleI;
 import jalview.bin.Cache;
 import jalview.commands.CommandI;
+import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
@@ -52,9 +55,12 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.UserColourScheme;
+import jalview.structure.CommandListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
+import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.AutoCalcSetting;
 
@@ -62,11 +68,17 @@ import java.awt.Container;
 import java.awt.Dimension;
 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!
  * 
@@ -74,7 +86,7 @@ import java.util.Vector;
  * @version $Revision: 1.141 $
  */
 public class AlignViewport extends AlignmentViewport implements
-        SelectionSource, VamsasSource, AlignViewportI
+        SelectionSource, VamsasSource, AlignViewportI, CommandListener
 {
   int startRes;
 
@@ -101,9 +113,9 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean gatherViewsHere = false;
 
-  Stack<CommandI> historyList = new Stack<CommandI>();
+  private Deque<CommandI> historyList = new ArrayDeque<CommandI>();
 
-  Stack<CommandI> redoList = new Stack<CommandI>();
+  private Deque<CommandI> redoList = new ArrayDeque<CommandI>();
 
   private AnnotationColumnChooser annotationColumnSelectionState;
   /**
@@ -678,6 +690,9 @@ public class AlignViewport extends AlignmentViewport implements
     return followSelection;
   }
 
+  /**
+   * Send the current selection to be broadcast to any selection listeners.
+   */
   public void sendSelection()
   {
     jalview.structure.StructureSelectionManager
@@ -698,7 +713,6 @@ public class AlignViewport extends AlignmentViewport implements
   {
     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(this
             .getSequenceSetId());
-    AlignmentPanel ap = null;
     for (int p = 0; aps != null && p < aps.length; p++)
     {
       if (aps[p].av == this)
@@ -859,6 +873,305 @@ public class AlignViewport extends AlignmentViewport implements
     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();
+    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. 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)
+    {
+      /*
+       * 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(), 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;
index f493fcf..d0da010 100644 (file)
@@ -95,9 +95,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * Creates a new AlignmentPanel object.
    * 
    * @param af
-   *          DOCUMENT ME!
    * @param av
-   *          DOCUMENT ME!
    */
   public AlignmentPanel(AlignFrame af, final AlignViewport av)
   {
@@ -672,7 +670,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-
     int oldX = av.getStartRes();
     int oldY = av.getStartSeq();
 
@@ -1170,8 +1167,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (alignFrame != null && !headless)
     {
       alignFrame.setProgressBar(MessageManager.formatMessage(
-              "status.saving_file",
-              new String[]
+              "status.saving_file", new Object[]
               { type.getLabel() }), progress);
     }
     try
@@ -1497,6 +1493,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
               .getStructureSelectionManager();
       ssm.removeStructureViewerListener(getSeqPanel(), null);
       ssm.removeSelectionListener(getSeqPanel());
+      ssm.removeEditListener(av);
+      ssm.removeStructureViewerListener(getSeqPanel(), null);
+      ssm.removeSelectionListener(getSeqPanel());
       av.setAlignment(null);
       av = null;
     }
index f9a9e7e..c34a5c5 100755 (executable)
@@ -683,7 +683,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
         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
                         .isIgnoreGapsConsensus());
@@ -709,7 +709,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
         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()
           {
@@ -728,7 +728,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
           });
           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()
           {
@@ -747,7 +747,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
           });
           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()
           {
@@ -772,7 +772,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
         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)
@@ -791,7 +791,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
           });
           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)
@@ -810,7 +810,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
           });
           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)
index a738a2c..03799e1 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.structure.AtomSpec;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -827,8 +828,6 @@ public class AppVarnaBinding extends jalview.ext.varna.JalviewVarnaBinding
 
   public void onWarningEmitted(String s)
   {
-    // TODO Auto-generated method stub
-
   }
 
   public void mouseClicked(MouseEvent e)
@@ -852,127 +851,73 @@ public class AppVarnaBinding extends jalview.ext.varna.JalviewVarnaBinding
 
   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); } }
- */
index 7fd091f..40a02f8 100644 (file)
@@ -33,6 +33,7 @@ import javax.swing.event.HyperlinkEvent.EventType;
 import jalview.io.*;
 import jalview.jbgui.*;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 /**
  * Cut'n'paste files into the desktop See JAL-1105
@@ -43,7 +44,7 @@ import jalview.util.MessageManager;
 public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
 {
 
-  AlignViewport viewport;
+  AlignmentViewport viewport;
 
   public CutAndPasteHtmlTransfer()
   {
@@ -102,7 +103,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
   /**
    * DOCUMENT ME!
    */
-  public void setForInput(AlignViewport viewport)
+  public void setForInput(AlignmentViewport viewport)
   {
     this.viewport = viewport;
     if (viewport != null)
index 0c22b14..6705f42 100644 (file)
  */
 package jalview.gui;
 
-import java.awt.*;
-import java.awt.datatransfer.*;
-import java.awt.event.*;
-import javax.swing.*;
-
-import jalview.datamodel.*;
-import jalview.io.*;
-import jalview.jbgui.*;
+import jalview.datamodel.Alignment;
+import jalview.io.FormatAdapter;
+import jalview.io.IdentifyFile;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.jbgui.GCutAndPasteTransfer;
 import jalview.util.MessageManager;
 
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+
 /**
  * Cut'n'paste files into the desktop See JAL-1105
  * 
@@ -190,24 +202,19 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
 
     if (al != null)
     {
+      String title = MessageManager.formatMessage(
+              "label.input_cut_paste_params", new String[]
+              { format });
       if (viewport != null)
       {
-        for (int i = 0; i < al.getHeight(); i++)
-        {
-          viewport.getAlignment().addSequence(al.getSequenceAt(i));
-        }
-
-        viewport.firePropertyChange("alignment", null, viewport
-                .getAlignment().getSequences());
+        viewport.addAlignment(al, title);
       }
       else
       {
         AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
         af.currentFileFormat = format;
-        Desktop.addInternalFrame(af, MessageManager.formatMessage(
-                "label.input_cut_paste_params", new String[]
-                { format }), AlignFrame.DEFAULT_WIDTH,
+        Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
         af.statusBar.setText(MessageManager
                 .getString("label.successfully_pasted_alignment_file"));
index c737be8..b9e796e 100644 (file)
@@ -30,6 +30,7 @@ import jalview.jbgui.GStructureViewer;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.ParamManager;
 
 import java.awt.BorderLayout;
@@ -69,6 +70,7 @@ import java.lang.reflect.Constructor;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.StringTokenizer;
 import java.util.Vector;
 import java.util.concurrent.ExecutorService;
@@ -162,6 +164,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
   static final int yOffset = 30;
 
+  private static final int THREE = 3;
+
   public static jalview.ws.jws1.Discoverer discoverer;
 
   public static Object[] jalviewClipboard;
@@ -1087,7 +1091,10 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       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;
@@ -1363,7 +1370,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       return;
     }
 
-    AlignViewport source = null, target = null;
+    AlignmentViewport source = null, target = null;
     if (frames[0] instanceof AlignFrame)
     {
       source = ((AlignFrame) frames[0]).getCurrentView();
@@ -1475,7 +1482,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         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());
@@ -1495,7 +1502,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
                     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);
           }
@@ -1569,7 +1576,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       {
         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
           {
@@ -1582,7 +1591,11 @@ public class Desktop extends jalview.jbgui.GDesktop implements
             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());
@@ -1604,7 +1617,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements
   {
     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++;
   }
@@ -1670,7 +1685,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
   public static int getViewCount(String alignmentId)
   {
-    AlignViewport[] aps = getViewports(alignmentId);
+    AlignmentViewport[] aps = getViewports(alignmentId);
     return (aps == null) ? 0 : aps.length;
   }
 
@@ -1682,40 +1697,41 @@ public class Desktop extends jalview.jbgui.GDesktop implements
    */
   public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
   {
-    int count = 0;
     if (Desktop.desktop == null)
     {
       // no frames created and in headless mode
       // TODO: verify that frames are recoverable when in headless mode
       return null;
     }
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
-    ArrayList aps = new ArrayList();
-    for (int t = 0; t < frames.length; t++)
+    List<AlignmentPanel> aps = new ArrayList<AlignmentPanel>();
+    AlignFrame[] frames = getAlignFrames();
+    if (frames == null)
     {
-      if (frames[t] instanceof AlignFrame)
+      return null;
+    }
+    for (AlignFrame af : frames)
+    {
+      for (AlignmentPanel ap : af.alignPanels)
       {
-        AlignFrame af = (AlignFrame) frames[t];
-        for (int a = 0; a < af.alignPanels.size(); a++)
+        if (alignmentId==null || alignmentId.equals(ap.av.getSequenceSetId()))
         {
-          if (alignmentId == null
-                  || alignmentId.equals(((AlignmentPanel) af.alignPanels
-                  .elementAt(a)).av.getSequenceSetId()))
-          {
-            aps.add(af.alignPanels.elementAt(a));
-          }
+          aps.add(ap);
         }
       }
+      // for (int a = 0; a < af.alignPanels.size(); a++)
+      // {
+      // if (alignmentId.equals(af.alignPanels
+      // .get(a).av.getSequenceSetId()))
+      // {
+      // aps.add(af.alignPanels.get(a));
+      // }
+      // }
     }
     if (aps.size() == 0)
     {
       return null;
     }
-    AlignmentPanel[] vap = new AlignmentPanel[aps.size()];
-    for (int t = 0; t < vap.length; t++)
-    {
-      vap[t] = (AlignmentPanel) aps.get(t);
-    }
+    AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
     return vap;
   }
 
@@ -1727,48 +1743,37 @@ public class Desktop extends jalview.jbgui.GDesktop implements
    *          case)
    * @return all viewports on the alignment bound to sequenceSetId
    */
-  public static AlignViewport[] getViewports(String sequenceSetId)
+  public static AlignmentViewport[] getViewports(String sequenceSetId)
   {
-    Vector viewp = new Vector();
+    List<AlignmentViewport> viewp = new ArrayList<AlignmentViewport>();
     if (desktop != null)
     {
-      javax.swing.JInternalFrame[] frames = instance.getAllFrames();
+      AlignFrame[] frames = Desktop.getAlignFrames();
 
-      for (int t = 0; t < frames.length; t++)
+      for (AlignFrame afr : frames)
       {
-        if (frames[t] instanceof AlignFrame)
+        if (sequenceSetId==null || afr.getViewport().getSequenceSetId().equals(sequenceSetId))
         {
-          AlignFrame afr = ((AlignFrame) frames[t]);
-          if (sequenceSetId == null
-                  || afr.getViewport().getSequenceSetId()
-                          .equals(sequenceSetId))
+          if (afr.alignPanels != null)
           {
-            if (afr.alignPanels != null)
+            for (AlignmentPanel ap : afr.alignPanels)
             {
-              for (int a = 0; a < afr.alignPanels.size(); a++)
+              if (sequenceSetId == null
+                      || sequenceSetId.equals(ap.av.getSequenceSetId()))
               {
-                if (sequenceSetId == null
-                        || sequenceSetId
-                                .equals(((AlignmentPanel) afr.alignPanels
-                        .elementAt(a)).av.getSequenceSetId()))
-                {
-                  viewp.addElement(((AlignmentPanel) afr.alignPanels
-                          .elementAt(a)).av);
-                }
+                viewp.add(ap.av);
               }
             }
-            else
-            {
-              viewp.addElement(((AlignFrame) frames[t]).getViewport());
-            }
+          }
+          else
+          {
+            viewp.add(afr.getViewport());
           }
         }
       }
       if (viewp.size() > 0)
       {
-        AlignViewport[] vp = new AlignViewport[viewp.size()];
-        viewp.copyInto(vp);
-        return vp;
+        return viewp.toArray(new AlignmentViewport[viewp.size()]);
       }
     }
     return null;
@@ -1784,7 +1789,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
     for (int i = 0; i < size; i++)
     {
-      AlignmentPanel ap = (AlignmentPanel) af.alignPanels.elementAt(i);
+      AlignmentPanel ap = af.alignPanels.get(i);
       AlignFrame newaf = new AlignFrame(ap);
       if (ap.av.explodedPosition != null
               && !ap.av.explodedPosition.equals(af.getBounds()))
@@ -1818,7 +1823,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         boolean gatherThis = false;
         for (int a = 0; a < af.alignPanels.size(); a++)
         {
-          AlignmentPanel ap = (AlignmentPanel) af.alignPanels.elementAt(a);
+          AlignmentPanel ap = af.alignPanels.get(a);
           if (viewId.equals(ap.av.getSequenceSetId()))
           {
             gatherThis = true;
@@ -1865,7 +1870,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
                           Desktop.desktop,
                           MessageManager.formatMessage(
                                   "label.couldnt_import_as_vamsas_session",
-                                  new String[]
+                                  new Object[]
                                   { fle }),
                           MessageManager
                                   .getString("label.vamsas_document_import_failed"),
@@ -1942,14 +1947,14 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       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);
@@ -1957,7 +1962,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     }
     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();
   }
@@ -2060,7 +2065,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
           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];
@@ -2124,7 +2129,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       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;
@@ -2211,6 +2216,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
   public class MyDesktopPane extends JDesktopPane implements Runnable
   {
 
+    private static final float ONE_MB = 1048576f;
+
     boolean showMemoryUsage = false;
 
     Runtime runtime;
@@ -2250,9 +2257,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       {
         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;
@@ -2286,7 +2293,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         {
           g.drawString(MessageManager.formatMessage(
                   "label.memory_stats",
-                  new String[]
+                  new Object[]
                   { df.format(totalFreeMemory), df.format(maxMemory),
                       df.format(percentUsage) }), 10,
                   getHeight() - fm.getHeight());
@@ -2326,8 +2333,10 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
   /**
    * Accessor method to quickly get all the AlignmentFrames loaded.
+   * 
+   * @return an array of AlignFrame, or null if none found
    */
-  public static AlignFrame[] getAlignframes()
+  public static AlignFrame[] getAlignFrames()
   {
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 
@@ -2335,35 +2344,43 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     {
       return null;
     }
-    Vector avp = new Vector();
-    try
+    List<AlignFrame> avp = new ArrayList<AlignFrame>();
+    // 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 AlignFrame)
       {
-        if (frames[i] instanceof AlignFrame)
+        avp.add((AlignFrame) frames[i]);
+      }
+      else if (frames[i] instanceof SplitFrame)
+      {
+        /*
+         * Also check for a split frame containing an AlignFrame
+         */
+        SplitFrame sf = (SplitFrame) frames[i];
+        if (sf.getTopComponent() instanceof AlignFrame)
         {
-          AlignFrame af = (AlignFrame) frames[i];
-          avp.addElement(af);
+          avp.add((AlignFrame) sf.getTopComponent());
+        }
+        if (sf.getBottomComponent() instanceof AlignFrame)
+        {
+          avp.add((AlignFrame) sf.getBottomComponent());
         }
       }
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
     }
     if (avp.size() == 0)
     {
       return null;
     }
-    AlignFrame afs[] = new AlignFrame[avp.size()];
-    for (int i = 0, j = avp.size(); i < j; i++)
-    {
-      afs[i] = (AlignFrame) avp.elementAt(i);
-    }
-    avp.clear();
+    AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
     return afs;
   }
 
+  /**
+   * Returns an array of any AppJmol frames in the Desktop (or null if none).
+   * 
+   * @return
+   */
   public GStructureViewer[] getJmols()
   {
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
@@ -2372,32 +2389,21 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     {
       return null;
     }
-    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)
       {
-        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;
     }
-    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;
   }
 
@@ -2498,7 +2504,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         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);
@@ -2521,12 +2527,12 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
   /**
    * This will return the first AlignFrame viewing AlignViewport av. It will
-   * break if there are more than one AlignFrames viewing a particular av. This
+   * break if there are more than one AlignFrames viewing a particular av.
    * 
    * @param av
    * @return alignFrame for av
    */
-  public static AlignFrame getAlignFrameFor(AlignViewport av)
+  public static AlignFrame getAlignFrameFor(AlignmentViewport av)
   {
     if (desktop != null)
     {
@@ -2760,7 +2766,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         {
           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)
index 3475fe3..c23ad26 100644 (file)
@@ -28,6 +28,7 @@ import jalview.io.JalviewFileChooser;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.GraduatedColor;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.dbsources.das.api.jalviewSourceI;
 
 import java.awt.BorderLayout;
@@ -1213,7 +1214,7 @@ public class FeatureSettings extends JPanel
   {
     SequenceI[] dataset, seqs;
     int iSize;
-    AlignViewport vp = af.getViewport();
+    AlignmentViewport vp = af.getViewport();
     if (vp.getSelectionGroup() != null
             && vp.getSelectionGroup().getSize() > 0)
     {
index 60daf28..9b0d9b9 100755 (executable)
@@ -363,6 +363,9 @@ public class IdPanel extends JPanel implements MouseListener,
     {
       selectSeq(seq);
     }
+    // TODO is this addition ok here?
+    av.isSelectionGroupChanged(true);
+
     alignPanel.paintAlignment(true);
   }
 
index e355ffe..d720cc2 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
+import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
@@ -30,7 +31,6 @@ 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;
@@ -359,7 +359,7 @@ public class Jalview2XML
    */
   public void saveState(JarOutputStream jout)
   {
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    AlignFrame[] frames = Desktop.getAlignFrames(); // Desktop.desktop.getAllFrames();
 
     if (frames == null)
     {
@@ -379,67 +379,61 @@ public class Jalview2XML
       // 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++;
+        }
 
-          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";
+        }
 
-          shortNames.addElement(shortName);
+        int ap, apSize = af.alignPanels.size();
 
-          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";
           }
 
-          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);
           }
         }
       }
@@ -473,15 +467,15 @@ public class Jalview2XML
   {
     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 = (AlignmentPanel) af.alignPanels
-                .elementAt(ap);
         String jfileName = apSize == 1 ? fileName : fileName + ap;
+        ap++;
         if (!jfileName.endsWith(".xml"))
         {
           jfileName = jfileName + ".xml";
@@ -780,8 +774,7 @@ public class Jalview2XML
                 {
                   byte[] data = new byte[(int) file.length()];
                   jout.putNextEntry(new JarEntry(entry.getId()));
-                  dis = new DataInputStream(
-                          new FileInputStream(file));
+                  dis = new DataInputStream(new FileInputStream(file));
                   dis.readFully(data);
 
                   DataOutputStream dout = new DataOutputStream(jout);
@@ -837,31 +830,18 @@ public class Jalview2XML
       jal = av.getAlignment();
     }
     // SAVE MAPPINGS
-    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);
-        for (int p = 0; p < jac[i].aaWidth; p++)
-        {
-          Alcodon cmap = new Alcodon();
-          if (jac[i].codons[p] != null)
-          {
-            // Null codons indicate a gapped column in the translated peptide
-            // alignment.
-            cmap.setPos1(jac[i].codons[p][0]);
-            cmap.setPos2(jac[i].codons[p][1]);
-            cmap.setPos3(jac[i].codons[p][2]);
-          }
-          alc.addAlcodon(cmap);
-        }
-        if (jac[i].getProtMappings() != null
-                && jac[i].getProtMappings().length > 0)
+        if (acf.getProtMappings() != null
+                && acf.getProtMappings().length > 0)
         {
-          SequenceI[] dnas = jac[i].getdnaSeqs();
-          jalview.datamodel.Mapping[] pmaps = jac[i].getProtMappings();
+          SequenceI[] dnas = acf.getdnaSeqs();
+          jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
           for (int m = 0; m < pmaps.length; m++)
           {
             AlcodMap alcmap = new AlcodMap();
@@ -871,6 +851,37 @@ public class Jalview2XML
             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);
+//          }
+//        }
       }
     }
 
@@ -1124,8 +1135,9 @@ public class Jalview2XML
       {
         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
 
-        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;
@@ -1152,7 +1164,8 @@ public class Jalview2XML
             }
             else
             {
-              setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+              setting.setColour(ap.getSeqPanel().seqCanvas
+                      .getFeatureRenderer()
                       .getColour(renderOrder[ro]).getRGB());
             }
 
@@ -1196,8 +1209,8 @@ public class Jalview2XML
           settingsAdded.addElement(key);
         }
         // 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())
         {
@@ -1230,8 +1243,8 @@ public class Jalview2XML
           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
                   .size(); c++)
           {
-            int[] region = av.getColumnSelection()
-                    .getHiddenColumns().get(c);
+            int[] region = av.getColumnSelection().getHiddenColumns()
+                    .get(c);
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
@@ -1302,17 +1315,14 @@ public class Jalview2XML
           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;
       }
@@ -1320,8 +1330,7 @@ public class Jalview2XML
       {
         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): "
@@ -1334,8 +1343,7 @@ public class Jalview2XML
       // 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])
@@ -1349,8 +1357,7 @@ public class Jalview2XML
           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.
@@ -1645,7 +1652,9 @@ public class Jalview2XML
         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() }));
   }
 
   /**
@@ -2443,8 +2452,7 @@ public class Jalview2XML
               }
             }
             StructureSelectionManager.getStructureSelectionManager(
-                    Desktop.instance)
-                    .registerPDBEntry(entry);
+                    Desktop.instance).registerPDBEntry(entry);
             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
           }
         }
@@ -2461,35 +2469,13 @@ public class Jalview2XML
       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
       for (int i = 0; i < alc.length; i++)
       {
-        jalview.datamodel.AlignedCodonFrame cf = new jalview.datamodel.AlignedCodonFrame(
-                alc[i].getAlcodonCount());
-        if (alc[i].getAlcodonCount() > 0)
-        {
-          Alcodon[] alcods = alc[i].getAlcodon();
-          for (int p = 0; p < cf.codons.length; p++)
-          {
-            if (alcods[p].hasPos1() && alcods[p].hasPos2()
-                    && alcods[p].hasPos3())
-            {
-              // translated codons require three valid positions
-              cf.codons[p] = new int[3];
-              cf.codons[p][0] = (int) alcods[p].getPos1();
-              cf.codons[p][1] = (int) alcods[p].getPos2();
-              cf.codons[p][2] = (int) alcods[p].getPos3();
-            }
-            else
-            {
-              cf.codons[p] = null;
-            }
-          }
-        }
+        AlignedCodonFrame cf = new AlignedCodonFrame();
         if (alc[i].getAlcodMapCount() > 0)
         {
           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.
@@ -2511,7 +2497,6 @@ public class Jalview2XML
         }
         al.addCodonFrame(cf);
       }
-
     }
 
     // ////////////////////////////////
@@ -2766,8 +2751,7 @@ public class Jalview2XML
         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)
           {
@@ -3057,10 +3041,10 @@ public class Jalview2XML
           for (int s = 0; s < structureStateCount; 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
@@ -3077,8 +3061,8 @@ public class Jalview2XML
             // 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)
             {
               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
@@ -3086,8 +3070,8 @@ public class Jalview2XML
             }
             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
@@ -3117,8 +3101,7 @@ public class Jalview2XML
              * pre-2.7 projects)
              */
             boolean colourByViewer = jmoldat.isColourByViewer();
-            colourByViewer &= structureState
-                    .hasColourByJmol() ? structureState
+            colourByViewer &= structureState.hasColourByJmol() ? structureState
                     .getColourByJmol() : true;
             jmoldat.setColourByViewer(colourByViewer);
 
@@ -3155,11 +3138,12 @@ public class Jalview2XML
         }
       }
     }
-      // 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);
+    }
   }
 
   /**
@@ -3206,8 +3190,8 @@ public class Jalview2XML
    * @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();
@@ -3232,12 +3216,11 @@ public class Jalview2XML
       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
     {
@@ -3255,7 +3238,8 @@ public class Jalview2XML
    * @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();
@@ -3282,8 +3266,7 @@ public class Jalview2XML
         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.
@@ -3307,8 +3290,7 @@ public class Jalview2XML
         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("\"");
@@ -3414,8 +3396,8 @@ public class Jalview2XML
          * 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?
@@ -3628,8 +3610,8 @@ public class Jalview2XML
       if (av != null)
       {
         // propagate shared settings to this new view
-        af.viewport.historyList = av.historyList;
-        af.viewport.redoList = av.redoList;
+        af.viewport.setHistoryList(av.getHistoryList());
+        af.viewport.setRedoList(av.getRedoList());
       }
       else
       {
@@ -4242,9 +4224,8 @@ public class Jalview2XML
   {
     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
     // xRef Codon Maps
-    jalview.datamodel.Sequence sq = (jalview.datamodel.Sequence) seqRefIds
-            .get(vamsasSeq.getId());
-    jalview.datamodel.SequenceI dsq = null;
+    SequenceI sq = seqRefIds.get(vamsasSeq.getId());
+    SequenceI dsq = null;
     if (sq != null && sq.getDatasetSequence() != null)
     {
       dsq = sq.getDatasetSequence();
@@ -4469,14 +4450,14 @@ public class Jalview2XML
          * local sequence definition
          */
         Sequence ms = mc.getSequence();
-        jalview.datamodel.Sequence djs = null;
+        SequenceI djs = null;
         String sqid = ms.getDsseqid();
         if (sqid != null && sqid.length() > 0)
         {
           /*
            * recover dataset sequence
            */
-          djs = (jalview.datamodel.Sequence) seqRefIds.get(sqid);
+          djs = seqRefIds.get(sqid);
         }
         else
         {
index b4e0e00..1cc8a92 100644 (file)
@@ -34,6 +34,7 @@ import javax.swing.JLabel;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
+import javax.swing.JScrollBar;
 import javax.swing.SwingConstants;
 
 /**
@@ -212,6 +213,53 @@ public final class JvSwingUtils
     }
   }
 
+  /**
+   * 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);
index d2d6a98..412c25a 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.SequenceI;
 import jalview.jbgui.GPCAPanel;
 import jalview.schemes.ResidueProperties;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.PCAModel;
 
 import java.awt.BorderLayout;
@@ -67,7 +68,7 @@ public class PCAPanel extends GPCAPanel implements Runnable,
 
   AlignmentPanel ap;
 
-  AlignViewport av;
+  AlignmentViewport av;
 
   PCAModel pcaModel;
 
index 01dfa3b..215090b 100755 (executable)
  */
 package jalview.gui;
 
-import java.util.*;
-import java.util.List;
-
-import java.awt.*;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
 
-import jalview.datamodel.*;
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Route datamodel/view update events for a sequence set to any display
@@ -36,57 +39,61 @@ import jalview.datamodel.*;
  */
 public class PaintRefresher
 {
-  static Hashtable components;
+  static Map<String, List<Component>> components = new HashMap<String, List<Component>>();
 
   /**
-   * DOCUMENT ME!
+   * Add the given component to those registered under the given sequence set
+   * id. Does nothing if already added.
    * 
    * @param comp
-   *          DOCUMENT ME!
    * @param al
-   *          DOCUMENT ME!
    */
   public static void Register(Component comp, String seqSetId)
   {
-    if (components == null)
-    {
-      components = new Hashtable();
-    }
-
     if (components.containsKey(seqSetId))
     {
-      Vector comps = (Vector) components.get(seqSetId);
+      List<Component> comps = components.get(seqSetId);
       if (!comps.contains(comp))
       {
-        comps.addElement(comp);
+        comps.add(comp);
       }
     }
     else
     {
-      Vector vcoms = new Vector();
-      vcoms.addElement(comp);
+      List<Component> vcoms = new ArrayList<Component>();
+      vcoms.add(comp);
       components.put(seqSetId, vcoms);
     }
   }
 
+  /**
+   * Remove this component from all registrations. Also removes a registered
+   * sequence set id if there are no remaining components registered against it.
+   * 
+   * @param comp
+   */
   public static void RemoveComponent(Component comp)
   {
-    if (components == null)
-    {
-      return;
-    }
-
-    Enumeration en = components.keys();
-    while (en.hasMoreElements())
+    List<String> emptied = new ArrayList<String>();
+    for (Entry<String, List<Component>> registered : components.entrySet())
     {
-      String id = en.nextElement().toString();
-      Vector comps = (Vector) components.get(id);
+      String id = registered.getKey();
+      List<Component> comps = components.get(id);
       comps.remove(comp);
-      if (comps.size() == 0)
+      if (comps.isEmpty())
       {
-        components.remove(id);
+        emptied.add(id);
       }
     }
+
+    /*
+     * Remove now empty ids after the above (to avoid
+     * ConcurrentModificationException).
+     */
+    for (String id : emptied)
+    {
+      components.remove(id);
+    }
   }
 
   public static void Refresh(Component source, String id)
@@ -97,24 +104,15 @@ public class PaintRefresher
   public static void Refresh(Component source, String id,
           boolean alignmentChanged, boolean validateSequences)
   {
-    if (components == null)
-    {
-      return;
-    }
-
-    Component comp;
-    Vector comps = (Vector) components.get(id);
+    List<Component> comps = components.get(id);
 
     if (comps == null)
     {
       return;
     }
 
-    Enumeration e = comps.elements();
-    while (e.hasMoreElements())
+    for (Component comp : comps)
     {
-      comp = (Component) e.nextElement();
-
       if (comp == source)
       {
         continue;
@@ -242,30 +240,20 @@ public class PaintRefresher
 
   static AlignmentPanel[] getAssociatedPanels(String id)
   {
-    if (components == null)
-    {
-      return new AlignmentPanel[0];
-    }
-    ;
-    Vector comps = (Vector) components.get(id);
+    List<Component> comps = components.get(id);
     if (comps == null)
     {
       return new AlignmentPanel[0];
     }
-    ;
-    Vector tmp = new Vector();
-    int i, iSize = comps.size();
-    for (i = 0; i < iSize; i++)
+    List<AlignmentPanel> tmp = new ArrayList<AlignmentPanel>();
+    for (Component comp : comps)
     {
-      if (comps.elementAt(i) instanceof AlignmentPanel)
+      if (comp instanceof AlignmentPanel)
       {
-        tmp.addElement(comps.elementAt(i));
+        tmp.add((AlignmentPanel) comp);
       }
     }
-    AlignmentPanel[] result = new AlignmentPanel[tmp.size()];
-    tmp.toArray(result);
-
-    return result;
+    return tmp.toArray(new AlignmentPanel[tmp.size()]);
   }
 
 }
index bc2c27c..29d6b71 100755 (executable)
@@ -26,6 +26,7 @@ import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.jbgui.GPairwiseAlignPanel;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.event.ActionEvent;
 import java.util.Vector;
@@ -39,7 +40,7 @@ import java.util.Vector;
 public class PairwiseAlignPanel extends GPairwiseAlignPanel
 {
 
-  AlignViewport av;
+  AlignmentViewport av;
 
   Vector sequences;
 
@@ -49,7 +50,7 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
    * @param av
    *          DOCUMENT ME!
    */
-  public PairwiseAlignPanel(AlignViewport av)
+  public PairwiseAlignPanel(AlignmentViewport av)
   {
     super();
     this.av = av;
index bccbf8c..06efcf3 100644 (file)
@@ -2548,15 +2548,7 @@ public class PopupMenu extends JPopupMenu
     }
 
     int gsize = sg.getSize();
-    SequenceI[] hseqs;
-
-    hseqs = new SequenceI[gsize];
-
-    int index = 0;
-    for (int i = 0; i < gsize; i++)
-    {
-      hseqs[index++] = sg.getSequenceAt(i);
-    }
+    SequenceI[] hseqs = sg.getSequences().toArray(new SequenceI[gsize]);
 
     ap.av.hideSequence(hseqs);
     // refresh(); TODO: ? needed ?
index c8a0ec7..1b5695c 100755 (executable)
@@ -276,10 +276,10 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
     }
     
     CommandI command = historyList.pop();
-    if (ap.av.historyList.contains(command))
+    if (ap.av.getHistoryList().contains(command))
     {
       command.undoCommand(af.getViewAlignments());
-      ap.av.historyList.remove(command);
+      ap.av.getHistoryList().remove(command);
       ap.av.firePropertyChange("alignment", null, ap.av.getAlignment().getSequences());
       af.updateEditMenuBar();
     }
index 1ff78b4..6aff578 100755 (executable)
@@ -30,6 +30,7 @@ import jalview.api.RotatableCanvasI;
 import jalview.datamodel.*;
 import jalview.math.*;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 /**
  * DOCUMENT ME!
@@ -101,7 +102,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
 
   float scalefactor = 1;
 
-  AlignViewport av;
+  AlignmentViewport av;
 
   AlignmentPanel ap;
 
index 21160ef..1eede34 100755 (executable)
@@ -480,21 +480,16 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
           maxX = (i - startx + 1) * avCharWidth + fm.stringWidth(string);
         }
 
-        gg.drawLine(
-((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
+        gg.drawLine(((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
                 y + 2,
                 ((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
                 y + (fm.getDescent() * 2));
-
       }
       else
       {
-        gg.drawLine(
-((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
-                y + fm.getDescent(),
- ((i - startx - 1) * avCharWidth)
-                + (avCharWidth / 2),
-                y + (fm.getDescent() * 2));
+        gg.drawLine(((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
+                y + fm.getDescent(), ((i - startx - 1) * avCharWidth)
+                + (avCharWidth / 2), y + (fm.getDescent() * 2));
       }
     }
 
index 6080cf5..395e261 100755 (executable)
@@ -654,9 +654,8 @@ public class SeqCanvas extends JComponent
       int blockStart = startRes;
       int blockEnd = endRes;
 
-      for (int i = 0; regions != null && i < regions.size(); i++)
+      for (int[] region : regions)
       {
-        int[] region = regions.get(i);
         int hideStart = region[0];
         int hideEnd = region[1];
 
index 4a43798..bd8bb7c 100644 (file)
  */
 package jalview.gui;
 
+import jalview.api.AlignViewportI;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
+import jalview.commands.EditCommand.Edit;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.Sequence;
@@ -34,7 +36,11 @@ import jalview.structure.SelectionListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.SequenceListener;
 import jalview.structure.StructureSelectionManager;
+import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
+import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -245,22 +251,33 @@ public class SeqPanel extends JPanel implements MouseListener,
     return seq;
   }
 
+  /**
+   * 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()
@@ -640,6 +657,11 @@ public class SeqPanel extends JPanel implements MouseListener,
   }
 
   @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");
@@ -777,35 +799,36 @@ public class SeqPanel extends JPanel implements MouseListener,
   int setStatusMessage(SequenceI sequence, int res, int seq)
   {
     int pos = -1;
-    StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
-            + sequence.getName());
+    StringBuilder text = new StringBuilder(32);
+    text.append("Sequence " + (seq + 1) + " ID: " + sequence.getName());
 
-    Object obj = null;
+    String residue = null;
+    /*
+     * Try to translate the display character to residue name (null for gap).
+     */
+    final String displayChar = String.valueOf(sequence.getCharAt(res));
     if (av.getAlignment().isNucleotide())
     {
-      obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
-              + "");
-      if (obj != null)
+      residue = ResidueProperties.nucleotideName.get(displayChar);
+      if (residue != null)
       {
-        text.append(" Nucleotide: ");
+        text.append(" Nucleotide: ").append(residue);
       }
     }
     else
     {
-      obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
-      if (obj != null)
+      residue = "X".equalsIgnoreCase(displayChar) ? "STOP"
+              : ResidueProperties.aa2Triplet.get(displayChar);
+      if (residue != null)
       {
-        text.append("  Residue: ");
+        text.append(" Residue: ").append(residue);
       }
     }
 
-    if (obj != null)
+    if (residue != null)
     {
       pos = sequence.findPosition(res);
-      if (obj != "")
-      {
-        text.append(obj + " (" + pos + ")");
-      }
+      text.append(" (").append(Integer.toString(pos)).append(")");
     }
     ap.alignFrame.statusBar.setText(text.toString());
     return pos;
@@ -926,7 +949,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
 
-    StringBuffer message = new StringBuffer();
+    StringBuilder message = new StringBuilder(64);
     if (groupEditing)
     {
       message.append("Edit group:");
@@ -1151,8 +1174,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
         else
         {
-          editCommand.appendEdit(Action.INSERT_GAP, groupSeqs,
-                  startres, startres - lastres, av.getAlignment(), true);
+          appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres
+                  - lastres);
         }
       }
       else
@@ -1167,8 +1190,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
         else
         {
-          editCommand.appendEdit(Action.DELETE_GAP, groupSeqs,
-                  startres, lastres - startres, av.getAlignment(), true);
+          appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres
+                  - startres);
         }
 
       }
@@ -1189,8 +1212,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
         else
         {
-          editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[]
-          { seq }, lastres, startres - lastres, av.getAlignment(), true);
+          appendEdit(Action.INSERT_GAP, new SequenceI[]
+          { seq }, lastres, startres - lastres);
         }
       }
       else
@@ -1202,7 +1225,7 @@ public class SeqPanel extends JPanel implements MouseListener,
           {
             for (int j = lastres; j > startres; j--)
             {
-              if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
+              if (!Comparison.isGap(seq.getCharAt(startres)))
               {
                 endEditing();
                 break;
@@ -1217,7 +1240,7 @@ public class SeqPanel extends JPanel implements MouseListener,
             int max = 0;
             for (int m = startres; m < lastres; m++)
             {
-              if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
+              if (!Comparison.isGap(seq.getCharAt(m)))
               {
                 break;
               }
@@ -1226,9 +1249,8 @@ public class SeqPanel extends JPanel implements MouseListener,
 
             if (max > 0)
             {
-              editCommand.appendEdit(Action.DELETE_GAP,
-                      new SequenceI[]
-                      { seq }, startres, max, av.getAlignment(), true);
+              appendEdit(Action.DELETE_GAP, new SequenceI[]
+              { seq }, startres, max);
             }
           }
         }
@@ -1244,8 +1266,8 @@ public class SeqPanel extends JPanel implements MouseListener,
           }
           else
           {
-            editCommand.appendEdit(Action.INSERT_NUC, new SequenceI[]
-            { seq }, lastres, startres - lastres, av.getAlignment(), true);
+            appendEdit(Action.INSERT_NUC, new SequenceI[]
+            { seq }, lastres, startres - lastres);
           }
         }
       }
@@ -1280,22 +1302,37 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
 
-    editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.DELETE_GAP, seq, blankColumn, 1);
 
-    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.INSERT_GAP, seq, j, 1);
 
   }
 
+  /**
+   * Helper method to add and perform one edit action.
+   * 
+   * @param action
+   * @param seq
+   * @param pos
+   * @param count
+   */
+  protected void appendEdit(Action action, SequenceI[] seq, int pos,
+          int count)
+  {
+
+    final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
+            av.getAlignment().getGapCharacter());
+
+    editCommand.appendEdit(edit, av.getAlignment(),
+            true, null);
+  }
+
   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
   {
 
-    editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.DELETE_GAP, seq, j, 1);
 
-    editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1);
   }
 
   /**
@@ -1801,52 +1838,68 @@ public class SeqPanel extends JPanel implements MouseListener,
     // handles selection messages...
     // TODO: extend config options to allow user to control if selections may be
     // shared between viewports.
-    if (av == source
-            || !av.followSelection
-            || (av.isSelectionGroupChanged(false) || av
-                    .isColSelChanged(false))
-            || (source instanceof AlignViewport && ((AlignViewport) source)
-                    .getSequenceSetId().equals(av.getSequenceSetId())))
+    boolean iSentTheSelection = (av == source
+            || (source instanceof AlignViewport && ((AlignmentViewport) source)
+            .getSequenceSetId().equals(av.getSequenceSetId())));
+    if (iSentTheSelection || !av.followSelection)
+    {
+      return;
+    }
+
+    /*
+     * Ignore the selection if there is one of our own pending.
+     */
+    if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
+    {
+      return;
+    }
+
+    /*
+     * Check for selection in a view of which this one is a dna/protein
+     * complement.
+     */
+    if (selectionFromTranslation(seqsel, colsel, source))
     {
       return;
     }
+
     // do we want to thread this ? (contention with seqsel and colsel locks, I
     // suspect)
     // rules are: colsel is copied if there is a real intersection between
     // sequence selection
-    boolean repaint = false, copycolsel = true;
-    // if (!av.isSelectionGroupChanged(false))
+    boolean repaint = false;
+    boolean copycolsel = true;
+
+    SequenceGroup sgroup = null;
+    if (seqsel != null && seqsel.getSize() > 0)
     {
-      SequenceGroup sgroup = null;
-      if (seqsel != null && seqsel.getSize() > 0)
+      if (av.getAlignment() == null)
       {
-        if (av.getAlignment() == null)
-        {
-          jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
-                  + av.getSequenceSetId() + " ViewId=" + av.getViewId()
-                  + " 's alignment is NULL! returning immediatly.");
-          return;
-        }
-        sgroup = seqsel.intersect(av.getAlignment(),
-                (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
-        if ((sgroup == null || sgroup.getSize() == 0)
-                || (colsel == null || colsel.size() == 0))
-        {
-          // don't copy columns if the region didn't intersect.
-          copycolsel = false;
-        }
+        jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
+                + av.getSequenceSetId() + " ViewId=" + av.getViewId()
+                + " 's alignment is NULL! returning immediately.");
+        return;
       }
-      if (sgroup != null && sgroup.getSize() > 0)
+      sgroup = seqsel.intersect(av.getAlignment(),
+              (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
+      if ((sgroup == null || sgroup.getSize() == 0)
+              || (colsel == null || colsel.size() == 0))
       {
-        av.setSelectionGroup(sgroup);
+        // don't copy columns if the region didn't intersect.
+        copycolsel = false;
       }
-      else
-      {
-        av.setSelectionGroup(null);
-      }
-      av.isSelectionGroupChanged(true);
-      repaint = true;
     }
+    if (sgroup != null && sgroup.getSize() > 0)
+    {
+      av.setSelectionGroup(sgroup);
+    }
+    else
+    {
+      av.setSelectionGroup(null);
+    }
+    av.isSelectionGroupChanged(true);
+    repaint = true;
+
     if (copycolsel)
     {
       // the current selection is unset or from a previous message
@@ -1874,6 +1927,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       av.isColSelChanged(true);
       repaint = true;
     }
+
     if (copycolsel
             && av.hasHiddenColumns()
             && (av.getColumnSelection() == null || av.getColumnSelection()
@@ -1881,11 +1935,52 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       System.err.println("Bad things");
     }
-    if (repaint)
+    if (repaint) // always true!
     {
       // probably finessing with multiple redraws here
       PaintRefresher.Refresh(this, av.getSequenceSetId());
       // ap.paintAlignment(false);
     }
   }
+
+  /**
+   * If this panel is a cdna/protein translation view of the selection source,
+   * tries to map the source selection to a local one, and returns true. Else
+   * returns false.
+   * 
+   * @param seqsel
+   * @param colsel
+   * @param source
+   */
+  protected boolean selectionFromTranslation(SequenceGroup seqsel,
+          ColumnSelection colsel, SelectionSource source)
+  {
+    if (!(source instanceof AlignViewportI)) {
+      return false;
+    }
+    final AlignViewportI sourceAv = (AlignViewportI) source;
+    if (sourceAv.getCodingComplement() != av && av.getCodingComplement() != sourceAv)
+    {
+      return false;
+    }
+
+    /*
+     * Map sequence selection
+     */
+    SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
+    av.setSelectionGroup(sg);
+    av.isSelectionGroupChanged(true);
+
+    /*
+     * Map column selection
+     */
+    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
+            av);
+    av.setColumnSelection(cs);
+    av.isColSelChanged(true);
+
+    PaintRefresher.Refresh(this, av.getSequenceSetId());
+
+    return true;
+  }
 }
index 35bc29a..e159d41 100755 (executable)
  */
 package jalview.gui;
 
-import java.util.*;
-import java.util.List;
-
-import java.awt.*;
-import java.awt.event.*;
-
-import javax.swing.*;
-import javax.swing.tree.DefaultMutableTreeNode;
-
-import com.stevesoft.pat.Regex;
-
-import jalview.datamodel.*;
-import jalview.io.*;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.DBRefSource;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.io.FormatAdapter;
+import jalview.io.IdentifyFile;
 import jalview.util.DBRefUtils;
 import jalview.util.MessageManager;
 import jalview.ws.dbsources.das.api.DasSourceRegistryI;
 import jalview.ws.seqfetcher.DbSourceProxy;
+
 import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingConstants;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import com.stevesoft.pat.Regex;
 
 public class SequenceFetcher extends JPanel implements Runnable
 {
@@ -283,7 +301,9 @@ public class SequenceFetcher extends JPanel implements Runnable
       public void keyPressed(KeyEvent e)
       {
         if (e.getKeyCode() == KeyEvent.VK_ENTER)
+        {
           ok_actionPerformed();
+        }
       }
     });
     jPanel3.setLayout(borderLayout1);
@@ -821,21 +841,7 @@ public class SequenceFetcher extends JPanel implements Runnable
       }
       else
       {
-        for (int i = 0; i < al.getHeight(); i++)
-        {
-          alignFrame.viewport.getAlignment().addSequence(
-                  al.getSequenceAt(i)); // this
-          // also
-          // creates
-          // dataset
-          // sequence
-          // entries
-        }
-        alignFrame.viewport.setEndSeq(alignFrame.viewport.getAlignment()
-                .getHeight());
-        alignFrame.viewport.getAlignment().getWidth();
-        alignFrame.viewport.firePropertyChange("alignment", null,
-                alignFrame.viewport.getAlignment().getSequences());
+        alignFrame.viewport.addAlignment(al, title);
       }
     }
     return al;
diff --git a/src/jalview/gui/SplitFrame.java b/src/jalview/gui/SplitFrame.java
new file mode 100644 (file)
index 0000000..f98eea7
--- /dev/null
@@ -0,0 +1,219 @@
+package jalview.gui;
+
+import jalview.jbgui.GSplitFrame;
+
+import java.awt.Component;
+import java.awt.MouseInfo;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.Map.Entry;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+public class SplitFrame extends GSplitFrame
+{
+  private static final long serialVersionUID = 1L;
+
+  public SplitFrame(JComponent top, JComponent bottom)
+  {
+    super(top, bottom);
+    init();
+  }
+
+  /**
+   * Initialise this frame.
+   */
+  protected void init()
+  {
+    setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20);
+
+    addCloseFrameListener();
+    
+    addKeyListener();
+
+    addKeyBindings();
+
+  }
+
+  /**
+   * Add a listener to tidy up when the frame is closed.
+   */
+  protected void addCloseFrameListener()
+  {
+    addInternalFrameListener(new InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosed(InternalFrameEvent evt)
+      {
+        if (getTopComponent() instanceof AlignFrame)
+        {
+          ((AlignFrame) getTopComponent())
+                  .closeMenuItem_actionPerformed(true);
+        }
+        if (getBottomComponent() instanceof AlignFrame)
+        {
+          ((AlignFrame) getBottomComponent())
+                  .closeMenuItem_actionPerformed(true);
+        }
+      };
+    });
+  }
+
+  /**
+   * Add a key listener that delegates to whichever split component the mouse is
+   * in (or does nothing if neither).
+   */
+  protected void addKeyListener()
+  {
+    // TODO Key Bindings rather than KeyListener are recommended for Swing
+    addKeyListener(new KeyAdapter() {
+
+      @Override
+      public void keyPressed(KeyEvent e)
+      {
+        Component c = getComponentAtMouse();
+        if (c != null)
+        {
+          for (KeyListener kl : c.getKeyListeners())
+          {
+            kl.keyPressed(e);
+          }
+        }
+      }
+
+      @Override
+      public void keyReleased(KeyEvent e)
+      {
+        Component c = getComponentAtMouse();
+        if (c != null)
+        {
+          for (KeyListener kl : c.getKeyListeners())
+          {
+            kl.keyReleased(e);
+          }
+        }
+      }
+      
+    });
+  }
+
+  /**
+   * Returns the split pane component the mouse is in, or null if neither.
+   * 
+   * @return
+   */
+  protected Component getComponentAtMouse()
+  {
+    Point loc = MouseInfo.getPointerInfo().getLocation();
+    
+    if (isIn(loc, getTopComponent())) {
+      return getTopComponent();
+    }
+    else if (isIn(loc, getBottomComponent()))
+    {
+      return getBottomComponent();
+    }
+    return null;
+  }
+
+  private boolean isIn(Point loc, JComponent comp)
+  {
+    Point p = comp.getLocationOnScreen();
+    Rectangle r = new Rectangle(p.x, p.y, comp.getWidth(), comp.getHeight());
+    return r.contains(loc);
+  }
+
+  /**
+   * Set key bindings (recommended for Swing over key accelerators). For now,
+   * delegate to the corresponding key accelerator for the AlignFrame that the
+   * mouse is in. Hopefully can be simplified in future if AlignFrame is changed
+   * to use key bindings rather than accelerators.
+   */
+  private void addKeyBindings()
+  {
+    if (getTopComponent() instanceof AlignFrame)
+    {
+      for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopComponent())
+              .getAccelerators().entrySet())
+      {
+
+        final KeyStroke ks = acc.getKey();
+        this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, ks);
+        this.getActionMap().put(ks, new AbstractAction()
+        {
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            Component c = getComponentAtMouse();
+            if (c instanceof AlignFrame)
+            {
+              for (ActionListener a : ((AlignFrame) c).getAccelerators()
+                      .get(ks).getActionListeners())
+              {
+
+                a.actionPerformed(null);
+              }
+            }
+          }
+        });
+      }
+      /*
+       * Disable unwanted here
+       */
+      // X expand views - wrecks the split pane view
+      KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
+      disableAccelerator(key_X);
+    }
+  }
+
+  /**
+   * Ugly hack for Proof of Concept that disables the key binding in this frame
+   * _and_ disables the bound menu item _and_ removes the key accelerator in the
+   * child frames.
+   * 
+   * @param key
+   */
+  protected void disableAccelerator(KeyStroke key)
+  {
+    disableAccelerator(key, getTopComponent());
+    disableAccelerator(key, getBottomComponent());
+    this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(key);
+  }
+
+  /**
+   * Disable the menu item for which this key is the accelerator, also removes
+   * its action listeners to prevent key accelerator working.
+   * 
+   * @param key
+   * @param comp
+   */
+  private void disableAccelerator(KeyStroke key, JComponent comp)
+  {
+    // HACKED ONLY FOR PROOF OF CONCEPT
+    // Proper solution might involve explicit 'configure menu' method on
+    // AlignFrame, or
+    // changing key listeners to key bindings in AlignFrame, or both
+    if (comp instanceof AlignFrame)
+    {
+      JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
+      if (mi != null)
+      {
+        mi.setEnabled(false);
+        for (ActionListener al : mi.getActionListeners())
+        {
+          mi.removeActionListener(al);
+        }
+      }
+    }
+  }
+}
index 61d7d7e..d02a3eb 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import jalview.analysis.Conservation;
 import jalview.analysis.NJTree;
+import jalview.api.AlignViewportI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -30,7 +31,9 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
+import jalview.structure.SelectionSource;
 import jalview.util.Format;
+import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
@@ -66,7 +69,7 @@ import javax.swing.ToolTipManager;
  * @version $Revision$
  */
 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
-        Printable, MouseMotionListener
+        Printable, MouseMotionListener, SelectionSource
 {
   /** DOCUMENT ME!! */
   public static final String PLACEHOLDER = " * ";
@@ -520,7 +523,8 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         {
           for (int a = 0; a < aps.length; a++)
           {
-            aps[a].av.setSequenceColour((SequenceI) node.element(), c);
+            final SequenceI seq = (SequenceI) node.element();
+            aps[a].av.setSequenceColour(seq, c);
           }
         }
       }
@@ -881,12 +885,20 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
         AlignmentPanel[] aps = getAssociatedPanels();
 
+        // TODO push calls below into a single AlignViewportI method?
+        // see also AlignViewController.deleteGroups
         for (int a = 0; a < aps.length; a++)
         {
           aps[a].av.setSelectionGroup(null);
           aps[a].av.getAlignment().deleteAllGroups();
           aps[a].av.clearSequenceColours();
         }
+        if (av.getCodingComplement() != null)
+        {
+          av.getCodingComplement().setSelectionGroup(null);
+          av.getCodingComplement().getAlignment().deleteAllGroups();
+          av.getCodingComplement().clearSequenceColours();
+        }
         colourGroups();
       }
 
@@ -950,6 +962,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       // sg.recalcConservation();
       sg.setName("JTreeGroup:" + sg.hashCode());
       sg.setIdColour(col);
+
       for (int a = 0; a < aps.length; a++)
       {
         if (aps[a].av.getGlobalColourScheme() != null
@@ -966,7 +979,26 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
         aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
       }
+
+      // TODO can we push all of the below into AlignViewportI?
+      av.getAlignment().addGroup(sg);
+      final AlignViewportI codingComplement = av.getCodingComplement();
+      if (codingComplement != null)
+      {
+        SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,
+                codingComplement);
+        if (mappedGroup.getSequences().size() > 0)
+        {
+          codingComplement.getAlignment().addGroup(mappedGroup);
+          for (SequenceI seq : mappedGroup.getSequences())
+          {
+            codingComplement.setSequenceColour(seq,
+                    mappedGroup.getIdColour().brighter());
+          }
+        }
+      }
     }
+
     // notify the panel to redo any group specific stuff.
     for (int a = 0; a < aps.length; a++)
     {
@@ -975,6 +1007,13 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       // to any Jmols listening in
     }
 
+    if (av.getCodingComplement() != null)
+    {
+      ((AlignViewport) av.getCodingComplement()).getAlignPanel().updateAnnotation();
+      /*
+       * idPanel. repaint ()
+       */
+    }
   }
 
   /**
index 6ce68b3..d7809cb 100755 (executable)
@@ -242,7 +242,8 @@ public class TreePanel extends GTreePanel
       associateLeavesMenu.add(item);
     }
 
-    final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
+    final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
+            "label.all_views");
     buttonGroup.add(itemf);
     itemf.setSelected(treeCanvas.applyToAllViews);
     itemf.addActionListener(new ActionListener()
@@ -343,7 +344,7 @@ public class TreePanel extends GTreePanel
       av.setCurrentTree(tree);
       if (av.getSortByTree())
       {
-        sortByTree_actionPerformed(null);
+        sortByTree_actionPerformed();
       }
     }
   }
@@ -531,7 +532,7 @@ public class TreePanel extends GTreePanel
         // 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);
       }
@@ -555,7 +556,8 @@ public class TreePanel extends GTreePanel
    * 
    * @param e
    */
-  public void sortByTree_actionPerformed(ActionEvent e)
+  @Override
+  public void sortByTree_actionPerformed()
   {
 
     if (treeCanvas.applyToAllViews)
index 77a7693..04cc9e3 100644 (file)
@@ -32,12 +32,12 @@ import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasListener;
 import jalview.structure.VamsasSource;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.io.File;
 import java.io.IOException;
-import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -347,7 +347,9 @@ public class VamsasApplication implements SelectionSource, VamsasSource
   public void end_session(boolean promptUser)
   {
     if (!inSession())
+    {
       throw new Error(MessageManager.getString("error.jalview_no_connected_vamsas_session"));
+    }
     Cache.log.info("Jalview disconnecting from the Vamsas Session.");
     try
     {
@@ -958,11 +960,14 @@ public class VamsasApplication implements SelectionSource, VamsasSource
 
           int i = -1;
 
-          public void mouseOver(SequenceI seq, int index,
+          @Override
+          public void mouseOverSequence(SequenceI seq, int index,
                   VamsasSource source)
           {
             if (jv2vobj == null)
+            {
               return;
+            }
             if (seq != last || i != index)
             {
               VorbaId v = (VorbaId) jv2vobj.get(seq);
@@ -998,7 +1003,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
               AlignmentI visal = null;
               if (source instanceof AlignViewport)
               {
-                visal = ((AlignViewport) source).getAlignment();
+                visal = ((AlignmentViewport) source).getAlignment();
               }
               SelectionMessage sm = null;
               if ((seqsel == null || seqsel.getSize() == 0)
@@ -1009,7 +1014,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                 {
                   // the empty selection.
                   sm = new SelectionMessage("jalview", new String[]
-                  { ((AlignViewport) source).getSequenceSetId() }, null,
+                  { ((AlignmentViewport) source).getSequenceSetId() }, null,
                           true);
                 }
                 else
@@ -1050,10 +1055,9 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                   {
                     // gather selected columns outwith the sequence positions
                     // too
-                    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);
index 4a74c9c..0772781 100644 (file)
@@ -1306,7 +1306,7 @@ public class WsJobParameters extends JPanel implements ItemListener,
    */
   protected void updateWebServiceMenus()
   {
-    for (AlignFrame alignFrame : Desktop.getAlignframes())
+    for (AlignFrame alignFrame : Desktop.getAlignFrames())
     {
       alignFrame.BuildWebServiceMenu();
     }
index 89adead..f316a7e 100755 (executable)
@@ -46,10 +46,24 @@ public class AppletFormatAdapter
    * 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, "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", "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", "HTML" };
 
   /**
    * List of valid format strings for use by callers of the formatSequences
@@ -75,26 +89,6 @@ public class AppletFormatAdapter
   { "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
index 0285bf0..906e823 100755 (executable)
@@ -332,13 +332,7 @@ public class FileLoader implements Runnable
           }
           if (viewport != null)
           {
-            // TODO: create undo object for this JAL-1101
-            for (int i = 0; i < al.getHeight(); i++)
-            {
-              viewport.getAlignment().addSequence(al.getSequenceAt(i));
-            }
-            viewport.firePropertyChange("alignment", null, viewport
-                    .getAlignment().getSequences());
+            viewport.addAlignment(al, title);
           }
           else
           {
index 90447db..1d36865 100644 (file)
@@ -34,6 +34,7 @@ import jalview.io.vamsas.DatastoreItem;
 import jalview.io.vamsas.DatastoreRegistry;
 import jalview.io.vamsas.Rangetype;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 import java.io.IOException;
 import java.util.Enumeration;
@@ -42,12 +43,35 @@ import java.util.Hashtable;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.Vector;
 import java.util.jar.JarInputStream;
 import java.util.jar.JarOutputStream;
 
-import uk.ac.vamsas.client.*;
-import uk.ac.vamsas.objects.core.*;
+import uk.ac.vamsas.client.IClientAppdata;
+import uk.ac.vamsas.client.IClientDocument;
+import uk.ac.vamsas.client.Vobject;
+import uk.ac.vamsas.client.VorbaId;
+import uk.ac.vamsas.objects.core.Alignment;
+import uk.ac.vamsas.objects.core.AlignmentSequence;
+import uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation;
+import uk.ac.vamsas.objects.core.AnnotationElement;
+import uk.ac.vamsas.objects.core.DataSet;
+import uk.ac.vamsas.objects.core.DataSetAnnotations;
+import uk.ac.vamsas.objects.core.DbRef;
+import uk.ac.vamsas.objects.core.Entry;
+import uk.ac.vamsas.objects.core.Glyph;
+import uk.ac.vamsas.objects.core.Local;
+import uk.ac.vamsas.objects.core.MapType;
+import uk.ac.vamsas.objects.core.Mapped;
+import uk.ac.vamsas.objects.core.Property;
+import uk.ac.vamsas.objects.core.Provenance;
+import uk.ac.vamsas.objects.core.RangeAnnotation;
+import uk.ac.vamsas.objects.core.RangeType;
+import uk.ac.vamsas.objects.core.Seg;
+import uk.ac.vamsas.objects.core.Sequence;
+import uk.ac.vamsas.objects.core.SequenceType;
+import uk.ac.vamsas.objects.core.VAMSAS;
 import uk.ac.vamsas.objects.utils.Properties;
 
 /*
@@ -127,7 +151,7 @@ public class VamsasAppDatastore
   private void buildSkipList()
   {
     skipList = new Hashtable();
-    AlignFrame[] al = Desktop.getAlignframes();
+    AlignFrame[] al = Desktop.getAlignFrames();
     for (int f = 0; al != null && f < al.length; f++)
     {
       skipList.put(al[f].getViewport().getSequenceSetId(), al[f]);
@@ -728,12 +752,12 @@ public class VamsasAppDatastore
    * @return true if alignment associated with this view will be stored in
    *         document.
    */
-  public boolean alignmentWillBeSkipped(AlignViewport av)
+  public boolean alignmentWillBeSkipped(AlignmentViewport av)
   {
     return (!av.getAlignment().isAligned());
   }
 
-  private void addToSkipList(AlignViewport av)
+  private void addToSkipList(AlignmentViewport av)
   {
     if (skipList == null)
     {
@@ -1068,8 +1092,10 @@ public class VamsasAppDatastore
         an.addProperty(Properties.newProperty(THRESHOLD,
                 Properties.FLOATTYPE, "" + alan.getThreshold().value));
         if (alan.getThreshold().label != null)
+        {
           an.addProperty(Properties.newProperty(THRESHOLD + "Name",
                   Properties.STRINGTYPE, "" + alan.getThreshold().label));
+        }
       }
       ((DataSet) sref.getV_parent()).addDataSetAnnotations(an);
       bindjvvobj(alan, an);
@@ -1381,12 +1407,12 @@ public class VamsasAppDatastore
     // sync,
     // and if any contain more than one view, then remove the one generated by
     // document update.
-    AlignViewport views[], av = null;
+    AlignmentViewport views[], av = null;
     AlignFrame af = null;
     Iterator newviews = newAlignmentViews.iterator();
     while (newviews.hasNext())
     {
-      av = (AlignViewport) newviews.next();
+      av = (AlignmentViewport) newviews.next();
       af = Desktop.getAlignFrameFor(av);
       // TODO implement this : af.getNumberOfViews
       String seqsetidobj = av.getSequenceSetId();
@@ -1403,7 +1429,8 @@ public class VamsasAppDatastore
         // to the align frames.
         boolean gathered = false;
         String newviewid = null;
-        AlignedCodonFrame[] mappings = av.getAlignment().getCodonFrames();
+        Set<AlignedCodonFrame> mappings = av.getAlignment()
+                .getCodonFrames();
         for (int i = 0; i < views.length; i++)
         {
           if (views[i] != av)
@@ -1438,7 +1465,7 @@ public class VamsasAppDatastore
         {
           // ensure sequence mappings from vamsas document view still
           // active
-          if (mappings != null && mappings.length > 0)
+          if (mappings != null)
           {
             jalview.structure.StructureSelectionManager
                     .getStructureSelectionManager(Desktop.instance)
@@ -1682,7 +1709,7 @@ public class VamsasAppDatastore
             uk.ac.vamsas.objects.core.Alignment alignment = dataset
                     .getAlignment(al);
             // TODO check this handles multiple views properly
-            AlignViewport av = findViewport(alignment);
+            AlignmentViewport av = findViewport(alignment);
 
             jalview.datamodel.AlignmentI jal = null;
             if (av != null)
@@ -1956,10 +1983,10 @@ public class VamsasAppDatastore
     return newAlignmentViews.size();
   }
 
-  public AlignViewport findViewport(Alignment alignment)
+  public AlignmentViewport findViewport(Alignment alignment)
   {
-    AlignViewport av = null;
-    AlignViewport[] avs = Desktop
+    AlignmentViewport av = null;
+    AlignmentViewport[] avs = Desktop
             .getViewports((String) getvObj2jv(alignment));
     if (avs != null)
     {
@@ -2207,6 +2234,7 @@ public class VamsasAppDatastore
             Cache.log.warn("Failed to parse threshold property");
           }
           if (val != null)
+          {
             if (gl == null)
             {
               gl = new GraphLine(val.floatValue(), "", java.awt.Color.black);
@@ -2215,11 +2243,14 @@ public class VamsasAppDatastore
             {
               gl.value = val.floatValue();
             }
+          }
         }
         else if (props[p].getName().equalsIgnoreCase(THRESHOLD + "Name"))
         {
           if (gl == null)
+          {
             gl = new GraphLine(0, "", java.awt.Color.black);
+          }
           gl.label = props[p].getContent();
         }
       }
@@ -2670,10 +2701,10 @@ public class VamsasAppDatastore
     return vobj2jv;
   }
 
-  public void storeSequenceMappings(AlignViewport viewport, String title)
+  public void storeSequenceMappings(AlignmentViewport viewport, String title)
           throws Exception
   {
-    AlignViewport av = viewport;
+    AlignmentViewport av = viewport;
     try
     {
       jalview.datamodel.AlignmentI jal = av.getAlignment();
@@ -2695,18 +2726,15 @@ public class VamsasAppDatastore
 
       }
       // Store any sequence mappings.
-      if (av.getAlignment().getCodonFrames() != null
-              && av.getAlignment().getCodonFrames().length > 0)
+      Set<AlignedCodonFrame> cframes = av.getAlignment().getCodonFrames();
+      if (cframes != null)
       {
-        jalview.datamodel.AlignedCodonFrame[] cframes = av.getAlignment()
-                .getCodonFrames();
-        for (int cf = 0; cf < cframes.length; cf++)
+        for (AlignedCodonFrame acf : cframes)
         {
-          if (cframes[cf].getdnaSeqs() != null
-                  && cframes[cf].getdnaSeqs().length > 0)
+          if (acf.getdnaSeqs() != null && acf.getdnaSeqs().length > 0)
           {
-            jalview.datamodel.SequenceI[] dmps = cframes[cf].getdnaSeqs();
-            jalview.datamodel.Mapping[] mps = cframes[cf].getProtMappings();
+            jalview.datamodel.SequenceI[] dmps = acf.getdnaSeqs();
+            jalview.datamodel.Mapping[] mps = acf.getProtMappings();
             for (int smp = 0; smp < mps.length; smp++)
             {
               uk.ac.vamsas.objects.core.SequenceType mfrom = (SequenceType) getjv2vObj(dmps[smp]);
index 3878a0a..3e20dcd 100644 (file)
  */
 package jalview.io.vamsas;
 
-import java.util.Vector;
-
 import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.SequenceI;
 import jalview.gui.Desktop;
 import jalview.io.VamsasAppDatastore;
-import uk.ac.vamsas.client.Vobject;
+
+import java.util.Vector;
+
 import uk.ac.vamsas.objects.core.AlignmentSequence;
 import uk.ac.vamsas.objects.core.DataSet;
 import uk.ac.vamsas.objects.core.Sequence;
@@ -283,12 +284,12 @@ public class Sequencemapping extends Rangetype
       jalview.bin.Cache.log.info("Ignoring non sequence-sequence mapping");
       return;
     }
-    mobj = this.getvObj2jv((Vobject) sdloc);
+    mobj = this.getvObj2jv(sdloc);
     if (mobj instanceof SequenceI)
     {
       from = (SequenceI) mobj;
     }
-    mobj = this.getvObj2jv((Vobject) sdmap);
+    mobj = this.getvObj2jv(sdmap);
     if (mobj instanceof SequenceI)
     {
       to = (SequenceI) mobj;
@@ -325,19 +326,17 @@ public class Sequencemapping extends Rangetype
     }
     // create mapping storage object and make each dataset alignment reference
     // it.
-    jalview.datamodel.AlignmentI dsLoc = (jalview.datamodel.AlignmentI) getvObj2jv(sdloc
-            .getV_parent());
-    jalview.datamodel.AlignmentI dsMap = (jalview.datamodel.AlignmentI) getvObj2jv(sdmap
-            .getV_parent());
-    AlignedCodonFrame afc = new AlignedCodonFrame(0);
+    AlignmentI dsLoc = (AlignmentI) getvObj2jv(sdloc.getV_parent());
+    AlignmentI dsMap = (AlignmentI) getvObj2jv(sdmap.getV_parent());
+    AlignedCodonFrame acf = new AlignedCodonFrame();
 
     if (dsLoc != null && dsLoc != dsMap)
     {
-      dsLoc.addCodonFrame(afc);
+      dsLoc.addCodonFrame(acf);
     }
     if (dsMap != null)
     {
-      dsMap.addCodonFrame(afc);
+      dsMap.addCodonFrame(acf);
     }
     // create and add the new mapping to (each) dataset's codonFrame
 
@@ -350,24 +349,22 @@ public class Sequencemapping extends Rangetype
         mapping = new jalview.util.MapList(mapping.getToRanges(),
                 mapping.getFromRanges(), mapping.getToRatio(),
                 mapping.getFromRatio());
-        afc.addMap(to, from, mapping);
+        acf.addMap(to, from, mapping);
       }
       else
       {
         mapping = this.parsemapType(sequenceMapping, 3, 1); // correct sense
-        afc.addMap(from, to, mapping);
+        acf.addMap(from, to, mapping);
       }
     }
     else
     {
       mapping = this.parsemapType(sequenceMapping, 1, 1); // correct sense
-      afc.addMap(from, to, mapping);
+      acf.addMap(from, to, mapping);
     }
     bindjvvobj(mapping, sequenceMapping);
     jalview.structure.StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance).addMappings(
-                    new AlignedCodonFrame[]
-                    { afc });
+            .getStructureSelectionManager(Desktop.instance).addMapping(acf);
     // Try to link up any conjugate database references in the two sequences
     // matchConjugateDBRefs(from, to, mapping);
     // Try to propagate any dbrefs across this mapping.
index dbfaf37..40d9f7c 100644 (file)
@@ -35,10 +35,10 @@ import jalview.datamodel.SeqCigar;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequenceNode;
-import jalview.gui.AlignViewport;
 import jalview.gui.TreePanel;
 import jalview.io.NewickFile;
 import jalview.io.VamsasAppDatastore;
+import jalview.viewmodel.AlignmentViewport;
 import uk.ac.vamsas.client.Vobject;
 import uk.ac.vamsas.objects.core.AlignmentSequence;
 import uk.ac.vamsas.objects.core.Entry;
@@ -510,7 +510,7 @@ public class Tree extends DatastoreItem
    */
   public Object[] recoverInputData(Provenance tp)
   {
-    AlignViewport javport = null;
+    AlignmentViewport javport = null;
     jalview.datamodel.AlignmentI jal = null;
     jalview.datamodel.CigarArray view = null;
     for (int pe = 0; pe < tp.getEntryCount(); pe++)
@@ -604,7 +604,7 @@ public class Tree extends DatastoreItem
     return null;
   }
 
-  private AlignViewport getViewport(Vobject v_parent)
+  private AlignmentViewport getViewport(Vobject v_parent)
   {
     if (v_parent instanceof uk.ac.vamsas.objects.core.Alignment)
     {
index 19f7b11..2b0aab2 100644 (file)
@@ -37,7 +37,9 @@ public class MouseOverListener extends JSFunctionExec implements
 
   int i = -1;
 
-  public void mouseOver(SequenceI seq, int index, VamsasSource source)
+  @Override
+  public void mouseOverSequence(SequenceI seq, int index,
+          VamsasSource source)
   {
     if (seq != last || i != index)
     {
index 2ecaf6c..3cc7c98 100644 (file)
@@ -20,9 +20,6 @@
  */
 package jalview.javascript;
 
-import java.awt.Color;
-import java.util.ArrayList;
-
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
@@ -30,11 +27,15 @@ import jalview.appletgui.AlignFrame;
 import jalview.bin.JalviewLite;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JmolCommands;
+import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Propagate events involving PDB structures associated with sequences to a
  * javascript function. Generally, the javascript handler is called with a
@@ -133,7 +134,6 @@ public class MouseOverStructureListener extends JSFunctionExec implements
     return modelSet;
   }
 
-  @Override
   public void mouseOverStructure(int atomIndex, String strInfo)
   {
 
@@ -144,24 +144,25 @@ public class MouseOverStructureListener extends JSFunctionExec implements
   }
 
   @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.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
@@ -283,13 +284,6 @@ public class MouseOverStructureListener extends JSFunctionExec implements
   }
 
   @Override
-  public Color getColour(int atomIndex, int pdbResNum, String chain,
-          String pdbId)
-  {
-    return null;
-  }
-
-  @Override
   public AlignFrame getAlignFrame()
   {
     // associated with all alignframes, always.
index 37ec47d..df930c4 100755 (executable)
@@ -35,8 +35,11 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusAdapter;
 import java.awt.event.FocusEvent;
+import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.swing.BorderFactory;
 import javax.swing.ButtonGroup;
@@ -50,6 +53,7 @@ import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JRadioButtonMenuItem;
 import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
 import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.MenuEvent;
@@ -256,6 +260,8 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenuItem showTranslation = new JMenuItem();
 
+  protected JMenu cdna = new JMenu();
+
   protected JMenuItem extractScores = new JMenuItem();
 
   protected JMenuItem expandAlignment = new JMenuItem();
@@ -388,6 +394,8 @@ public class GAlignFrame extends JInternalFrame
 
   private boolean showAutoCalculatedAbove = false;
 
+  private Map<KeyStroke, JMenuItem> accelerators = new HashMap<KeyStroke, JMenuItem>();
+
   public GAlignFrame()
   {
     try
@@ -401,7 +409,7 @@ public class GAlignFrame extends JInternalFrame
         JMenuItem item = new JMenuItem(
                 jalview.io.FormatAdapter.WRITEABLE_FORMATS[i]);
 
-        item.addActionListener(new java.awt.event.ActionListener()
+        item.addActionListener(new ActionListener()
         {
           @Override
           public void actionPerformed(ActionEvent e)
@@ -610,81 +618,85 @@ public class GAlignFrame extends JInternalFrame
   private void jbInit() throws Exception
   {
     fileMenu.setText(MessageManager.getString("action.file"));
+
     saveAs.setText(MessageManager.getString("action.save_as") + "...");
-    saveAs.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_S, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask()
-                    | java.awt.event.KeyEvent.SHIFT_MASK, false));
-    saveAs.addActionListener(new ActionListener()
+    ActionListener al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         saveAs_actionPerformed(e);
       }
-    });
+    };
+    KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask()
+            | KeyEvent.SHIFT_MASK, false);
+    addMenuActionAndAccelerator(keyStroke, saveAs, al);
+
     closeMenuItem.setText(MessageManager.getString("action.close"));
-    closeMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_W, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    closeMenuItem.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         closeMenuItem_actionPerformed(false);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, closeMenuItem, al);
+
     editMenu.setText(MessageManager.getString("action.edit"));
     viewMenu.setText(MessageManager.getString("action.view"));
     annotationsMenu.setText(MessageManager.getString("action.annotations"));
     colourMenu.setText(MessageManager.getString("action.colour"));
     calculateMenu.setText(MessageManager.getString("action.calculate"));
     webService.setText(MessageManager.getString("action.web_service"));
+
     selectAllSequenceMenuItem.setText(MessageManager
             .getString("action.select_all"));
-    selectAllSequenceMenuItem.setAccelerator(javax.swing.KeyStroke
-            .getKeyStroke(java.awt.event.KeyEvent.VK_A, Toolkit
-                    .getDefaultToolkit().getMenuShortcutKeyMask(), false));
-    selectAllSequenceMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                selectAllSequenceMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        selectAllSequenceMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, selectAllSequenceMenuItem, al);
+
     deselectAllSequenceMenuItem.setText(MessageManager
             .getString("action.deselect_all"));
-    deselectAllSequenceMenuItem.setAccelerator(javax.swing.KeyStroke
-            .getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0, false));
-    deselectAllSequenceMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                deselectAllSequenceMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        deselectAllSequenceMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, deselectAllSequenceMenuItem, al);
+
     invertSequenceMenuItem.setText(MessageManager
             .getString("action.invert_sequence_selection"));
-    invertSequenceMenuItem.setAccelerator(javax.swing.KeyStroke
-            .getKeyStroke(java.awt.event.KeyEvent.VK_I, Toolkit
-                    .getDefaultToolkit().getMenuShortcutKeyMask(), false));
-    invertSequenceMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                invertSequenceMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        invertSequenceMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, invertSequenceMenuItem, al);
+
     grpsFromSelection.setText(MessageManager
             .getString("action.make_groups_selection"));
-    grpsFromSelection.addActionListener(new java.awt.event.ActionListener()
+    grpsFromSelection.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -696,7 +708,7 @@ public class GAlignFrame extends JInternalFrame
             .getString("action.view_flanking_regions"));
     expandAlignment.setToolTipText(MessageManager
             .getString("label.view_flanking_regions"));
-    expandAlignment.addActionListener(new java.awt.event.ActionListener()
+    expandAlignment.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -706,86 +718,84 @@ public class GAlignFrame extends JInternalFrame
     });
     remove2LeftMenuItem.setText(MessageManager
             .getString("action.remove_left"));
-    remove2LeftMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_L, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    remove2LeftMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                remove2LeftMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_L, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        remove2LeftMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, remove2LeftMenuItem, al);
+
     remove2RightMenuItem.setText(MessageManager
             .getString("action.remove_right"));
-    remove2RightMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_R, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    remove2RightMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                remove2RightMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        remove2RightMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, remove2RightMenuItem, al);
+
     removeGappedColumnMenuItem.setText(MessageManager
             .getString("action.remove_empty_columns"));
-    removeGappedColumnMenuItem.setAccelerator(javax.swing.KeyStroke
-            .getKeyStroke(java.awt.event.KeyEvent.VK_E, Toolkit
-                    .getDefaultToolkit().getMenuShortcutKeyMask(), false));
-    removeGappedColumnMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                removeGappedColumnMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        removeGappedColumnMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, removeGappedColumnMenuItem, al);
+
     removeAllGapsMenuItem.setText(MessageManager
             .getString("action.remove_all_gaps"));
-    removeAllGapsMenuItem.setAccelerator(javax.swing.KeyStroke
-            .getKeyStroke(java.awt.event.KeyEvent.VK_E, Toolkit
-                    .getDefaultToolkit().getMenuShortcutKeyMask()
-                    | java.awt.event.KeyEvent.SHIFT_MASK, false));
-    removeAllGapsMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                removeAllGapsMenuItem_actionPerformed(e);
-              }
-            });
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask()
+            | KeyEvent.SHIFT_MASK, false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        removeAllGapsMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, removeAllGapsMenuItem, al);
+
     justifyLeftMenuItem.setText(MessageManager
             .getString("action.left_justify_alignment"));
-    justifyLeftMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                justifyLeftMenuItem_actionPerformed(e);
-              }
-            });
+    justifyLeftMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        justifyLeftMenuItem_actionPerformed(e);
+      }
+    });
     justifyRightMenuItem.setText(MessageManager
             .getString("action.right_justify_alignment"));
-    justifyRightMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                justifyRightMenuItem_actionPerformed(e);
-              }
-            });
+    justifyRightMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        justifyRightMenuItem_actionPerformed(e);
+      }
+    });
     viewBoxesMenuItem.setText(MessageManager.getString("action.boxes"));
     viewBoxesMenuItem.setState(true);
-    viewBoxesMenuItem.addActionListener(new java.awt.event.ActionListener()
+    viewBoxesMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -795,7 +805,7 @@ public class GAlignFrame extends JInternalFrame
     });
     viewTextMenuItem.setText(MessageManager.getString("action.text"));
     viewTextMenuItem.setState(true);
-    viewTextMenuItem.addActionListener(new java.awt.event.ActionListener()
+    viewTextMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -806,28 +816,26 @@ public class GAlignFrame extends JInternalFrame
     showNonconservedMenuItem.setText(MessageManager
             .getString("label.show_non_conversed"));
     showNonconservedMenuItem.setState(false);
-    showNonconservedMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                showUnconservedMenuItem_actionPerformed(e);
-              }
-            });
+    showNonconservedMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        showUnconservedMenuItem_actionPerformed(e);
+      }
+    });
     sortPairwiseMenuItem.setText(MessageManager
             .getString("action.by_pairwise_id"));
-    sortPairwiseMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                sortPairwiseMenuItem_actionPerformed(e);
-              }
-            });
+    sortPairwiseMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        sortPairwiseMenuItem_actionPerformed(e);
+      }
+    });
     sortIDMenuItem.setText(MessageManager.getString("action.by_id"));
-    sortIDMenuItem.addActionListener(new java.awt.event.ActionListener()
+    sortIDMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -837,17 +845,16 @@ public class GAlignFrame extends JInternalFrame
     });
     sortLengthMenuItem
             .setText(MessageManager.getString("action.by_length"));
-    sortLengthMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                sortLengthMenuItem_actionPerformed(e);
-              }
-            });
+    sortLengthMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        sortLengthMenuItem_actionPerformed(e);
+      }
+    });
     sortGroupMenuItem.setText(MessageManager.getString("action.by_group"));
-    sortGroupMenuItem.addActionListener(new java.awt.event.ActionListener()
+    sortGroupMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -855,34 +862,34 @@ public class GAlignFrame extends JInternalFrame
         sortGroupMenuItem_actionPerformed(e);
       }
     });
-    removeRedundancyMenuItem.setText(MessageManager
-            .getString("action.remove_redundancy").concat("..."));
-    removeRedundancyMenuItem.setAccelerator(javax.swing.KeyStroke
-            .getKeyStroke(java.awt.event.KeyEvent.VK_D, Toolkit
-                    .getDefaultToolkit().getMenuShortcutKeyMask(), false));
-    removeRedundancyMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                removeRedundancyMenuItem_actionPerformed(e);
-              }
-            });
+
+    removeRedundancyMenuItem.setText(MessageManager.getString(
+            "action.remove_redundancy").concat("..."));
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        removeRedundancyMenuItem_actionPerformed(e);
+      }
+    };
+    addMenuActionAndAccelerator(keyStroke, removeRedundancyMenuItem, al);
+
     pairwiseAlignmentMenuItem.setText(MessageManager
             .getString("action.pairwise_alignment"));
-    pairwiseAlignmentMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                pairwiseAlignmentMenuItem_actionPerformed(e);
-              }
-            });
+    pairwiseAlignmentMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        pairwiseAlignmentMenuItem_actionPerformed(e);
+      }
+    });
     PCAMenuItem.setText(MessageManager
             .getString("label.principal_component_analysis"));
-    PCAMenuItem.addActionListener(new java.awt.event.ActionListener()
+    PCAMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -892,26 +899,24 @@ public class GAlignFrame extends JInternalFrame
     });
     averageDistanceTreeMenuItem.setText(MessageManager
             .getString("label.average_distance_identity"));
-    averageDistanceTreeMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                averageDistanceTreeMenuItem_actionPerformed(e);
-              }
-            });
+    averageDistanceTreeMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        averageDistanceTreeMenuItem_actionPerformed(e);
+      }
+    });
     neighbourTreeMenuItem.setText(MessageManager
             .getString("label.neighbour_joining_identity"));
-    neighbourTreeMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                neighbourTreeMenuItem_actionPerformed(e);
-              }
-            });
+    neighbourTreeMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        neighbourTreeMenuItem_actionPerformed(e);
+      }
+    });
     this.getContentPane().setLayout(borderLayout1);
     alignFrameMenuBar.setFont(new java.awt.Font("Verdana", 0, 11));
     statusBar.setBackground(Color.white);
@@ -922,7 +927,7 @@ public class GAlignFrame extends JInternalFrame
             .getString("label.out_to_textbox"));
     clustalColour.setText(MessageManager.getString("label.clustalx"));
 
-    clustalColour.addActionListener(new java.awt.event.ActionListener()
+    clustalColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -931,7 +936,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     zappoColour.setText(MessageManager.getString("label.zappo"));
-    zappoColour.addActionListener(new java.awt.event.ActionListener()
+    zappoColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -940,7 +945,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     taylorColour.setText(MessageManager.getString("label.taylor"));
-    taylorColour.addActionListener(new java.awt.event.ActionListener()
+    taylorColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -950,17 +955,16 @@ public class GAlignFrame extends JInternalFrame
     });
     hydrophobicityColour.setText(MessageManager
             .getString("label.hydrophobicity"));
-    hydrophobicityColour
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                hydrophobicityColour_actionPerformed(e);
-              }
-            });
+    hydrophobicityColour.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        hydrophobicityColour_actionPerformed(e);
+      }
+    });
     helixColour.setText(MessageManager.getString("label.helix_propensity"));
-    helixColour.addActionListener(new java.awt.event.ActionListener()
+    helixColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -970,7 +974,7 @@ public class GAlignFrame extends JInternalFrame
     });
     strandColour.setText(MessageManager
             .getString("label.strand_propensity"));
-    strandColour.addActionListener(new java.awt.event.ActionListener()
+    strandColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -979,7 +983,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     turnColour.setText(MessageManager.getString("label.turn_propensity"));
-    turnColour.addActionListener(new java.awt.event.ActionListener()
+    turnColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -988,7 +992,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     buriedColour.setText(MessageManager.getString("label.buried_index"));
-    buriedColour.addActionListener(new java.awt.event.ActionListener()
+    buriedColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -998,7 +1002,7 @@ public class GAlignFrame extends JInternalFrame
     });
     userDefinedColour.setText(MessageManager
             .getString("action.user_defined"));
-    userDefinedColour.addActionListener(new java.awt.event.ActionListener()
+    userDefinedColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1008,7 +1012,7 @@ public class GAlignFrame extends JInternalFrame
     });
     PIDColour
             .setText(MessageManager.getString("label.percentage_identity"));
-    PIDColour.addActionListener(new java.awt.event.ActionListener()
+    PIDColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1018,7 +1022,7 @@ public class GAlignFrame extends JInternalFrame
     });
     BLOSUM62Colour
             .setText(MessageManager.getString("label.blosum62_score"));
-    BLOSUM62Colour.addActionListener(new java.awt.event.ActionListener()
+    BLOSUM62Colour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1027,7 +1031,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     nucleotideColour.setText(MessageManager.getString("label.nucleotide"));
-    nucleotideColour.addActionListener(new java.awt.event.ActionListener()
+    nucleotideColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1038,55 +1042,51 @@ public class GAlignFrame extends JInternalFrame
 
     purinePyrimidineColour.setText(MessageManager
             .getString("label.purine_pyrimidine"));
-    purinePyrimidineColour
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                purinePyrimidineColour_actionPerformed(e);
-              }
-            });
+    purinePyrimidineColour.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        purinePyrimidineColour_actionPerformed(e);
+      }
+    });
 
     RNAInteractionColour.setText("RNA Interaction type");
-    RNAInteractionColour
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                RNAInteractionColour_actionPerformed(e);
-              }
-            });
+    RNAInteractionColour.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        RNAInteractionColour_actionPerformed(e);
+      }
+    });
     /*
      * covariationColour.setText("Covariation");
-     * covariationColour.addActionListener(new java.awt.event.ActionListener() {
-     * public void actionPerformed(ActionEvent e) {
-     * covariationColour_actionPerformed(e); } });
+     * covariationColour.addActionListener(new ActionListener() { public void
+     * actionPerformed(ActionEvent e) { covariationColour_actionPerformed(e); }
+     * });
      */
 
     avDistanceTreeBlosumMenuItem.setText(MessageManager
             .getString("label.average_distance_bloslum62"));
-    avDistanceTreeBlosumMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                avTreeBlosumMenuItem_actionPerformed(e);
-              }
-            });
+    avDistanceTreeBlosumMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        avTreeBlosumMenuItem_actionPerformed(e);
+      }
+    });
     njTreeBlosumMenuItem.setText(MessageManager
             .getString("label.neighbour_blosum62"));
-    njTreeBlosumMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                njTreeBlosumMenuItem_actionPerformed(e);
-              }
-            });
+    njTreeBlosumMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        njTreeBlosumMenuItem_actionPerformed(e);
+      }
+    });
     annotationPanelMenuItem.setActionCommand("");
     annotationPanelMenuItem.setText(MessageManager
             .getString("label.show_annotations"));
@@ -1183,17 +1183,16 @@ public class GAlignFrame extends JInternalFrame
     });
     colourTextMenuItem.setText(MessageManager
             .getString("label.colour_text"));
-    colourTextMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                colourTextMenuItem_actionPerformed(e);
-              }
-            });
+    colourTextMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        colourTextMenuItem_actionPerformed(e);
+      }
+    });
     htmlMenuItem.setText(MessageManager.getString("label.html"));
-    htmlMenuItem.addActionListener(new java.awt.event.ActionListener()
+    htmlMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1216,7 +1215,7 @@ public class GAlignFrame extends JInternalFrame
 
     overviewMenuItem.setText(MessageManager
             .getString("label.overview_window"));
-    overviewMenuItem.addActionListener(new java.awt.event.ActionListener()
+    overviewMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1224,45 +1223,47 @@ public class GAlignFrame extends JInternalFrame
         overviewMenuItem_actionPerformed(e);
       }
     });
+
     undoMenuItem.setEnabled(false);
     undoMenuItem.setText(MessageManager.getString("action.undo"));
-    undoMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_Z, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    undoMenuItem.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         undoMenuItem_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, undoMenuItem, al);
+
     redoMenuItem.setEnabled(false);
     redoMenuItem.setText(MessageManager.getString("action.redo"));
-    redoMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_Y, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    redoMenuItem.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         redoMenuItem_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, redoMenuItem, al);
+
     conservationMenuItem.setText(MessageManager
             .getString("action.by_conservation"));
-    conservationMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                conservationMenuItem_actionPerformed(e);
-              }
-            });
+    conservationMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        conservationMenuItem_actionPerformed(e);
+      }
+    });
     noColourmenuItem.setText(MessageManager.getString("label.none"));
-    noColourmenuItem.addActionListener(new java.awt.event.ActionListener()
+    noColourmenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1271,7 +1272,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     wrapMenuItem.setText(MessageManager.getString("label.wrap"));
-    wrapMenuItem.addActionListener(new java.awt.event.ActionListener()
+    wrapMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1279,47 +1280,50 @@ public class GAlignFrame extends JInternalFrame
         wrapMenuItem_actionPerformed(e);
       }
     });
+
     printMenuItem.setText(MessageManager.getString("action.print") + "...");
-    printMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_P, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    printMenuItem.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_P, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         printMenuItem_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, printMenuItem, al);
+
     renderGapsMenuItem
             .setText(MessageManager.getString("action.show_gaps"));
     renderGapsMenuItem.setState(true);
-    renderGapsMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                renderGapsMenuItem_actionPerformed(e);
-              }
-            });
+    renderGapsMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        renderGapsMenuItem_actionPerformed(e);
+      }
+    });
+
     findMenuItem.setText(MessageManager.getString("action.find"));
-    findMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_F, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
     findMenuItem.setToolTipText(JvSwingUtils.wrapTooltip(true,
             MessageManager.getString("label.find_tip")));
-    findMenuItem.addActionListener(new java.awt.event.ActionListener()
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         findMenuItem_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, findMenuItem, al);
+
     abovePIDThreshold.setText(MessageManager
             .getString("label.above_identity_threshold"));
-    abovePIDThreshold.addActionListener(new java.awt.event.ActionListener()
+    abovePIDThreshold.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1446,8 +1450,7 @@ public class GAlignFrame extends JInternalFrame
     buttonGroup.add(showAutoLast);
     showAutoFirst.setText(MessageManager.getString("label.show_first"));
     showAutoFirst.setSelected(Cache.getDefault(
-            Preferences.SHOW_AUTOCALC_ABOVE,
-            false));
+            Preferences.SHOW_AUTOCALC_ABOVE, false));
     showAutoFirst.addActionListener(new ActionListener()
     {
       @Override
@@ -1470,7 +1473,7 @@ public class GAlignFrame extends JInternalFrame
     });
 
     nucleotideColour.setText(MessageManager.getString("label.nucleotide"));
-    nucleotideColour.addActionListener(new java.awt.event.ActionListener()
+    nucleotideColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1493,69 +1496,74 @@ public class GAlignFrame extends JInternalFrame
 
     deleteGroups
             .setText(MessageManager.getString("action.undefine_groups"));
-    deleteGroups.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_U, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    deleteGroups.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         deleteGroups_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, deleteGroups, al);
+
     createGroup.setText(MessageManager.getString("action.create_groups"));
-    createGroup.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_G, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    createGroup.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         createGroup_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, createGroup, al);
+
     unGroup.setText(MessageManager.getString("action.remove_group"));
-    unGroup.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_G, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask()
-                    | java.awt.event.KeyEvent.SHIFT_MASK, false));
-    unGroup.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask()
+            | KeyEvent.SHIFT_MASK, false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         unGroup_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, unGroup, al);
+
     copy.setText(MessageManager.getString("action.copy"));
-    copy.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_C, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
 
-    copy.addActionListener(new java.awt.event.ActionListener()
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         copy_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, copy, al);
+
     cut.setText(MessageManager.getString("action.cut"));
-    cut.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_X, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    cut.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         cut_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, cut, al);
+
     delete.setText(MessageManager.getString("action.delete"));
-    delete.addActionListener(new java.awt.event.ActionListener()
+    delete.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1563,35 +1571,38 @@ public class GAlignFrame extends JInternalFrame
         delete_actionPerformed(e);
       }
     });
+
     pasteMenu.setText(MessageManager.getString("action.paste"));
     pasteNew.setText(MessageManager.getString("label.to_new_alignment"));
-    pasteNew.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_V, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask()
-                    | java.awt.event.KeyEvent.SHIFT_MASK, false));
-    pasteNew.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask()
+            | KeyEvent.SHIFT_MASK, false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         pasteNew_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, pasteNew, al);
+
     pasteThis.setText(MessageManager.getString("label.to_this_alignment"));
-    pasteThis.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_V, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    pasteThis.addActionListener(new java.awt.event.ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         pasteThis_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, pasteThis, al);
+
     applyToAllGroups.setText(MessageManager
             .getString("label.apply_colour_to_all_groups"));
-    applyToAllGroups.addActionListener(new java.awt.event.ActionListener()
+    applyToAllGroups.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1599,7 +1610,7 @@ public class GAlignFrame extends JInternalFrame
         applyToAllGroups_actionPerformed(e);
       }
     });
-    createPNG.addActionListener(new java.awt.event.ActionListener()
+    createPNG.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1612,7 +1623,7 @@ public class GAlignFrame extends JInternalFrame
     createPNG.setText("PNG");
 
     font.setText(MessageManager.getString("action.font"));
-    font.addActionListener(new java.awt.event.ActionListener()
+    font.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1623,7 +1634,7 @@ public class GAlignFrame extends JInternalFrame
     seqLimits.setText(MessageManager
             .getString("label.show_sequence_limits"));
     seqLimits.setState(jalview.bin.Cache.getDefault("SHOW_JVSUFFIX", true));
-    seqLimits.addActionListener(new java.awt.event.ActionListener()
+    seqLimits.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1632,7 +1643,7 @@ public class GAlignFrame extends JInternalFrame
       }
     });
     epsFile.setText("EPS");
-    epsFile.addActionListener(new java.awt.event.ActionListener()
+    epsFile.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1642,7 +1653,7 @@ public class GAlignFrame extends JInternalFrame
     });
 
     createSVG.setText("SVG");
-    createSVG.addActionListener(new java.awt.event.ActionListener()
+    createSVG.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1655,7 +1666,7 @@ public class GAlignFrame extends JInternalFrame
             .getString("label.load_tree_for_sequence_set"));
     LoadtreeMenuItem.setText(MessageManager
             .getString("label.load_associated_tree"));
-    LoadtreeMenuItem.addActionListener(new java.awt.event.ActionListener()
+    LoadtreeMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1666,7 +1677,7 @@ public class GAlignFrame extends JInternalFrame
 
     scaleAbove.setVisible(false);
     scaleAbove.setText(MessageManager.getString("action.scale_above"));
-    scaleAbove.addActionListener(new java.awt.event.ActionListener()
+    scaleAbove.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1677,7 +1688,7 @@ public class GAlignFrame extends JInternalFrame
     scaleLeft.setVisible(false);
     scaleLeft.setSelected(true);
     scaleLeft.setText(MessageManager.getString("action.scale_left"));
-    scaleLeft.addActionListener(new java.awt.event.ActionListener()
+    scaleLeft.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1688,7 +1699,7 @@ public class GAlignFrame extends JInternalFrame
     scaleRight.setVisible(false);
     scaleRight.setSelected(true);
     scaleRight.setText(MessageManager.getString("action.scale_right"));
-    scaleRight.addActionListener(new java.awt.event.ActionListener()
+    scaleRight.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1700,15 +1711,14 @@ public class GAlignFrame extends JInternalFrame
     centreColumnLabelsMenuItem.setState(false);
     centreColumnLabelsMenuItem.setText(MessageManager
             .getString("label.centre_column_labels"));
-    centreColumnLabelsMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                centreColumnLabels_actionPerformed(e);
-              }
-            });
+    centreColumnLabelsMenuItem.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        centreColumnLabels_actionPerformed(e);
+      }
+    });
     followHighlightMenuItem.setVisible(true);
     followHighlightMenuItem.setState(true);
     followHighlightMenuItem.setText(MessageManager
@@ -1726,7 +1736,7 @@ public class GAlignFrame extends JInternalFrame
 
     modifyPID.setText(MessageManager
             .getString("label.modify_identity_thereshold"));
-    modifyPID.addActionListener(new java.awt.event.ActionListener()
+    modifyPID.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1736,15 +1746,14 @@ public class GAlignFrame extends JInternalFrame
     });
     modifyConservation.setText(MessageManager
             .getString("label.modify_conservation_thereshold"));
-    modifyConservation
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                modifyConservation_actionPerformed(e);
-              }
-            });
+    modifyConservation.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        modifyConservation_actionPerformed(e);
+      }
+    });
     sortByTreeMenu
             .setText(MessageManager.getString("action.by_tree_order"));
     sort.setText(MessageManager.getString("action.sort"));
@@ -1825,6 +1834,55 @@ public class GAlignFrame extends JInternalFrame
         showTranslation_actionPerformed(e);
       }
     });
+
+    /*
+     * cDNA menu options
+     */
+    cdna.setText(MessageManager.getString("label.cdna"));
+    // link to available cDNA
+    JMenuItem linkCdna = new JMenuItem(
+            MessageManager.getString("label.link_cdna"));
+    linkCdna.setToolTipText(JvSwingUtils.wrapTooltip(true,
+            MessageManager.getString("label.link_cdna_tip")));
+    linkCdna.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        linkCdna_actionPerformed();
+      }
+    });
+    cdna.add(linkCdna);
+    // align linked cDNA
+    JMenuItem alignCdna = new JMenuItem(
+            MessageManager.getString("label.align_cdna"));
+    alignCdna.setToolTipText(JvSwingUtils.wrapTooltip(true,
+            MessageManager.getString("label.align_cdna_tip")));
+    alignCdna.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        alignCdna_actionPerformed();
+      }
+    });
+    cdna.add(alignCdna);
+
+    // view alignment as cDNA (when known)
+    JMenuItem viewAsCdna = new JMenuItem(
+            MessageManager.getString("label.view_as_cdna"));
+    viewAsCdna.setToolTipText(JvSwingUtils.wrapTooltip(true,
+            MessageManager.getString("label.view_as_cdna_tip")));
+    viewAsCdna.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        viewAsCdna_actionPerformed();
+      }
+    });
+    cdna.add(viewAsCdna);
+
     extractScores.setText(MessageManager.getString("label.extract_scores")
             + "...");
     extractScores.addActionListener(new ActionListener()
@@ -1835,15 +1893,11 @@ public class GAlignFrame extends JInternalFrame
         extractScores_actionPerformed(e);
       }
     });
-    extractScores.setVisible(true); // JBPNote: TODO: make gui for regex based
-    // score extraction
+    extractScores.setVisible(true);
+    // JBPNote: TODO: make gui for regex based score extraction
+
+    // for show products actions see AlignFrame.canShowProducts
     showProducts.setText(MessageManager.getString("label.get_cross_refs"));
-    /*
-     * showProducts.addActionListener(new ActionListener() {
-     * 
-     * public void actionPerformed(ActionEvent e) {
-     * showProducts_actionPerformed(e); } });
-     */
     openFeatureSettings.setText(MessageManager
             .getString("label.feature_settings"));
     openFeatureSettings.addActionListener(new ActionListener()
@@ -2094,20 +2148,22 @@ public class GAlignFrame extends JInternalFrame
         hiddenMarkers_actionPerformed(e);
       }
     });
+
     invertColSel.setText(MessageManager
             .getString("action.invert_column_selection"));
-    invertColSel.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_I, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask()
-                    | java.awt.event.KeyEvent.ALT_MASK, false));
-    invertColSel.addActionListener(new ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
+            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
+            | KeyEvent.ALT_MASK, false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         invertColSel_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, invertColSel, al);
+
     tabbedPane.addChangeListener(new javax.swing.event.ChangeListener()
     {
       @Override
@@ -2134,18 +2190,20 @@ public class GAlignFrame extends JInternalFrame
         tabbedPane_focusGained(e);
       }
     });
+
     save.setText(MessageManager.getString("action.save"));
-    save.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_S, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    save.addActionListener(new ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         save_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, save, al);
+
     reload.setEnabled(false);
     reload.setText(MessageManager.getString("action.reload"));
     reload.addActionListener(new ActionListener()
@@ -2156,18 +2214,20 @@ public class GAlignFrame extends JInternalFrame
         reload_actionPerformed(e);
       }
     });
+
     newView.setText(MessageManager.getString("action.new_view"));
-    newView.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_T, Toolkit.getDefaultToolkit()
-                    .getMenuShortcutKeyMask(), false));
-    newView.addActionListener(new ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
+            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         newView_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, newView, al);
+
     tabbedPane.setToolTipText("<html><i>"
             + MessageManager.getString("label.rename_tab_eXpand_reGroup")
             + "</i></html>");
@@ -2193,30 +2253,33 @@ public class GAlignFrame extends JInternalFrame
         idRightAlign_actionPerformed(e);
       }
     });
+
     gatherViews.setEnabled(false);
     gatherViews.setText(MessageManager.getString("action.gather_views"));
-    gatherViews.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_G, 0, false));
-    gatherViews.addActionListener(new ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         gatherViews_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, gatherViews, al);
+
     expandViews.setEnabled(false);
     expandViews.setText(MessageManager.getString("action.expand_views"));
-    expandViews.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
-            java.awt.event.KeyEvent.VK_X, 0, false));
-    expandViews.addActionListener(new ActionListener()
+    keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
+    al = new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         expandViews_actionPerformed(e);
       }
-    });
+    };
+    addMenuActionAndAccelerator(keyStroke, expandViews, al);
+
     pageSetup
             .setText(MessageManager.getString("action.page_setup") + "...");
     pageSetup.addActionListener(new ActionListener()
@@ -2362,6 +2425,7 @@ public class GAlignFrame extends JInternalFrame
     calculateMenu.add(PCAMenuItem);
     calculateMenu.addSeparator();
     calculateMenu.add(showTranslation);
+    calculateMenu.add(cdna);
     calculateMenu.add(showProducts);
     calculateMenu.add(autoCalculate);
     calculateMenu.add(sortByTree);
@@ -2430,6 +2494,34 @@ public class GAlignFrame extends JInternalFrame
   }
 
   /**
+   * Adds the given action listener and key accelerator to the given menu item.
+   * Also saves in a lookup table to support lookup of action by key stroke.
+   * 
+   * @param keyStroke
+   * @param menuItem
+   * @param actionListener
+   */
+  protected void addMenuActionAndAccelerator(KeyStroke keyStroke,
+          JMenuItem menuItem, ActionListener actionListener)
+  {
+    menuItem.setAccelerator(keyStroke);
+    accelerators.put(keyStroke, menuItem);
+    menuItem.addActionListener(actionListener);
+  }
+
+  protected void viewAsCdna_actionPerformed()
+  {
+  }
+
+  protected void alignCdna_actionPerformed()
+  {
+  }
+
+  protected void linkCdna_actionPerformed()
+  {
+  }
+
+  /**
    * Action on clicking sort annotations by type.
    * 
    * @param sortOrder
@@ -2595,10 +2687,6 @@ public class GAlignFrame extends JInternalFrame
   {
   }
 
-  protected void showProducts_actionPerformed(ActionEvent e)
-  {
-  }
-
   protected void buildSortByAnnotationScoresMenu()
   {
   }
@@ -3160,4 +3248,9 @@ public class GAlignFrame extends JInternalFrame
   {
     this.annotationSortOrder = annotationSortOrder;
   }
+
+  public Map<KeyStroke, JMenuItem> getAccelerators()
+  {
+    return this.accelerators;
+  }
 }
diff --git a/src/jalview/jbgui/GSplitFrame.java b/src/jalview/jbgui/GSplitFrame.java
new file mode 100644 (file)
index 0000000..281a93e
--- /dev/null
@@ -0,0 +1,43 @@
+package jalview.jbgui;
+
+import javax.swing.JComponent;
+import javax.swing.JInternalFrame;
+import javax.swing.JSplitPane;
+
+public class GSplitFrame extends JInternalFrame
+{
+  private static final long serialVersionUID = 1L;
+
+  private JComponent topComponent;
+
+  private JComponent bottomComponent;
+
+  /**
+   * Constructor
+   * 
+   * @param top
+   * @param bottom
+   */
+  public GSplitFrame(JComponent top, JComponent bottom)
+  {
+    this.topComponent = top;
+    this.bottomComponent = bottom;
+    JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, top,
+            bottom);
+    splitPane.setVisible(true);
+    add(splitPane);
+    splitPane.setDividerLocation(0.5d);
+    splitPane.setResizeWeight(0.5d);
+    splitPane.setDividerSize(0);
+  }
+
+  public JComponent getTopComponent()
+  {
+    return topComponent;
+  }
+
+  public JComponent getBottomComponent()
+  {
+    return bottomComponent;
+  }
+}
index b492c21..83a9866 100755 (executable)
@@ -22,10 +22,19 @@ package jalview.jbgui;
 
 import jalview.util.MessageManager;
 
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-import javax.swing.event.*;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JInternalFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JScrollPane;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
 
 public class GTreePanel extends JInternalFrame
 {
@@ -124,7 +133,7 @@ public class GTreePanel extends JInternalFrame
     {
       public void actionPerformed(ActionEvent e)
       {
-        sortByTree_actionPerformed(e);
+        sortByTree_actionPerformed();
       }
     });
     font.setText(MessageManager.getString("action.font"));
@@ -282,7 +291,7 @@ public class GTreePanel extends JInternalFrame
   {
   }
 
-  public void sortByTree_actionPerformed(ActionEvent e)
+  public void sortByTree_actionPerformed()
   {
 
   }
index d13f0a9..4bb246a 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.api.analysis.ScoreModelI;
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -43,11 +44,11 @@ public class ResidueProperties
 
   public static final int[] purinepyrimidineIndex;
 
-  public static final Hashtable aa3Hash = new Hashtable();
+  public static final Map<String, Integer> aa3Hash = new HashMap<String, Integer>();
 
-  public static final Hashtable aa2Triplet = new Hashtable();
+  public static final Map<String, String> aa2Triplet = new HashMap<String, String>();
 
-  public static final Hashtable nucleotideName = new Hashtable();
+  public static final Map<String, String> nucleotideName = new HashMap<String, String>();
 
   static
   {
@@ -676,6 +677,8 @@ public class ResidueProperties
 
   public static Vector STOP = new Vector();
 
+  public static String START = "ATG";
+
   static
   {
     codonHash.put("K", Lys);
@@ -1521,7 +1524,7 @@ public class ResidueProperties
     return hyd;
   }
 
-  public static Hashtable getAA3Hash()
+  public static Map<String, Integer> getAA3Hash()
   {
     return aa3Hash;
   }
diff --git a/src/jalview/structure/AtomSpec.java b/src/jalview/structure/AtomSpec.java
new file mode 100644 (file)
index 0000000..d3e8d42
--- /dev/null
@@ -0,0 +1,64 @@
+package jalview.structure;
+
+/**
+ * Java bean representing an atom in a PDB (or similar) structure model.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class AtomSpec
+{
+  // 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 pdbFile
+   * @param chain
+   * @param resNo
+   * @param atomNo
+   */
+  public AtomSpec(String pdbFile, String chain, int resNo, int atomNo)
+  {
+    this.pdbFile = pdbFile;
+    this.chain = chain;
+    this.pdbResNum = resNo;
+    this.atomIndex = atomNo;
+  }
+
+  public String getPdbFile()
+  {
+    return pdbFile;
+  }
+
+  public String getChain()
+  {
+    return chain;
+  }
+
+  public int getPdbResNum()
+  {
+    return pdbResNum;
+  }
+
+  public int getAtomIndex()
+  {
+    return atomIndex;
+  }
+
+  @Override
+  public String toString()
+  {
+    return "pdbFile: " + pdbFile + ", chain: " + chain + ", res: "
+            + pdbResNum + ", atom: " + atomIndex;
+  }
+}
diff --git a/src/jalview/structure/CommandListener.java b/src/jalview/structure/CommandListener.java
new file mode 100644 (file)
index 0000000..e5f3e36
--- /dev/null
@@ -0,0 +1,35 @@
+package jalview.structure;
+
+import jalview.commands.CommandI;
+
+/**
+ * Defines a listener for commands performed on another alignment. This is to
+ * support linked editing of two alternative representations of an alignment (in
+ * particular, cDNA and protein).
+ * 
+ * @author gmcarstairs
+ *
+ */
+public interface CommandListener
+{
+  /**
+   * The listener may attempt to perform the specified command; the region acted
+   * on is determined by a callback to the StructureSelectionManager (which
+   * holds mappings between alignments).
+   * 
+   * @param command
+   * @param undo
+   * @param ssm
+   * @param source
+   *          the originator of the command
+   */
+  public void mirrorCommand(CommandI command, boolean undo,
+          StructureSelectionManager ssm, VamsasSource source);
+
+  /**
+   * Temporary workaround to make check for source == listener work.
+   * 
+   * @return
+   */
+  public VamsasSource getVamsasSource();
+}
index 83a9fd0..24c11e5 100644 (file)
@@ -20,7 +20,8 @@
  */
 package jalview.structure;
 
-import jalview.datamodel.*;
+import jalview.datamodel.SequenceI;
+
 
 public interface SequenceListener
 {
@@ -29,4 +30,7 @@ public interface SequenceListener
   public void highlightSequence(jalview.datamodel.SearchResults results);
 
   public void updateColours(SequenceI sequence, int index);
+
+  public VamsasSource getVamsasSource();
+
 }
index 8ac002b..032c40b 100644 (file)
  */
 package jalview.structure;
 
+import java.util.List;
+
 public interface StructureListener
 {
   /**
-   * 
-   * @return list of structure files (unique IDs/filenames) that this listener
-   *         handles messages for, or null if generic listener (only used by
-   *         removeListener method)
+   * Returns a list of structure files (unique IDs/filenames) that this listener
+   * handles messages for, or null if generic listener (only used by
+   * removeListener method)
    */
   public String[] getPdbFile();
 
   /**
-   * NOT A LISTENER METHOD! called by structure viewer when the given
-   * atom/structure has been moused over. Typically, implementors call
-   * StructureSelectionManager.mouseOverStructure
-   * 
-   * @param atomIndex
-   * @param strInfo
-   */
-  public void mouseOverStructure(int atomIndex, String strInfo);
-
-  /**
-   * called by StructureSelectionManager to inform viewer to highlight given
-   * atomspec
+   * Called by StructureSelectionManager to inform viewer to highlight given
+   * atom positions
    * 
-   * @param atomIndex
-   * @param pdbResNum
-   * @param chain
-   * @param pdbId
+   * @param atoms
    */
-  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
-          String pdbId);
+  public void highlightAtoms(List<AtomSpec> atoms);
 
   /**
-   * called by StructureSelectionManager when the colours of a sequence
+   * Called by StructureSelectionManager when the colours of a sequence
    * associated with a structure have changed.
    * 
    * @param source
@@ -62,19 +49,7 @@ public interface StructureListener
   public void updateColours(Object source);
 
   /**
-   * called by Jalview to get the colour for the given atomspec
-   * 
-   * @param atomIndex
-   * @param pdbResNum
-   * @param chain
-   * @param pdbId
-   * @return
-   */
-  public java.awt.Color getColour(int atomIndex, int pdbResNum,
-          String chain, String pdbId);
-
-  /**
-   * called by structureSelectionManager to instruct implementor to release any
+   * Called by structureSelectionManager to instruct implementor to release any
    * direct references it may hold to the given object (typically, these are
    * Jalview alignment panels).
    * 
index af00798..eb9abab 100644 (file)
@@ -22,19 +22,29 @@ package jalview.structure;
 
 import jalview.analysis.AlignSeq;
 import jalview.api.StructureSelectionManagerProvider;
+import jalview.commands.CommandI;
+import jalview.commands.EditCommand;
+import jalview.commands.OrderCommand;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
+import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 
 import java.io.PrintStream;
+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;
@@ -44,10 +54,28 @@ public class StructureSelectionManager
 {
   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;
+
+  /*
+   * 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
@@ -115,17 +143,18 @@ public class StructureSelectionManager
    */
   public void reportMapping()
   {
-    if (mappings == null)
+    if (mappings.isEmpty())
     {
       System.err.println("reportMapping: No PDB/Sequence mappings.");
     }
     else
     {
-      System.err.println("reportMapping: There are " + mappings.length
+      System.err.println("reportMapping: There are " + mappings.size()
               + " mappings.");
-      for (int m = 0; m < mappings.length; m++)
+      int i = 0;
+      for (StructureMapping sm : mappings)
       {
-        System.err.println("mapping " + m + " : " + mappings[m].pdbfile);
+        System.err.println("mapping " + i++ + " : " + sm.pdbfile);
       }
     }
   }
@@ -246,16 +275,19 @@ public class StructureSelectionManager
     }
   }
 
+  /**
+   * Returns the file name for a mapped PDB id (or null if not mapped).
+   * 
+   * @param pdbid
+   * @return
+   */
   public String alreadyMappedToFile(String pdbid)
   {
-    if (mappings != null)
+    for (StructureMapping sm : mappings)
     {
-      for (int i = 0; i < mappings.length; i++)
+      if (sm.getPdbId().equals(pdbid))
       {
-        if (mappings[i].getPdbId().equals(pdbid))
-        {
-          return mappings[i].pdbfile;
-        }
+        return sm.pdbfile;
       }
     }
     return null;
@@ -482,19 +514,7 @@ public class StructureSelectionManager
               mappingDetails.toString());
       if (forStructureView)
       {
-
-        if (mappings == null)
-        {
-          mappings = new StructureMapping[1];
-        }
-        else
-        {
-          StructureMapping[] tmp = new StructureMapping[mappings.length + 1];
-          System.arraycopy(mappings, 0, tmp, 0, mappings.length);
-          mappings = tmp;
-        }
-
-        mappings[mappings.length - 1] = newMapping;
+        mappings.add(newMapping);
       }
       maxChain.transferResidueAnnotation(newMapping, sqmpping);
     }
@@ -546,19 +566,18 @@ public class StructureSelectionManager
       }
     }
 
-    if (pdbs.size() > 0 && mappings != null)
+    if (pdbs.size() > 0)
     {
-      Vector tmp = new Vector();
-      for (int i = 0; i < mappings.length; i++)
+      List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+      for (StructureMapping sm : mappings)
       {
-        if (!pdbs.contains(mappings[i].pdbfile))
+        if (!pdbs.contains(sm.pdbfile))
         {
-          tmp.addElement(mappings[i]);
+          tmp.add(sm);
         }
       }
 
-      mappings = new StructureMapping[tmp.size()];
-      tmp.copyInto(mappings);
+      mappings = tmp;
     }
   }
 
@@ -569,7 +588,6 @@ public class StructureSelectionManager
       // old or prematurely sent event
       return;
     }
-    boolean hasSequenceListeners = handlingVamsasMo || seqmappings != null;
     SearchResults results = null;
     SequenceI lastseq = null;
     int lastipos = -1, indexpos;
@@ -581,33 +599,21 @@ public class StructureSelectionManager
         {
           results = new SearchResults();
         }
-        if (mappings != null)
+        for (StructureMapping sm : mappings)
         {
-          for (int j = 0; j < mappings.length; j++)
+          if (sm.pdbfile.equals(pdbfile) && sm.pdbchain.equals(chain))
           {
-            if (mappings[j].pdbfile.equals(pdbfile)
-                    && mappings[j].pdbchain.equals(chain))
+            indexpos = sm.getSeqPos(pdbResNum);
+            if (lastipos != indexpos && lastseq != sm.sequence)
             {
-              indexpos = mappings[j].getSeqPos(pdbResNum);
-              if (lastipos != indexpos && lastseq != mappings[j].sequence)
+              results.addResult(sm.sequence, indexpos, indexpos);
+              lastipos = indexpos;
+              lastseq = sm.sequence;
+              // construct highlighted sequence list
+              for (AlignedCodonFrame acf : seqmappings)
               {
-                results.addResult(mappings[j].sequence, indexpos, indexpos);
-                lastipos = indexpos;
-                lastseq = mappings[j].sequence;
-                // construct highlighted sequence list
-                if (seqmappings != null)
-                {
-
-                  Enumeration e = seqmappings.elements();
-                  while (e.hasMoreElements())
-
-                  {
-                    ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
-                            mappings[j].sequence, indexpos, results);
-                  }
-                }
+                acf.markMappedRegion(sm.sequence, indexpos, results);
               }
-
             }
           }
         }
@@ -626,13 +632,11 @@ public class StructureSelectionManager
     }
   }
 
-  Vector seqmappings = null; // should be a simpler list of mapped seuqence
-
   /**
    * highlight regions associated with a position (indexpos) in seq
    * 
    * @param seq
-   *          the sequeence that the mouse over occured on
+   *          the sequence that the mouse over occurred on
    * @param indexpos
    *          the absolute position being mouseovered in seq (0 to seq.length())
    * @param index
@@ -642,93 +646,54 @@ public class StructureSelectionManager
   public void mouseOverSequence(SequenceI seq, int indexpos, int index,
           VamsasSource source)
   {
-    boolean hasSequenceListeners = handlingVamsasMo || seqmappings != null;
+    boolean hasSequenceListeners = handlingVamsasMo
+            || !seqmappings.isEmpty();
     SearchResults results = null;
     if (index == -1)
     {
       index = seq.findPosition(indexpos);
     }
-    StructureListener sl;
-    int atomNo = 0;
     for (int i = 0; i < listeners.size(); i++)
     {
       Object listener = listeners.elementAt(i);
       if (listener == source)
       {
+        // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
+        // Temporary fudge with SequenceListener.getVamsasSource()
         continue;
       }
       if (listener instanceof StructureListener)
       {
-        sl = (StructureListener) listener;
-        if (mappings == null)
-        {
-          continue;
-        }
-        for (int j = 0; j < mappings.length; j++)
-        {
-          if (mappings[j].sequence == seq
-                  || mappings[j].sequence == seq.getDatasetSequence())
-          {
-            atomNo = mappings[j].getAtomNum(index);
-
-            if (atomNo > 0)
-            {
-              sl.highlightAtom(atomNo, mappings[j].getPDBResNum(index),
-                      mappings[j].pdbchain, mappings[j].pdbfile);
-            }
-          }
-        }
+        highlightStructure((StructureListener) listener, seq, index);
       }
       else
       {
-        if (relaySeqMappings && hasSequenceListeners
-                && listener instanceof SequenceListener)
+        if (listener instanceof SequenceListener)
         {
-          // DEBUG
-          // System.err.println("relay Seq " + seq.getDisplayId(false) + " " +
-          // index);
-
-          if (results == null)
+          final SequenceListener seqListener = (SequenceListener) listener;
+          if (hasSequenceListeners
+                  && seqListener.getVamsasSource() != source)
           {
-            results = new SearchResults();
-            if (index >= seq.getStart() && index <= seq.getEnd())
+            if (relaySeqMappings)
             {
-              // construct highlighted sequence list
-
-              if (seqmappings != null)
+              if (results == null)
               {
-                Enumeration e = seqmappings.elements();
-                while (e.hasMoreElements())
-
-                {
-                  ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
-                          seq, index, results);
-                }
+                results = MappingUtils.buildSearchResults(seq, index,
+                        seqmappings);
               }
-              // hasSequenceListeners = results.getSize() > 0;
               if (handlingVamsasMo)
               {
-                // maybe have to resolve seq to a dataset seqeunce...
-                // add in additional direct sequence and/or dataset sequence
-                // highlighting
                 results.addResult(seq, index, index);
+
               }
+              seqListener.highlightSequence(results);
             }
           }
-          if (hasSequenceListeners)
-          {
-            ((SequenceListener) listener).highlightSequence(results);
-          }
         }
         else if (listener instanceof VamsasListener && !handlingVamsasMo)
         {
-          // DEBUG
-          // System.err.println("Vamsas from Seq " + seq.getDisplayId(false) + "
-          // " +
-          // index);
-          // pass the mouse over and absolute position onto the
-          // VamsasListener(s)
-          ((VamsasListener) listener).mouseOver(seq, indexpos, source);
+          ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
+                  source);
         }
         else if (listener instanceof SecondaryStructureListener)
         {
@@ -740,6 +705,35 @@ public class StructureSelectionManager
   }
 
   /**
+   * Send suitable messages to a StructureListener to highlight atoms
+   * corresponding to the given sequence position.
+   * 
+   * @param sl
+   * @param seq
+   * @param index
+   */
+  protected void highlightStructure(StructureListener sl, SequenceI seq,
+          int index)
+  {
+    int atomNo;
+    List<AtomSpec> atoms = new ArrayList<AtomSpec>();
+    for (StructureMapping sm : mappings)
+    {
+      if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
+      {
+        atomNo = sm.getAtomNum(index);
+
+        if (atomNo > 0)
+        {
+          atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
+                  .getPDBResNum(index), atomNo));
+        }
+      }
+    }
+    sl.highlightAtoms(atoms);
+  }
+
+  /**
    * true if a mouse over event from an external (ie Vamsas) source is being
    * handled
    */
@@ -825,119 +819,114 @@ public class StructureSelectionManager
 
   public StructureMapping[] getMapping(String pdbfile)
   {
-    Vector tmp = new Vector();
-    if (mappings != null)
-    {
-      for (int i = 0; i < mappings.length; i++)
+    List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+    for (StructureMapping sm : mappings)
       {
-        if (mappings[i].pdbfile.equals(pdbfile))
+      if (sm.pdbfile.equals(pdbfile))
         {
-          tmp.addElement(mappings[i]);
+        tmp.add(sm);
         }
-      }
     }
-    StructureMapping[] ret = new StructureMapping[tmp.size()];
-    for (int i = 0; i < tmp.size(); i++)
-    {
-      ret[i] = (StructureMapping) tmp.elementAt(i);
-    }
-
-    return ret;
+    return tmp.toArray(new StructureMapping[tmp.size()]);
   }
 
   public String printMapping(String pdbfile)
   {
-    StringBuffer sb = new StringBuffer();
-    for (int i = 0; i < mappings.length; i++)
+    StringBuilder sb = new StringBuilder(64);
+    for (StructureMapping sm : mappings)
     {
-      if (mappings[i].pdbfile.equals(pdbfile))
+      if (sm.pdbfile.equals(pdbfile))
       {
-        sb.append(mappings[i].mappingDetails);
+        sb.append(sm.mappingDetails);
       }
     }
 
     return sb.toString();
   }
 
-  private int[] seqmappingrefs = null; // refcount for seqmappings elements
-
-  private synchronized void modifySeqMappingList(boolean add,
-          AlignedCodonFrame[] codonFrames)
+  /**
+   * 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 = new Vector();
+      for (AlignedCodonFrame acf : set)
+      {
+        removeMapping(acf);
+      }
     }
-    if (codonFrames != null && codonFrames.length > 0)
+  }
+
+  /**
+   * 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)
+    {
+      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)
+    {
+      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))
     {
-      sel_listeners.addElement(selecter);
+      sel_listeners.add(selecter);
     }
   }
 
@@ -945,7 +934,7 @@ public class StructureSelectionManager
   {
     if (sel_listeners.contains(toremove))
     {
-      sel_listeners.removeElement(toremove);
+      sel_listeners.remove(toremove);
     }
   }
 
@@ -953,18 +942,11 @@ public class StructureSelectionManager
           jalview.datamodel.SequenceGroup selection,
           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
   {
-    if (sel_listeners != null && sel_listeners.size() > 0)
+    for (SelectionListener slis : sel_listeners)
     {
-      Enumeration listeners = sel_listeners.elements();
-      while (listeners.hasMoreElements())
+      if (slis != source)
       {
-        SelectionListener slis = ((SelectionListener) listeners
-                .nextElement());
-        if (slis != source)
-        {
-          slis.selection(selection, colsel, source);
-        }
-        ;
+        slis.selection(selection, colsel, source);
       }
     }
   }
@@ -992,32 +974,6 @@ public class StructureSelectionManager
     }
   }
 
-  public void finalize() throws Throwable
-  {
-    if (listeners != null)
-    {
-      listeners.clear();
-      listeners = null;
-    }
-    if (pdbIdFileName != null)
-    {
-      pdbIdFileName.clear();
-      pdbIdFileName = null;
-    }
-    if (sel_listeners != null)
-    {
-      sel_listeners.clear();
-      sel_listeners = null;
-    }
-    if (view_listeners != null)
-    {
-      view_listeners.clear();
-      view_listeners = null;
-    }
-    mappings = null;
-    seqmappingrefs = null;
-  }
-
   /**
    * release all references associated with this manager provider
    * 
@@ -1041,7 +997,6 @@ public class StructureSelectionManager
         } catch (Throwable x)
         {
         }
-        ;
       }
     }
   }
@@ -1055,4 +1010,67 @@ public class StructureSelectionManager
     }
   }
 
+  public void addCommandListener(CommandListener cl)
+  {
+    if (!commandListeners.contains(cl))
+    {
+      commandListeners.add(cl);
+    }
+  }
+
+  public boolean hasCommandListener(CommandListener cl)
+  {
+    return this.commandListeners.contains(cl);
+  }
+
+  public boolean removeEditListener(CommandListener l)
+  {
+    return commandListeners.remove(l);
+  }
+
+  /**
+   * Forward a command to any command listeners (except for the command's
+   * source).
+   * 
+   * @param command
+   *          the command to be broadcast (in its form after being performed)
+   * @param undo
+   *          if true, the command was being 'undone'
+   * @param source
+   */
+  public void commandPerformed(CommandI command, boolean undo,
+          VamsasSource source)
+  {
+    for (CommandListener listener : commandListeners)
+    {
+      listener.mirrorCommand(command, undo, this, source);
+    }
+  }
+
+  /**
+   * Returns a new CommandI representing the given command as mapped to the
+   * given sequences. If no mapping could be made, or the command is not of a
+   * mappable kind, returns null.
+   * 
+   * @param command
+   * @param undo
+   * @param mapTo
+   * @param gapChar
+   * @return
+   */
+  public CommandI mapCommand(CommandI command, boolean undo,
+          final AlignmentI mapTo, char gapChar)
+  {
+    if (command instanceof EditCommand)
+    {
+      return MappingUtils.mapEditCommand((EditCommand) command, undo,
+              mapTo, gapChar, seqmappings);
+    }
+    else if (command instanceof OrderCommand)
+    {
+      return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
+              mapTo, seqmappings);
+    }
+    return null;
+  }
 }
index 26ebdc1..1c49252 100644 (file)
@@ -36,5 +36,6 @@ import jalview.datamodel.SequenceI;
  */
 public interface VamsasListener
 {
-  public void mouseOver(SequenceI seq, int index, VamsasSource source);
+  public void mouseOverSequence(SequenceI seq, int index,
+          VamsasSource source);
 }
index 664c903..653ec2d 100644 (file)
@@ -3,15 +3,16 @@ 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.
@@ -361,4 +362,21 @@ public abstract class AAStructureBindingModel extends
     }
   }
 
+  @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);
+
 }
\ No newline at end of file
index 8931b23..b9a95be 100644 (file)
  */
 package jalview.util;
 
-import jalview.datamodel.*;
+import jalview.datamodel.SequenceI;
 
 /**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision$
+ * Assorted methods for analysing or comparing sequences.
  */
 public class Comparison
 {
-  /** DOCUMENT ME!! */
-  public static final String GapChars = " .-";
+  private static final int EIGHTY_FIVE = 85;
+
+  private static final int TO_UPPER_CASE = 'a' - 'A';
+
+  private static final char GAP_SPACE = ' ';
+
+  private static final char GAP_DOT = '.';
+
+  private static final char GAP_DASH = '-';
+
+  public static final String GapChars = new String(new char[]
+  { GAP_SPACE, GAP_DOT, GAP_DASH });
 
   /**
    * DOCUMENT ME!
@@ -69,12 +76,12 @@ public class Comparison
     int ilen = si.length() - 1;
     int jlen = sj.length() - 1;
 
-    while (jalview.util.Comparison.isGap(si.charAt(start + ilen)))
+    while (Comparison.isGap(si.charAt(start + ilen)))
     {
       ilen--;
     }
 
-    while (jalview.util.Comparison.isGap(sj.charAt(start + jlen)))
+    while (Comparison.isGap(sj.charAt(start + jlen)))
     {
       jlen--;
     }
@@ -225,47 +232,60 @@ public class Comparison
   }
 
   /**
-   * DOCUMENT ME!
+   * Answers true if the supplied character is a recognised gap character, else
+   * false. Currently hard-coded to recognise '-', '-' or ' ' (hyphen / dot /
+   * space).
    * 
    * @param c
-   *          DOCUMENT ME!
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
   public static final boolean isGap(char c)
   {
-    return (c == '-' || c == '.' || c == ' ') ? true : false;
+    return (c == GAP_DASH || c == GAP_DOT || c == GAP_SPACE) ? true : false;
   }
 
+  /**
+   * Answers true if more than 85% of the sequence residues (ignoring gaps) are
+   * A, G, C, T or U, else false. This is just a heuristic guess and may give a
+   * wrong answer (as AGCT are also animo acid codes).
+   * 
+   * @param seqs
+   * @return
+   */
   public static final boolean isNucleotide(SequenceI[] seqs)
   {
-    int i = 0, iSize = seqs.length, j, jSize;
-    float nt = 0, aa = 0;
-    char c;
-    while (i < iSize)
+    if (seqs == null)
+    {
+      return false;
+    }
+    int ntCount = 0;
+    int aaCount = 0;
+    for (SequenceI seq : seqs)
     {
-      jSize = seqs[i].getLength();
-      for (j = 0; j < jSize; j++)
+      for (char c : seq.getSequence())
       {
-        c = seqs[i].getCharAt(j);
         if ('a' <= c && c <= 'z')
         {
-          c -= ('a' - 'A');
+          c -= TO_UPPER_CASE;
         }
 
         if (c == 'A' || c == 'G' || c == 'C' || c == 'T' || c == 'U')
         {
-          nt++;
+          ntCount++;
         }
-        else if (!jalview.util.Comparison.isGap(seqs[i].getCharAt(j)))
+        else if (!Comparison.isGap(c))
         {
-          aa++;
+          aaCount++;
         }
       }
-      i++;
     }
 
-    if ((nt / (nt + aa)) > 0.85f)
+    /*
+     * Check for nucleotide count > 85% of total count (in a form that evades
+     * int / float conversion or divide by zero).
+     */
+    if (ntCount * 100 > EIGHTY_FIVE * (ntCount + aaCount))
     {
       return true;
     }
index eb414b9..32f0256 100644 (file)
  */
 package jalview.util;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
- * MapList Simple way of bijectively mapping a non-contiguous linear range to
- * another non-contiguous linear range Use at your own risk! TODO: efficient
- * implementation of private posMap method TODO: test/ensure that sense of from
- * and to ratio start position is conserved (codon start position recovery)
- * TODO: optimize to use int[][] arrays rather than vectors.
+ * A simple way of bijectively mapping a non-contiguous linear range to another
+ * non-contiguous linear range.
+ * 
+ * Use at your own risk!
+ * 
+ * TODO: efficient implementation of private posMap method
+ * 
+ * TODO: test/ensure that sense of from and to ratio start position is conserved
+ * (codon start position recovery)
  */
 public class MapList
 {
+
   /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#equals(java.lang.Object)
+   * Subregions (base 1) described as { [start1, end1], [start2, end2], ...}
+   */
+  private List<int[]> fromShifts = new ArrayList<int[]>();
+
+  /*
+   * Same format as fromShifts, for the 'mapped to' sequence
+   */
+  private List<int[]> toShifts = new ArrayList<int[]>();
+
+  /*
+   * number of steps in fromShifts to one toRatio unit
+   */
+  private int fromRatio;
+
+  /*
+   * number of steps in toShifts to one fromRatio
+   */
+  private int toRatio;
+
+  /*
+   * lowest and highest value in the from Map
    */
-  public boolean equals(MapList obj)
+  private int fromLowest;
+
+  private int fromHighest;
+
+  /*
+   * lowest and highest value in the to Map
+   */
+  private int toLowest;
+
+  private int toHighest;
+
+  /**
+   * Two MapList objects are equal if they are the same object, or they both
+   * have populated shift ranges and all values are the same.
+   */
+  @Override
+  public boolean equals(Object o)
   {
+    if (o == null || !(o instanceof MapList))
+    {
+      return false;
+    }
+
+    MapList obj = (MapList) o;
     if (obj == this)
-      return true;
-    if (obj != null && obj.fromRatio == fromRatio && obj.toRatio == toRatio
-            && obj.fromShifts != null && obj.toShifts != null)
     {
-      int i, iSize = fromShifts.size(), j, jSize = obj.fromShifts.size();
-      if (iSize != jSize)
-        return false;
-      for (i = 0, iSize = fromShifts.size(), j = 0, jSize = obj.fromShifts
-              .size(); i < iSize;)
-      {
-        int[] mi = (int[]) fromShifts.elementAt(i++);
-        int[] mj = (int[]) obj.fromShifts.elementAt(j++);
-        if (mi[0] != mj[0] || mi[1] != mj[1])
-          return false;
-      }
-      iSize = toShifts.size();
-      jSize = obj.toShifts.size();
-      if (iSize != jSize)
-        return false;
-      for (i = 0, j = 0; i < iSize;)
-      {
-        int[] mi = (int[]) toShifts.elementAt(i++);
-        int[] mj = (int[]) obj.toShifts.elementAt(j++);
-        if (mi[0] != mj[0] || mi[1] != mj[1])
-          return false;
-      }
       return true;
     }
-    return false;
+    if (obj.fromRatio != fromRatio || obj.toRatio != toRatio
+            || obj.fromShifts == null || obj.toShifts == null)
+    {
+      return false;
+    }
+    return Arrays
+            .deepEquals(fromShifts.toArray(), obj.fromShifts.toArray())
+            && Arrays
+                    .deepEquals(toShifts.toArray(), obj.toShifts.toArray());
   }
 
-  public Vector fromShifts;
-
-  public Vector toShifts;
-
-  int fromRatio; // number of steps in fromShifts to one toRatio unit
-
-  int toRatio; // number of steps in toShifts to one fromRatio
-
   /**
+   * Returns the flattened 'from' ranges as [start1, end1, start2, end2, ...]
    * 
-   * @return series of intervals mapped in from
+   * @return
    */
   public int[] getFromRanges()
   {
     return getRanges(fromShifts);
   }
 
+  /**
+   * Returns the flattened 'to' ranges as [start1, end1, start2, end2, ...]
+   * 
+   * @return
+   */
   public int[] getToRanges()
   {
     return getRanges(toShifts);
   }
 
-  private int[] getRanges(Vector shifts)
+  /**
+   * Flattens a list of [start, end] into a single [start1, end1, start2,
+   * end2,...] array.
+   * 
+   * @param shifts
+   * @return
+   */
+  protected static int[] getRanges(List<int[]> shifts)
   {
     int[] rnges = new int[2 * shifts.size()];
-    Enumeration e = shifts.elements();
     int i = 0;
-    while (e.hasMoreElements())
+    for (int[] r : shifts)
     {
-      int r[] = (int[]) e.nextElement();
       rnges[i++] = r[0];
       rnges[i++] = r[1];
     }
@@ -107,16 +140,6 @@ public class MapList
   }
 
   /**
-   * lowest and highest value in the from Map
-   */
-  int[] fromRange = null;
-
-  /**
-   * lowest and highest value in the to Map
-   */
-  int[] toRange = null;
-
-  /**
    * 
    * @return length of mapped phrase in from
    */
@@ -136,88 +159,91 @@ public class MapList
 
   public int getFromLowest()
   {
-    return fromRange[0];
+    return fromLowest;
   }
 
   public int getFromHighest()
   {
-    return fromRange[1];
+    return fromHighest;
   }
 
   public int getToLowest()
   {
-    return toRange[0];
+    return toLowest;
   }
 
   public int getToHighest()
   {
-    return toRange[1];
-  }
-
-  private void ensureRange(int[] limits, int pos)
-  {
-    if (limits[0] > pos)
-      limits[0] = pos;
-    if (limits[1] < pos)
-      limits[1] = pos;
+    return toHighest;
   }
 
+  /**
+   * Constructor.
+   * 
+   * @param from
+   *          contiguous regions as [start1, end1, start2, end2, ...]
+   * @param to
+   *          same format as 'from'
+   * @param fromRatio
+   *          phrase length in 'from' (e.g. 3 for dna)
+   * @param toRatio
+   *          phrase length in 'to' (e.g. 1 for protein)
+   */
   public MapList(int from[], int to[], int fromRatio, int toRatio)
   {
-    fromRange = new int[]
-    { from[0], from[1] };
-    toRange = new int[]
-    { to[0], to[1] };
-
-    fromShifts = new Vector();
+    fromLowest = from[0];
+    fromHighest = from[1];
     for (int i = 0; i < from.length; i += 2)
     {
-      ensureRange(fromRange, from[i]);
-      ensureRange(fromRange, from[i + 1]);
+      fromLowest = Math.min(fromLowest, from[i]);
+      fromHighest = Math.max(fromHighest, from[i + 1]);
 
-      fromShifts.addElement(new int[]
+      fromShifts.add(new int[]
       { from[i], from[i + 1] });
     }
-    toShifts = new Vector();
+
+    toLowest = to[0];
+    toHighest = to[1];
     for (int i = 0; i < to.length; i += 2)
     {
-      ensureRange(toRange, to[i]);
-      ensureRange(toRange, to[i + 1]);
-      toShifts.addElement(new int[]
+      toLowest = Math.min(toLowest, to[i]);
+      toHighest = Math.max(toHighest, to[i + 1]);
+      toShifts.add(new int[]
       { to[i], to[i + 1] });
     }
     this.fromRatio = fromRatio;
     this.toRatio = toRatio;
   }
 
+  /**
+   * Copy constructor. Creates an identical mapping.
+   * 
+   * @param map
+   */
   public MapList(MapList map)
   {
-    this.fromRange = new int[]
-    { map.fromRange[0], map.fromRange[1] };
-    this.toRange = new int[]
-    { map.toRange[0], map.toRange[1] };
+    // TODO not used - remove?
+    this.fromLowest = map.fromLowest;
+    this.fromHighest = map.fromHighest;
+    this.toLowest = map.toLowest;
+    this.toHighest = map.toHighest;
+
     this.fromRatio = map.fromRatio;
     this.toRatio = map.toRatio;
     if (map.fromShifts != null)
     {
-      this.fromShifts = new Vector();
-      Enumeration e = map.fromShifts.elements();
-      while (e.hasMoreElements())
+      for (int[] r : map.fromShifts)
       {
-        int[] el = (int[]) e.nextElement();
-        fromShifts.addElement(new int[]
-        { el[0], el[1] });
+        fromShifts.add(new int[]
+        { r[0], r[1] });
       }
     }
     if (map.toShifts != null)
     {
-      this.toShifts = new Vector();
-      Enumeration e = map.toShifts.elements();
-      while (e.hasMoreElements())
+      for (int[] r : map.toShifts)
       {
-        int[] el = (int[]) e.nextElement();
-        toShifts.addElement(new int[]
-        { el[0], el[1] });
+        toShifts.add(new int[]
+        { r[0], r[1] });
       }
     }
   }
@@ -228,8 +254,9 @@ public class MapList
    * @return int[][] { int[] { fromStart, fromFinish, toStart, toFinish }, int
    *         [fromFinish-fromStart+2] { toStart..toFinish mappings}}
    */
-  public int[][] makeFromMap()
+  protected int[][] makeFromMap()
   {
+    // TODO not used - remove??
     return posMap(fromShifts, fromRatio, toShifts, toRatio);
   }
 
@@ -238,27 +265,30 @@ public class MapList
    * 
    * @return int[to position]=position mapped in from
    */
-  public int[][] makeToMap()
+  protected int[][] makeToMap()
   {
+    // TODO not used - remove??
     return posMap(toShifts, toRatio, fromShifts, fromRatio);
   }
 
   /**
    * construct an int map for intervals in intVals
    * 
-   * @param intVals
+   * @param shiftTo
    * @return int[] { from, to pos in range }, int[range.to-range.from+1]
    *         returning mapped position
    */
-  private int[][] posMap(Vector intVals, int ratio, Vector toIntVals,
+  private int[][] posMap(List<int[]> shiftTo, int ratio,
+          List<int[]> shiftFrom,
           int toRatio)
   {
-    int iv = 0, ivSize = intVals.size();
+    // TODO not used - remove??
+    int iv = 0, ivSize = shiftTo.size();
     if (iv >= ivSize)
     {
       return null;
     }
-    int[] intv = (int[]) intVals.elementAt(iv++);
+    int[] intv = shiftTo.get(iv++);
     int from = intv[0], to = intv[1];
     if (from > to)
     {
@@ -267,7 +297,7 @@ public class MapList
     }
     while (iv < ivSize)
     {
-      intv = (int[]) intVals.elementAt(iv++);
+      intv = shiftTo.get(iv++);
       if (intv[0] < from)
       {
         from = intv[0];
@@ -289,7 +319,7 @@ public class MapList
     int mp[][] = new int[to - from + 2][];
     for (int i = 0; i < mp.length; i++)
     {
-      int[] m = shift(i + from, intVals, ratio, toIntVals, toRatio);
+      int[] m = shift(i + from, shiftTo, ratio, shiftFrom, toRatio);
       if (m != null)
       {
         if (i == 0)
@@ -345,6 +375,7 @@ public class MapList
    *          shifts.insertElementAt(new int[] { pos, shift}, sidx); else
    *          rshift[1]+=shift; }
    */
+
   /**
    * shift from pos to To(pos)
    * 
@@ -373,23 +404,24 @@ public class MapList
 
   /**
    * 
-   * @param fromShifts
+   * @param shiftTo
    * @param fromRatio
-   * @param toShifts
+   * @param shiftFrom
    * @param toRatio
    * @return
    */
-  private int[] shift(int pos, Vector fromShifts, int fromRatio,
-          Vector toShifts, int toRatio)
+  protected static int[] shift(int pos, List<int[]> shiftTo, int fromRatio,
+          List<int[]> shiftFrom, int toRatio)
   {
-    int[] fromCount = countPos(fromShifts, pos);
+    // TODO: javadoc; tests
+    int[] fromCount = countPos(shiftTo, pos);
     if (fromCount == null)
     {
       return null;
     }
     int fromRemainder = (fromCount[0] - 1) % fromRatio;
     int toCount = 1 + (((fromCount[0] - 1) / fromRatio) * toRatio);
-    int[] toPos = countToPos(toShifts, toCount);
+    int[] toPos = countToPos(shiftFrom, toCount);
     if (toPos == null)
     {
       return null; // throw new Error("Bad Mapping!");
@@ -402,16 +434,16 @@ public class MapList
   /**
    * count how many positions pos is along the series of intervals.
    * 
-   * @param intVals
+   * @param shiftTo
    * @param pos
    * @return number of positions or null if pos is not within intervals
    */
-  private int[] countPos(Vector intVals, int pos)
+  protected static int[] countPos(List<int[]> shiftTo, int pos)
   {
-    int count = 0, intv[], iv = 0, ivSize = intVals.size();
+    int count = 0, intv[], iv = 0, ivSize = shiftTo.size();
     while (iv < ivSize)
     {
-      intv = (int[]) intVals.elementAt(iv++);
+      intv = shiftTo.get(iv++);
       if (intv[0] <= intv[1])
       {
         if (pos >= intv[0] && pos <= intv[1])
@@ -443,17 +475,18 @@ public class MapList
   /**
    * count out pos positions into a series of intervals and return the position
    * 
-   * @param intVals
+   * @param shiftFrom
    * @param pos
    * @return position pos in interval set
    */
-  private int[] countToPos(Vector intVals, int pos)
+  protected static int[] countToPos(List<int[]> shiftFrom, int pos)
   {
-    int count = 0, diff = 0, iv = 0, ivSize = intVals.size(), intv[] =
+    int count = 0, diff = 0, iv = 0, ivSize = shiftFrom.size();
+    int[] intv =
     { 0, 0 };
     while (iv < ivSize)
     {
-      intv = (int[]) intVals.elementAt(iv++);
+      intv = shiftFrom.get(iv++);
       diff = intv[1] - intv[0];
       if (diff >= 0)
       {
@@ -487,69 +520,68 @@ public class MapList
    * find series of intervals mapping from start-end in the From map.
    * 
    * @param start
-   *          position in to map
+   *          position mapped 'to'
    * @param end
-   *          position in to map
-   * @return series of ranges in from map
+   *          position mapped 'to'
+   * @return series of [start, end] ranges in sequence mapped 'from'
    */
   public int[] locateInFrom(int start, int end)
   {
     // inefficient implementation
     int fromStart[] = shiftTo(start);
-    int fromEnd[] = shiftTo(end); // needs to be inclusive of end of symbol
-    // position
-    if (fromStart == null || fromEnd == null)
-      return null;
-    int iv[] = getIntervals(fromShifts, fromStart, fromEnd, fromRatio);
-    return iv;
+    // needs to be inclusive of end of symbol position
+    int fromEnd[] = shiftTo(end);
+
+    return getIntervals(fromShifts, fromStart, fromEnd, fromRatio);
   }
 
   /**
    * find series of intervals mapping from start-end in the to map.
    * 
    * @param start
-   *          position in from map
+   *          position mapped 'from'
    * @param end
-   *          position in from map
-   * @return series of ranges in to map
+   *          position mapped 'from'
+   * @return series of [start, end] ranges in sequence mapped 'to'
    */
   public int[] locateInTo(int start, int end)
   {
-    // inefficient implementation
     int toStart[] = shiftFrom(start);
     int toEnd[] = shiftFrom(end);
-    if (toStart == null || toEnd == null)
-      return null;
-    int iv[] = getIntervals(toShifts, toStart, toEnd, toRatio);
-    return iv;
+    return getIntervals(toShifts, toStart, toEnd, toRatio);
   }
 
   /**
    * like shift - except returns the intervals in the given vector of shifts
    * which were spanned in traversing fromStart to fromEnd
    * 
-   * @param fromShifts2
+   * @param shiftFrom
    * @param fromStart
    * @param fromEnd
    * @param fromRatio2
    * @return series of from,to intervals from from first position of starting
    *         region to final position of ending region inclusive
    */
-  private int[] getIntervals(Vector fromShifts2, int[] fromStart,
+  protected static int[] getIntervals(List<int[]> shiftFrom,
+          int[] fromStart,
           int[] fromEnd, int fromRatio2)
   {
+    if (fromStart == null || fromEnd == null)
+    {
+      return null;
+    }
     int startpos, endpos;
     startpos = fromStart[0]; // first position in fromStart
     endpos = fromEnd[0]; // last position in fromEnd
     int endindx = (fromRatio2 - 1); // additional positions to get to last
     // position from endpos
-    int intv = 0, intvSize = fromShifts2.size();
+    int intv = 0, intvSize = shiftFrom.size();
     int iv[], i = 0, fs = -1, fe_s = -1, fe = -1; // containing intervals
     // search intervals to locate ones containing startpos and count endindx
     // positions on from endpos
     while (intv < intvSize && (fs == -1 || fe == -1))
     {
-      iv = (int[]) fromShifts2.elementAt(intv++);
+      iv = shiftFrom.get(intv++);
       if (fe_s > -1)
       {
         endpos = iv[0]; // start counting from beginning of interval
@@ -612,39 +644,45 @@ public class MapList
       i++;
     }
     if (fs == fe && fe == -1)
+    {
       return null;
-    Vector ranges = new Vector();
+    }
+    List<int[]> ranges = new ArrayList<int[]>();
     if (fs <= fe)
     {
       intv = fs;
       i = fs;
       // truncate initial interval
-      iv = (int[]) fromShifts2.elementAt(intv++);
+      iv = shiftFrom.get(intv++);
       iv = new int[]
       { iv[0], iv[1] };// clone
       if (i == fs)
+      {
         iv[0] = startpos;
+      }
       while (i != fe)
       {
-        ranges.addElement(iv); // add initial range
-        iv = (int[]) fromShifts2.elementAt(intv++); // get next interval
+        ranges.add(iv); // add initial range
+        iv = shiftFrom.get(intv++); // get next interval
         iv = new int[]
         { iv[0], iv[1] };// clone
         i++;
       }
       if (i == fe)
+      {
         iv[1] = endpos;
-      ranges.addElement(iv); // add only - or final range
+      }
+      ranges.add(iv); // add only - or final range
     }
     else
     {
       // walk from end of interval.
-      i = fromShifts2.size() - 1;
+      i = shiftFrom.size() - 1;
       while (i > fs)
       {
         i--;
       }
-      iv = (int[]) fromShifts2.elementAt(i);
+      iv = shiftFrom.get(i);
       iv = new int[]
       { iv[1], iv[0] };// reverse and clone
       // truncate initial interval
@@ -654,8 +692,8 @@ public class MapList
       }
       while (--i != fe)
       { // fix apparent logic bug when fe==-1
-        ranges.addElement(iv); // add (truncated) reversed interval
-        iv = (int[]) fromShifts2.elementAt(i);
+        ranges.add(iv); // add (truncated) reversed interval
+        iv = shiftFrom.get(i);
         iv = new int[]
         { iv[1], iv[0] }; // reverse and clone
       }
@@ -664,7 +702,7 @@ public class MapList
         // interval is already reversed
         iv[1] = endpos;
       }
-      ranges.addElement(iv); // add only - or final range
+      ranges.add(iv); // add only - or final range
     }
     // create array of start end intervals.
     int[] range = null;
@@ -676,10 +714,10 @@ public class MapList
       i = 0;
       while (intv < intvSize)
       {
-        iv = (int[]) ranges.elementAt(intv);
+        iv = ranges.get(intv);
         range[i++] = iv[0];
         range[i++] = iv[1];
-        ranges.setElementAt(null, intv++); // remove
+        ranges.set(intv++, null); // remove
       }
     }
     return range;
@@ -694,6 +732,7 @@ public class MapList
    */
   public int getToPosition(int mpos)
   {
+    // TODO not used - remove??
     int[] mp = shiftTo(mpos);
     if (mp != null)
     {
@@ -730,6 +769,7 @@ public class MapList
    */
   public int getMappedPosition(int pos)
   {
+    // TODO not used - remove??
     int[] mp = shiftFrom(pos);
     if (mp != null)
     {
@@ -740,6 +780,7 @@ public class MapList
 
   public int[] getMappedWord(int pos)
   {
+    // TODO not used - remove??
     int[] mp = shiftFrom(pos);
     if (mp != null)
     {
@@ -750,223 +791,6 @@ public class MapList
   }
 
   /**
-   * test routine. not incremental.
-   * 
-   * @param ml
-   * @param fromS
-   * @param fromE
-   */
-  public static void testMap(MapList ml, int fromS, int fromE)
-  {
-    for (int from = 1; from <= 25; from++)
-    {
-      int[] too = ml.shiftFrom(from);
-      System.out.print("ShiftFrom(" + from + ")==");
-      if (too == null)
-      {
-        System.out.print("NaN\n");
-      }
-      else
-      {
-        System.out.print(too[0] + " % " + too[1] + " (" + too[2] + ")");
-        System.out.print("\t+--+\t");
-        int[] toofrom = ml.shiftTo(too[0]);
-        if (toofrom != null)
-        {
-          if (toofrom[0] != from)
-          {
-            System.err.println("Mapping not reflexive:" + from + " "
-                    + too[0] + "->" + toofrom[0]);
-          }
-          System.out.println("ShiftTo(" + too[0] + ")==" + toofrom[0]
-                  + " % " + toofrom[1] + " (" + toofrom[2] + ")");
-        }
-        else
-        {
-          System.out.println("ShiftTo(" + too[0] + ")=="
-                  + "NaN! - not Bijective Mapping!");
-        }
-      }
-    }
-    int mmap[][] = ml.makeFromMap();
-    System.out.println("FromMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
-            + mmap[0][2] + " " + mmap[0][3] + " ");
-    for (int i = 1; i <= mmap[1].length; i++)
-    {
-      if (mmap[1][i - 1] == -1)
-      {
-        System.out.print(i + "=XXX");
-
-      }
-      else
-      {
-        System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
-      }
-      if (i % 20 == 0)
-      {
-        System.out.print("\n");
-      }
-      else
-      {
-        System.out.print(",");
-      }
-    }
-    // test range function
-    System.out.print("\nTest locateInFrom\n");
-    {
-      int f = mmap[0][2], t = mmap[0][3];
-      while (f <= t)
-      {
-        System.out.println("Range " + f + " to " + t);
-        int rng[] = ml.locateInFrom(f, t);
-        if (rng != null)
-        {
-          for (int i = 0; i < rng.length; i++)
-          {
-            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
-          }
-        }
-        else
-        {
-          System.out.println("No range!");
-        }
-        System.out.print("\nReversed\n");
-        rng = ml.locateInFrom(t, f);
-        if (rng != null)
-        {
-          for (int i = 0; i < rng.length; i++)
-          {
-            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
-          }
-        }
-        else
-        {
-          System.out.println("No range!");
-        }
-        System.out.print("\n");
-        f++;
-        t--;
-      }
-    }
-    System.out.print("\n");
-    mmap = ml.makeToMap();
-    System.out.println("ToMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
-            + mmap[0][2] + " " + mmap[0][3] + " ");
-    for (int i = 1; i <= mmap[1].length; i++)
-    {
-      if (mmap[1][i - 1] == -1)
-      {
-        System.out.print(i + "=XXX");
-
-      }
-      else
-      {
-        System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
-      }
-      if (i % 20 == 0)
-      {
-        System.out.print("\n");
-      }
-      else
-      {
-        System.out.print(",");
-      }
-    }
-    System.out.print("\n");
-    // test range function
-    System.out.print("\nTest locateInTo\n");
-    {
-      int f = mmap[0][2], t = mmap[0][3];
-      while (f <= t)
-      {
-        System.out.println("Range " + f + " to " + t);
-        int rng[] = ml.locateInTo(f, t);
-        if (rng != null)
-        {
-          for (int i = 0; i < rng.length; i++)
-          {
-            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
-          }
-        }
-        else
-        {
-          System.out.println("No range!");
-        }
-        System.out.print("\nReversed\n");
-        rng = ml.locateInTo(t, f);
-        if (rng != null)
-        {
-          for (int i = 0; i < rng.length; i++)
-          {
-            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
-          }
-        }
-        else
-        {
-          System.out.println("No range!");
-        }
-        f++;
-        t--;
-        System.out.print("\n");
-      }
-    }
-
-  }
-
-  public static void main(String argv[])
-  {
-    MapList ml = new MapList(new int[]
-    { 1, 5, 10, 15, 25, 20 }, new int[]
-    { 51, 1 }, 1, 3);
-    MapList ml1 = new MapList(new int[]
-    { 1, 3, 17, 4 }, new int[]
-    { 51, 1 }, 1, 3);
-    MapList ml2 = new MapList(new int[]
-    { 1, 60 }, new int[]
-    { 1, 20 }, 3, 1);
-    // test internal consistency
-    int to[] = new int[51];
-    MapList.testMap(ml, 1, 60);
-    MapList mldna = new MapList(new int[]
-    { 2, 2, 6, 8, 12, 16 }, new int[]
-    { 1, 3 }, 3, 1);
-    int[] frm = mldna.locateInFrom(1, 1);
-    testLocateFrom(mldna, 1, 1, new int[]
-    { 2, 2, 6, 7 });
-    MapList.testMap(mldna, 1, 3);
-    /*
-     * for (int from=1; from<=51; from++) { int[] too=ml.shiftTo(from); int[]
-     * toofrom=ml.shiftFrom(too[0]);
-     * System.out.println("ShiftFrom("+from+")=="+too[0]+" %
-     * "+too[1]+"\t+-+\tShiftTo("+too[0]+")=="+toofrom[0]+" % "+toofrom[1]); }
-     */
-    System.out.print("Success?\n"); // if we get here - something must be
-    // working!
-  }
-
-  private static void testLocateFrom(MapList mldna, int i, int j, int[] ks)
-  {
-    int[] frm = mldna.locateInFrom(i, j);
-    if (frm == ks || java.util.Arrays.equals(frm, ks))
-    {
-      System.out.println("Success test locate from " + i + " to " + j);
-    }
-    else
-    {
-      System.err.println("Failed test locate from " + i + " to " + j);
-      for (int c = 0; c < frm.length; c++)
-      {
-        System.err.print(frm[c] + ((c % 2 == 0) ? "," : ";"));
-      }
-      System.err.println("Expected");
-      for (int c = 0; c < ks.length; c++)
-      {
-        System.err.print(ks[c] + ((c % 2 == 0) ? "," : ";"));
-      }
-    }
-  }
-
-  /**
    * 
    * @return a MapList whose From range is this maplist's To Range, and vice
    *         versa
@@ -987,6 +811,7 @@ public class MapList
    */
   public boolean containsEither(boolean local, MapList map)
   {
+    // TODO not used - remove?
     if (local)
     {
       return ((getFromLowest() >= map.getFromLowest() && getFromHighest() <= map
diff --git a/src/jalview/util/MappingUtils.java b/src/jalview/util/MappingUtils.java
new file mode 100644 (file)
index 0000000..6dfebfe
--- /dev/null
@@ -0,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 (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;
+  }
+
+}
diff --git a/src/jalview/util/ReverseListIterator.java b/src/jalview/util/ReverseListIterator.java
new file mode 100644 (file)
index 0000000..69a7345
--- /dev/null
@@ -0,0 +1,42 @@
+package jalview.util;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * An iterator that traverses a list backwards.
+ * 
+ * @author gmcarstairs (and checked against
+ *         org.codehaus.groovey.runtime.ReverseListIterator)
+ *
+ * @param <E>
+ */
+public class ReverseListIterator<E> implements Iterator<E>
+{
+
+  private ListIterator<E> iterator;
+
+  public ReverseListIterator(List<E> stuff)
+  {
+    this.iterator = stuff.listIterator(stuff.size());
+  }
+  @Override
+  public boolean hasNext()
+  {
+    return iterator.hasPrevious();
+  }
+
+  @Override
+  public E next()
+  {
+    return iterator.previous();
+  }
+
+  @Override
+  public void remove()
+  {
+    iterator.remove();
+  }
+
+}
index 2b3a8a4..3b8c68f 100644 (file)
@@ -20,7 +20,8 @@
  */
 package jalview.util;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * ShiftList Simple way of mapping a linear series to a new linear range with
@@ -29,11 +30,11 @@ import java.util.*;
  */
 public class ShiftList
 {
-  public Vector shifts;
+  private List<int[]> shifts;
 
   public ShiftList()
   {
-    shifts = new Vector();
+    shifts = new ArrayList<int[]>();
   }
 
   /**
@@ -48,15 +49,14 @@ public class ShiftList
   {
     int sidx = 0;
     int[] rshift = null;
-    while (sidx < shifts.size()
-            && (rshift = (int[]) shifts.elementAt(sidx))[0] < pos)
+    while (sidx < shifts.size() && (rshift = shifts.get(sidx))[0] < pos)
     {
       sidx++;
     }
     if (sidx == shifts.size())
     {
-      shifts.insertElementAt(new int[]
-      { pos, shift }, sidx);
+      shifts.add(sidx, new int[]
+      { pos, shift });
     }
     else
     {
@@ -81,7 +81,7 @@ public class ShiftList
     int sidx = 0;
     int rshift[];
     while (sidx < shifts.size()
-            && (rshift = ((int[]) shifts.elementAt(sidx++)))[0] <= pos)
+            && (rshift = (shifts.get(sidx++)))[0] <= pos)
     {
       shifted += rshift[1];
     }
@@ -93,7 +93,7 @@ public class ShiftList
    */
   public void clear()
   {
-    shifts.removeAllElements();
+    shifts.clear();
   }
 
   /**
@@ -108,10 +108,10 @@ public class ShiftList
     {
       for (int i = 0, j = shifts.size(); i < j; i++)
       {
-        int[] sh = (int[]) shifts.elementAt(i);
+        int[] sh = shifts.get(i);
         if (sh != null)
         {
-          inverse.shifts.addElement(new int[]
+          inverse.shifts.add(new int[]
           { sh[0], -sh[1] });
         }
       }
@@ -120,8 +120,8 @@ public class ShiftList
   }
 
   /**
-   * parse a 1d map of position 1<i<n to L<pos[i]<N such as that returned from
-   * SequenceI.gapMap()
+   * parse a 1d map of position 1&lt;i&lt;n to L&lt;pos[i]&lt;N such as that
+   * returned from SequenceI.gapMap()
    * 
    * @param gapMap
    * @return shifts from map index to mapped position
@@ -143,4 +143,9 @@ public class ShiftList
     }
     return shiftList;
   }
+
+  public List<int[]> getShifts()
+  {
+    return shifts;
+  }
 }
diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java
new file mode 100644 (file)
index 0000000..0544864
--- /dev/null
@@ -0,0 +1,106 @@
+package jalview.util;
+
+
+public class StringUtils
+{
+
+  /**
+   * Returns a new character array, after inserting characters into the given
+   * character array.
+   * 
+   * @param in
+   *          the character array to insert into
+   * @param position
+   *          the 0-based position for insertion
+   * @param count
+   *          the number of characters to insert
+   * @param ch
+   *          the character to insert
+   */
+  public static final char[] insertCharAt(char[] in, int position,
+          int count,
+          char ch)
+  {
+    char[] tmp = new char[in.length + count];
+  
+    if (position >= in.length)
+    {
+      System.arraycopy(in, 0, tmp, 0, in.length);
+      position = in.length;
+    }
+    else
+    {
+      System.arraycopy(in, 0, tmp, 0, position);
+    }
+  
+    int index = position;
+    while (count > 0)
+    {
+      tmp[index++] = ch;
+      count--;
+    }
+  
+    if (position < in.length)
+    {
+      System.arraycopy(in, position, tmp, index,
+              in.length - position);
+    }
+  
+    return tmp;
+  }
+
+  /**
+   * Delete
+   * 
+   * @param in
+   * @param from
+   * @param to
+   * @return
+   */
+  public static final char[] deleteChars(char[] in, int from, int to)
+  {
+    if (from >= in.length)
+    {
+      return in;
+    }
+
+    char[] tmp;
+
+    if (to >= in.length)
+    {
+      tmp = new char[from];
+      System.arraycopy(in, 0, tmp, 0, from);
+      to = in.length;
+    }
+    else
+    {
+      tmp = new char[in.length - to + from];
+      System.arraycopy(in, 0, tmp, 0, from);
+      System.arraycopy(in, to, tmp, from, in.length - to);
+    }
+    return tmp;
+  }
+
+  /**
+   * Returns the last part of 'input' after the last occurrence of 'token'. For
+   * example to extract only the filename from a full path or URL.
+   * 
+   * @param input
+   * @param token
+   *          a delimiter which must be in regular expression format
+   * @return
+   */
+  public static String getLastToken(String input, String token)
+  {
+    if (input == null)
+    {
+      return null;
+    }
+    if (token == null)
+    {
+      return input;
+    }
+    String[] st = input.split(token);
+    return st[st.length - 1];
+  }
+}
index 3a40a16..3293291 100644 (file)
@@ -30,6 +30,7 @@ import jalview.datamodel.AlignmentAnnotation;
 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;
@@ -47,6 +48,7 @@ import jalview.workers.StrucConsensusThread;
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -63,8 +65,13 @@ public abstract class AlignmentViewport implements AlignViewportI,
         ViewStyleI
 {
   protected ViewStyleI viewStyle = new ViewStyle();
-  
-  
+
+  /**
+   * A viewport that hosts the cDna view of this (protein), or vice versa (if
+   * set).
+   */
+  AlignViewportI codingComplement = null;
+
   /**
    * @param name
    * @see jalview.api.ViewStyleI#setFontName(java.lang.String)
@@ -1127,7 +1134,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected boolean showConsensus = true;
 
-  Hashtable sequenceColours;
+  private Map<SequenceI, Color> sequenceColours = new HashMap<SequenceI, Color>();
 
   /**
    * Property change listener for changes in alignment
@@ -1364,9 +1371,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
-  public abstract void sendSelection();
-
-  @Override
   public void invertColumnSelection()
   {
     colSel.invertColumnSelection(0, alignment.getWidth());
@@ -1420,10 +1424,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
 
   @Override
-  public jalview.datamodel.CigarArray getViewAsCigars(
+  public CigarArray getViewAsCigars(
           boolean selectedRegionOnly)
   {
-    return new jalview.datamodel.CigarArray(alignment, colSel,
+    return new CigarArray(alignment, colSel,
             (selectedRegionOnly ? selectionGroup : null));
   }
 
@@ -1877,35 +1881,22 @@ public abstract class AlignmentViewport implements AlignViewportI,
     viewStyle.setDisplayReferenceSeq(displayReferenceSeq);
   }
 
+  @Override
   public boolean isColourByReferenceSeq()
   {
     return alignment.hasSeqrep() && viewStyle.isColourByReferenceSeq();
   }
 
-
   @Override
   public Color getSequenceColour(SequenceI seq)
   {
-    Color sqc = Color.white;
-    if (sequenceColours != null)
-    {
-      sqc = (Color) sequenceColours.get(seq);
-      if (sqc == null)
-      {
-        sqc = Color.white;
-      }
-    }
-    return sqc;
+    Color sqc = sequenceColours.get(seq);
+    return (sqc == null ? Color.white : sqc);
   }
 
   @Override
   public void setSequenceColour(SequenceI seq, Color col)
   {
-    if (sequenceColours == null)
-    {
-      sequenceColours = new Hashtable();
-    }
-
     if (col == null)
     {
       sequenceColours.remove(seq);
@@ -1919,10 +1910,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public void updateSequenceIdColours()
   {
-    if (sequenceColours == null)
-    {
-      sequenceColours = new Hashtable();
-    }
     for (SequenceGroup sg : alignment.getGroups())
     {
       if (sg.idColour != null)
@@ -1938,9 +1925,43 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @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
@@ -2081,7 +2102,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     return viewStyle.isShowColourText();
   }
-
   /**
    * @return
    * @see jalview.api.ViewStyleI#isShowSeqFeaturesHeight()
index d3682d4..0945b2a 100644 (file)
@@ -28,8 +28,11 @@ import jalview.datamodel.AlignmentView;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 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
 {
@@ -57,7 +60,7 @@ public abstract class AWSThread extends Thread
   /**
    * dataset sequence relationships to be propagated onto new results
    */
-  protected AlignedCodonFrame[] codonframe = null;
+  protected Set<AlignedCodonFrame> codonframe = null;
 
   /**
    * are there jobs still running in this thread.
@@ -124,8 +127,9 @@ 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.");
@@ -189,7 +193,7 @@ public abstract class AWSThread extends Thread
     {
       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();
     }
@@ -295,13 +299,12 @@ public abstract class AWSThread extends Thread
       SequenceI[] alignment = al.getSequencesArray();
       for (int sq = 0; sq < alignment.length; sq++)
       {
-        for (int i = 0; i < codonframe.length; i++)
+        for (AlignedCodonFrame acf : codonframe)
         {
-          if (codonframe[i] != null
-                  && codonframe[i].involvesSequence(alignment[sq]))
+          final SequenceI seq = alignment[sq];
+          if (acf != null && acf.involvesSequence(seq))
           {
-            al.addCodonFrame(codonframe[i]);
-            codonframe[i] = null;
+            al.addCodonFrame(acf);
             break;
           }
         }
@@ -369,12 +372,12 @@ public abstract class AWSThread extends Thread
     WsUrl = wsurl2;
     if (alframe != null)
     {
-      AlignedCodonFrame[] cf = alframe.getViewport().getAlignment()
+      Set<AlignedCodonFrame> cf = alframe.getViewport().getAlignment()
               .getCodonFrames();
       if (cf != null)
       {
-        codonframe = new AlignedCodonFrame[cf.length];
-        System.arraycopy(cf, 0, codonframe, 0, cf.length);
+        codonframe = new LinkedHashSet<AlignedCodonFrame>();
+        codonframe.addAll(cf);
       }
     }
   }
index 18b4252..d1300fe 100644 (file)
  */
 package jalview.analysis;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
+import jalview.analysis.AlignmentUtils.MappingResult;
+import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
+import jalview.io.FormatAdapter;
+import jalview.util.MapList;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
 
 public class AlignmentUtilsTests 
 {
+  // @formatter:off
+  private static final String TEST_DATA = 
+          "# STOCKHOLM 1.0\n" +
+          "#=GS D.melanogaster.1 AC AY119185.1/838-902\n" +
+          "#=GS D.melanogaster.2 AC AC092237.1/57223-57161\n" +
+          "#=GS D.melanogaster.3 AC AY060611.1/560-627\n" +
+          "D.melanogaster.1          G.AGCC.CU...AUGAUCGA\n" +
+          "#=GR D.melanogaster.1 SS  ................((((\n" +
+          "D.melanogaster.2          C.AUUCAACU.UAUGAGGAU\n" +
+          "#=GR D.melanogaster.2 SS  ................((((\n" +
+          "D.melanogaster.3          G.UGGCGCU..UAUGACGCA\n" +
+          "#=GR D.melanogaster.3 SS  (.(((...(....(((((((\n" +
+          "//";
+
+  private static final String AA_SEQS_1 = 
+          ">Seq1Name\n" +
+          "K-QY--L\n" +
+          ">Seq2Name\n" +
+          "-R-FP-W-\n";
+
+  private static final String CDNA_SEQS_1 = 
+          ">Seq1Name\n" +
+          "AC-GG--CUC-CAA-CT\n" +
+          ">Seq2Name\n" +
+          "-CG-TTA--ACG---AAGT\n";
+
+  private static final String CDNA_SEQS_2 = 
+          ">Seq1Name\n" +
+          "GCTCGUCGTACT\n" +
+          ">Seq2Name\n" +
+          "GGGTCAGGCAGT\n";
+  // @formatter:on
+
   public static Sequence ts=new Sequence("short","ASDASDASDASDASDASDASDASDASDASDASDASDASD");
+
   @Test
   public void testExpandFlanks()
   {
@@ -55,6 +99,386 @@ public class AlignmentUtilsTests
           assertTrue("Flanking sequence not the same as original dataset sequence.\n"+ung+"\n"+sq.getDatasetSequence().getSequenceAsString(),ung.equalsIgnoreCase(sq.getDatasetSequence().getSequenceAsString()));
       }
       }
+    }
     }    
+
+  /**
+   * Test method that returns a map of lists of sequences by sequence name.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testGetSequencesByName() throws IOException
+  {
+    final String data = ">Seq1Name\nKQYL\n" + ">Seq2Name\nRFPW\n"
+            + ">Seq1Name\nABCD\n";
+    AlignmentI al = loadAlignment(data, "FASTA");
+    Map<String, List<SequenceI>> map = AlignmentUtils
+            .getSequencesByName(al);
+    assertEquals(2, map.keySet().size());
+    assertEquals(2, map.get("Seq1Name").size());
+    assertEquals("KQYL", map.get("Seq1Name").get(0).getSequenceAsString());
+    assertEquals("ABCD", map.get("Seq1Name").get(1).getSequenceAsString());
+    assertEquals(1, map.get("Seq2Name").size());
+    assertEquals("RFPW", map.get("Seq2Name").get(0).getSequenceAsString());
+  }
+  /**
+   * Helper method to load an alignment and ensure dataset sequences are set up.
+   * 
+   * @param data
+   * @param format TODO
+   * @return
+   * @throws IOException
+   */
+  protected AlignmentI loadAlignment(final String data, String format) throws IOException
+  {
+    Alignment a = new FormatAdapter().readFile(data,
+            AppletFormatAdapter.PASTE, format);
+    a.setDataset(null);
+    return a;
+  }
+  /**
+   * Test mapping of protein to cDNA.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testMapProteinToCdna() throws IOException
+  {
+    // protein: Human + Mouse, 3 residues
+    AlignmentI protein = loadAlignment(
+            ">Human\nKQY\n>Mouse\nAFP\n>Worm\nRST\n",
+            "FASTA");
+    // cDNA: Mouse, Human, Mouse, 9 bases
+    // @formatter:off
+    String dnaData = 
+            ">Mouse\nGAAATCCAG\n" + 
+            ">Human\nTTCGATTAC\n" + 
+            ">Mouse\nGTCGTTTGC\n" + 
+            ">Mouse\nGTCGTTTGCgac\n" + // not mapped - wrong length 
+            ">Fly\nGTCGTTTGC\n"; // not mapped - no name match
+    // @formatter:on
+    AlignmentI cdna1 = loadAlignment(
+            dnaData,
+            "FASTA");
+    MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna1);
+    assertEquals(mapped, MappingResult.Mapped);
+
+    /*
+     * Check two mappings (one for Mouse, one for Human)
+     */
+    assertEquals(2, protein.getCodonFrames().size());
+    assertEquals(1, protein.getCodonFrame(protein.getSequenceAt(0)).size());
+    assertEquals(1, protein.getCodonFrame(protein.getSequenceAt(1)).size());
+
+    /*
+     * Inspect mapping for Human protein
+     */
+    AlignedCodonFrame humanMapping = protein.getCodonFrame(
+            protein.getSequenceAt(0)).get(0);
+    assertEquals(1, humanMapping.getdnaSeqs().length);
+    assertEquals(cdna1.getSequenceAt(1).getDatasetSequence(),
+            humanMapping.getdnaSeqs()[0]);
+    Mapping[] protMappings = humanMapping.getProtMappings();
+    assertEquals(1, protMappings.length);
+    MapList mapList = protMappings[0].getMap();
+    assertEquals(3, mapList.getFromRatio());
+    assertEquals(1, mapList.getToRatio());
+    assertTrue(Arrays.equals(new int[]
+    { 1, 9 }, mapList.getFromRanges()));
+    assertTrue(Arrays.equals(new int[]
+    { 1, 3 }, mapList.getToRanges()));
+
+    /*
+     * Inspect mappings for Mouse protein
+     */
+    AlignedCodonFrame mouseMapping1 = protein.getCodonFrame(
+            protein.getSequenceAt(1)).get(0);
+    assertEquals(2, mouseMapping1.getdnaSeqs().length);
+    assertEquals(cdna1.getSequenceAt(0).getDatasetSequence(),
+            mouseMapping1.getdnaSeqs()[0]);
+    assertEquals(cdna1.getSequenceAt(2).getDatasetSequence(),
+            mouseMapping1.getdnaSeqs()[1]);
+    protMappings = mouseMapping1.getProtMappings();
+    assertEquals(2, protMappings.length);
+    for (int i = 0; i < 2; i++)
+    {
+      mapList = protMappings[i].getMap();
+      assertEquals(3, mapList.getFromRatio());
+      assertEquals(1, mapList.getToRatio());
+      assertTrue(Arrays.equals(new int[]
+      { 1, 9 }, mapList.getFromRanges()));
+      assertTrue(Arrays.equals(new int[]
+      { 1, 3 }, mapList.getToRanges()));
+    }
+  }
+
+  /**
+   * Test mapping of protein to cDNA which may include start and/or stop codons.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testMapProteinToCdna_stopStartCodons() throws IOException
+  {
+    // protein: Human + Mouse, 3 residues
+    AlignmentI protein = loadAlignment(
+            ">Human\nKQY\n>Mouse\nAFP\n>Worm\nRST\n", "FASTA");
+    // @formatter:off
+    String dnaData = 
+            ">Mouse\natgGAAATCCAG\n" + // Mouse with start codon
+            ">Human\nTTCGATtactaa\n" + // Human with stop codon TAA
+            ">Mouse\nGTCGTTTGctaG\n" + // Mouse with stop codon TAG 
+            ">Human\nGTCGTTTgctGa\n" + // Human with stop codon TGA
+            ">Mouse\nATGGTCGTTTGCtag\n"; // Mouse with start and stop codons 
+    // @formatter:on
+    AlignmentI cdna1 = loadAlignment(
+            dnaData,
+            "FASTA");
+    MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna1);
+    assertEquals(mapped, MappingResult.Mapped);
+
+    /*
+     * Check two mappings (one for Mouse, one for Human)
+     */
+    assertEquals(2, protein.getCodonFrames().size());
+    assertEquals(1, protein.getCodonFrame(protein.getSequenceAt(0)).size());
+    assertEquals(1, protein.getCodonFrame(protein.getSequenceAt(1)).size());
+
+    /*
+     * Inspect mapping for Human protein - should map to 2nd and 4th cDNA seqs
+     */
+    AlignedCodonFrame humanMapping = protein.getCodonFrame(
+            protein.getSequenceAt(0)).get(0);
+    assertEquals(2, humanMapping.getdnaSeqs().length);
+    assertEquals(cdna1.getSequenceAt(1).getDatasetSequence(),
+            humanMapping.getdnaSeqs()[0]);
+    assertEquals(cdna1.getSequenceAt(3).getDatasetSequence(),
+            humanMapping.getdnaSeqs()[1]);
+    Mapping[] protMappings = humanMapping.getProtMappings();
+    // two mappings, both to cDNA with stop codon
+    assertEquals(2, protMappings.length);
+    MapList mapList = protMappings[0].getMap();
+    assertEquals(3, mapList.getFromRatio());
+    assertEquals(1, mapList.getToRatio());
+    assertTrue(Arrays.equals(new int[]
+    { 1, 9 }, mapList.getFromRanges()));
+    assertTrue(Arrays.equals(new int[]
+    { 1, 3 }, mapList.getToRanges()));
+    mapList = protMappings[1].getMap();
+    assertEquals(3, mapList.getFromRatio());
+    assertEquals(1, mapList.getToRatio());
+    assertTrue(Arrays.equals(new int[]
+    { 1, 9 }, mapList.getFromRanges()));
+    assertTrue(Arrays.equals(new int[]
+    { 1, 3 }, mapList.getToRanges()));
+
+    /*
+     * Inspect mapping for Mouse protein - should map to 1st/3rd/5th cDNA seqs
+     */
+    AlignedCodonFrame mouseMapping = protein.getCodonFrame(
+            protein.getSequenceAt(1)).get(0);
+    assertEquals(3, mouseMapping.getdnaSeqs().length);
+    assertEquals(cdna1.getSequenceAt(0).getDatasetSequence(),
+            mouseMapping.getdnaSeqs()[0]);
+    assertEquals(cdna1.getSequenceAt(2).getDatasetSequence(),
+            mouseMapping.getdnaSeqs()[1]);
+    assertEquals(cdna1.getSequenceAt(4).getDatasetSequence(),
+            mouseMapping.getdnaSeqs()[2]);
+
+    // three mappings
+    protMappings = mouseMapping.getProtMappings();
+    assertEquals(3, protMappings.length);
+
+    // first mapping to cDNA with start codon
+    mapList = protMappings[0].getMap();
+    assertEquals(3, mapList.getFromRatio());
+    assertEquals(1, mapList.getToRatio());
+    assertTrue(Arrays.equals(new int[]
+    { 4, 12 }, mapList.getFromRanges()));
+    assertTrue(Arrays.equals(new int[]
+    { 1, 3 }, mapList.getToRanges()));
+
+    // second mapping to cDNA with stop codon
+    mapList = protMappings[1].getMap();
+    assertEquals(3, mapList.getFromRatio());
+    assertEquals(1, mapList.getToRatio());
+    assertTrue(Arrays.equals(new int[]
+    { 1, 9 }, mapList.getFromRanges()));
+    assertTrue(Arrays.equals(new int[]
+    { 1, 3 }, mapList.getToRanges()));
+
+    // third mapping to cDNA with start and stop codon
+    mapList = protMappings[2].getMap();
+    assertEquals(3, mapList.getFromRatio());
+    assertEquals(1, mapList.getToRatio());
+    assertTrue(Arrays.equals(new int[]
+    { 4, 12 }, mapList.getFromRanges()));
+    assertTrue(Arrays.equals(new int[]
+    { 1, 3 }, mapList.getToRanges()));
+  }
+
+  /**
+   * Test for the alignSequenceAs method that takes two sequences and a mapping.
+   */
+  @Test
+  public void testAlignSequenceAs_withMapping_noIntrons()
+  {
+    MapList map = new MapList(new int[]
+    { 1, 6 }, new int[]
+    { 1, 2 }, 3, 1);
+
+    /*
+     * No existing gaps in dna:
+     */
+    checkAlignSequenceAs("GGGAAA", "-A-L-", false, false, map,
+            "---GGG---AAA");
+
+    /*
+     * Now introduce gaps in dna but ignore them when realigning.
+     */
+    checkAlignSequenceAs("-G-G-G-A-A-A-", "-A-L-", false, false, map,
+            "---GGG---AAA");
+
+    /*
+     * Now include gaps in dna when realigning. First retaining 'mapped' gaps
+     * only, i.e. those within the exon region.
+     */
+    checkAlignSequenceAs("-G-G--G-A--A-A-", "-A-L-", true, false, map,
+            "---G-G--G---A--A-A");
+
+    /*
+     * Include all gaps in dna when realigning (within and without the exon
+     * region). The leading gap, and the gaps between codons, are subsumed by
+     * the protein alignment gap.
+     */
+    checkAlignSequenceAs("-G-GG--AA-A-", "-A-L-", true, true, map,
+            "---G-GG---AA-A-");
+
+    /*
+     * Include only unmapped gaps in dna when realigning (outside the exon
+     * region). The leading gap, and the gaps between codons, are subsumed by
+     * the protein alignment gap.
+     */
+    checkAlignSequenceAs("-G-GG--AA-A-", "-A-L-", false, true, map,
+            "---GGG---AAA-");
+  }
+
+  /**
+   * Test for the alignSequenceAs method that takes two sequences and a mapping.
+   */
+  @Test
+  public void testAlignSequenceAs_withMapping_withIntrons()
+  {
+    /*
+     * Exons at codon 2 (AAA) and 4 (TTT)
+     */
+    MapList map = new MapList(new int[]
+    { 4, 6, 10, 12 }, new int[]
+    { 1, 2 }, 3, 1);
+
+    /*
+     * Simple case: no gaps in dna
+     */
+    checkAlignSequenceAs("GGGAAACCCTTTGGG", "--A-L-", false, false, map,
+            "GGG---AAACCCTTTGGG");
+
+    /*
+     * Add gaps to dna - but ignore when realigning.
+     */
+    checkAlignSequenceAs("-G-G-G--A--A---AC-CC-T-TT-GG-G-", "--A-L-",
+            false, false, map, "GGG---AAACCCTTTGGG");
+
+    /*
+     * Add gaps to dna - include within exons only when realigning.
+     */
+    checkAlignSequenceAs("-G-G-G--A--A---A-C-CC-T-TT-GG-G-", "--A-L-",
+            true, false, map, "GGG---A--A---ACCCT-TTGGG");
+
+    /*
+     * Include gaps outside exons only when realigning.
+     */
+    checkAlignSequenceAs("-G-G-G--A--A---A-C-CC-T-TT-GG-G-", "--A-L-",
+            false, true, map, "-G-G-GAAAC-CCTTT-GG-G-");
+
+    /*
+     * Include gaps following first intron if we are 'preserving mapped gaps'
+     */
+    checkAlignSequenceAs("-G-G-G--A--A---A-C-CC-T-TT-GG-G-", "--A-L-",
+            true, true, map, "-G-G-G--A--A---A-C-CC-T-TT-GG-G-");
+
+    /*
+     * Include all gaps in dna when realigning.
+     */
+    checkAlignSequenceAs("-G-G-G--A--A---A-C-CC-T-TT-GG-G-", "--A-L-",
+            true, true, map, "-G-G-G--A--A---A-C-CC-T-TT-GG-G-");
+  }
+
+  /**
+   * Test for the case where not all of the protein sequence is mapped to cDNA.
+   */
+  @Test
+  public void testAlignSequenceAs_withMapping_withUnmappedProtein()
+  {
+    
+    /*
+     * Exons at codon 2 (AAA) and 4 (TTT) mapped to A and P
+     */
+    final MapList map = new MapList(new int[]
+    { 4, 6, 10, 12 }, new int[]
+    { 1, 1, 3, 3 }, 3, 1);
+    
+
+    /*
+     * Expect alignment does nothing (aborts realignment). Change this test
+     * first if different behaviour wanted.
+     */
+    checkAlignSequenceAs("GGGAAACCCTTTGGG", "-A-L-P-", false,
+            false, map, "GGGAAACCCTTTGGG");
+  }
+
+  /**
+   * Helper method that performs and verifies the method under test.
+   * 
+   * @param dnaSeq
+   * @param proteinSeq
+   * @param preserveMappedGaps
+   * @param preserveUnmappedGaps
+   * @param map
+   * @param expected
+   */
+  protected void checkAlignSequenceAs(final String dnaSeq,
+          final String proteinSeq, final boolean preserveMappedGaps,
+          final boolean preserveUnmappedGaps, MapList map,
+          final String expected)
+  {
+    SequenceI dna = new Sequence("Seq1", dnaSeq);
+    dna.createDatasetSequence();
+    SequenceI protein = new Sequence("Seq1", proteinSeq);
+    protein.createDatasetSequence();
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    acf.addMap(dna.getDatasetSequence(), protein.getDatasetSequence(), map);
+
+    AlignmentUtils.alignSequenceAs(dna, protein, acf, "---", '-',
+            preserveMappedGaps, preserveUnmappedGaps);
+    assertEquals(expected, dna.getSequenceAsString());
+  }
+
+  /**
+   * Test for the alignSequenceAs method where we preserve gaps in introns only.
+   */
+  @Test
+  public void testAlignSequenceAs_keepIntronGapsOnly()
+  {
+
+    /*
+     * Intron GGGAAA followed by exon CCCTTT
+     */
+    MapList map = new MapList(new int[]
+    { 7, 12 }, new int[]
+    { 1, 2 }, 3, 1);
+    
+    checkAlignSequenceAs("GG-G-AA-A-C-CC-T-TT", "AL",
+            false, true, map, "GG-G-AA-ACCCTTT");
   }
 }
diff --git a/test/jalview/analysis/DnaAlignmentGenerator.java b/test/jalview/analysis/DnaAlignmentGenerator.java
new file mode 100644 (file)
index 0000000..1b8964f
--- /dev/null
@@ -0,0 +1,253 @@
+package jalview.analysis;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.io.FastaFile;
+
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Generates, and outputs in Fasta format, a random DNA alignment for given
+ * sequence length and count. Will regenerate the same alignment each time if
+ * the same random seed is used (so may be used for reproducible unit tests).
+ * Not guaranteed to reproduce the same results between versions, as the rules
+ * may get tweaked to produce more 'realistic' results.
+ * 
+ * Arguments:
+ * <ul>
+ * <li>length (number of bases in each sequence)</li>
+ * <li>height (number of sequences)</li>
+ * <li>a whole number random seed</li>
+ * <li>percentage of gaps to include (0-100)</li>
+ * <li>percentage chance of variation of each position (0-100)</li>
+ * </ul>
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class DnaAlignmentGenerator
+{
+  private static final char GAP = '-';
+
+  private static final char ZERO = '0';
+
+  private static final char[] BASES = new char[]
+  { 'G', 'T', 'C', 'A' };
+
+  private Random random;
+  
+  /**
+   * Outputs a DNA 'alignment' where each position is a random choice from
+   * 'GTCA-'.
+   * 
+   * @param args
+   */
+  public static void main(String[] args)
+  {
+    if (args.length != 5)
+    {
+      usage();
+      return;
+    }
+    int width = Integer.parseInt(args[0]);
+    int height = Integer.parseInt(args[1]);
+    long randomSeed = Long.valueOf(args[2]);
+    int gapPercentage = Integer.valueOf(args[3]);
+    int changePercentage = Integer.valueOf(args[4]);
+    AlignmentI al = new DnaAlignmentGenerator().generate(width, height,
+            randomSeed, gapPercentage, changePercentage);
+
+    System.out.println("; " + height + " sequences of " + width
+            + " bases with " + gapPercentage + "% gaps and "
+            + changePercentage + "% mutations (random seed = " + randomSeed
+            + ")");
+    System.out.println(new FastaFile().print(al.getSequencesArray()));
+  }
+
+  /**
+   * Print parameter help.
+   */
+  private static void usage()
+  {
+    System.out.println("Usage:");
+    System.out.println("arg0: number of (non-gap) bases per sequence");
+    System.out.println("arg1: number sequences");
+    System.out
+            .println("arg2: an integer as random seed (same seed = same results)");
+    System.out.println("arg3: percentage of gaps to (randomly) generate");
+    System.out
+            .println("arg4: percentage of 'mutations' to (randomly) generate");
+    System.out.println("Example: DnaAlignmentGenerator 12 15 387 10 5");
+    System.out
+            .println("- 15 sequences of 12 bases each, approx 10% gaps and 5% mutations, random seed = 387");
+
+  }
+
+  /**
+   * Default constructor
+   */
+  public DnaAlignmentGenerator()
+  {
+
+  }
+
+  /**
+   * Outputs a DNA 'alignment' of given width and height, where each position is
+   * a random choice from 'GTCA-'.
+   * 
+   * @param width
+   * @param height
+   * @param randomSeed
+   * @param changePercentage
+   * @param gapPercentage
+   */
+  public AlignmentI generate(int width, int height, long randomSeed,
+          int gapPercentage, int changePercentage)
+  {
+    SequenceI[] seqs = new SequenceI[height];
+    random = new Random(randomSeed);
+    seqs[0] = generateSequence(1, width, gapPercentage);
+    for (int seqno = 1; seqno < height; seqno++)
+    {
+      seqs[seqno] = generateAnotherSequence(seqs[0].getSequence(),
+              seqno + 1, width, changePercentage);
+    }
+    AlignmentI al = new Alignment(seqs);
+    return al;
+  }
+
+  /**
+   * Outputs a DNA 'sequence' of given length, with some random gaps included.
+   * 
+   * @param seqno
+   * @param length
+   * @param gapPercentage
+   */
+  private SequenceI generateSequence(int seqno, int length,
+          int gapPercentage)
+  {
+    StringBuilder seq = new StringBuilder(length);
+
+    /*
+     * Loop till we've added 'length' bases (excluding gaps)
+     */
+    for (int count = 0; count < length;)
+    {
+      boolean addGap = random.nextInt(100) < gapPercentage;
+      char c = addGap ? GAP : BASES[random.nextInt(Integer.MAX_VALUE) % 4];
+      seq.append(c);
+      if (!addGap)
+      {
+        count++;
+      }
+    }
+    final String seqName = "SEQ" + seqno;
+    final String seqString = seq.toString();
+    SequenceI sq = new Sequence(seqName, seqString);
+    sq.createDatasetSequence();
+    return sq;
+  }
+
+  /**
+   * Generate a sequence approximately aligned to the first one.
+   * 
+   * @param ds
+   * @param seqno
+   * @param width
+   *          number of bases
+   * @param changePercentage
+   * @return
+   */
+  private SequenceI generateAnotherSequence(char[] ds, int seqno,
+          int width, int changePercentage)
+  {
+    int length = ds.length;
+    char[] seq = new char[length];
+    Arrays.fill(seq, ZERO);
+    int gapsWanted = length - width;
+    int gapsAdded = 0;
+
+    /*
+     * First 'randomly' mimic gaps in model sequence.
+     */
+    for (int pos = 0; pos < length; pos++)
+    {
+      if (ds[pos] == GAP)
+      {
+        /*
+         * Add a gap at the same position with changePercentage likelihood
+         */
+        seq[pos] = randomCharacter(GAP, changePercentage);
+        if (seq[pos] == GAP)
+        {
+          gapsAdded++;
+        }
+      }
+    }
+
+    /*
+     * Next scatter any remaining gaps (if any) at random. This gives an even
+     * distribution.
+     */
+    while (gapsAdded < gapsWanted)
+    {
+      boolean added = false;
+      while (!added)
+      {
+        int pos = random.nextInt(length);
+        if (seq[pos] != GAP)
+        {
+          seq[pos] = GAP;
+          added = true;
+          gapsAdded++;
+        }
+      }
+    }
+
+    /*
+     * Finally fill in the rest with randomly mutated bases.
+     */
+    for (int pos = 0; pos < length; pos++)
+    {
+      if (seq[pos] == ZERO)
+      {
+        char c = randomCharacter(ds[pos], changePercentage);
+        seq[pos] = c;
+      }
+    }
+    final String seqName = "SEQ" + seqno;
+    final String seqString = new String(seq);
+    SequenceI sq = new Sequence(seqName, seqString);
+    sq.createDatasetSequence();
+    return sq;
+  }
+
+  /**
+   * Returns a random character that is changePercentage% likely to match the
+   * given type (as base or gap).
+   * 
+   * @param changePercentage
+   * 
+   * @param c
+   * @return
+   */
+  private char randomCharacter(char c, int changePercentage)
+  {
+    final boolean mutation = random.nextInt(100) < changePercentage;
+
+    if (!mutation)
+    {
+      return c;
+    }
+
+    char newchar = c;
+    while (newchar == c)
+    {
+      newchar = BASES[random.nextInt(Integer.MAX_VALUE) % 4];
+    }
+    return newchar;
+  }
+}
diff --git a/test/jalview/analysis/DnaTest.java b/test/jalview/analysis/DnaTest.java
new file mode 100644 (file)
index 0000000..01ed183
--- /dev/null
@@ -0,0 +1,445 @@
+package jalview.analysis;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import jalview.api.AlignViewportI;
+import jalview.datamodel.AlignedCodon;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignViewport;
+import jalview.io.FormatAdapter;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class DnaTest
+{
+  // @formatter:off
+  // AA encoding codons as ordered on the Jalview help page Amino Acid Table
+  private static String fasta = ">B\n" + "GCT" + "GCC" + "GCA" + "GCG"
+          + "TGT" + "TGC" + "GAT" + "GAC" + "GAA" + "GAG" + "TTT" + "TTC"
+          + "GGT" + "GGC" + "GGA" + "GGG" + "CAT" + "CAC" + "ATT" + "ATC"
+          + "ATA" + "AAA" + "AAG" + "TTG" + "TTA" + "CTT" + "CTC" + "CTA"
+          + "CTG" + "ATG" + "AAT" + "AAC" + "CCT" + "CCC" + "CCA" + "CCG"
+          + "CAA" + "CAG" + "CGT" + "CGC" + "CGA" + "CGG" + "AGA" + "AGG"
+          + "TCT" + "TCC" + "TCA" + "TCG" + "AGT" + "AGC" + "ACT" + "ACC"
+          + "ACA" + "ACG" + "GTT" + "GTC" + "GTA" + "GTG" + "TGG" + "TAT"
+          + "TAC" + "TAA" + "TAG" + "TGA";
+
+  private static String JAL_1312_example_align_fasta = ">B.FR.83.HXB2_LAI_IIIB_BRU_K03455/45-306\n"
+          + "ATGGGAAAAAATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATAAATTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGAACGATTCGCAGTTAATCCTGGCCTGTTAGAAACATCAGAAGGCTGTAGACAAATACTGGGACA\n"
+          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAGATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTGCATCAAAGGATAGAGATAAAAGACACCAAGGAAGCTTTAGAC\n"
+          + ">gi|27804621|gb|AY178912.1|/1-259\n"
+          + "-TGGGAGAA-ATTCGGTT-CGGCCAGGGGGAAAGAAAAAATATCAGTTAAAACATATAGTATGGGCAAGCAG\n"
+          + "AGAGCTAGAACGATTCGCAGTTAACCCTGGCCTTTTAGAGACATCACAAGGCTGTAGACAAATACTGGGACA\n"
+          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTTCATCAAAGGATAGATATAAAAGACACCAAGGAAGCTTTAGAT\n"
+          + ">gi|27804623|gb|AY178913.1|/1-259\n"
+          + "-TGGGAGAA-ATTCGGTT-CGGCCAGGGGGAAAGAAAAAATATCAGTTAAAACATATAGTATGGGCAAGCAG\n"
+          + "AGAGCTAGAACGATTCGCAGTTAACCCTGGCCTTTTAGAGACATCACAAGGCTGTAGACAAATACTGGAACA\n"
+          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTTCATCAAAGGATAGATGTAAAAGACACCAAGGAAGCTTTAGAT\n"
+          + ">gi|27804627|gb|AY178915.1|/1-260\n"
+          + "-TGGGAAAA-ATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGAACGATTCGCAGTTAACCCTGGCCTGTTAGAAACATCAGAAGGTTGTAGACAAATATTGGGACA\n"
+          + "GCTACAACCATCCCTTGAGACAGGATCAGAAGAACTTAAATCATTATWTAATACCATAGCAGTCCTCTATTG\n"
+          + "TGTACATCAAAGGATAGATATAAAAGACACCAAGGAAGCTTTAGAG\n"
+          + ">gi|27804631|gb|AY178917.1|/1-261\n"
+          + "-TGGGAAAAAATTCGGTTGAGGCCAGGGGGAAAGAAAAAATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGAACGATTCGCAGTCAACCCTGGCCTGTTAGAAACACCAGAAGGCTGTAGACAAATACTGGGACA\n"
+          + "GCTACAACCGTCCCTTCAGACAGGATCGGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTGCATCAAAGGATAGATGTAAAAGACACCAAGGAGGCTTTAGAC\n"
+          + ">gi|27804635|gb|AY178919.1|/1-261\n"
+          + "-TGGGAGAGAATTCGGTTACGGCCAGGAGGAAAGAAAAAATATAAATTGAAACATATAGTATGGGCAGGCAG\n"
+          + "AGAGCTAGATCGATTCGCAGTCAATCCTGGCCTGTTAGAAACATCAGAAGGCTGCAGACAGATATTGGGACA\n"
+          + "GCTACAACCGTCCCTTAAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTACATCAAAGGATAGATGTAAAAGACACCAAGGAAGCTTTAGAT\n"
+          + ">gi|27804641|gb|AY178922.1|/1-261\n"
+          + "-TGGGAGAAAATTCGGTTACGGCCAGGGGGAAAGAAAAGATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGAACGATTCGCAGTCAACCCTGGCCTGTTAGAAACATCAGAAGGCTGCAGACAAATACTGGGACA\n"
+          + "GTTACACCCATCCCTTCATACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTGCATCAAAGGATAGAAGTAAAAGACACCAAGGAAGCTTTAGAC\n"
+          + ">gi|27804647|gb|AY178925.1|/1-261\n"
+          + "-TGGGAAAAAATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATCAATTAAAACATGTAGTATGGGCAAGCAG\n"
+          + "GGAACTAGAACGATTCGCAGTTAATCCTGGCCTGTTAGAAACATCAGAAGGCTGTAGACAAATATTGGGACA\n"
+          + "GCTACAACCATCCCTTCAGACAGGATCAGAGGAACTTAAATCATTATTTAATACAGTAGCAGTCCTCTATTG\n"
+          + "TGTACATCAAAGAATAGATGTAAAAGACACCAAGGAAGCTCTAGAA\n"
+          + ">gi|27804649|gb|AY178926.1|/1-261\n"
+          + "-TGGGAAAAAATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGAACGATTCGCGGTCAATCCTGGCCTGTTAGAAACATCAGAAGGCTGTAGACAACTACTGGGACA\n"
+          + "GTTACAACCATCCCTTCAGACAGGATCAGAAGAACTCAAATCATTATATAATACAATAGCAACCCTCTATTG\n"
+          + "TGTGCATCAAAGGATAGAGATAAAAGACACCAAGGAAGCCTTAGAT\n"
+          + ">gi|27804653|gb|AY178928.1|/1-261\n"
+          + "-TGGGAAAGAATTCGGTTAAGGCCAGGGGGAAAGAAACAATATAAATTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGACCGATTCGCACTTAACCCCGGCCTGTTAGAAACATCAGAAGGCTGTAGACAAATATTGGGACA\n"
+          + "GCTACAATCGTCCCTTCAGACAGGATCAGAAGAACTTAGATCACTATATAATACAGTAGCAGTCCTCTATTG\n"
+          + "TGTGCATCAAAAGATAGATGTAAAAGACACCAAGGAAGCCTTAGAC\n"
+          + ">gi|27804659|gb|AY178931.1|/1-261\n"
+          + "-TGGGAAAAAATTCGGTTACGGCCAGGAGGAAAGAAAAGATATAAATTAAAACATATAGTATGGGCAAGCAG\n"
+          + "GGAGCTAGAACGATTYGCAGTTAATCCTGGCCTTTTAGAAACAGCAGAAGGCTGTAGACAAATACTGGGACA\n"
+          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
+          + "TGTACATCAAAGGATAGAGATAAAAGACACCAAGGAAGCTTTAGAA\n";
+  // @formatter:on
+
+  /**
+   * Corner case for this test is the presence of codons after codons that were
+   * not translated.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testTranslateCdna_withUntranslatableCodons()
+          throws IOException
+  {
+    AlignmentI alf = new FormatAdapter().readFile(
+            JAL_1312_example_align_fasta, jalview.io.FormatAdapter.PASTE,
+            "FASTA");
+    ColumnSelection cs = new ColumnSelection();
+    AlignViewportI av = new AlignViewport(alf, cs);
+    Dna dna = new Dna(av, new int[]
+    { 0, alf.getWidth() - 1 });
+    AlignmentI translated = dna.translateCdna();
+    assertNotNull("Couldn't do a full width translation of test data.",
+            translated);
+  }
+
+  /**
+   * Test variant in which 15 column blocks at a time are translated (the rest
+   * hidden).
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testTranslateCdna_withUntranslatableCodonsAndHiddenColumns()
+          throws IOException
+  {
+    AlignmentI alf = new FormatAdapter().readFile(
+            JAL_1312_example_align_fasta, jalview.io.FormatAdapter.PASTE,
+            "FASTA");
+    int vwidth = 15;
+    for (int ipos = 0; ipos + vwidth < alf.getWidth(); ipos += vwidth)
+    {
+      ColumnSelection cs = new ColumnSelection();
+      if (ipos > 0)
+      {
+        cs.hideColumns(0, ipos - 1);
+      }
+      cs.hideColumns(ipos + vwidth, alf.getWidth());
+      int[] vcontigs = cs.getVisibleContigs(0, alf.getWidth());
+      AlignViewportI av = new AlignViewport(alf, cs);
+      Dna dna = new Dna(av, vcontigs);
+      AlignmentI transAlf = dna.translateCdna();
+
+      assertTrue("Translation failed (ipos=" + ipos
+              + ") No alignment data.", transAlf != null);
+      assertTrue("Translation failed (ipos=" + ipos + ") Empty alignment.",
+              transAlf.getHeight() > 0);
+      assertTrue("Translation failed (ipos=" + ipos + ") Translated "
+              + transAlf.getHeight() + " sequences from " + alf.getHeight()
+              + " sequences", alf.getHeight() == transAlf.getHeight());
+    }
+  }
+
+  /**
+   * Test simple translation to Amino Acids (with STOP codons translated to X).
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testTranslateCdna_simple() throws IOException
+  {
+    AlignmentI alf = new FormatAdapter().readFile(fasta,
+            FormatAdapter.PASTE, "FASTA");
+    ColumnSelection cs = new ColumnSelection();
+    AlignViewportI av = new AlignViewport(alf, cs);
+    Dna dna = new Dna(av, new int[]
+    { 0, alf.getWidth() - 1 });
+    AlignmentI translated = dna.translateCdna();
+    String aa = translated.getSequenceAt(0).getSequenceAsString();
+    assertEquals(
+            "AAAACCDDEEFFGGGGHHIIIKKLLLLLLMNNPPPPQQRRRRRRSSSSSSTTTTVVVVWYYXXX",
+            aa);
+  }
+
+  /**
+   * Test translation excluding hidden columns.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testTranslateCdna_hiddenColumns() throws IOException
+  {
+    AlignmentI alf = new FormatAdapter().readFile(fasta,
+            FormatAdapter.PASTE, "FASTA");
+    ColumnSelection cs = new jalview.datamodel.ColumnSelection();
+    cs.hideColumns(6, 14); // hide codons 3/4/5
+    cs.hideColumns(24, 35); // hide codons 9-12
+    cs.hideColumns(177, 191); // hide codons 60-64
+    AlignViewportI av = new AlignViewport(alf, cs);
+    Dna dna = new Dna(av, new int[]
+    { 0, alf.getWidth() - 1 });
+    AlignmentI translated = dna.translateCdna();
+    String aa = translated.getSequenceAt(0).getSequenceAsString();
+    assertEquals("AACDDGGGGHHIIIKKLLLLLLMNNPPPPQQRRRRRRSSSSSSTTTTVVVVW", aa);
+  }
+
+  /**
+   * Use this test to help debug into any cases of interest.
+   */
+  @Test
+  public void testCompareCodonPos_oneOnly()
+  {
+    assertFollows("-AA--A", "G--GG"); // 2 shifted seq2, 3 shifted seq1
+  }
+
+  /**
+   * Tests for method that compares 'alignment' of two codon position triplets.
+   */
+  @Test
+  public void testCompareCodonPos()
+  {
+    /*
+     * Returns 0 for any null argument
+     */
+    assertEquals(0, Dna.compareCodonPos(new AlignedCodon(1, 2, 3), null));
+    assertEquals(0, Dna.compareCodonPos(null, new AlignedCodon(1, 2, 3)));
+
+    /*
+     * Work through 27 combinations. First 9 cases where first position matches.
+     */
+    assertMatches("AAA", "GGG"); // 2 and 3 match
+    assertFollows("AA-A", "GGG"); // 2 matches, 3 shifted seq1
+    assertPrecedes("AAA", "GG-G"); // 2 matches, 3 shifted seq2
+    assertFollows("A-AA", "GG-G"); // 2 shifted seq1, 3 matches
+    assertFollows("A-A-A", "GG-G"); // 2 shifted seq1, 3 shifted seq1
+    assertPrecedes("A-AA", "GG--G"); // 2 shifted seq1, 3 shifted seq2
+    assertPrecedes("AA-A", "G-GG"); // 2 shifted seq2, 3 matches
+    assertFollows("AA--A", "G-GG"); // 2 shifted seq2, 3 shifted seq1
+    assertPrecedes("AAA", "G-GG"); // 2 shifted seq2, 3 shifted seq2
+
+    /*
+     * 9 cases where first position is shifted in first sequence.
+     */
+    assertFollows("-AAA", "G-GG"); // 2 and 3 match
+    assertFollows("-AA-A", "G-GG"); // 2 matches, 3 shifted seq1
+    // 'enclosing' case: pick first to start precedes
+    assertFollows("-AAA", "G-G-G"); // 2 matches, 3 shifted seq2
+    assertFollows("-A-AA", "G-G-G"); // 2 shifted seq1, 3 matches
+    assertFollows("-A-A-A", "G-G-G"); // 2 shifted seq1, 3 shifted seq1
+    // 'enclosing' case: pick first to start precedes
+    assertFollows("-A-AA", "G-G--G"); // 2 shifted seq1, 3 shifted seq2
+    assertFollows("-AA-A", "G--GG"); // 2 shifted seq2, 3 matches
+    assertFollows("-AA--A", "G--GG"); // 2 shifted seq2, 3 shifted seq1
+    assertPrecedes("-AAA", "G--GG"); // 2 shifted seq2, 3 shifted seq2
+
+    /*
+     * 9 cases where first position is shifted in second sequence.
+     */
+    assertPrecedes("A-AA", "-GGG"); // 2 and 3 match
+    assertPrecedes("A-A-A", "-GGG"); // 2 matches, 3 shifted seq1
+    assertPrecedes("A-AA", "-GG-G"); // 2 matches, 3 shifted seq2
+    assertPrecedes("A--AA", "-GG-G"); // 2 shifted seq1, 3 matches
+    // 'enclosing' case with middle base deciding:
+    assertFollows("A--AA", "-GGG"); // 2 shifted seq1, 3 shifted seq1
+    assertPrecedes("A--AA", "-GG--G"); // 2 shifted seq1, 3 shifted seq2
+    assertPrecedes("AA-A", "-GGG"); // 2 shifted seq2, 3 matches
+    assertPrecedes("AA--A", "-GGG"); // 2 shifted seq2, 3 shifted seq1
+    assertPrecedes("AAA", "-GGG"); // 2 shifted seq2, 3 shifted seq2
+  }
+
+  /**
+   * This test generates a random cDNA alignment and its translation, then
+   * reorders the cDNA and retranslates, and verifies that the translations are
+   * the same (apart from ordering).
+   */
+  @Test
+  public void testTranslateCdna_sequenceOrderIndependent()
+  {
+    /*
+     * Generate cDNA - 8 sequences of 12 bases each.
+     */
+    AlignmentI cdna = new DnaAlignmentGenerator().generate(12, 8, 97, 5, 5);
+    ColumnSelection cs = new ColumnSelection();
+    AlignViewportI av = new AlignViewport(cdna, cs);
+    Dna dna = new Dna(av, new int[]
+    { 0, cdna.getWidth() - 1 });
+    AlignmentI translated = dna.translateCdna();
+
+    /*
+     * Jumble the cDNA sequences and translate.
+     */
+    SequenceI[] sorted = new SequenceI[cdna.getHeight()];
+    final int[] jumbler = new int[]
+    { 6, 7, 3, 4, 2, 0, 1, 5 };
+    int seqNo = 0;
+    for (int i : jumbler)
+    {
+      sorted[seqNo++] = cdna.getSequenceAt(i);
+    }
+    AlignmentI cdnaReordered = new Alignment(sorted);
+    av = new AlignViewport(cdnaReordered, cs);
+    dna = new Dna(av, new int[]
+    { 0, cdna.getWidth() - 1 });
+    AlignmentI translated2 = dna.translateCdna();
+
+    /*
+     * Check translated sequences are the same in both alignments.
+     */
+    System.out.println("Original");
+    System.out.println(translated.toString());
+    System.out.println("Sorted");
+    System.out.println(translated2.toString());
+
+    int sortedSequenceIndex = 0;
+    for (int originalSequenceIndex : jumbler)
+    {
+      final String translation1 = translated.getSequenceAt(
+              originalSequenceIndex).getSequenceAsString();
+      final String translation2 = translated2.getSequenceAt(sortedSequenceIndex)
+              .getSequenceAsString();
+      assertEquals(translation2, translation1);
+      sortedSequenceIndex++;
+    }
+  }
+
+  /**
+   * Test that all the cases in testCompareCodonPos have a 'symmetric'
+   * comparison (without checking the actual comparison result).
+   */
+  @Test
+  public void testCompareCodonPos_isSymmetric()
+  {
+    assertSymmetric("AAA", "GGG");
+    assertSymmetric("AA-A", "GGG");
+    assertSymmetric("AAA", "GG-G");
+    assertSymmetric("A-AA", "GG-G");
+    assertSymmetric("A-A-A", "GG-G");
+    assertSymmetric("A-AA", "GG--G");
+    assertSymmetric("AA-A", "G-GG");
+    assertSymmetric("AA--A", "G-GG");
+    assertSymmetric("AAA", "G-GG");
+    assertSymmetric("-AAA", "G-GG");
+    assertSymmetric("-AA-A", "G-GG");
+    assertSymmetric("-AAA", "G-G-G");
+    assertSymmetric("-A-AA", "G-G-G");
+    assertSymmetric("-A-A-A", "G-G-G");
+    assertSymmetric("-A-AA", "G-G--G");
+    assertSymmetric("-AA-A", "G--GG");
+    assertSymmetric("-AA--A", "G--GG");
+    assertSymmetric("-AAA", "G--GG");
+    assertSymmetric("A-AA", "-GGG");
+    assertSymmetric("A-A-A", "-GGG");
+    assertSymmetric("A-AA", "-GG-G");
+    assertSymmetric("A--AA", "-GG-G");
+    assertSymmetric("A--AA", "-GGG");
+    assertSymmetric("A--AA", "-GG--G");
+    assertSymmetric("AA-A", "-GGG");
+    assertSymmetric("AA--A", "-GGG");
+    assertSymmetric("AAA", "-GGG");
+  }
+
+  private void assertSymmetric(String codon1, String codon2)
+  {
+    assertEquals("Comparison of '" + codon1 + "' and '" + codon2
+            + " not symmetric", Integer.signum(compare(codon1, codon2)),
+            -Integer.signum(compare(codon2, codon1)));
+  }
+
+  /**
+   * Assert that the first sequence should map to the same position as the
+   * second in a translated alignment. Also checks that this is true if the
+   * order of the codons is reversed.
+   * 
+   * @param codon1
+   * @param codon2
+   */
+  private void assertMatches(String codon1, String codon2)
+  {
+    assertEquals("Expected '" + codon1 + "' matches '" + codon2 + "'", 0,
+            compare(codon1, codon2));
+    assertEquals("Expected '" + codon2 + "' matches '" + codon1 + "'", 0,
+            compare(codon2, codon1));
+  }
+
+  /**
+   * Assert that the first sequence should precede the second in a translated
+   * alignment
+   * 
+   * @param codon1
+   * @param codon2
+   */
+  private void assertPrecedes(String codon1, String codon2)
+  {
+    assertEquals("Expected '" + codon1 + "'  precedes '" + codon2 + "'",
+            -1, compare(codon1, codon2));
+  }
+
+  /**
+   * Assert that the first sequence should follow the second in a translated
+   * alignment
+   * 
+   * @param codon1
+   * @param codon2
+   */
+  private void assertFollows(String codon1, String codon2)
+  {
+    assertEquals("Expected '" + codon1 + "'  follows '" + codon2 + "'", 1,
+            compare(codon1, codon2));
+  }
+
+  /**
+   * Convert two nucleotide strings to base positions and pass to
+   * Dna.compareCodonPos, return the result.
+   * 
+   * @param s1
+   * @param s2
+   * @return
+   */
+  private int compare(String s1, String s2)
+  {
+    final AlignedCodon cd1 = convertCodon(s1);
+    final AlignedCodon cd2 = convertCodon(s2);
+    System.out.println("K: " + s1 + "  " + cd1.toString());
+    System.out.println("G: " + s2 + "  " + cd2.toString());
+    System.out.println();
+    return Dna.compareCodonPos(cd1, cd2);
+  }
+
+  /**
+   * Convert a string e.g. "-GC-T" to base positions e.g. [1, 2, 4]. The string
+   * should have exactly 3 non-gap characters, and use '-' for gaps.
+   * 
+   * @param s
+   * @return
+   */
+  private AlignedCodon convertCodon(String s)
+  {
+    int[] codon = new int[3];
+    int i = 0;
+    for (int j = 0; j < s.length(); j++)
+    {
+      if (s.charAt(j) != '-')
+      {
+        codon[i++] = j;
+      }
+    }
+    return new AlignedCodon(codon[0], codon[1], codon[2]);
+  }
+
+  /**
+   * Weirdly, maybe worth a test to prove the helper method of this test class.
+   */
+  @Test
+  public void testConvertCodon()
+  {
+    assertEquals("[0, 1, 2]", convertCodon("AAA").toString());
+    assertEquals("[0, 2, 5]", convertCodon("A-A--A").toString());
+    assertEquals("[1, 3, 4]", convertCodon("-A-AA-").toString());
+  }
+}
diff --git a/test/jalview/analysis/DnaTranslation.java b/test/jalview/analysis/DnaTranslation.java
deleted file mode 100644 (file)
index 708ee21..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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.analysis;
-
-import static org.junit.Assert.*;
-import jalview.datamodel.ColumnSelection;
-
-import java.io.IOException;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-public class DnaTranslation
-{
-
-  private static String JAL_1312_example_align_fasta = ">B.FR.83.HXB2_LAI_IIIB_BRU_K03455/45-306\n"
-          + "ATGGGAAAAAATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATAAATTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGAACGATTCGCAGTTAATCCTGGCCTGTTAGAAACATCAGAAGGCTGTAGACAAATACTGGGACA\n"
-          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAGATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTGCATCAAAGGATAGAGATAAAAGACACCAAGGAAGCTTTAGAC\n"
-          + ">gi|27804621|gb|AY178912.1|/1-259\n"
-          + "-TGGGAGAA-ATTCGGTT-CGGCCAGGGGGAAAGAAAAAATATCAGTTAAAACATATAGTATGGGCAAGCAG\n"
-          + "AGAGCTAGAACGATTCGCAGTTAACCCTGGCCTTTTAGAGACATCACAAGGCTGTAGACAAATACTGGGACA\n"
-          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTTCATCAAAGGATAGATATAAAAGACACCAAGGAAGCTTTAGAT\n"
-          + ">gi|27804623|gb|AY178913.1|/1-259\n"
-          + "-TGGGAGAA-ATTCGGTT-CGGCCAGGGGGAAAGAAAAAATATCAGTTAAAACATATAGTATGGGCAAGCAG\n"
-          + "AGAGCTAGAACGATTCGCAGTTAACCCTGGCCTTTTAGAGACATCACAAGGCTGTAGACAAATACTGGAACA\n"
-          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTTCATCAAAGGATAGATGTAAAAGACACCAAGGAAGCTTTAGAT\n"
-          + ">gi|27804627|gb|AY178915.1|/1-260\n"
-          + "-TGGGAAAA-ATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGAACGATTCGCAGTTAACCCTGGCCTGTTAGAAACATCAGAAGGTTGTAGACAAATATTGGGACA\n"
-          + "GCTACAACCATCCCTTGAGACAGGATCAGAAGAACTTAAATCATTATWTAATACCATAGCAGTCCTCTATTG\n"
-          + "TGTACATCAAAGGATAGATATAAAAGACACCAAGGAAGCTTTAGAG\n"
-          + ">gi|27804631|gb|AY178917.1|/1-261\n"
-          + "-TGGGAAAAAATTCGGTTGAGGCCAGGGGGAAAGAAAAAATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGAACGATTCGCAGTCAACCCTGGCCTGTTAGAAACACCAGAAGGCTGTAGACAAATACTGGGACA\n"
-          + "GCTACAACCGTCCCTTCAGACAGGATCGGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTGCATCAAAGGATAGATGTAAAAGACACCAAGGAGGCTTTAGAC\n"
-          + ">gi|27804635|gb|AY178919.1|/1-261\n"
-          + "-TGGGAGAGAATTCGGTTACGGCCAGGAGGAAAGAAAAAATATAAATTGAAACATATAGTATGGGCAGGCAG\n"
-          + "AGAGCTAGATCGATTCGCAGTCAATCCTGGCCTGTTAGAAACATCAGAAGGCTGCAGACAGATATTGGGACA\n"
-          + "GCTACAACCGTCCCTTAAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTACATCAAAGGATAGATGTAAAAGACACCAAGGAAGCTTTAGAT\n"
-          + ">gi|27804641|gb|AY178922.1|/1-261\n"
-          + "-TGGGAGAAAATTCGGTTACGGCCAGGGGGAAAGAAAAGATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGAACGATTCGCAGTCAACCCTGGCCTGTTAGAAACATCAGAAGGCTGCAGACAAATACTGGGACA\n"
-          + "GTTACACCCATCCCTTCATACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTGCATCAAAGGATAGAAGTAAAAGACACCAAGGAAGCTTTAGAC\n"
-          + ">gi|27804647|gb|AY178925.1|/1-261\n"
-          + "-TGGGAAAAAATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATCAATTAAAACATGTAGTATGGGCAAGCAG\n"
-          + "GGAACTAGAACGATTCGCAGTTAATCCTGGCCTGTTAGAAACATCAGAAGGCTGTAGACAAATATTGGGACA\n"
-          + "GCTACAACCATCCCTTCAGACAGGATCAGAGGAACTTAAATCATTATTTAATACAGTAGCAGTCCTCTATTG\n"
-          + "TGTACATCAAAGAATAGATGTAAAAGACACCAAGGAAGCTCTAGAA\n"
-          + ">gi|27804649|gb|AY178926.1|/1-261\n"
-          + "-TGGGAAAAAATTCGGTTAAGGCCAGGGGGAAAGAAAAAATATAAGTTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGAACGATTCGCGGTCAATCCTGGCCTGTTAGAAACATCAGAAGGCTGTAGACAACTACTGGGACA\n"
-          + "GTTACAACCATCCCTTCAGACAGGATCAGAAGAACTCAAATCATTATATAATACAATAGCAACCCTCTATTG\n"
-          + "TGTGCATCAAAGGATAGAGATAAAAGACACCAAGGAAGCCTTAGAT\n"
-          + ">gi|27804653|gb|AY178928.1|/1-261\n"
-          + "-TGGGAAAGAATTCGGTTAAGGCCAGGGGGAAAGAAACAATATAAATTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGACCGATTCGCACTTAACCCCGGCCTGTTAGAAACATCAGAAGGCTGTAGACAAATATTGGGACA\n"
-          + "GCTACAATCGTCCCTTCAGACAGGATCAGAAGAACTTAGATCACTATATAATACAGTAGCAGTCCTCTATTG\n"
-          + "TGTGCATCAAAAGATAGATGTAAAAGACACCAAGGAAGCCTTAGAC\n"
-          + ">gi|27804659|gb|AY178931.1|/1-261\n"
-          + "-TGGGAAAAAATTCGGTTACGGCCAGGAGGAAAGAAAAGATATAAATTAAAACATATAGTATGGGCAAGCAG\n"
-          + "GGAGCTAGAACGATTYGCAGTTAATCCTGGCCTTTTAGAAACAGCAGAAGGCTGTAGACAAATACTGGGACA\n"
-          + "GCTACAACCATCCCTTCAGACAGGATCAGAAGAACTTAAATCATTATATAATACAGTAGCAACCCTCTATTG\n"
-          + "TGTACATCAAAGGATAGAGATAAAAGACACCAAGGAAGCTTTAGAA\n";
-
-  @Test
-  public void translationWithUntranslatableCodonsTest()
-  {
-    // Corner case for this test is the presence of codons after codons that
-    // were not translated.
-    jalview.datamodel.AlignmentI alf = null;
-    try
-    {
-      alf = new jalview.io.FormatAdapter().readFile(
-              JAL_1312_example_align_fasta, jalview.io.FormatAdapter.PASTE,
-              "FASTA");
-    } catch (IOException x)
-    {
-      x.printStackTrace();
-      fail("Unexpected IOException (" + x.getMessage()
-              + ") - check test environment");
-    }
-    {
-      // full translation
-      ColumnSelection cs = new jalview.datamodel.ColumnSelection();
-      assertNotNull("Couldn't do a full width translation of test data.",
-              jalview.analysis.Dna.CdnaTranslate(
-                      alf.getSequencesArray(),
-                      cs.getVisibleSequenceStrings(0, alf.getWidth(),
-                              alf.getSequencesArray()), new int[]
-                      { 0, alf.getWidth() - 1 }, alf.getGapCharacter(),
-                      null, alf.getWidth(), null));
-    }
-    int vwidth = 15; // translate in 15 base stretches
-    for (int ipos = 0; ipos + vwidth < alf.getWidth(); ipos += vwidth)
-    {
-      ColumnSelection cs = new jalview.datamodel.ColumnSelection();
-      if (ipos > 0)
-      {
-        cs.hideColumns(0, ipos - 1);
-      }
-      cs.hideColumns(ipos + vwidth, alf.getWidth());
-      int[] vcontigs = cs.getVisibleContigs(0, alf.getWidth());
-      String[] sel = cs.getVisibleSequenceStrings(0, alf.getWidth(),
-              alf.getSequencesArray());
-      jalview.datamodel.AlignmentI transAlf = jalview.analysis.Dna
-              .CdnaTranslate(alf.getSequencesArray(), sel, vcontigs,
-                      alf.getGapCharacter(), null, alf.getWidth(), null);
-
-      assertTrue("Translation failed (ipos=" + ipos
-              + ") No alignment data.", transAlf != null);
-      assertTrue("Translation failed (ipos=" + ipos + ") Empty algnment.",
-              transAlf.getHeight() > 0);
-      assertTrue("Translation failed (ipos=" + ipos + ") Translated "
-              + transAlf.getHeight() + " sequences from " + alf.getHeight()
-              + " sequences", alf.getHeight() == transAlf.getHeight());
-    }
-
-  }
-}
index ca4f18d..6f331ff 100644 (file)
@@ -20,7 +20,9 @@
  */
 package jalview.analysis;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
@@ -74,4 +76,13 @@ public class TestAlignSeq
     }
   }
 
+  @Test
+  public void testExtractGaps()
+  {
+    assertNull(AlignSeq.extractGaps(null, null));
+    assertNull(AlignSeq.extractGaps(". -", null));
+    assertNull(AlignSeq.extractGaps(null, "AB-C"));
+
+    assertEquals("ABCD", AlignSeq.extractGaps(" .-", ". -A-B.C D."));
+  }
 }
index fc821b9..6ea05e6 100644 (file)
@@ -1,6 +1,7 @@
 package jalview.commands;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
 import jalview.datamodel.Alignment;
@@ -8,6 +9,8 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 
+import java.util.Map;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -33,9 +36,13 @@ public class EditCommandTest
     testee = new EditCommand();
     seqs = new SequenceI[4];
     seqs[0] = new Sequence("seq0", "abcdefghjk");
+    seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk"));
     seqs[1] = new Sequence("seq1", "fghjklmnopq");
+    seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq"));
     seqs[2] = new Sequence("seq2", "qrstuvwxyz");
+    seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz"));
     seqs[3] = new Sequence("seq3", "1234567890");
+    seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890"));
     al = new Alignment(seqs);
     al.setGapCharacter('?');
   }
@@ -227,6 +234,373 @@ public class EditCommandTest
     assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString());
     assertEquals("1234567890", seqs[3].getSequenceAsString());
     seqs[1] = new Sequence("seq1", "fghjZXYnopq");
+  }
+
+  /**
+   * Test that the addEdit command correctly merges insert gap commands when
+   * possible.
+   */
+  @Test
+  public void testAddEdit_multipleInsertGap()
+  {
+    /*
+     * 3 insert gap in a row (aka mouse drag right):
+     */
+    Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { seqs[0] }, 1, 1, al);
+    testee.addEdit(e);
+    SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { edited }, 2, 1, al);
+    testee.addEdit(e);
+    edited = new Sequence("seq0", "a??bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { edited }, 3, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+    assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
+    assertEquals(1, testee.getEdit(0).getPosition());
+    assertEquals(3, testee.getEdit(0).getNumber());
+
+    /*
+     * Add a non-contiguous edit - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { edited }, 5, 2, al);
+    testee.addEdit(e);
+    assertEquals(2, testee.getSize());
+    assertEquals(5, testee.getEdit(1).getPosition());
+    assertEquals(2, testee.getEdit(1).getNumber());
+
+    /*
+     * Add a Delete after the Insert - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { edited }, 6, 2, al);
+    testee.addEdit(e);
+    assertEquals(3, testee.getSize());
+    assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
+    assertEquals(6, testee.getEdit(2).getPosition());
+    assertEquals(2, testee.getEdit(2).getNumber());
+  }
+
+  /**
+   * Test that the addEdit command correctly merges delete gap commands when
+   * possible.
+   */
+  @Test
+  public void testAddEdit_multipleDeleteGap()
+  {
+    /*
+     * 3 delete gap in a row (aka mouse drag left):
+     */
+    seqs[0].setSequence("a???bcdefghjk");
+    Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { seqs[0] }, 4, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+
+    SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { edited }, 3, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+
+    edited = new Sequence("seq0", "a?bcdefghjk");
+    edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { edited }, 2, 1, al);
+    testee.addEdit(e);
+    assertEquals(1, testee.getSize());
+    assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
+    assertEquals(2, testee.getEdit(0).getPosition());
+    assertEquals(3, testee.getEdit(0).getNumber());
+
+    /*
+     * Add a non-contiguous edit - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { edited }, 2, 1, al);
+    testee.addEdit(e);
+    assertEquals(2, testee.getSize());
+    assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
+    assertEquals(2, testee.getEdit(1).getPosition());
+    assertEquals(1, testee.getEdit(1).getNumber());
+
+    /*
+     * Add an Insert after the Delete - should not be merged.
+     */
+    e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { edited }, 1, 1, al);
+    testee.addEdit(e);
+    assertEquals(3, testee.getSize());
+    assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
+    assertEquals(1, testee.getEdit(2).getPosition());
+    assertEquals(1, testee.getEdit(2).getNumber());
+  }
+
+  /**
+   * Test that the addEdit command correctly handles 'remove gaps' edits for the
+   * case when they appear contiguous but are acting on different sequences.
+   * They should not be merged.
+   */
+  @Test
+  public void testAddEdit_removeAllGaps()
+  {
+    seqs[0].setSequence("a???bcdefghjk");
+    Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { seqs[0] }, 4, 1, al);
+    testee.addEdit(e);
+
+    seqs[1].setSequence("f??ghjklmnopq");
+    Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+    { seqs[1] }, 3, 1, al);
+    testee.addEdit(e2);
+    assertEquals(2, testee.getSize());
+    assertSame(e, testee.getEdit(0));
+    assertSame(e2, testee.getEdit(1));
+  }
+
+  /**
+   * Test that the addEdit command correctly merges insert gap commands acting
+   * on a multi-sequence selection.
+   */
+  @Test
+  public void testAddEdit_groupInsertGaps()
+  {
+    /*
+     * 2 insert gap in a row (aka mouse drag right), on two sequences:
+     */
+    Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { seqs[0], seqs[1] }, 1, 1, al);
+    testee.addEdit(e);
+    SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
+    seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
+    SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
+    seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
+    e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[]
+    { seq1edited, seq2edited }, 2, 1, al);
+    testee.addEdit(e);
+
+    assertEquals(1, testee.getSize());
+    assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
+    assertEquals(1, testee.getEdit(0).getPosition());
+    assertEquals(2, testee.getEdit(0).getNumber());
+    assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
+            .getSequences()[0].getDatasetSequence());
+    assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
+            .getSequences()[1].getDatasetSequence());
+  }
+
+  /**
+   * Test for 'undoing' a series of gap insertions.
+   * <ul>
+   * <li>Start: ABCDEF insert 2 at pos 1</li>
+   * <li>next: A--BCDEF insert 1 at pos 4</li>
+   * <li>next: A--B-CDEF insert 2 at pos 0</li>
+   * <li>last: --A--B-CDEF</li>
+   * </ul>
+   */
+  @Test
+  public void testPriorState_multipleInserts()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "--A--B-CDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test for 'undoing' a series of gap deletions.
+   * <ul>
+   * <li>Start: A-B-C delete 1 at pos 1</li>
+   * <li>Next: AB-C delete 1 at pos 2</li>
+   * <li>End: ABC</li>
+   * </ul>
+   */
+  @Test
+  public void testPriorState_removeAllGaps()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "ABC");
+    SequenceI ds = new Sequence("", "ABC");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
+    command.addEdit(e);
+    e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test for 'undoing' a single delete edit.
+   */
+  @Test
+  public void testPriorState_singleDelete()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "ABCDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-');
+    command.addEdit(e);
+  
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test 'undoing' a single gap insertion edit command.
+   */
+  @Test
+  public void testPriorState_singleInsert()
+  {
+    EditCommand command = new EditCommand();
+    SequenceI seq = new Sequence("", "AB---CDEF");
+    SequenceI ds = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-');
+    command.addEdit(e);
+  
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
+  }
+
+  /**
+   * Test that mimics 'remove all gaps' action. This generates delete gap edits
+   * for contiguous gaps in each sequence separately.
+   */
+  @Test
+  public void testPriorState_removeGapsMultipleSeqs()
+  {
+    EditCommand command = new EditCommand();
+    String original1 = "--ABC-DEF";
+    String original2 = "FG-HI--J";
+    String original3 = "M-NOPQ";
+
+    /*
+     * Two edits for the first sequence
+     */
+    SequenceI seq = new Sequence("", "ABC-DEF");
+    SequenceI ds1 = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds1);
+    SequenceI[] seqs = new SequenceI[]
+    { seq };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-');
+    command.addEdit(e);
+    seq = new Sequence("", "ABCDEF");
+    seq.setDatasetSequence(ds1);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-');
+    command.addEdit(e);
+  
+    /*
+     * Two edits for the second sequence
+     */
+    seq = new Sequence("", "FGHI--J");
+    SequenceI ds2 = new Sequence("", "FGHIJ");
+    seq.setDatasetSequence(ds2);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-');
+    command.addEdit(e);
+    seq = new Sequence("", "FGHIJ");
+    seq.setDatasetSequence(ds2);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
+    command.addEdit(e);
+
+    /*
+     * One edit for the third sequence.
+     */
+    seq = new Sequence("", "MNOPQ");
+    SequenceI ds3 = new Sequence("", "MNOPQ");
+    seq.setDatasetSequence(ds3);
+    seqs = new SequenceI[]
+    { seq };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
+    command.addEdit(e);
+
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals(original1, unwound.get(ds1).getSequenceAsString());
+    assertEquals(original2, unwound.get(ds2).getSequenceAsString());
+    assertEquals(original3, unwound.get(ds3).getSequenceAsString());
+  }
+
+  /**
+   * Test that mimics 'remove all gapped columns' action. This generates a
+   * series Delete Gap edits that each act on all sequences that share a gapped
+   * column region.
+   */
+  @Test
+  public void testPriorState_removeGappedCols()
+  {
+    EditCommand command = new EditCommand();
+    String original1 = "--ABC--DEF";
+    String original2 = "-G-HI--J";
+    String original3 = "-M-NO--PQ";
+
+    /*
+     * First edit deletes the first column.
+     */
+    SequenceI seq1 = new Sequence("", "-ABC--DEF");
+    SequenceI ds1 = new Sequence("", "ABCDEF");
+    seq1.setDatasetSequence(ds1);
+    SequenceI seq2 = new Sequence("", "G-HI--J");
+    SequenceI ds2 = new Sequence("", "GHIJ");
+    seq2.setDatasetSequence(ds2);
+    SequenceI seq3 = new Sequence("", "M-NO--PQ");
+    SequenceI ds3 = new Sequence("", "MNOPQ");
+    seq3.setDatasetSequence(ds3);
+    SequenceI[] seqs = new SequenceI[]
+    { seq1, seq2, seq3 };
+    Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 1, '-');
+    command.addEdit(e);
+
+    /*
+     * Second edit deletes what is now columns 4 and 5.
+     */
+    seq1 = new Sequence("", "-ABCDEF");
+    seq1.setDatasetSequence(ds1);
+    seq2 = new Sequence("", "G-HIJ");
+    seq2.setDatasetSequence(ds2);
+    seq3 = new Sequence("", "M-NOPQ");
+    seq3.setDatasetSequence(ds3);
+    seqs = new SequenceI[]
+    { seq1, seq2, seq3 };
+    e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-');
+    command.addEdit(e);
 
+    Map<SequenceI, SequenceI> unwound = command.priorState(false);
+    assertEquals(original1, unwound.get(ds1).getSequenceAsString());
+    assertEquals(original2, unwound.get(ds2).getSequenceAsString());
+    assertEquals(original3, unwound.get(ds3).getSequenceAsString());
+    assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
+    assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
+    assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
   }
 }
diff --git a/test/jalview/datamodel/AlignedCodonFrameTest.java b/test/jalview/datamodel/AlignedCodonFrameTest.java
new file mode 100644 (file)
index 0000000..25d0155
--- /dev/null
@@ -0,0 +1,112 @@
+package jalview.datamodel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import jalview.util.MapList;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class AlignedCodonFrameTest
+{
+
+  /**
+   * Test the method that locates the first aligned sequence that has a mapping.
+   */
+  @Test
+  public void testFindAlignedSequence()
+  {
+    AlignmentI cdna = new Alignment(new SequenceI[]
+    {});
+    final Sequence seq1 = new Sequence("Seq1", "C-G-TA-GC");
+    seq1.createDatasetSequence();
+    cdna.addSequence(seq1);
+    final Sequence seq2 = new Sequence("Seq2", "-TA-GG-GG");
+    seq2.createDatasetSequence();
+    cdna.addSequence(seq2);
+
+    AlignmentI aa = new Alignment(new SequenceI[]
+    {});
+    final Sequence aseq1 = new Sequence("Seq1", "-P-R");
+    aseq1.createDatasetSequence();
+    aa.addSequence(aseq1);
+    final Sequence aseq2 = new Sequence("Seq2", "-LY-");
+    aseq2.createDatasetSequence();
+    aa.addSequence(aseq2);
+
+    /*
+     * Mapping from first DNA sequence to second AA sequence.
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+
+    assertNull(acf.findAlignedSequence(seq1, aa));
+
+    MapList map = new MapList(new int[]
+    { 1, 6 }, new int[]
+    { 1, 2 }, 3, 1);
+    acf.addMap(seq1.getDatasetSequence(), aseq2.getDatasetSequence(), map);
+
+    /*
+     * DNA seq1 maps to AA seq2
+     */
+    assertEquals(aa.getSequenceAt(1),
+ acf.findAlignedSequence(cdna
+            .getSequenceAt(0).getDatasetSequence(), aa));
+
+    assertEquals(cdna.getSequenceAt(0),
+ acf.findAlignedSequence(aa
+            .getSequenceAt(1).getDatasetSequence(), cdna));
+  }
+
+  /**
+   * Test the method that locates the mapped codon for a protein position.
+   */
+    @Test
+  public void testGetMappedRegion()
+  {
+    // introns lower case, exons upper case
+    final Sequence seq1 = new Sequence("Seq1", "c-G-TA-gC-gT-T");
+    seq1.createDatasetSequence();
+    final Sequence seq2 = new Sequence("Seq2", "-TA-gG-Gg-CG-a");
+    seq2.createDatasetSequence();
+
+    final Sequence aseq1 = new Sequence("Seq1", "-P-R");
+    aseq1.createDatasetSequence();
+    final Sequence aseq2 = new Sequence("Seq2", "-LY-");
+    aseq2.createDatasetSequence();
+
+    /*
+     * First with no mappings
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+
+    assertNull(acf.getMappedRegion(seq1, aseq1, 1));
+
+    /*
+     * Set up the mappings for the exons (upper-case bases)
+     */
+    MapList map = new MapList(new int[]
+    { 2, 4, 6, 6, 8, 9 }, new int[]
+    { 1, 2 }, 3, 1);
+    acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
+    map = new MapList(new int[]
+    { 1, 2, 4, 5, 7, 8 }, new int[]
+    { 1, 2 }, 3, 1);
+    acf.addMap(seq2.getDatasetSequence(), aseq2.getDatasetSequence(), map);
+
+    assertEquals("[2, 4]",
+            Arrays.toString(acf.getMappedRegion(seq1, aseq1, 1)));
+    assertEquals("[6, 6, 8, 9]",
+            Arrays.toString(acf.getMappedRegion(seq1, aseq1, 2)));
+    assertEquals("[1, 2, 4, 4]",
+            Arrays.toString(acf.getMappedRegion(seq2, aseq2, 1)));
+    assertEquals("[5, 5, 7, 8]",
+            Arrays.toString(acf.getMappedRegion(seq2, aseq2, 2)));
+
+    /*
+     * No mapping from sequence 1 to sequence 2
+     */
+    assertNull(acf.getMappedRegion(seq1, aseq2, 1));
+  }
+}
diff --git a/test/jalview/datamodel/AlignedCodonTest.java b/test/jalview/datamodel/AlignedCodonTest.java
new file mode 100644 (file)
index 0000000..60368b1
--- /dev/null
@@ -0,0 +1,28 @@
+package jalview.datamodel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class AlignedCodonTest
+{
+
+  @Test
+  public void testEquals()
+  {
+    AlignedCodon ac = new AlignedCodon(1, 3, 4);
+    assertTrue(ac.equals(null));
+    assertFalse(ac.equals("hello"));
+    assertFalse(ac.equals(new AlignedCodon(1, 3, 5)));
+    assertTrue(ac.equals(new AlignedCodon(1, 3, 4)));
+    assertTrue(ac.equals(ac));
+  }
+
+  @Test
+  public void testToString() {
+    AlignedCodon ac = new AlignedCodon(1, 3, 4);
+    assertEquals("[1, 3, 4]", ac.toString());
+  }
+}
index 93170b7..3b3d926 100644 (file)
@@ -4,6 +4,8 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import jalview.io.AppletFormatAdapter;
+import jalview.io.FormatAdapter;
+import jalview.util.MapList;
 
 import java.io.IOException;
 import java.util.Iterator;
@@ -32,10 +34,45 @@ public class AlignmentTest
           "D.melanogaster.3          G.UGGCGCU..UAUGACGCA\n" +
           "#=GR D.melanogaster.3 SS  (.(((...(....(((((((\n" +
           "//";
+
+  private static final String AA_SEQS_1 = 
+          ">Seq1Name\n" +
+          "K-QY--L\n" +
+          ">Seq2Name\n" +
+          "-R-FP-W-\n";
+
+  private static final String CDNA_SEQS_1 = 
+          ">Seq1Name\n" +
+          "AC-GG--CUC-CAA-CT\n" +
+          ">Seq2Name\n" +
+          "-CG-TTA--ACG---AAGT\n";
+
+  private static final String CDNA_SEQS_2 = 
+          ">Seq1Name\n" +
+          "GCTCGUCGTACT\n" +
+          ">Seq2Name\n" +
+          "GGGTCAGGCAGT\n";
   // @formatter:on
 
+  private AlignmentI al;
 
-  private Alignment al;
+  /**
+   * Helper method to load an alignment and ensure dataset sequences are set up.
+   * 
+   * @param data
+   * @param format
+   *          TODO
+   * @return
+   * @throws IOException
+   */
+  protected AlignmentI loadAlignment(final String data, String format)
+          throws IOException
+  {
+    Alignment a = new FormatAdapter().readFile(data,
+            AppletFormatAdapter.PASTE, format);
+    a.setDataset(null);
+    return a;
+  }
 
   /*
    * Read in Stockholm format test data including secondary structure
@@ -44,13 +81,12 @@ public class AlignmentTest
   @Before
   public void setUp() throws IOException
   {
-    al = new jalview.io.FormatAdapter().readFile(TEST_DATA,
-            AppletFormatAdapter.PASTE, "STH");
-    for (int i = 0; i < al.getSequencesArray().length; ++i)
+    al = loadAlignment(TEST_DATA, "STH");
+    int i = 0;
+    for (AlignmentAnnotation ann : al.getAlignmentAnnotation())
     {
-      al.addAnnotation(al.getSequenceAt(i).getAnnotation()[0]);
-      al.getSequenceAt(i).getAnnotation()[0].setCalcId("CalcIdFor"
-              + al.getSequenceAt(i).getName());
+      ann.setCalcId("CalcIdFor" + al.getSequenceAt(i).getName());
+      i++;
     }
   }
 
@@ -68,4 +104,174 @@ public class AlignmentTest
     assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
     assertFalse(iter.hasNext());
   }
+
+  @Test
+  public void testDeleteAllAnnotations_includingAutocalculated()
+  {
+    AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
+            "Consensus", 0.5);
+    aa.autoCalculated = true;
+    al.addAnnotation(aa);
+    AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
+    assertEquals("Wrong number of annotations before deleting", 4,
+            anns.length);
+    al.deleteAllAnnotations(true);
+    assertEquals("Not all deleted", 0, al.getAlignmentAnnotation().length);
+  }
+
+  @Test
+  public void testDeleteAllAnnotations_excludingAutocalculated()
+  {
+    AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
+            "Consensus", 0.5);
+    aa.autoCalculated = true;
+    al.addAnnotation(aa);
+    AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
+    assertEquals("Wrong number of annotations before deleting", 4,
+            anns.length);
+    al.deleteAllAnnotations(false);
+    assertEquals("Not just one annotation left", 1,
+            al.getAlignmentAnnotation().length);
+  }
+
+  /**
+   * Tests for realigning as per a supplied alignment: Dna as Dna.
+   * 
+   * Note: AlignedCodonFrame's state variables are named for protein-to-cDNA
+   * mapping, but can be exploited for a general 'sequence-to-sequence' mapping
+   * as here.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testAlignAs_dnaAsDna() throws IOException
+  {
+    // aligned cDNA:
+    AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
+    // unaligned cDNA:
+    AlignmentI al2 = loadAlignment(CDNA_SEQS_2, "FASTA");
+
+    /*
+     * Make mappings between sequences. The 'aligned cDNA' is playing the role
+     * of what would normally be protein here.
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    MapList ml = new MapList(new int[]
+    { 1, 12 }, new int[]
+    { 1, 12 }, 1, 1);
+    acf.addMap(al2.getSequenceAt(0), al1.getSequenceAt(0), ml);
+    acf.addMap(al2.getSequenceAt(1), al1.getSequenceAt(1), ml);
+    al1.addCodonFrame(acf);
+
+    ((Alignment) al2).alignAs(al1, false, true);
+    assertEquals("GC-TC--GUC-GTA-CT", al2.getSequenceAt(0)
+            .getSequenceAsString());
+    assertEquals("-GG-GTC--AGG---CAGT", al2.getSequenceAt(1)
+            .getSequenceAsString());
+  }
+
+  /**
+   * Aligning protein from cDNA yet to be implemented, does nothing.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testAlignAs_proteinAsCdna() throws IOException
+  {
+    AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
+    AlignmentI al2 = loadAlignment(AA_SEQS_1, "FASTA");
+    String before0 = al2.getSequenceAt(0).getSequenceAsString();
+    String before1 = al2.getSequenceAt(1).getSequenceAsString();
+
+    ((Alignment) al2).alignAs(al1, false, true);
+    assertEquals(before0, al2.getSequenceAt(0).getSequenceAsString());
+    assertEquals(before1, al2.getSequenceAt(1).getSequenceAsString());
+  }
+
+  /**
+   * Test aligning cdna as per protein alignment.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testAlignAs_cdnaAsProtein() throws IOException
+  {
+    /*
+     * Load alignments and add mappings for cDNA to protein
+     */
+    AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
+    AlignmentI al2 = loadAlignment(AA_SEQS_1, "FASTA");
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    MapList ml = new MapList(new int[]
+    { 1, 12 }, new int[]
+    { 1, 4 }, 3, 1);
+    acf.addMap(al1.getSequenceAt(0), al2.getSequenceAt(0), ml);
+    acf.addMap(al1.getSequenceAt(1), al2.getSequenceAt(1), ml);
+    al2.addCodonFrame(acf);
+
+    /*
+     * Realign DNA; currently keeping existing gaps in introns only
+     */
+    ((Alignment) al1).alignAs(al2, false, true);
+    assertEquals("ACG---GCUCCA------ACT", al1.getSequenceAt(0)
+            .getSequenceAsString());
+    assertEquals("---CGT---TAACGA---AGT", al1.getSequenceAt(1)
+            .getSequenceAsString());
+  }
+
+  /**
+   * Test aligning dna as per protein alignment, for the case where there are
+   * introns (i.e. some dna sites have no mapping from a peptide).
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void testAlignAs_dnaAsProtein_withIntrons() throws IOException
+  {
+    /*
+     * Load alignments and add mappings for cDNA to protein
+     */
+    String dna1 = "A-Aa-gG-GCC-cT-TT";
+    String dna2 = "c--CCGgg-TT--T-AA-A";
+    AlignmentI al1 = loadAlignment(">Seq1\n" + dna1 + "\n>Seq2\n" + dna2
+            + "\n", "FASTA");
+    AlignmentI al2 = loadAlignment(">Seq1\n-P--YK\n>Seq2\nG-T--F\n",
+            "FASTA");
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    // Seq1 has intron at dna positions 3,4,9 so splice is AAG GCC TTT
+    // Seq2 has intron at dna positions 1,5,6 so splice is CCG TTT AAA
+    MapList ml1 = new MapList(new int[]
+    { 1, 2, 5, 8, 10, 12 }, new int[]
+    { 1, 3 }, 3, 1);
+    acf.addMap(al1.getSequenceAt(0), al2.getSequenceAt(0), ml1);
+    MapList ml2 = new MapList(new int[]
+    { 2, 4, 7, 12 }, new int[]
+    { 1, 3 }, 3, 1);
+    acf.addMap(al1.getSequenceAt(1), al2.getSequenceAt(1), ml2);
+    al2.addCodonFrame(acf);
+
+    /*
+     * Align ignoring gaps in dna introns and exons
+     */
+    ((Alignment) al1).alignAs(al2, false, false);
+    assertEquals("---AAagG------GCCcTTT", al1.getSequenceAt(0)
+            .getSequenceAsString());
+    // note 1 gap in protein corresponds to 'gg-' in DNA (3 positions)
+    assertEquals("cCCGgg-TTT------AAA", al1.getSequenceAt(1)
+            .getSequenceAsString());
+
+    /*
+     * Reset and realign, preserving gaps in dna introns and exons
+     */
+    al1.getSequenceAt(0).setSequence(dna1);
+    al1.getSequenceAt(1).setSequence(dna2);
+    ((Alignment) al1).alignAs(al2, true, true);
+    // String dna1 = "A-Aa-gG-GCC-cT-TT";
+    // String dna2 = "c--CCGgg-TT--T-AA-A";
+    // assumption: we include 'the greater of' protein/dna gap lengths, not both
+    assertEquals("---A-Aa-gG------GCC-cT-TT", al1.getSequenceAt(0)
+            .getSequenceAsString());
+    assertEquals("c--CCGgg-TT--T------AA-A", al1.getSequenceAt(1)
+            .getSequenceAsString());
+  }
 }
diff --git a/test/jalview/datamodel/ColumnSelectionTest.java b/test/jalview/datamodel/ColumnSelectionTest.java
new file mode 100644 (file)
index 0000000..228156a
--- /dev/null
@@ -0,0 +1,48 @@
+package jalview.datamodel;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import org.junit.Test;
+
+public class ColumnSelectionTest
+{
+
+  @Test
+  public void testAddElement()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(5);
+    List<Integer> sel = cs.getSelected();
+    assertEquals(2, sel.size());
+    assertEquals(new Integer(2), sel.get(0));
+    assertEquals(new Integer(5), sel.get(1));
+  }
+
+  /**
+   * Test the remove method - in particular to verify that remove(int i) removes
+   * the element whose value is i, _NOT_ the i'th element.
+   */
+  @Test
+  public void testRemoveElement()
+  {
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(5);
+
+    // removing elements not in the list has no effect
+    cs.removeElement(0);
+    cs.removeElement(1);
+    List<Integer> sel = cs.getSelected();
+    assertEquals(2, sel.size());
+    assertEquals(new Integer(2), sel.get(0));
+    assertEquals(new Integer(5), sel.get(1));
+
+    // removing an element in the list removes it
+    cs.removeElement(2);
+    assertEquals(1, sel.size());
+    assertEquals(new Integer(5), sel.get(0));
+  }
+}
index 40476a0..2a2d2ab 100644 (file)
@@ -5,6 +5,7 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.junit.Before;
@@ -135,4 +136,130 @@ public class SequenceTest
     assertSame(annotation2, anns[1]);
 
   }
+
+  @Test
+  public void testGetStartGetEnd()
+  {
+    SequenceI seq = new Sequence("test", "ABCDEF");
+    assertEquals(1, seq.getStart());
+    assertEquals(6, seq.getEnd());
+
+    seq = new Sequence("test", "--AB-C-DEF--");
+    assertEquals(1, seq.getStart());
+    assertEquals(6, seq.getEnd());
+
+    seq = new Sequence("test", "----");
+    assertEquals(1, seq.getStart());
+    assertEquals(0, seq.getEnd()); // ??
+  }
+
+  /**
+   * Tests for the method that returns an alignment column position (base 1) for
+   * a given sequence position (base 1).
+   */
+  @Test
+  public void testFindIndex()
+  {
+    SequenceI seq = new Sequence("test", "ABCDEF");
+    assertEquals(0, seq.findIndex(0));
+    assertEquals(1, seq.findIndex(1));
+    assertEquals(5, seq.findIndex(5));
+    assertEquals(6, seq.findIndex(6));
+    assertEquals(6, seq.findIndex(9));
+
+    seq = new Sequence("test", "-A--B-C-D-E-F--");
+    assertEquals(2, seq.findIndex(1));
+    assertEquals(5, seq.findIndex(2));
+    assertEquals(7, seq.findIndex(3));
+
+    // before start returns 0
+    assertEquals(0, seq.findIndex(0));
+    assertEquals(0, seq.findIndex(-1));
+
+    // beyond end returns last residue column
+    assertEquals(13, seq.findIndex(99));
+
+  }
+
+  /**
+   * Tests for the method that returns a dataset sequence position (base 1) for
+   * an aligned column position (base 0).
+   */
+  @Test
+  public void testFindPosition()
+  {
+    SequenceI seq = new Sequence("test", "ABCDEF");
+    assertEquals(1, seq.findPosition(0));
+    assertEquals(6, seq.findPosition(5));
+    // assertEquals(-1, seq.findPosition(6)); // fails
+
+    seq = new Sequence("test", "AB-C-D--");
+    assertEquals(1, seq.findPosition(0));
+    assertEquals(2, seq.findPosition(1));
+    // gap position 'finds' residue to the right (not the left as per javadoc)
+    assertEquals(3, seq.findPosition(2));
+    assertEquals(3, seq.findPosition(3));
+    assertEquals(4, seq.findPosition(4));
+    assertEquals(4, seq.findPosition(5));
+    // returns 1 more than sequence length if off the end ?!?
+    assertEquals(5, seq.findPosition(6));
+    assertEquals(5, seq.findPosition(7));
+
+    seq = new Sequence("test", "--AB-C-DEF--");
+    assertEquals(1, seq.findPosition(0));
+    assertEquals(1, seq.findPosition(1));
+    assertEquals(1, seq.findPosition(2));
+    assertEquals(2, seq.findPosition(3));
+    assertEquals(3, seq.findPosition(4));
+    assertEquals(3, seq.findPosition(5));
+    assertEquals(4, seq.findPosition(6));
+    assertEquals(4, seq.findPosition(7));
+    assertEquals(5, seq.findPosition(8));
+    assertEquals(6, seq.findPosition(9));
+    assertEquals(7, seq.findPosition(10));
+    assertEquals(7, seq.findPosition(11));
+  }
+
+  @Test
+  public void testDeleteChars()
+  {
+    SequenceI seq = new Sequence("test", "ABCDEF");
+    assertEquals(1, seq.getStart());
+    assertEquals(6, seq.getEnd());
+    seq.deleteChars(2, 3);
+    assertEquals("ABDEF", seq.getSequenceAsString());
+    assertEquals(1, seq.getStart());
+    assertEquals(5, seq.getEnd());
+
+    seq = new Sequence("test", "ABCDEF");
+    seq.deleteChars(0, 2);
+    assertEquals("CDEF", seq.getSequenceAsString());
+    assertEquals(3, seq.getStart());
+    assertEquals(6, seq.getEnd());
+  }
+
+  @Test
+  public void testInsertCharAt()
+  {
+    // non-static methods:
+    SequenceI seq = new Sequence("test", "ABCDEF");
+    seq.insertCharAt(0, 'z');
+    assertEquals("zABCDEF", seq.getSequenceAsString());
+    seq.insertCharAt(2, 2, 'x');
+    assertEquals("zAxxBCDEF", seq.getSequenceAsString());
+    
+    // for static method see StringUtilsTest
+  }
+
+  /**
+   * Test the method that returns an array of aligned sequence positions where
+   * the array index is the data sequence position (both base 0).
+   */
+  @Test
+  public void testGapMap()
+  {
+    SequenceI seq = new Sequence("test", "-A--B-CD-E--F-");
+    seq.createDatasetSequence();
+    assertEquals("[1, 4, 6, 7, 9, 12]", Arrays.toString(seq.gapMap()));
+  }
 }
diff --git a/test/jalview/gui/JvSwingUtilsTest.java b/test/jalview/gui/JvSwingUtilsTest.java
new file mode 100644 (file)
index 0000000..11e6ea5
--- /dev/null
@@ -0,0 +1,41 @@
+package jalview.gui;
+
+import static org.junit.Assert.assertEquals;
+
+import javax.swing.JScrollBar;
+
+import org.junit.Test;
+
+public class JvSwingUtilsTest
+{
+
+  @Test
+  public void testGetScrollBarProportion()
+  {
+    /*
+     * orientation, value, extent (width), min, max
+     */
+    JScrollBar sb = new JScrollBar(0, 125, 50, 0, 450);
+
+    /*
+     * operating range is 25 - 425 (400 wide) so value 125 is 100/400ths of this
+     * range
+     */
+    assertEquals(0.25f, JvSwingUtils.getScrollBarProportion(sb), 0.001f);
+  }
+
+  @Test
+  public void testGetScrollValueForProportion()
+  {
+    /*
+     * orientation, value, extent (width), min, max
+     */
+    JScrollBar sb = new JScrollBar(0, 125, 50, 0, 450);
+
+    /*
+     * operating range is 25 - 425 (400 wide) so value 125 is a quarter of this
+     * range
+     */
+    assertEquals(125, JvSwingUtils.getScrollValueForProportion(sb, 0.25f));
+  }
+}
diff --git a/test/jalview/gui/PaintRefresherTest.java b/test/jalview/gui/PaintRefresherTest.java
new file mode 100644 (file)
index 0000000..2737dd0
--- /dev/null
@@ -0,0 +1,115 @@
+package jalview.gui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import java.awt.Component;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JPanel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PaintRefresherTest
+{
+  // TODO would prefer PaintRefresher to be a single rather than static
+  @Before
+  public void setUp()
+  {
+    PaintRefresher.components.clear();
+  }
+
+  @After
+  public void tearDown()
+  {
+    PaintRefresher.components.clear();
+  }
+
+  @Test
+  public void testRegister()
+  {
+    JPanel jp = new JPanel();
+    JPanel jp2 = new JPanel();
+    JPanel jp3 = new JPanel();
+    JPanel jp4 = new JPanel();
+    PaintRefresher.Register(jp, "22");
+    PaintRefresher.Register(jp, "22");
+    PaintRefresher.Register(jp2, "22");
+    PaintRefresher.Register(jp3, "33");
+    PaintRefresher.Register(jp3, "44");
+    PaintRefresher.Register(jp4, "44");
+
+    Map<String, List<Component>> registered = PaintRefresher.components;
+    assertEquals(3, registered.size());
+    assertEquals(2, registered.get("22").size());
+    assertEquals(1, registered.get("33").size());
+    assertEquals(2, registered.get("44").size());
+    assertTrue(registered.get("22").contains(jp));
+    assertTrue(registered.get("22").contains(jp2));
+    assertTrue(registered.get("33").contains(jp3));
+    assertTrue(registered.get("44").contains(jp3));
+    assertTrue(registered.get("44").contains(jp4));
+  }
+
+  @Test
+  public void testRemoveComponent()
+  {
+    Map<String, List<Component>> registered = PaintRefresher.components;
+    
+    // no error with an empty PaintRefresher
+    JPanel jp = new JPanel();
+    JPanel jp2 = new JPanel();
+    PaintRefresher.RemoveComponent(jp);
+    assertTrue(registered.isEmpty());
+
+    /*
+     * Add then remove one item
+     */
+    PaintRefresher.Register(jp, "11");
+    PaintRefresher.RemoveComponent(jp);
+    assertTrue(registered.isEmpty());
+
+    /*
+     * Add one item under two ids, then remove it. It is removed from both ids,
+     * and the now empty id is removed.
+     */
+    PaintRefresher.Register(jp, "11");
+    PaintRefresher.Register(jp, "22");
+    PaintRefresher.Register(jp2, "22");
+    PaintRefresher.RemoveComponent(jp);
+    // "11" is removed as now empty, only 22/jp2 left
+    assertEquals(1, registered.size());
+    assertEquals(1, registered.get("22").size());
+    assertTrue(registered.get("22").contains(jp2));
+  }
+
+  @Test
+  public void testGetAssociatedPanels()
+  {
+    SequenceI [] seqs = new SequenceI[]{new Sequence("", "ABC")};
+    Alignment al = new Alignment(seqs);
+
+    /*
+     * AlignFrame constructor has side-effects: AlignmentPanel is constructed,
+     * and SeqCanvas, IdPanel, AlignmentPanel are all registered under the
+     * sequence set id of the viewport.
+     */
+    AlignViewport av = new AlignViewport(al);
+    AlignFrame af = new AlignFrame(al, 4, 1);
+    AlignmentPanel ap1 = af.alignPanel;
+    AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(av
+            .getSequenceSetId());
+    assertEquals(1, panels.length);
+    assertSame(ap1, panels[0]);
+    
+    panels = PaintRefresher.getAssociatedPanels(av.getSequenceSetId() + 1);
+    assertEquals(0, panels.length);
+  }
+}
index be9fcdc..6892e5f 100644 (file)
@@ -241,13 +241,13 @@ public class Jalview2xmlTests
   @Test
   public void gatherViewsHere() throws Exception
   {
-    int origCount = Desktop.getAlignframes() == null ? 0 : Desktop
-            .getAlignframes().length;
+    int origCount = Desktop.getAlignFrames() == null ? 0 : Desktop
+            .getAlignFrames().length;
     AlignFrame af = new jalview.io.FileLoader().LoadFileWaitTillLoaded(
             "examples/exampleFile_2_7.jar", FormatAdapter.FILE);
     assertTrue("Didn't read in the example file correctly.", af != null);
     assertTrue("Didn't gather the views in the example file.",
-            Desktop.getAlignframes().length == 1 + origCount);
+            Desktop.getAlignFrames().length == 1 + origCount);
 
   }
 
diff --git a/test/jalview/util/ComparisonTest.java b/test/jalview/util/ComparisonTest.java
new file mode 100644 (file)
index 0000000..bfc2610
--- /dev/null
@@ -0,0 +1,89 @@
+package jalview.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import org.junit.Test;
+
+public class ComparisonTest
+{
+
+  @Test
+  public void testIsGap()
+  {
+    assertTrue(Comparison.isGap('-'));
+    assertTrue(Comparison.isGap('.'));
+    assertTrue(Comparison.isGap(' '));
+    assertFalse(Comparison.isGap('X'));
+    assertFalse(Comparison.isGap('x'));
+    assertFalse(Comparison.isGap('*'));
+    assertFalse(Comparison.isGap('G'));
+  }
+
+  /**
+   * Test for isNucleotide is that sequences in a dataset are more than 85%
+   * AGCTU. Test is not case-sensitive and ignores gaps.
+   */
+  @Test
+  public void testIsNucleotide() {
+    SequenceI seq = new Sequence("eightypercent", "agctuAGCPV");
+    assertFalse(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    seq = new Sequence("eightyfivepercent", "agctuAGCPVagctuAGCUV");
+    assertFalse(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    seq = new Sequence("nineypercent", "agctuAGCgVagctuAGCUV");
+    assertTrue(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    seq = new Sequence("eightyfivepercentgapped",
+            "--agc--tuA--GCPV-a---gct-uA-GC---UV");
+    assertFalse(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    seq = new Sequence("nineypercentgapped",
+            "ag--ct-u-A---GC---g----Vag--c---tuAGCUV");
+    assertTrue(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    seq = new Sequence("allgap", "---------");
+    assertFalse(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    seq = new Sequence("DNA", "ACTugGCCAG");
+    SequenceI seq2 = new Sequence("Protein", "FLIMVSPTYW");
+    assertTrue(Comparison.isNucleotide(new SequenceI[]
+    { seq, seq, seq, seq, seq, seq, seq, seq, seq, seq2 })); // 90% DNA
+    assertFalse(Comparison.isNucleotide(new SequenceI[]
+    { seq, seq, seq, seq, seq, seq, seq, seq, seq2, seq2 })); // 80% DNA
+
+    seq = new Sequence("ProteinThatLooksLikeDNA", "WYATGCCTGAgtcgt");
+    // 12/14 = 85.7%
+    assertTrue(Comparison.isNucleotide(new SequenceI[]
+    { seq }));
+
+    assertFalse(Comparison.isNucleotide(null));
+  }
+
+  /**
+   * Test percentage identity calculation for two sequences.
+   */
+  @Test
+  public void testPID_matchGaps()
+  {
+    String seq1 = "ABCDEF";
+    String seq2 = "abcdef";
+    assertEquals("identical", 100f, Comparison.PID(seq1, seq2), 0.001f);
+
+    // comparison range defaults to length of first sequence
+    seq2 = "abcdefghijklmnopqrstuvwxyz";
+    assertEquals("identical", 100f, Comparison.PID(seq1, seq2), 0.001f);
+
+    seq2 = "a---bcdef";
+  }
+}
diff --git a/test/jalview/util/MapListTest.java b/test/jalview/util/MapListTest.java
new file mode 100644 (file)
index 0000000..1913a70
--- /dev/null
@@ -0,0 +1,477 @@
+package jalview.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MapListTest
+{
+
+  @Test
+  public void testSomething()
+  {
+    MapList ml = new MapList(new int[]
+    { 1, 5, 10, 15, 25, 20 }, new int[]
+    { 51, 1 }, 1, 3);
+    MapList ml1 = new MapList(new int[]
+    { 1, 3, 17, 4 }, new int[]
+    { 51, 1 }, 1, 3);
+    MapList ml2 = new MapList(new int[]
+    { 1, 60 }, new int[]
+    { 1, 20 }, 3, 1);
+    // test internal consistency
+    int to[] = new int[51];
+    testMap(ml, 1, 60);
+    MapList mldna = new MapList(new int[]
+    { 2, 2, 6, 8, 12, 16 }, new int[]
+    { 1, 3 }, 3, 1);
+    int[] frm = mldna.locateInFrom(1, 1);
+    testLocateFrom(mldna, 1, 1, new int[]
+    { 2, 2, 6, 7 });
+    testMap(mldna, 1, 3);
+    /*
+     * for (int from=1; from<=51; from++) { int[] too=ml.shiftTo(from); int[]
+     * toofrom=ml.shiftFrom(too[0]);
+     * System.out.println("ShiftFrom("+from+")=="+too[0]+" %
+     * "+too[1]+"\t+-+\tShiftTo("+too[0]+")=="+toofrom[0]+" % "+toofrom[1]); }
+     */
+  }
+
+  private static void testLocateFrom(MapList mldna, int i, int j, int[] ks)
+  {
+    int[] frm = mldna.locateInFrom(i, j);
+    Assert.assertEquals("Failed test locate from " + i + " to " + j,
+            Arrays.toString(frm), Arrays.toString(ks));
+  }
+
+  /**
+   * test routine. not incremental.
+   * 
+   * @param ml
+   * @param fromS
+   * @param fromE
+   */
+  private void testMap(MapList ml, int fromS, int fromE)
+  {
+    // todo convert to JUnit style tests
+    for (int from = 1; from <= 25; from++)
+    {
+      int[] too = ml.shiftFrom(from);
+      System.out.print("ShiftFrom(" + from + ")==");
+      if (too == null)
+      {
+        System.out.print("NaN\n");
+      }
+      else
+      {
+        System.out.print(too[0] + " % " + too[1] + " (" + too[2] + ")");
+        System.out.print("\t+--+\t");
+        int[] toofrom = ml.shiftTo(too[0]);
+        if (toofrom != null)
+        {
+          if (toofrom[0] != from)
+          {
+            System.err.println("Mapping not reflexive:" + from + " "
+                    + too[0] + "->" + toofrom[0]);
+          }
+          System.out.println("ShiftTo(" + too[0] + ")==" + toofrom[0]
+                  + " % " + toofrom[1] + " (" + toofrom[2] + ")");
+        }
+        else
+        {
+          System.out.println("ShiftTo(" + too[0] + ")=="
+                  + "NaN! - not Bijective Mapping!");
+        }
+      }
+    }
+    int mmap[][] = ml.makeFromMap();
+    System.out.println("FromMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
+            + mmap[0][2] + " " + mmap[0][3] + " ");
+    for (int i = 1; i <= mmap[1].length; i++)
+    {
+      if (mmap[1][i - 1] == -1)
+      {
+        System.out.print(i + "=XXX");
+  
+      }
+      else
+      {
+        System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
+      }
+      if (i % 20 == 0)
+      {
+        System.out.print("\n");
+      }
+      else
+      {
+        System.out.print(",");
+      }
+    }
+    // test range function
+    System.out.print("\nTest locateInFrom\n");
+    {
+      int f = mmap[0][2], t = mmap[0][3];
+      while (f <= t)
+      {
+        System.out.println("Range " + f + " to " + t);
+        int rng[] = ml.locateInFrom(f, t);
+        if (rng != null)
+        {
+          for (int i = 0; i < rng.length; i++)
+          {
+            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
+          }
+        }
+        else
+        {
+          System.out.println("No range!");
+        }
+        System.out.print("\nReversed\n");
+        rng = ml.locateInFrom(t, f);
+        if (rng != null)
+        {
+          for (int i = 0; i < rng.length; i++)
+          {
+            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
+          }
+        }
+        else
+        {
+          System.out.println("No range!");
+        }
+        System.out.print("\n");
+        f++;
+        t--;
+      }
+    }
+    System.out.print("\n");
+    mmap = ml.makeToMap();
+    System.out.println("ToMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
+            + mmap[0][2] + " " + mmap[0][3] + " ");
+    for (int i = 1; i <= mmap[1].length; i++)
+    {
+      if (mmap[1][i - 1] == -1)
+      {
+        System.out.print(i + "=XXX");
+  
+      }
+      else
+      {
+        System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
+      }
+      if (i % 20 == 0)
+      {
+        System.out.print("\n");
+      }
+      else
+      {
+        System.out.print(",");
+      }
+    }
+    System.out.print("\n");
+    // test range function
+    System.out.print("\nTest locateInTo\n");
+    {
+      int f = mmap[0][2], t = mmap[0][3];
+      while (f <= t)
+      {
+        System.out.println("Range " + f + " to " + t);
+        int rng[] = ml.locateInTo(f, t);
+        if (rng != null)
+        {
+          for (int i = 0; i < rng.length; i++)
+          {
+            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
+          }
+        }
+        else
+        {
+          System.out.println("No range!");
+        }
+        System.out.print("\nReversed\n");
+        rng = ml.locateInTo(t, f);
+        if (rng != null)
+        {
+          for (int i = 0; i < rng.length; i++)
+          {
+            System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
+          }
+        }
+        else
+        {
+          System.out.println("No range!");
+        }
+        f++;
+        t--;
+        System.out.print("\n");
+      }
+    }
+  }
+
+  /**
+   * Tests for method that locates ranges in the 'from' map for given range in
+   * the 'to' map.
+   */
+  @Test
+  public void testLocateInFrom_noIntrons()
+  {
+    /*
+     * Simple mapping with no introns
+     */
+    int[] codons = new int[]
+    { 1, 12 };
+    int[] protein = new int[]
+    { 1, 4 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+    assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(1, 1)));
+    assertEquals("[4, 6]", Arrays.toString(ml.locateInFrom(2, 2)));
+    assertEquals("[7, 9]", Arrays.toString(ml.locateInFrom(3, 3)));
+    assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
+    assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
+    assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
+    assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
+    assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
+    assertEquals("[4, 12]", Arrays.toString(ml.locateInFrom(2, 4)));
+    assertEquals("[7, 12]", Arrays.toString(ml.locateInFrom(3, 4)));
+    assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
+
+    assertNull(ml.locateInFrom(0, 0));
+    assertNull(ml.locateInFrom(1, 5));
+    assertNull(ml.locateInFrom(-1, 1));
+  }
+
+  /**
+   * Tests for method that locates ranges in the 'from' map for given range in
+   * the 'to' map.
+   */
+  @Test
+  public void testLocateInFrom_withIntrons()
+  {
+    /*
+     * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
+     * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
+     */
+    int[] codons =
+    { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
+    int[] protein =
+    { 1, 4 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+    assertEquals("[2, 3, 5, 5]", Arrays.toString(ml.locateInFrom(1, 1)));
+    assertEquals("[6, 7, 9, 9]", Arrays.toString(ml.locateInFrom(2, 2)));
+    assertEquals("[10, 10, 12, 12, 14, 14]",
+            Arrays.toString(ml.locateInFrom(3, 3)));
+    assertEquals("[16, 18]", Arrays.toString(ml.locateInFrom(4, 4)));
+  }
+
+  /**
+   * Tests for method that locates ranges in the 'to' map for given range in the
+   * 'from' map.
+   */
+  @Test
+  public void testLocateInTo_noIntrons()
+  {
+    /*
+     * Simple mapping with no introns
+     */
+    int[] codons = new int[]
+    { 1, 12 };
+    int[] protein = new int[]
+    { 1, 4 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 3)));
+    assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
+    assertEquals("[3, 3]", Arrays.toString(ml.locateInTo(7, 9)));
+    assertEquals("[4, 4]", Arrays.toString(ml.locateInTo(10, 12)));
+    assertEquals("[1, 2]", Arrays.toString(ml.locateInTo(1, 6)));
+    assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(1, 9)));
+    assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 12)));
+    assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
+    assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(4, 12)));
+
+    /*
+     * A part codon is treated as if a whole one.
+     */
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 1)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 2)));
+    assertEquals("[1, 2]", Arrays.toString(ml.locateInTo(1, 4)));
+    assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(2, 8)));
+    assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
+    assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(5, 11)));
+
+    assertNull(ml.locateInTo(0, 0));
+    assertNull(ml.locateInTo(1, 13));
+    assertNull(ml.locateInTo(-1, 1));
+  }
+
+  /**
+   * Tests for method that locates ranges in the 'to' map for given range in the
+   * 'from' map.
+   */
+  @Test
+  public void testLocateInTo_withIntrons()
+  {
+    /*
+     * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
+     * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
+     */
+    int[] codons =
+    { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
+    /*
+     * Mapped proteins at positions 1, 3, 4, 6 in the sequence
+     */
+    int[] protein =
+    { 1, 1, 3, 4, 6, 6 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+
+    /*
+     * Can't map from an unmapped position
+     */
+    assertNull(ml.locateInTo(1, 2));
+    assertNull(ml.locateInTo(2, 4));
+    assertNull(ml.locateInTo(4, 4));
+
+    /*
+     * Valid range or subrange of codon1 maps to protein1.
+     */
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 2)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 3)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 5)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 3)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 5)));
+
+    // codon position 6 starts the next protein:
+    assertEquals("[1, 1, 3, 3]", Arrays.toString(ml.locateInTo(3, 6)));
+
+    // codon positions 7 to 17 (part) cover proteins 2/3/4 at positions 3/4/6
+    assertEquals("[3, 4, 6, 6]", Arrays.toString(ml.locateInTo(7, 17)));
+
+  }
+
+  /**
+   * Test equals method.
+   */
+  @Test
+  public void testEquals()
+  {
+    int[] codons = new int[]
+    { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
+    int[] protein = new int[]
+    { 1, 4 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+    MapList ml1 = new MapList(codons, protein, 3, 1); // same values
+    MapList ml2 = new MapList(codons, protein, 2, 1); // fromRatio differs
+    MapList ml3 = new MapList(codons, protein, 3, 2); // toRatio differs
+    codons[2] = 4;
+    MapList ml6 = new MapList(codons, protein, 3, 1); // fromShifts differ
+    protein[1] = 3;
+    MapList ml7 = new MapList(codons, protein, 3, 1); // toShifts differ
+
+    assertTrue(ml.equals(ml));
+    assertTrue(ml.equals(ml1));
+    assertTrue(ml1.equals(ml));
+
+    assertFalse(ml.equals(null));
+    assertFalse(ml.equals("hello"));
+    assertFalse(ml.equals(ml2));
+    assertFalse(ml.equals(ml3));
+    assertFalse(ml.equals(ml6));
+    assertFalse(ml.equals(ml7));
+    assertFalse(ml6.equals(ml7));
+
+    try
+    {
+      MapList ml4 = new MapList(codons, null, 3, 1); // toShifts null
+      assertFalse(ml.equals(ml4));
+    } catch (NullPointerException e)
+    {
+      // actually thrown by constructor before equals can be called
+    }
+    try
+    {
+      MapList ml5 = new MapList(null, protein, 3, 1); // fromShifts null
+      assertFalse(ml.equals(ml5));
+    } catch (NullPointerException e)
+    {
+      // actually thrown by constructor before equals can be called
+    }
+  }
+
+  /**
+   * Test for the method that flattens a list of ranges into a single array.
+   */
+  @Test
+  public void testGetRanges()
+  {
+    List<int[]> ranges = new ArrayList<int[]>();
+    ranges.add(new int[]
+    { 2, 3 });
+    ranges.add(new int[]
+    { 5, 6 });
+    assertEquals("[2, 3, 5, 6]", Arrays.toString(MapList.getRanges(ranges)));
+  }
+
+  /**
+   * Check state after construction
+   */
+  @Test
+  public void testConstructor()
+  {
+    int[] codons =
+    { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
+    int[] protein =
+    { 1, 1, 3, 4, 6, 6 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+    assertEquals(3, ml.getFromRatio());
+    assertEquals(2, ml.getFromLowest());
+    assertEquals(18, ml.getFromHighest());
+    assertEquals(1, ml.getToLowest());
+    assertEquals(6, ml.getToHighest());
+    assertEquals("[2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18]",
+            Arrays.toString(ml.getFromRanges()));
+    assertEquals("[1, 1, 3, 4, 6, 6]", Arrays.toString(ml.getToRanges()));
+
+    /*
+     * Also copy constructor
+     */
+    MapList ml2 = new MapList(ml);
+    assertEquals(3, ml2.getFromRatio());
+    assertEquals(2, ml2.getFromLowest());
+    assertEquals(18, ml2.getFromHighest());
+    assertEquals(1, ml2.getToLowest());
+    assertEquals(6, ml2.getToHighest());
+    assertEquals("[2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18]",
+            Arrays.toString(ml2.getFromRanges()));
+    assertEquals("[1, 1, 3, 4, 6, 6]", Arrays.toString(ml2.getToRanges()));
+  }
+
+  /**
+   * Test the method that creates an inverse mapping
+   */
+  @Test
+  public void testGetInverse()
+  {
+    int[] codons =
+    { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
+    int[] protein =
+    { 1, 1, 3, 4, 6, 6 };
+
+    MapList ml = new MapList(codons, protein, 3, 1);
+    MapList ml2 = ml.getInverse();
+    assertEquals(ml.getFromRatio(), ml2.getToRatio());
+    assertEquals(ml.getFromRatio(), ml2.getToRatio());
+    assertEquals(ml.getToHighest(), ml2.getFromHighest());
+    assertEquals(ml.getFromHighest(), ml2.getToHighest());
+    assertEquals(Arrays.toString(ml.getFromRanges()),
+            Arrays.toString(ml2.getToRanges()));
+    assertEquals(Arrays.toString(ml.getToRanges()),
+            Arrays.toString(ml2.getFromRanges()));
+  }
+}
diff --git a/test/jalview/util/ShiftListTest.java b/test/jalview/util/ShiftListTest.java
new file mode 100644 (file)
index 0000000..f680d6c
--- /dev/null
@@ -0,0 +1,35 @@
+package jalview.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+public class ShiftListTest
+{
+
+  @Test
+  public void testParseMap()
+  {
+    assertNull(ShiftList.parseMap(null));
+    assertNull(ShiftList.parseMap(new int[]
+    {}));
+    
+    /*
+     * Gap map showing residues in aligned positions 2,3,6,8,9,10,12
+     */
+    int[] gm = new int[]
+    { 2, 3, 6, 8, 9, 10, 12 };
+    List<int[]> shifts = ShiftList.parseMap(gm).getShifts();
+    assertEquals(4, shifts.size());
+
+    // TODO are these results (which pass) correct??
+    assertEquals("[0, 2]", Arrays.toString(shifts.get(0)));
+    assertEquals("[4, 2]", Arrays.toString(shifts.get(1)));
+    assertEquals("[7, 1]", Arrays.toString(shifts.get(2)));
+    assertEquals("[11, 1]", Arrays.toString(shifts.get(3)));
+  }
+}
diff --git a/test/jalview/util/StringUtilsTest.java b/test/jalview/util/StringUtilsTest.java
new file mode 100644 (file)
index 0000000..22a4130
--- /dev/null
@@ -0,0 +1,71 @@
+package jalview.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class StringUtilsTest
+{
+
+  @Test
+  public void testInsertCharAt()
+  {
+    char[] c1 = "ABC".toCharArray();
+    char[] expected = new char[]
+    { 'A', 'B', 'C', 'w', 'w' };
+    assertTrue(Arrays.equals(expected,
+            StringUtils.insertCharAt(c1, 3, 2, 'w')));
+    expected = new char[]
+    { 'A', 'B', 'C', 'w', 'w' };
+    assertTrue(Arrays.equals(expected,
+            StringUtils.insertCharAt(c1, 4, 2, 'w')));
+    assertTrue(Arrays.equals(expected,
+            StringUtils.insertCharAt(c1, 5, 2, 'w')));
+    assertTrue(Arrays.equals(expected,
+            StringUtils.insertCharAt(c1, 6, 2, 'w')));
+    assertTrue(Arrays.equals(expected,
+            StringUtils.insertCharAt(c1, 7, 2, 'w')));
+  }
+
+  @Test
+  public void testDeleteChars()
+  {
+    char[] c1 = "ABC".toCharArray();
+
+    // delete second position
+    assertTrue(Arrays.equals(new char[]
+    { 'A', 'C' }, StringUtils.deleteChars(c1, 1, 2)));
+
+    // delete positions 1 and 2
+    assertTrue(Arrays.equals(new char[]
+    { 'C' }, StringUtils.deleteChars(c1, 0, 2)));
+
+    // delete positions 1-3
+    assertTrue(Arrays.equals(new char[]
+    {}, StringUtils.deleteChars(c1, 0, 3)));
+
+    // delete position 3
+    assertTrue(Arrays.equals(new char[]
+    { 'A', 'B' }, StringUtils.deleteChars(c1, 2, 3)));
+
+    // out of range deletion is ignore
+    assertTrue(Arrays.equals(c1, StringUtils.deleteChars(c1, 3, 4)));
+  }
+
+  @Test
+  public void testGetLastToken()
+  {
+    assertNull(StringUtils.getLastToken(null, null));
+    assertNull(StringUtils.getLastToken(null, "/"));
+    assertEquals("a", StringUtils.getLastToken("a", null));
+
+    assertEquals("abc", StringUtils.getLastToken("abc", "/"));
+    assertEquals("c", StringUtils.getLastToken("abc", "b"));
+    assertEquals("file1.dat", StringUtils.getLastToken(
+            "file://localhost:8080/data/examples/file1.dat", "/"));
+  }
+}