JAL-845 first working linked edit protein -> cDNA
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Sun, 28 Dec 2014 09:54:56 +0000 (09:54 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Sun, 28 Dec 2014 09:54:56 +0000 (09:54 +0000)
33 files changed:
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/commands/EditCommand.java
src/jalview/datamodel/SearchResults.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/ext/varna/JalviewVarnaBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AppVarnaBinding.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/VamsasApplication.java
src/jalview/javascript/MouseOverListener.java
src/jalview/javascript/MouseOverStructureListener.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/util/Comparison.java
src/jalview/util/ReverseListIterator.java [new file with mode: 0644]
src/jalview/util/StringUtils.java [new file with mode: 0644]
test/jalview/commands/EditCommandTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/util/ComparisonTest.java [new file with mode: 0644]
test/jalview/util/StringUtilsTest.java [new file with mode: 0644]

index a1bb272..a196edd 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 dc0f718..40bd370 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 592fd4f..ba42621 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;
@@ -680,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");
index 82de3b2..ba01b1c 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;
 
 /**
  * 
@@ -110,13 +115,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 +289,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 +328,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 +344,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 +421,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 +453,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 +471,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 +535,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;
@@ -548,7 +653,7 @@ public class EditCommand implements CommandI
     command.string = null;
   }
 
-  void replace(Edit command)
+  static void replace(Edit command)
   {
     StringBuffer tmp;
     String oldstring;
@@ -625,7 +730,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 +1054,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 +1132,110 @@ 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
+          {
+            throw new IllegalStateException("Can't undo edit action " + action);
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  public class Edit
   {
     public SequenceI[] oldds;
 
@@ -1053,7 +1261,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 +1307,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 8434e81..6b7a3eb 100755 (executable)
@@ -193,4 +193,14 @@ public class SearchResults
       this.end = end;
     }
   }
+
+  /**
+   * Returns true if no search result matches are held.
+   * 
+   * @return
+   */
+  public boolean isEmpty()
+  {
+    return (matches == null) || (matches.length == 0);
+  }
 }
index 0652fb5..6b0e9fc 100755 (executable)
@@ -21,6 +21,7 @@
 package jalview.datamodel;
 
 import jalview.analysis.AlignSeq;
+import jalview.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -614,6 +615,14 @@ public class Sequence implements SequenceI
     }
   }
 
+  /**
+   * Returns the (1-based) residue position for a (0-based) alignment position
+   * 
+   * @param i
+   *          column index in alignment (from 0..<length)
+   * 
+   * @return residue number for residue (left of and) nearest ith column
+   */
   @Override
   public int findPosition(int i)
   {
@@ -685,38 +694,48 @@ public class Sequence implements SequenceI
     return map;
   }
 
-  /*
-   * (non-Javadoc)
+  /**
+   * Delete sequence characters from position 'from' (inclusive) to 'to'
+   * (exclusive). The end range is limited to the length of the sequence. If the
+   * start position is out of range, do nothing.
    * 
    * @see jalview.datamodel.SequenceI#deleteChars(int, int)
    */
