bugfix todo for the 'edit sequence' command
[jalview.git] / src / jalview / commands / EditCommand.java
index 45b19af..e859e3e 100644 (file)
@@ -44,6 +44,7 @@ public class EditCommand
   public static final int DELETE_GAP = 1;
   public static final int CUT = 2;
   public static final int PASTE = 3;
+  public static final int REPLACE = 4;
 
   Edit[] edits;
 
@@ -72,7 +73,25 @@ public class EditCommand
           new Edit(command, seqs, position, number, al)};
     }
 
-    performEdit(0);
+    performEdit(0, null);
+  }
+
+  public EditCommand(String description,
+                     int command,
+                     String replace,
+                     SequenceI[] seqs,
+                     int position,
+                     int number,
+                     AlignmentI al)
+  {
+    this.description = description;
+    if (command == REPLACE)
+    {
+      edits = new Edit[]
+          { new Edit(command, seqs, position, number, al, replace)};
+    }
+
+    performEdit(0, null);
   }
 
   final public String getDescription()
@@ -90,12 +109,42 @@ public class EditCommand
     return edits[0].al;
   }
 
+  /**
+   * append a new editCommand
+   * Note. this shouldn't be called if the edit is an operation affects more alignment objects than the one referenced
+   * in al (for example, cut or pasting whole sequences). Use the form with an additional AlignmentI[] views parameter.
+   * @param command
+   * @param seqs
+   * @param position
+   * @param number
+   * @param al
+   * @param performEdit
+   */
+  final public void appendEdit(int command,
+          SequenceI[] seqs,
+          int position,
+          int number,
+          AlignmentI al,
+          boolean performEdit)
+  {
+    appendEdit(command, seqs, position, number, al, performEdit, null);
+  }
+  /**
+   * append a new edit command with a set of alignment views that may be operated on
+   * @param command
+   * @param seqs
+   * @param position
+   * @param number
+   * @param al
+   * @param performEdit
+   * @param views
+   */
   final public void appendEdit(int command,
                                SequenceI[] seqs,
                                int position,
                                int number,
                                AlignmentI al,
-                               boolean performEdit)
+                               boolean performEdit, AlignmentI[] views)
   {
     Edit edit = new Edit(command, seqs, position, number, al.getGapCharacter());
     if (al.getHeight() == seqs.length)
@@ -120,65 +169,70 @@ public class EditCommand
 
     if (performEdit)
     {
-      performEdit(edits.length - 1);
+      performEdit(edits.length - 1, views);
     }
   }
 
-  final void performEdit(int commandIndex)
+  final void performEdit(int commandIndex, AlignmentI[] views)
   {
     int eSize = edits.length;
     for (int e = commandIndex; e < eSize; e++)
     {
-      if (edits[e].command == INSERT_GAP)
+      switch(edits[e].command)
       {
+        case INSERT_GAP:
         insertGap(edits[e]);
-      }
-      else if (edits[e].command == DELETE_GAP)
-      {
+          break;
+        case DELETE_GAP:
         deleteGap(edits[e]);
-      }
-      else if (edits[e].command == CUT)
-      {
-        cut(edits[e]);
-      }
-      else if (edits[e].command == PASTE)
-      {
-        paste(edits[e]);
+          break;
+        case CUT:
+        cut(edits[e], views);
+          break;
+        case PASTE:
+        paste(edits[e], views);
+          break;
+        case REPLACE:
+          replace(edits[e]);
+          break;
       }
     }
   }
 
-  final public void doCommand()
+  final public void doCommand(AlignmentI[] views)
   {
-    performEdit(0);
+    performEdit(0,views);
   }
 
-  final public void undoCommand()
+  final public void undoCommand(AlignmentI[] views)
   {
     int e = 0, eSize = edits.length;
     for (e = eSize - 1; e > -1; e--)
     {
-      if (edits[e].command == INSERT_GAP)
+      switch (edits[e].command)
       {
+        case INSERT_GAP:
         deleteGap(edits[e]);
-      }
-      else if (edits[e].command == DELETE_GAP)
-      {
+          break;
+        case DELETE_GAP:
         insertGap(edits[e]);
-      }
-      else if (edits[e].command == CUT)
-      {
-        paste(edits[e]);
-      }
-      else if (edits[e].command == PASTE)
-      {
-        cut(edits[e]);
+          break;
+        case CUT:
+        paste(edits[e], views);
+          break;
+        case PASTE:
+        cut(edits[e], views);
+          break;
+        case REPLACE:
+          replace(edits[e]);
+          break;
       }
     }
   }
 
   final void insertGap(Edit command)
   {
+
     for (int s = 0; s < command.seqs.length; s++)
     {
       command.seqs[s].insertCharAt(command.position,
@@ -186,7 +240,7 @@ public class EditCommand
                                    command.gapChar);
     }
 
-    adjustAnnotations(command, true, false);
+    adjustAnnotations(command, true, false, null);
   }
 
   final void deleteGap(Edit command)