-  public void deleteChars(int i, int j)
+  @Override
+  public void deleteChars(int from, int to)
   {
-    int newstart = start, newend = end;
-    if (i >= sequence.length)
+    if (from >= sequence.length)
     {
       return;
     }
-
-    char[] tmp;
-
-    if (j >= sequence.length)
-    {
-      tmp = new char[i];
-      System.arraycopy(sequence, 0, tmp, 0, i);
-      j = sequence.length;
-    }
-    else
+    if (to > sequence.length)
     {
-      tmp = new char[sequence.length - j + i];
-      System.arraycopy(sequence, 0, tmp, 0, i);
-      System.arraycopy(sequence, j, tmp, i, sequence.length - j);
+      to = sequence.length;
     }
+    char[] tmp = StringUtils.deleteChars(sequence, from, to);
+
+    recreateDatasetAfterDeletions(from, to);
+    sequence = tmp;
+  }
+
+  /**
+   * Reconstruct the dataset sequence, if necessary, after deletion of columns
+   * in the aligned sequence.
+   * 
+   * @param from
+   *          start column of deletions (zero-based, inclusive)
+   * @param to
+   *          end column of deletions (exclusive)
+   */
+  protected boolean recreateDatasetAfterDeletions(int from, int to)
+  {
+    boolean created = false;
     boolean createNewDs = false;
+    int newstart = start, newend = end;
     // TODO: take a look at the new dataset creation validation method below -
-    // this could become time comsuming for large sequences - consider making it
+    // this could become time consuming for large sequences - consider making it
     // more efficient
-    for (int s = i; s < j; s++)
+    for (int s = from; s < to; s++)
     {
       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
       {
@@ -730,17 +749,17 @@ public class Sequence implements SequenceI
           if (sindex == s)
           {
             // delete characters including start of sequence
-            newstart = findPosition(j);
+            newstart = findPosition(to);
             break; // don't need to search for any more residue characters.
           }
           else
           {
             // delete characters after start.
             int eindex = findIndex(end) - 1;
-            if (eindex < j)
+            if (eindex < to)
             {
               // delete characters at end of sequence
-              newend = findPosition(i - 1);
+              newend = findPosition(from - 1);
               break; // don't need to search for any more residue characters.
             }
             else
@@ -760,56 +779,25 @@ public class Sequence implements SequenceI
       Sequence ds = new Sequence(datasetSequence);
       // TODO: remove any non-inheritable properties ?
       // TODO: create a sequence mapping (since there is a relation here ?)
-      ds.deleteChars(i, j);
+      ds.deleteChars(from, to);
       datasetSequence = ds;
+      created = true;
     }
     start = newstart;
     end = newend;
-    sequence = tmp;
+    return created;
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * @param c
-   *          DOCUMENT ME!
-   * @param chop
-   *          DOCUMENT ME!
-   */
-  public void insertCharAt(int i, int length, char c)
+  @Override
+  public void insertCharAt(int i, char c)
   {
-    char[] tmp = new char[sequence.length + length];
-
-    if (i >= sequence.length)
-    {
-      System.arraycopy(sequence, 0, tmp, 0, sequence.length);
-      i = sequence.length;
-    }
-    else
-    {
-      System.arraycopy(sequence, 0, tmp, 0, i);
-    }
-
-    int index = i;
-    while (length > 0)
-    {
-      tmp[index++] = c;
-      length--;
-    }
-
-    if (i < sequence.length)
-    {
-      System.arraycopy(sequence, i, tmp, index, sequence.length - i);
-    }
-
-    sequence = tmp;
+    insertCharAt(i, 1, c);
   }
 
-  public void insertCharAt(int i, char c)
+  @Override
+  public void insertCharAt(int pos, int count, char c)
   {
-    insertCharAt(i, 1, c);
+    sequence = StringUtils.insertCharAt(sequence, pos, count, c);
   }
 
   public String getVamsasId()
@@ -1010,7 +998,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);
         }
       }
@@ -1247,8 +1235,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 fc67efd..3ca759d 100755 (executable)
@@ -220,9 +220,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 +238,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 9187912..921e521 100644 (file)
@@ -32,6 +32,7 @@ import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
+import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
@@ -47,6 +48,7 @@ import java.net.URL;
 import java.security.AccessControlException;
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
@@ -232,7 +234,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
               + (1 + getModelNum((String) chainFile.get(lbl))) + " or ");
     }
     if (cmd.length() > 0)
+    {
       cmd.setLength(cmd.length() - 4);
+    }
     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
   }
 
@@ -630,7 +634,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
           jalview.api.AlignmentViewPanel alignmentv)
   {
     if (!colourBySequence || !isLoadingFinished())
+    {
       return;
+    }
     if (ssm == null)
     {
       return;
@@ -649,10 +655,12 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     for (jalview.structure.StructureMappingcommandSet cpdbbyseq : JmolCommands
             .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
                     alignment))
+    {
       for (String cbyseq : cpdbbyseq.commands)
       {
         evalStateCommand(cbyseq);
       }
+    }
   }
 
   public boolean isColourBySequence()
@@ -702,7 +710,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
           String pdbfile)
   {
     if (getModelNum(pdbfile) < 0)
+    {
       return null;
+    }
     // TODO: verify atomIndex is selecting correct model.
     return new Color(viewer.getAtomArgb(atomIndex));
   }
@@ -735,7 +745,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     for (int i = 0; i < mfn.length; i++)
     {
       if (mfn[i].equalsIgnoreCase(modelFileName))
+      {
         return i;
+      }
     }
     return -1;
   }
@@ -831,64 +843,76 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     jmolpopup.show(x, y);
   }
 
-  // jmol/ssm only
-  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 (modelFileNames == null)
     {
       return;
     }
 
-    // look up file model number for this pdbfile
-    int mdlNum = 0;
-    String fn;
-    // may need to adjust for URLencoding here - we don't worry about that yet.
-    while (mdlNum < modelFileNames.length
-            && !pdbfile.equals(modelFileNames[mdlNum]))
-    {
-      // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
-      mdlNum++;
-    }
-    if (mdlNum == modelFileNames.length)
+    for (AtomSpec atom : atoms)
     {
-      return;
-    }
+      String pdbfile = atom.getPdbId();
+      int pdbResNum = atom.getPdbResNum();
+      String chain = atom.getChain();
+
+      // look up file model number for this pdbfile
+      int mdlNum = 0;
+      String fn;
+      // may need to adjust for URLencoding here - we don't worry about that
+      // yet.
+      while (mdlNum < modelFileNames.length
+              && !pdbfile.equals(modelFileNames[mdlNum]))
+      {
+        // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
+        mdlNum++;
+      }
+      if (mdlNum == modelFileNames.length)
+      {
+        return;
+      }
 
-    jmolHistory(false);
-    // if (!pdbfile.equals(pdbentry.getFile()))
-    // return;
-    if (resetLastRes.length() > 0)
-    {
-      viewer.evalStringQuiet(resetLastRes.toString());
-    }
+      jmolHistory(false);
+      // if (!pdbfile.equals(pdbentry.getFile()))
+      // return;
+      if (resetLastRes.length() > 0)
+      {
+        viewer.evalStringQuiet(resetLastRes.toString());
+      }
 
-    eval.setLength(0);
-    eval.append("select " + pdbResNum); // +modelNum
+      eval.setLength(0);
+      eval.append("select " + pdbResNum); // +modelNum
 
-    resetLastRes.setLength(0);
-    resetLastRes.append("select " + pdbResNum); // +modelNum
+      resetLastRes.setLength(0);
+      resetLastRes.append("select " + pdbResNum); // +modelNum
 
-    eval.append(":");
-    resetLastRes.append(":");
-    if (!chain.equals(" "))
-    {
-      eval.append(chain);
-      resetLastRes.append(chain);
-    }
-    {
-      eval.append(" /" + (mdlNum + 1));
-      resetLastRes.append("/" + (mdlNum + 1));
-    }
-    eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
+      eval.append(":");
+      resetLastRes.append(":");
+      if (!chain.equals(" "))
+      {
+        eval.append(chain);
+        resetLastRes.append(chain);
+      }
+      {
+        eval.append(" /" + (mdlNum + 1));
+        resetLastRes.append("/" + (mdlNum + 1));
+      }
+      eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
 
-    resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
-            + " and not hetero; spacefill 0;");
+      resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
+              + " and not hetero; spacefill 0;");
 
-    eval.append("spacefill 200;select none");
+      eval.append("spacefill 200;select none");
 
-    viewer.evalStringQuiet(eval.toString());
-    jmolHistory(true);
+      viewer.evalStringQuiet(eval.toString());
+      jmolHistory(true);
+    }
 
   }
 
@@ -944,8 +968,10 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     String chainId;
 
     if (strInfo.indexOf(":") > -1)
+    {
       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
               strInfo.indexOf("."));
+    }
     else
     {
       chainId = " ";
@@ -983,7 +1009,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
       ;
     }
     if (lastMessage == null || !lastMessage.equals(strInfo))
+    {
       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
+    }
 
     lastMessage = strInfo;
   }
@@ -1017,13 +1045,17 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     int chainSeparator = strInfo.indexOf(":");
     int p = 0;
     if (chainSeparator == -1)
+    {
       chainSeparator = strInfo.indexOf(".");
+    }
 
     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
             chainSeparator);
     String mdlString = "";
     if ((p = strInfo.indexOf(":")) > -1)