@@ -197,10 +251,10 @@ public class EditCommand
                                   command.position + command.number);
     }
 
-    adjustAnnotations(command, false, false);
+    adjustAnnotations(command, false, false, null);
   }
 
-  void cut(Edit command)
+  void cut(Edit command, AlignmentI[] views)
   {
     boolean seqDeleted=false;
     command.string = new char[command.seqs.length][];
@@ -211,27 +265,36 @@ public class EditCommand
       {
         command.string[i] = command.seqs[i].getSequence(command.position,
             command.position + command.number);
-
-        if (command.seqs[i].getDatasetSequence() != null
-            || command.seqs[i].getSequenceFeatures() != null)
+        SequenceI oldds = command.seqs[i].getDatasetSequence();
+        if (command.oldds!=null && command.oldds[i]!=null)
+        {
+          // we are redoing an undone cut.
+          command.seqs[i].setDatasetSequence(null);
+        }
+        command.seqs[i].deleteChars(command.position,
+                command.position + command.number);
+        if (command.oldds!=null && command.oldds[i]!=null)
         {
-          for (int s = command.position; s < command.position + command.number;
-               s++)
+          // oldds entry contains the cut dataset sequence.
+          command.seqs[i].setDatasetSequence(command.oldds[i]);
+          command.oldds[i] = oldds;
+        } else {
+          // modify the oldds if necessary
+          if (oldds!=command.seqs[i].getDatasetSequence()
+            || command.seqs[i].getSequenceFeatures() != null)
           {
-            if (jalview.schemes.ResidueProperties
-                .aaIndex[command.seqs[i].getCharAt(s)] != 23)
+            if (command.oldds==null)
             {
-              adjustFeatures(command, i,
-                             command.seqs[i].findPosition(command.position),
-                             command.seqs[i].findPosition(command.position +
-                  command.number),
-                             false);
-              break;
+              command.oldds = new SequenceI[command.seqs.length];
             }
+            command.oldds[i] = oldds;
+            adjustFeatures(command, i,
+                    command.seqs[i].findPosition(command.position),
+                    command.seqs[i].findPosition(command.position +
+                            command.number),
+                            false);
           }
         }
-        command.seqs[i].deleteChars(command.position,
-                                    command.position + command.number);
       }
 
       if (command.seqs[i].getLength() < 1)
@@ -241,19 +304,22 @@ public class EditCommand
       }
     }
 
-    adjustAnnotations(command, false, seqDeleted);
+    adjustAnnotations(command, false, seqDeleted, views);
   }
 
-  void paste(Edit command)
+  void paste(Edit command, AlignmentI[] views)
   {
     StringBuffer tmp;
     boolean newDSNeeded;
+    boolean newDSWasNeeded;
+    int newstart,newend;
     boolean seqWasDeleted=false;
     int start = 0, end = 0;
 
     for (int i = 0; i < command.seqs.length; i++)
     {
       newDSNeeded = false;
+      newDSWasNeeded = command.oldds!=null && command.oldds[i]!=null;
       if (command.seqs[i].getLength() < 1)
       {
         // ie this sequence was deleted, we need to
@@ -269,9 +335,13 @@ public class EditCommand
         }
         seqWasDeleted=true;
       }
+      newstart = command.seqs[i].getStart();
+      newend = command.seqs[i].getEnd();
+
       tmp = new StringBuffer();
       tmp.append(command.seqs[i].getSequence());
-
+      //Undo of a delete does not replace original dataset sequence on to alignment sequence.
+      
       if (command.string != null && command.string[i] != null)
       {
         if (command.position >= tmp.length())
@@ -286,50 +356,109 @@ public class EditCommand
           }
         }
         tmp.insert(command.position, command.string[i]);
-
         for (int s = 0; s < command.string[i].length; s++)
         {
           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] !=
               23)
           {
-            newDSNeeded = true;
-            start = command.seqs[i].findPosition(command.position);
-            end = command.seqs[i].findPosition(command.position +
+            if (!newDSNeeded)
+            {
+              newDSNeeded = true;
+              start = command.seqs[i].findPosition(command.position);
+              end = command.seqs[i].findPosition(command.position +
                                                command.number);
-            break;
+            }
+            if (command.seqs[i].getStart()==start)
+              newstart--;
+            else
+              newend++;
           }
         }
         command.string[i] = null;
       }
 
       command.seqs[i].setSequence(tmp.toString());