+    {
       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
+    }
 
     if ((p = strInfo.indexOf("/")) > -1)
     {
@@ -1271,7 +1303,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
             for (int i = 0; i < pdb.chains.size(); i++)
             {
               String chid = new String(pdb.id + ":"
-                      + ((MCview.PDBChain) pdb.chains.elementAt(i)).id);
+                      + pdb.chains.elementAt(i).id);
               chainFile.put(chid, fileName);
               chainNames.addElement(chid);
             }
@@ -1364,7 +1396,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     colourBySequence = false;
 
     if (cs == null)
+    {
       return;
+    }
 
     String res;
     int index;
@@ -1378,7 +1412,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
       res = en.nextElement().toString();
       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
       if (index > 20)
+      {
         continue;
+      }
 
       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
 
index 82f5e5c..7230baf 100644 (file)
@@ -32,6 +32,7 @@ import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
+import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
@@ -913,44 +914,56 @@ public abstract class JalviewChimeraBinding extends
   public abstract SequenceRenderer getSequenceRenderer(
           AlignmentViewPanel alignment);
 
-  // jmol/ssm only
-  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
-          String pdbfile)
+  /**
+   * Highlight the specified atom positions in the structure.
+   * 
+   * @param atomIndex
+   * @param pdbResNum
+   * @param chain
+   * @param pdbfile
+   */
+  @Override
+  public void highlightAtoms(List<AtomSpec> atoms)
   {
-    List<ChimeraModel> cms = chimmaps.get(pdbfile);
-    if (cms != null)
+    for (AtomSpec atom : atoms)
     {
-      int mdlNum = cms.get(0).getModelNumber();
-
-      viewerCommandHistory(false);
-      // viewer.stopListening();
-      if (resetLastRes.length() > 0)
+      int pdbResNum = atom.getPdbResNum();
+      String chain = atom.getChain();
+      List<ChimeraModel> cms = chimmaps.get(atom.getPdbId());
+      if (cms != null)
       {
-        eval.setLength(0);
-        eval.append(resetLastRes.toString() + ";");
-      }
+        int mdlNum = cms.get(0).getModelNumber();
+
+        viewerCommandHistory(false);
+        // viewer.stopListening();
+        if (resetLastRes.length() > 0)
+        {
+          eval.setLength(0);
+          eval.append(resetLastRes.toString() + ";");
+        }
 
-      eval.append("display "); // +modelNum
+        eval.append("display "); // +modelNum
 
-      resetLastRes.setLength(0);
-      resetLastRes.append("~display ");
-      {
-        eval.append(" #" + (mdlNum));
-        resetLastRes.append(" #" + (mdlNum));
-      }
-      // complete select string
+        resetLastRes.setLength(0);
+        resetLastRes.append("~display ");
+        {
+          eval.append(" #" + (mdlNum));
+          resetLastRes.append(" #" + (mdlNum));
+        }
+        // complete select string
 
-      eval.append(":" + pdbResNum);
-      resetLastRes.append(":" + pdbResNum);
-      if (!chain.equals(" "))
-      {
-        eval.append("." + chain);
-        resetLastRes.append("." + chain);
+        eval.append(":" + pdbResNum);
+        resetLastRes.append(":" + pdbResNum);
+        if (!chain.equals(" "))
+        {
+          eval.append("." + chain);
+          resetLastRes.append("." + chain);
+        }
+
+        viewer.sendChimeraCommand(eval.toString(), false);
+        viewerCommandHistory(true);
+        // viewer.startListening();
       }
-      
-      viewer.sendChimeraCommand(eval.toString(), false);
-      viewerCommandHistory(true);
-      // viewer.startListening();
     }
   }
 
index 6a32f30..12a94d9 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.ext.varna;
 
 import java.awt.event.*;
+import java.util.List;
 
 import jalview.api.SequenceStructureBinding;
 import jalview.api.StructureSelectionManagerProvider;
@@ -33,4 +34,9 @@ public abstract class JalviewVarnaBinding extends SequenceStructureBindingModel
 
 {
 
+  @Override
+  public void highlightAtoms(List<AtomSpec> atoms)
+  {
+  }
+
 }
index 50043e6..865a9aa 100644 (file)
@@ -1410,10 +1410,10 @@ 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[]
               { command.getDescription() }));
@@ -1424,11 +1424,11 @@ 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[]
               { command.getDescription() }));
@@ -1444,8 +1444,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
@@ -1488,12 +1488,13 @@ 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);
+    // TODO: execute command before adding to redo list / broadcasting?
     command.undoCommand(getViewAlignments());
 
     AlignViewport originalSource = getOriginatingSource(command);
@@ -1526,13 +1527,13 @@ 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);
@@ -1702,11 +1703,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     boolean appendHistoryItem = false;
-    if (viewport.historyList != null && viewport.historyList.size() > 0
-            && viewport.historyList.peek() instanceof SlideSequencesCommand)
+    if (viewport.getHistoryList() != null
+            && viewport.getHistoryList().size() > 0
+            && viewport.getHistoryList().peek() instanceof SlideSequencesCommand)
     {
       appendHistoryItem = ssc
-              .appendSlideCommand((SlideSequencesCommand) viewport.historyList
+              .appendSlideCommand((SlideSequencesCommand) viewport
+                      .getHistoryList()
                       .peek());
     }
 
@@ -2600,6 +2603,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     new Finder();
   }
 
+  /**
+   * Create a new view of the current alignment.
+   */
   @Override
   public void newView_actionPerformed(ActionEvent e)
   {
@@ -2662,8 +2668,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       viewport.viewName = "Original";
     }
 
-    newap.av.historyList = viewport.historyList;
-    newap.av.redoList = viewport.redoList;
+    newap.av.setHistoryList(viewport.getHistoryList());
+    newap.av.setRedoList(viewport.getRedoList());
 
     int index = Desktop.getViewCount(viewport.getSequenceSetId());
     // make sure the new view has a unique name - this is essential for Jalview
@@ -4806,6 +4812,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
+  /**
+   * Construct and display a new frame containing the translation of this
+   * frame's cDNA sequences to their protein (amino acid) equivalents.
+   */
   @Override
   public void showTranslation_actionPerformed(ActionEvent e)
   {
@@ -4855,6 +4865,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       Desktop.addInternalFrame(af, MessageManager.formatMessage(
               "label.translation_of_params", new String[]
               { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT);
+      viewport.getStructureSelectionManager().addCommandListener(viewport);
     }
   }
 
index d24f6c4..ff3e329 100644 (file)
@@ -43,6 +43,7 @@ import jalview.analysis.NJTree;
 import jalview.api.AlignViewportI;
 import jalview.bin.Cache;
 import jalview.commands.CommandI;
+import jalview.commands.EditCommand;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
@@ -51,6 +52,7 @@ 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;
@@ -61,9 +63,11 @@ import java.awt.Color;
 import java.awt.Container;
 import java.awt.Font;
 import java.awt.Rectangle;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.Hashtable;
-import java.util.Stack;
+import java.util.List;
 import java.util.Vector;
 
 /**
@@ -73,7 +77,7 @@ import java.util.Vector;
  * @version $Revision: 1.141 $
  */
 public class AlignViewport extends AlignmentViewport implements
-        SelectionSource, VamsasSource, AlignViewportI
+        SelectionSource, VamsasSource, AlignViewportI, CommandListener
 {
   int startRes;
 
@@ -139,9 +143,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>();
 
   int thresholdTextColour = 0;
 
@@ -1293,4 +1297,112 @@ 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)
+  {
+    /*
+     * Only EditCommand is currently handled by listeners.
+     */
+    if (!(command instanceof EditCommand))
+    {
+      return;
+    }
+    EditCommand edit = (EditCommand) command;
+
+    List<SequenceI> seqs = getAlignment().getSequences();
+    EditCommand mappedCommand = ssm.mapEditCommand(edit, undo, seqs,
+            getGapCharacter());
+    AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
+    mappedCommand.performEdit(0, 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;
+  }
 }
index 2398bda..fc36ebb 100644 (file)
@@ -1464,6 +1464,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
               .getStructureSelectionManager();
       ssm.removeStructureViewerListener(seqPanel, null);
       ssm.removeSelectionListener(seqPanel);
+      ssm.removeEditListener(av);
       av.setAlignment(null);
       av = null;
     }