-
+      command.seqs[i].setStart(newstart);
+      command.seqs[i].setEnd(newend);
       if (newDSNeeded)
       {
         if (command.seqs[i].getDatasetSequence() != null)
-        { // use new ds mechanism here
-          Sequence ds = new Sequence(command.seqs[i].getName(),
+        { 
+          SequenceI ds;
+          if (newDSWasNeeded)
+          {
+            ds = command.oldds[i];
+          } else {
+            // make a new DS sequence
+            // use new ds mechanism here
+            ds= new Sequence(command.seqs[i].getName(),
                                      jalview.analysis.AlignSeq.extractGaps(
                                          jalview.util.Comparison.GapChars,
                                          command.seqs[i].getSequenceAsString()
                                      ),
                                      command.seqs[i].getStart(),
                                      command.seqs[i].getEnd());
-          ds.setDescription(command.seqs[i].getDescription());
+            ds.setDescription(command.seqs[i].getDescription());
+          }
+          if (command.oldds==null)
+          {
+            command.oldds = new SequenceI[command.seqs.length];
+          }
+          command.oldds[i]=command.seqs[i].getDatasetSequence();
           command.seqs[i].setDatasetSequence(ds);
         }
-
         adjustFeatures(command, i, start, end, true);
       }
     }
-    adjustAnnotations(command, true, seqWasDeleted);
+    adjustAnnotations(command, true, seqWasDeleted, views);
 
     command.string = null;
   }
 
-  final void adjustAnnotations(Edit command, boolean insert, boolean modifyVisibility)
+  void replace(Edit command)
   {
+    StringBuffer tmp;
+    String oldstring;
+    int start = command.position;
+    int end = command.number;
+    // TODO TUTORIAL - Fix for replacement with different length of sequence (or whole sequence)
+    // TODO Jalview 2.4 bugfix change to an aggregate command - original sequence string is cut, new string is pasted in.
+    command.number = start + command.string[0].length;
+    for (int i = 0; i < command.seqs.length; i++)
+    {
+      /**  cut
+       *     addHistoryItem(new EditCommand("Cut Sequences",
+                                      EditCommand.CUT,
+                                      cut,
+                                      sg.getStartRes(),
+                                      sg.getEndRes()-sg.getStartRes()+1,
+                                      viewport.alignment));
+
+       */
+      /** then
+       *        addHistoryItem(new EditCommand(
+               "Add sequences",
+               EditCommand.PASTE,
+               sequences,
+               0,
+               alignment.getWidth(),
+               alignment)
+              );
+
+       */
+      oldstring = command.seqs[i].getSequenceAsString();
+      tmp = new StringBuffer(oldstring.substring(0, start));
+      tmp.append(command.string[i]);
+      tmp.append(oldstring.substring(end));
+      command.seqs[i].setSequence(tmp.toString());
+      command.string[i] = oldstring.substring(start, end).toCharArray();
+      tmp = null;
+      oldstring = null;
+    }
+  }
 