index a738a2c..b68c3d7 100644 (file)
@@ -875,14 +875,6 @@ public class AppVarnaBinding extends jalview.ext.varna.JalviewVarnaBinding
   }
 
   @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
@@ -890,21 +882,6 @@ public class AppVarnaBinding extends jalview.ext.varna.JalviewVarnaBinding
   }
 
   @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
index cccf34d..8b74620 100644 (file)
@@ -3421,8 +3421,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
       {
index ab0a0b8..25e9d19 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 c10a4a9..2c9b7f6 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 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,6 +35,8 @@ 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.MessageManager;
 
 import java.awt.BorderLayout;
@@ -281,22 +284,33 @@ public class SeqPanel extends JPanel implements MouseListener,
     return features;
   }
 
+  /**
+   * When all of a sequence of edits are complete, put the resulting edit list
+   * on the history stack (undo list), and reset flags for editing in progress.
+   */
   void endEditing()
   {
-    if (editCommand != null && editCommand.getSize() > 0)
+    try
+    {
+      if (editCommand != null && editCommand.getSize() > 0)
+      {
+        ap.alignFrame.addHistoryItem(editCommand);
+        av.firePropertyChange("alignment", null, av.getAlignment()
+                .getSequences());
+      }
+    } finally
     {
-      ap.alignFrame.addHistoryItem(editCommand);
-      av.firePropertyChange("alignment", null, av.getAlignment()
-              .getSequences());
+      /*
+       * Tidy up come what may...
+       */
+      startseq = -1;
+      lastres = -1;
+      editingSeqs = false;
+      groupEditing = false;
+      keyboardNo1 = null;
+      keyboardNo2 = null;
+      editCommand = null;
     }
-
-    startseq = -1;
-    lastres = -1;
-    editingSeqs = false;
-    groupEditing = false;
-    keyboardNo1 = null;
-    keyboardNo2 = null;
-    editCommand = null;
   }
 
   void setCursorRow()
@@ -649,7 +663,6 @@ public class SeqPanel extends JPanel implements MouseListener,
 
   String lastMessage;
 
-  @Override
   public void mouseOverSequence(SequenceI sequence, int index, int pos)
   {
     String tmp = sequence.hashCode() + " " + index + " " + pos;
@@ -676,6 +689,10 @@ public class SeqPanel extends JPanel implements MouseListener,
   }
 
   @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");
@@ -962,7 +979,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
 
-    StringBuffer message = new StringBuffer();
+    StringBuilder message = new StringBuilder(64);
     if (groupEditing)
     {
       message.append("Edit group:");
@@ -1187,8 +1204,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
@@ -1203,8 +1220,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);
         }
 
       }
@@ -1225,8 +1242,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
@@ -1238,7 +1255,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;
@@ -1253,7 +1270,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;
               }
@@ -1262,9 +1279,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);
             }
           }
         }
@@ -1280,8 +1296,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);
           }
         }
       }
@@ -1316,22 +1332,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);
+
+    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)
+  {
 
-    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    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);
   }
 
   /**
index 77a7693..56feef5 100644 (file)
@@ -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);
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 465a672..8a32c5b 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,22 @@ 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
+      {
+        executeJavascriptFunction(_listenerfn, new String[]
+        { "mouseover", "" + atom.getPdbId(), "" + 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 +281,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.
diff --git a/src/jalview/structure/AtomSpec.java b/src/jalview/structure/AtomSpec.java
new file mode 100644 (file)
index 0000000..271c893
--- /dev/null
@@ -0,0 +1,61 @@
+package jalview.structure;
+
+/**
+ * Java bean representing an atom in a PDB (or similar) structure model.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class AtomSpec
+{
+  private String pdbId;
+
+  private String chain;
+
+  private int pdbResNum;
+
+  private int atomIndex;
+
+  /**
+   * Constructor
+   * 
+   * @param id
+   * @param chain
+   * @param resNo
+   * @param atomNo
+   */
+  public AtomSpec(String id, String chain, int resNo, int atomNo)
+  {
+    this.pdbId = id;
+    this.chain = chain;
+    this.pdbResNum = resNo;
+    this.atomIndex = atomNo;
+  }
+
+  public String getPdbId()
+  {
+    return pdbId;
+  }
+
+  public String getChain()
+  {
+    return chain;
+  }
+
+  public int getPdbResNum()
+  {
+    return pdbResNum;
+  }
+
+  public int getAtomIndex()
+  {
+    return atomIndex;
+  }
+
+  @Override
+  public String toString()
+  {
+    return "pdbId: " + pdbId + ", chain: " + chain + ", res: " + pdbResNum
+            + ", atom: " + atomIndex;
+  }
+}
diff --git a/src/jalview/structure/CommandListener.java b/src/jalview/structure/CommandListener.java
new file mode 100644 (file)
index 0000000..66f39fe
--- /dev/null
@@ -0,0 +1,33 @@
+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
+   */
+  public void mirrorCommand(CommandI command, boolean undo,
+          StructureSelectionManager ssm);
+
+  /**
+   * Temporary workaround to make check for source == listener work.
+   * 
+   * @return
+   */
+  public VamsasSource getVamsasSource();
+}
index 83a9fd0..f2a40a7 100644 (file)
  */
 package jalview.structure;
 
-import jalview.datamodel.*;
+import jalview.datamodel.SearchResults;
+
 
 public interface SequenceListener
 {
-  public void mouseOverSequence(SequenceI sequence, int index, int pos);
-
-  public void highlightSequence(jalview.datamodel.SearchResults results);
+  public void highlightSequence(SearchResults results);
 
-  public void updateColours(SequenceI sequence, int index);
+  // Required so we can check for (and skip if) target == source
+  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..3fead00 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.EditCommand.Action;
+import jalview.commands.EditCommand.Edit;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
+import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
 import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 
 import java.io.PrintStream;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Vector;
 
 import MCview.Atom;
@@ -49,6 +59,12 @@ public class StructureSelectionManager
   private boolean processSecondaryStructure = false,
           secStructServices = false, addTempFacAnnot = false;
 
+  List<AlignedCodonFrame> seqmappings = null;
+
+  private int[] seqmappingrefs = null; // refcount for seqmappings elements
+
+  private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
+
   /**
    * @return true if will try to use external services for processing secondary
    *         structure
@@ -597,17 +613,13 @@ public class StructureSelectionManager
                 // construct highlighted sequence list
                 if (seqmappings != null)
                 {
-
-                  Enumeration e = seqmappings.elements();
-                  while (e.hasMoreElements())
-
+                  for (AlignedCodonFrame acf : seqmappings)
                   {
-                    ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
-                            mappings[j].sequence, indexpos, results);
+                    acf.markMappedRegion(mappings[j].sequence, indexpos,
+                            results);
                   }
                 }
               }
-
             }
           }
         }
@@ -626,13 +638,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
@@ -648,87 +658,41 @@ public class StructureSelectionManager
     {
       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)
-              {
-                Enumeration e = seqmappings.elements();
-                while (e.hasMoreElements())
-
-                {
-                  ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
-                          seq, index, results);
-                }
-              }
-              // hasSequenceListeners = results.getSize() > 0;
-              if (handlingVamsasMo)
+              if (results == null)
               {
-                // maybe have to resolve seq to a dataset seqeunce...
-                // add in additional direct sequence and/or dataset sequence
-                // highlighting
-                results.addResult(seq, index, index);
+                results = buildSearchResults(seq, 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 +704,73 @@ public class StructureSelectionManager
   }
 
   /**
+   * Returns a SearchResults object describing the mapped region corresponding
+   * to the specified sequence position.
+   * 
+   * @param seq
+   * @param index
+   * @return
+   */
+  protected SearchResults buildSearchResults(SequenceI seq, int index)
+  {
+    SearchResults results;
+    results = new SearchResults();
+    if (index >= seq.getStart() && index <= seq.getEnd())
+    {
+      if (seqmappings != null)
+      {
+        for (AlignedCodonFrame acf : seqmappings)
+        {
+          acf.markMappedRegion(seq, index, results);
+        }
+      }
+      // hasSequenceListeners = results.getSize() > 0;
+      if (handlingVamsasMo)
+      {
+        // maybe have to resolve seq to a dataset sequence...
+        // add in additional direct sequence and/or dataset sequence
+        // highlighting
+        results.addResult(seq, index, index);
+      }
+    }
+    return results;
+  }
+
+  /**
+   * 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;
+    if (mappings != null)
+    {
+      List<AtomSpec> atoms = new ArrayList<AtomSpec>();
+      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)
+          {
+            atoms.add(new AtomSpec(mappings[j].pdbfile,
+                    mappings[j].pdbchain, mappings[j].getPDBResNum(index),
+                    atomNo));
+          }
+        }
+      }
+      sl.highlightAtoms(atoms);
+    }
+  }
+
+  /**
    * true if a mouse over event from an external (ie Vamsas) source is being
    * handled
    */
@@ -859,8 +890,6 @@ public class StructureSelectionManager
     return sb.toString();
   }
 