+  final void adjustAnnotations(Edit command, boolean insert, boolean modifyVisibility, AlignmentI[] views)
+  {
     AlignmentAnnotation[] annotations = null;
 
     if (modifyVisibility && !insert)
@@ -354,12 +483,55 @@ public class EditCommand
             // remove rows
             tmp = command.seqs[s].getAnnotation();
             if (tmp!=null) {
-              command.deletedAnnotationRows.put(command.seqs[s], tmp);
+              int alen=tmp.length;
               for (int aa =0; aa<tmp.length; aa++)
               {
-                command.al.deleteAnnotation(tmp[aa]);
+                if (!command.al.deleteAnnotation(tmp[aa]))
+                {
+                  // strip out annotation not in the current al (will be put back on insert in all views)
+                  tmp[aa] = null;
+                  alen--;
+                }
               }
               command.seqs[s].setAlignmentAnnotation(null);
+              if (alen!=tmp.length)
+              {
+                // save the non-null annotation references only
+                AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
+                for (int aa=0,aapos=0;aa<tmp.length;aa++)
+                {
+                  if (tmp[aa]!=null)
+                  {
+                    saved[aapos++] = tmp[aa];
+                    tmp[aa] = null;
+                  }
+                }
+                tmp = saved;
+                command.deletedAnnotationRows.put(command.seqs[s], saved);
+                // and then remove any annotation in the other views
+                for (int alview=0; views!=null && alview<views.length; alview++)
+                {
+                  if (views[alview]!=command.al)
+                  {
+                    AlignmentAnnotation[] toremove = views[alview].getAlignmentAnnotation();
+                    if (toremove==null || toremove.length==0)
+                    {
+                      continue;
+                    }
+                    // remove any alignment annotation on this sequence that's on that alignment view.
+                    for (int aa = 0; aa<toremove.length; aa++)
+                    {
+                      if (toremove[aa].sequenceRef==command.seqs[s])
+                      {
+                        views[alview].deleteAnnotation(toremove[aa]);
+                      }
+                    }
+                  }
+                }
+              } else {
+                // save all the annotation
+                command.deletedAnnotationRows.put(command.seqs[s], tmp);
+              }
             }
           } else {
             // recover rows
@@ -370,12 +542,30 @@ public class EditCommand
               if (revealed!=null) {
                 for (int aa =0; aa<revealed.length; aa++)
                 {
+                  // iterate through al adding original annotation
                   command.al.addAnnotation(revealed[aa]);
                 }
                 for (int aa =0; aa<revealed.length; aa++)
                 {
                   command.al.setAnnotationIndex(revealed[aa], aa);
                 }
+                // and then duplicate added annotation on every other alignment view
+                for (int vnum=0; views!=null && vnum<views.length; vnum++)
+                {
+                  if (views[vnum]!=command.al)
+                  {
+                    int avwidth = views[vnum].getWidth()+1;
+                    // duplicate in this view
+                    for (int a=0; a<revealed.length; a++)
+                    {
+                      AlignmentAnnotation newann = new AlignmentAnnotation(revealed[a]);
+                      command.seqs[s].addAlignmentAnnotation(newann);
+                      newann.padAnnotation(avwidth);
+                      views[vnum].addAnnotation(newann);
+                      views[vnum].setAnnotationIndex(newann, a);
+                    }
+                  }
+                }
               }
             }
           }
@@ -422,7 +612,7 @@ public class EditCommand
     Annotation[] temp;
     for (int a = 0; a < annotations.length; a++)
     {
-      if (annotations[a].autoCalculated)
+      if (annotations[a].autoCalculated || annotations[a].annotations == null)
       {
         continue;
       }
@@ -437,7 +627,7 @@ public class EditCommand
           for (int aa = 0; aa < temp.length; aa++)
           {
             temp[aa] = new Annotation(
-                command.al.getGapCharacter()+"",
+                command.gapChar+"",
                 null, ' ', 0);
           }
       }
@@ -445,13 +635,13 @@ public class EditCommand
       {
         if (command.position < aSize)
         {
-          if (command.position + command.number > aSize)
+          if (command.position + command.number >= aSize)
           {
             tSize = aSize;
           }
           else
           {
-            tSize = aSize - command.number + command.position;
+            tSize = aSize - command.number;
           }
         }
         else
@@ -464,9 +654,9 @@ public class EditCommand
           tSize = aSize;
         }
         temp = new Annotation[tSize];
-
       }
 
+
       if (insert)
       {
         if (command.position < annotations[a].annotations.length)
@@ -529,7 +719,7 @@ public class EditCommand
                            0, temp, 0, copylen); //command.position);
 
           Annotation[] deleted = new Annotation[command.number];
-          if (copylen>command.position) {
+          if (copylen>=command.position) {
             copylen = Math.min(command.number, annotations[a].annotations.length-command.position);
             if (copylen>0)
             {
@@ -658,6 +848,7 @@ public class EditCommand
 
   class Edit
   {
+    public SequenceI[] oldds;
     boolean fullAlignmentHeight = false;
     Hashtable deletedAnnotationRows;
     Hashtable deletedAnnotations;
@@ -704,6 +895,27 @@ public class EditCommand
 
       fullAlignmentHeight = (al.getHeight() == seqs.length);
     }
-  }
 
+    Edit(int command,
+         SequenceI[] seqs,
+         int position,
+         int number,
+         AlignmentI al,
+         String replace)
+    {
+      this.command = command;
+      this.seqs = seqs;
+      this.position = position;
+      this.number = number;
+      this.al = al;
+      this.gapChar = al.getGapCharacter();
+      string = new char[seqs.length][];
+      for (int i = 0; i < seqs.length; i++)
+      {
+        string[i] = replace.toCharArray();
+      }
+
+      fullAlignmentHeight = (al.getHeight() == seqs.length);
+    }
+  }
 }