-  private int[] seqmappingrefs = null; // refcount for seqmappings elements
-
   private synchronized void modifySeqMappingList(boolean add,
           AlignedCodonFrame[] codonFrames)
   {
@@ -870,7 +899,7 @@ public class StructureSelectionManager
     }
     if (seqmappings == null)
     {
-      seqmappings = new Vector();
+      seqmappings = new ArrayList<AlignedCodonFrame>();
     }
     if (codonFrames != null && codonFrames.length > 0)
     {
@@ -904,7 +933,7 @@ public class StructureSelectionManager
         {
           if (add)
           {
-            seqmappings.addElement(codonFrames[cf]);
+            seqmappings.add(codonFrames[cf]);
 
             int[] nsr = new int[(seqmappingrefs == null) ? 1
                     : seqmappingrefs.length + 1];
@@ -1055,4 +1084,176 @@ 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)
+    {
+      if (listener.getVamsasSource() != source)
+      {
+        listener.mirrorCommand(command, undo, this);
+      }
+    }
+  }
+
+  /**
+   * Returns a new EditCommand representing the given command as mapped to the
+   * given sequences. If there is no mapping, returns an empty EditCommand.
+   * 
+   * @param command
+   * @param undo
+   * @param targetSeqs
+   * @param gapChar
+   * @return
+   */
+  public EditCommand mapEditCommand(EditCommand command, boolean undo,
+          final List<SequenceI> targetSeqs, char gapChar)
+  {
+    /*
+     * 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 : targetSeqs)
+    {
+      SequenceI ds = seq.getDatasetSequence();
+      if (ds != null)
+      {
+        final Sequence 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();
+      Action action = edit.getAction();
+
+      /*
+       * Invert sense of action if an Undo.
+       */
+      if (undo)
+      {
+        action = action == Action.INSERT_GAP ? Action.DELETE_GAP
+                : (action == Action.DELETE_GAP ? Action.INSERT_GAP : action);
+      }
+      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);
+
+        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 = new EditCommand().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)));
+        }
+      }
+    }
+    return result;
+  }
 }
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 8931b23..259a5d8 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!! */
+  private static final int EIGHTY_FIVE = 85;
+
+  private static final int TO_UPPER_CASE = 'a' - 'A';
+
   public static final String GapChars = " .-";
 
   /**
@@ -69,12 +69,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 +225,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;
   }
 
+  /**
+   * 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;
     }
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();
+  }
+
+}
diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java
new file mode 100644 (file)
index 0000000..c1e050f
--- /dev/null
@@ -0,0 +1,82 @@
+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;
+  }
+}
index fc821b9..84671ae 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,374 @@ 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 testUnwindCommand_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 testUnwindCommand_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 testUnwindCommand_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 method that 'restores' edit commands to hold the sequence as it was
+   * before the edit was applied.
+   */
+  @Test
+  public void testUnwindCommand_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 testUnwindCommand_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 testUnwindCommand_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());
   }
 }
index 3f91710..2a23104 100644 (file)
@@ -120,4 +120,86 @@ 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()); // ??
+  }
+
+  @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
+  }
 }
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/StringUtilsTest.java b/test/jalview/util/StringUtilsTest.java
new file mode 100644 (file)
index 0000000..eba2da4
--- /dev/null
@@ -0,0 +1,56 @@
+package jalview.util;
+
+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)));
+  }
+}