JAL-353 updated patch for undo successive remove redundant sequences
[jalview.git] / src / jalview / commands / EditCommand.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.commands;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.Annotation;
26 import jalview.datamodel.Sequence;
27 import jalview.datamodel.SequenceFeature;
28 import jalview.datamodel.SequenceI;
29
30 import java.util.Hashtable;
31 import java.util.List;
32
33 /**
34  * 
35  * <p>
36  * Title: EditCommmand
37  * </p>
38  * 
39  * <p>
40  * Description: Essential information for performing undo and redo for cut/paste
41  * insert/delete gap which can be stored in the HistoryList
42  * </p>
43  * 
44  * <p>
45  * Copyright: Copyright (c) 2006
46  * </p>
47  * 
48  * <p>
49  * Company: Dundee University
50  * </p>
51  * 
52  * @author not attributable
53  * @version 1.0
54  */
55 public class EditCommand implements CommandI
56 {
57   public static final int INSERT_GAP = 0;
58
59   public static final int DELETE_GAP = 1;
60
61   public static final int CUT = 2;
62
63   public static final int PASTE = 3;
64
65   public static final int REPLACE = 4;
66
67   public static final int INSERT_NUC = 5;
68
69   Edit[] edits;
70
71   String description;
72
73   public EditCommand()
74   {
75   }
76
77   public EditCommand(String description)
78   {
79     this.description = description;
80   }
81
82   public EditCommand(String description, int command, SequenceI[] seqs,
83           int position, int number, AlignmentI al)
84   {
85     this.description = description;
86     if (command == CUT || command == PASTE)
87     {
88       edits = new Edit[]
89       { new Edit(command, seqs, position, number, al) };
90     }
91
92     performEdit(0, null);
93   }
94
95   public EditCommand(String description, int command, String replace,
96           SequenceI[] seqs, int position, int number, AlignmentI al)
97   {
98     this.description = description;
99     if (command == REPLACE)
100     {
101       edits = new Edit[]
102       { new Edit(command, seqs, position, number, al, replace) };
103     }
104
105     performEdit(0, null);
106   }
107
108   @Override
109   final public String getDescription()
110   {
111     return description;
112   }
113
114   @Override
115   public int getSize()
116   {
117     return edits == null ? 0 : edits.length;
118   }
119
120   final public AlignmentI getAlignment()
121   {
122     return edits[0].al;
123   }
124
125   /**
126    * append a new editCommand Note. this shouldn't be called if the edit is an
127    * operation affects more alignment objects than the one referenced in al (for
128    * example, cut or pasting whole sequences). Use the form with an additional
129    * AlignmentI[] views parameter.
130    * 
131    * @param command
132    * @param seqs
133    * @param position
134    * @param number
135    * @param al
136    * @param performEdit
137    */
138   final public void appendEdit(int command, SequenceI[] seqs, int position,
139           int number, AlignmentI al, boolean performEdit)
140   {
141     appendEdit(command, seqs, position, number, al, performEdit, null);
142   }
143
144   /**
145    * append a new edit command with a set of alignment views that may be
146    * operated on
147    * 
148    * @param command
149    * @param seqs
150    * @param position
151    * @param number
152    * @param al
153    * @param performEdit
154    * @param views
155    */
156   final public void appendEdit(int command, SequenceI[] seqs, int position,
157           int number, AlignmentI al, boolean performEdit, AlignmentI[] views)
158   {
159     Edit edit = new Edit(command, seqs, position, number,
160             al.getGapCharacter());
161     if (al.getHeight() == seqs.length)
162     {
163       edit.al = al;
164       edit.fullAlignmentHeight = true;
165     }
166
167     if (edits != null)
168     {
169       Edit[] temp = new Edit[edits.length + 1];
170       System.arraycopy(edits, 0, temp, 0, edits.length);
171       edits = temp;
172       edits[edits.length - 1] = edit;
173     }
174     else
175     {
176       edits = new Edit[]
177       { edit };
178     }
179
180     if (performEdit)
181     {
182       performEdit(edits.length - 1, views);
183     }
184   }
185
186   final void performEdit(int commandIndex, AlignmentI[] views)
187   {
188     int eSize = edits.length;
189     for (int e = commandIndex; e < eSize; e++)
190     {
191       switch (edits[e].command)
192       {
193       case INSERT_GAP:
194         insertGap(edits[e]);
195         break;
196       case DELETE_GAP:
197         deleteGap(edits[e]);
198         break;
199       case CUT:
200         cut(edits[e], views);
201         break;
202       case PASTE:
203         paste(edits[e], views);
204         break;
205       case REPLACE:
206         replace(edits[e]);
207         break;
208       // TODO:add deleteNuc for UNDO
209       // case INSERT_NUC:
210       // insertNuc(edits[e]);
211       // break;
212       }
213     }
214   }
215
216   @Override
217   final public void doCommand(AlignmentI[] views)
218   {
219     performEdit(0, views);
220   }
221
222   @Override
223   final public void undoCommand(AlignmentI[] views)
224   { 
225     for(Edit e : edits){
226         switch (e.command)
227         {
228         case INSERT_GAP:
229           deleteGap(e);
230           break;
231         case DELETE_GAP:
232           insertGap(e);
233           break;
234         case CUT:
235           paste(e, views);
236           break;
237         case PASTE:
238           cut(e, views);
239           break;
240         case REPLACE:
241           replace(e);
242           break;
243         }
244     }
245   }
246
247   final void insertGap(Edit command)
248   {
249
250     for (int s = 0; s < command.seqs.length; s++)
251     {
252       command.seqs[s].insertCharAt(command.position, command.number,
253               command.gapChar);
254       // System.out.println("pos: "+command.position+" number: "+command.number);
255     }
256
257     adjustAnnotations(command, true, false, null);
258   }
259
260   //
261   // final void insertNuc(Edit command)
262   // {
263   //
264   // for (int s = 0; s < command.seqs.length; s++)
265   // {
266   // System.out.println("pos: "+command.position+" number: "+command.number);
267   // command.seqs[s].insertCharAt(command.position, command.number,'A');
268   // }
269   //
270   // adjustAnnotations(command, true, false, null);
271   // }
272
273   final void deleteGap(Edit command)
274   {
275     for (int s = 0; s < command.seqs.length; s++)
276     {
277       command.seqs[s].deleteChars(command.position, command.position
278               + command.number);
279     }
280
281     adjustAnnotations(command, false, false, null);
282   }
283
284   void cut(Edit command, AlignmentI[] views)
285   {
286     boolean seqDeleted = false;
287     command.string = new char[command.seqs.length][];
288
289     for (int i = 0; i < command.seqs.length; i++)
290     {
291       if (command.seqs[i].getLength() > command.position)
292       {
293         command.string[i] = command.seqs[i].getSequence(command.position,
294                 command.position + command.number);
295         SequenceI oldds = command.seqs[i].getDatasetSequence();
296         if (command.oldds != null && command.oldds[i] != null)
297         {
298           // we are redoing an undone cut.
299           command.seqs[i].setDatasetSequence(null);
300         }
301         command.seqs[i].deleteChars(command.position, command.position
302                 + command.number);
303         if (command.oldds != null && command.oldds[i] != null)
304         {
305           // oldds entry contains the cut dataset sequence.
306           command.seqs[i].setDatasetSequence(command.oldds[i]);
307           command.oldds[i] = oldds;
308         }
309         else
310         {
311           // modify the oldds if necessary
312           if (oldds != command.seqs[i].getDatasetSequence()
313                   || command.seqs[i].getSequenceFeatures() != null)
314           {
315             if (command.oldds == null)
316             {
317               command.oldds = new SequenceI[command.seqs.length];
318             }
319             command.oldds[i] = oldds;
320             adjustFeatures(
321                     command,
322                     i,
323                     command.seqs[i].findPosition(command.position),
324                     command.seqs[i].findPosition(command.position
325                             + command.number), false);
326           }
327         }
328       }
329
330       if (command.seqs[i].getLength() < 1)
331       {
332         command.al.deleteSequence(command.seqs[i]);
333         seqDeleted = true;
334       }
335     }
336
337     adjustAnnotations(command, false, seqDeleted, views);
338   }
339
340   void paste(Edit command, AlignmentI[] views)
341   {
342     StringBuffer tmp;
343     boolean newDSNeeded;
344     boolean newDSWasNeeded;
345     int newstart, newend;
346     boolean seqWasDeleted = false;
347     int start = 0, end = 0;
348
349     for (int i = 0; i < command.seqs.length; i++)
350     {
351       newDSNeeded = false;
352       newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
353       if (command.seqs[i].getLength() < 1)
354       {
355         // ie this sequence was deleted, we need to
356         // read it to the alignment
357         if (command.alIndex[i] < command.al.getHeight())
358         {
359           List<SequenceI> sequences;
360           synchronized (sequences = command.al.getSequences())
361           {
362             // int index = command.al.findIndex(command.seqs[i]);
363             // sequences.add(index, command.seqs[i]);
364             sequences.add(command.alIndex[i] < 0 ? 0 : command.alIndex[i], command.seqs[i]);
365           }
366         }
367         else
368         {
369           command.al.addSequence(command.seqs[i]);
370         }
371         seqWasDeleted = true;
372       }
373       newstart = command.seqs[i].getStart();
374       newend = command.seqs[i].getEnd();
375
376       tmp = new StringBuffer();
377       tmp.append(command.seqs[i].getSequence());
378       // Undo of a delete does not replace original dataset sequence on to
379       // alignment sequence.
380
381       if (command.string != null && command.string[i] != null)
382       {
383         if (command.position >= tmp.length())
384         {
385           // This occurs if padding is on, and residues
386           // are removed from end of alignment
387           int length = command.position - tmp.length();
388           while (length > 0)
389           {
390             tmp.append(command.gapChar);
391             length--;
392           }
393         }
394         tmp.insert(command.position, command.string[i]);
395         for (int s = 0; s < command.string[i].length; s++)
396         {
397           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] != 23)
398           {
399             if (!newDSNeeded)
400             {
401               newDSNeeded = true;
402               start = command.seqs[i].findPosition(command.position);
403               end = command.seqs[i].findPosition(command.position
404                       + command.number);
405             }
406             if (command.seqs[i].getStart() == start)
407             {
408               newstart--;
409             }
410             else
411             {
412               newend++;
413             }
414           }
415         }
416         command.string[i] = null;
417       }
418
419       command.seqs[i].setSequence(tmp.toString());
420       command.seqs[i].setStart(newstart);
421       command.seqs[i].setEnd(newend);
422       if (newDSNeeded)
423       {
424         if (command.seqs[i].getDatasetSequence() != null)
425         {
426           SequenceI ds;
427           if (newDSWasNeeded)
428           {
429             ds = command.oldds[i];
430           }
431           else
432           {
433             // make a new DS sequence
434             // use new ds mechanism here
435             ds = new Sequence(command.seqs[i].getName(),
436                     jalview.analysis.AlignSeq.extractGaps(
437                             jalview.util.Comparison.GapChars,
438                             command.seqs[i].getSequenceAsString()),
439                     command.seqs[i].getStart(), command.seqs[i].getEnd());
440             ds.setDescription(command.seqs[i].getDescription());
441           }
442           if (command.oldds == null)
443           {
444             command.oldds = new SequenceI[command.seqs.length];
445           }
446           command.oldds[i] = command.seqs[i].getDatasetSequence();
447           command.seqs[i].setDatasetSequence(ds);
448         }
449         adjustFeatures(command, i, start, end, true);
450       }
451     }
452     adjustAnnotations(command, true, seqWasDeleted, views);
453
454     command.string = null;
455   }
456
457   void replace(Edit command)
458   {
459     StringBuffer tmp;
460     String oldstring;
461     int start = command.position;
462     int end = command.number;
463     // TODO TUTORIAL - Fix for replacement with different length of sequence (or
464     // whole sequence)
465     // TODO Jalview 2.4 bugfix change to an aggregate command - original
466     // sequence string is cut, new string is pasted in.
467     command.number = start + command.string[0].length;
468     for (int i = 0; i < command.seqs.length; i++)
469     {
470       boolean newDSWasNeeded = command.oldds != null
471               && command.oldds[i] != null;
472
473       /**
474        * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
475        * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
476        * viewport.alignment));
477        * 
478        */
479       /**
480        * then addHistoryItem(new EditCommand( "Add sequences",
481        * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
482        * 
483        */
484       oldstring = command.seqs[i].getSequenceAsString();
485       tmp = new StringBuffer(oldstring.substring(0, start));
486       tmp.append(command.string[i]);
487       String nogaprep = jalview.analysis.AlignSeq.extractGaps(
488               jalview.util.Comparison.GapChars, new String(
489                       command.string[i]));
490       int ipos = command.seqs[i].findPosition(start)
491               - command.seqs[i].getStart();
492       tmp.append(oldstring.substring(end));
493       command.seqs[i].setSequence(tmp.toString());
494       command.string[i] = oldstring.substring(start, end).toCharArray();
495       String nogapold = jalview.analysis.AlignSeq.extractGaps(
496               jalview.util.Comparison.GapChars, new String(
497                       command.string[i]));
498       if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
499       {
500         if (newDSWasNeeded)
501         {
502           SequenceI oldds = command.seqs[i].getDatasetSequence();
503           command.seqs[i].setDatasetSequence(command.oldds[i]);
504           command.oldds[i] = oldds;
505         }
506         else
507         {
508           if (command.oldds == null)
509           {
510             command.oldds = new SequenceI[command.seqs.length];
511           }
512           command.oldds[i] = command.seqs[i].getDatasetSequence();
513           SequenceI newds = new Sequence(
514                   command.seqs[i].getDatasetSequence());
515           String fullseq, osp = newds.getSequenceAsString();
516           fullseq = osp.substring(0, ipos) + nogaprep
517                   + osp.substring(ipos + nogaprep.length());
518           newds.setSequence(fullseq.toUpperCase());
519           // TODO: JAL-1131 ensure newly created dataset sequence is added to
520           // the set of
521           // dataset sequences associated with the alignment.
522           // TODO: JAL-1131 fix up any annotation associated with new dataset
523           // sequence to ensure that original sequence/annotation relationships
524           // are preserved.
525           command.seqs[i].setDatasetSequence(newds);
526
527         }
528       }
529       tmp = null;
530       oldstring = null;
531     }
532   }
533
534   final void adjustAnnotations(Edit command, boolean insert,
535           boolean modifyVisibility, AlignmentI[] views)
536   {
537     AlignmentAnnotation[] annotations = null;
538
539     if (modifyVisibility && !insert)
540     {
541       // only occurs if a sequence was added or deleted.
542       command.deletedAnnotationRows = new Hashtable();
543     }
544     if (command.fullAlignmentHeight)
545     {
546       annotations = command.al.getAlignmentAnnotation();
547     }
548     else
549     {
550       int aSize = 0;
551       AlignmentAnnotation[] tmp;
552       for (int s = 0; s < command.seqs.length; s++)
553       {
554         if (modifyVisibility)
555         {
556           // Rows are only removed or added to sequence object.
557           if (!insert)
558           {
559             // remove rows
560             tmp = command.seqs[s].getAnnotation();
561             if (tmp != null)
562             {
563               int alen = tmp.length;
564               for (int aa = 0; aa < tmp.length; aa++)
565               {
566                 if (!command.al.deleteAnnotation(tmp[aa]))
567                 {
568                   // strip out annotation not in the current al (will be put
569                   // back on insert in all views)
570                   tmp[aa] = null;
571                   alen--;
572                 }
573               }
574               command.seqs[s].setAlignmentAnnotation(null);
575               if (alen != tmp.length)
576               {
577                 // save the non-null annotation references only
578                 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
579                 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
580                 {
581                   if (tmp[aa] != null)
582                   {
583                     saved[aapos++] = tmp[aa];
584                     tmp[aa] = null;
585                   }
586                 }
587                 tmp = saved;
588                 command.deletedAnnotationRows.put(command.seqs[s], saved);
589                 // and then remove any annotation in the other views
590                 for (int alview = 0; views != null && alview < views.length; alview++)
591                 {
592                   if (views[alview] != command.al)
593                   {
594                     AlignmentAnnotation[] toremove = views[alview]
595                             .getAlignmentAnnotation();
596                     if (toremove == null || toremove.length == 0)
597                     {
598                       continue;
599                     }
600                     // remove any alignment annotation on this sequence that's
601                     // on that alignment view.
602                     for (int aa = 0; aa < toremove.length; aa++)
603                     {
604                       if (toremove[aa].sequenceRef == command.seqs[s])
605                       {
606                         views[alview].deleteAnnotation(toremove[aa]);
607                       }
608                     }
609                   }
610                 }
611               }
612               else
613               {
614                 // save all the annotation
615                 command.deletedAnnotationRows.put(command.seqs[s], tmp);
616               }
617             }
618           }
619           else
620           {
621             // recover rows
622             if (command.deletedAnnotationRows != null
623                     && command.deletedAnnotationRows
624                             .containsKey(command.seqs[s]))
625             {
626               AlignmentAnnotation[] revealed = (AlignmentAnnotation[]) command.deletedAnnotationRows
627                       .get(command.seqs[s]);
628               command.seqs[s].setAlignmentAnnotation(revealed);
629               if (revealed != null)
630               {
631                 for (int aa = 0; aa < revealed.length; aa++)
632                 {
633                   // iterate through al adding original annotation
634                   command.al.addAnnotation(revealed[aa]);
635                 }
636                 for (int aa = 0; aa < revealed.length; aa++)
637                 {
638                   command.al.setAnnotationIndex(revealed[aa], aa);
639                 }
640                 // and then duplicate added annotation on every other alignment
641                 // view
642                 for (int vnum = 0; views != null && vnum < views.length; vnum++)
643                 {
644                   if (views[vnum] != command.al)
645                   {
646                     int avwidth = views[vnum].getWidth() + 1;
647                     // duplicate in this view
648                     for (int a = 0; a < revealed.length; a++)
649                     {
650                       AlignmentAnnotation newann = new AlignmentAnnotation(
651                               revealed[a]);
652                       command.seqs[s].addAlignmentAnnotation(newann);
653                       newann.padAnnotation(avwidth);
654                       views[vnum].addAnnotation(newann);
655                       views[vnum].setAnnotationIndex(newann, a);
656                     }
657                   }
658                 }
659               }
660             }
661           }
662           continue;
663         }
664
665         if (command.seqs[s].getAnnotation() == null)
666         {
667           continue;
668         }
669
670         if (aSize == 0)
671         {
672           annotations = command.seqs[s].getAnnotation();
673         }
674         else
675         {
676           tmp = new AlignmentAnnotation[aSize
677                   + command.seqs[s].getAnnotation().length];
678
679           System.arraycopy(annotations, 0, tmp, 0, aSize);
680
681           System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
682                   command.seqs[s].getAnnotation().length);
683
684           annotations = tmp;
685         }
686         aSize = annotations.length;
687       }
688     }
689
690     if (annotations == null)
691     {
692       return;
693     }
694
695     if (!insert)
696     {
697       command.deletedAnnotations = new Hashtable();
698     }
699
700     int aSize;
701     Annotation[] temp;
702     for (int a = 0; a < annotations.length; a++)
703     {
704       if (annotations[a].autoCalculated
705               || annotations[a].annotations == null)
706       {
707         continue;
708       }
709
710       int tSize = 0;
711
712       aSize = annotations[a].annotations.length;
713       if (insert)
714       {
715         temp = new Annotation[aSize + command.number];
716         if (annotations[a].padGaps)
717         {
718           for (int aa = 0; aa < temp.length; aa++)
719           {
720             temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
721           }
722         }
723       }
724       else
725       {
726         if (command.position < aSize)
727         {
728           if (command.position + command.number >= aSize)
729           {
730             tSize = aSize;
731           }
732           else
733           {
734             tSize = aSize - command.number;
735           }
736         }
737         else
738         {
739           tSize = aSize;
740         }
741
742         if (tSize < 0)
743         {
744           tSize = aSize;
745         }
746         temp = new Annotation[tSize];
747       }
748
749       if (insert)
750       {
751         if (command.position < annotations[a].annotations.length)
752         {
753           System.arraycopy(annotations[a].annotations, 0, temp, 0,
754                   command.position);
755
756           if (command.deletedAnnotations != null
757                   && command.deletedAnnotations
758                           .containsKey(annotations[a].annotationId))
759           {
760             Annotation[] restore = (Annotation[]) command.deletedAnnotations
761                     .get(annotations[a].annotationId);
762
763             System.arraycopy(restore, 0, temp, command.position,
764                     command.number);
765
766           }
767
768           System.arraycopy(annotations[a].annotations, command.position,
769                   temp, command.position + command.number, aSize
770                           - command.position);
771         }
772         else
773         {
774           if (command.deletedAnnotations != null
775                   && command.deletedAnnotations
776                           .containsKey(annotations[a].annotationId))
777           {
778             Annotation[] restore = (Annotation[]) command.deletedAnnotations
779                     .get(annotations[a].annotationId);
780
781             temp = new Annotation[annotations[a].annotations.length
782                     + restore.length];
783             System.arraycopy(annotations[a].annotations, 0, temp, 0,
784                     annotations[a].annotations.length);
785             System.arraycopy(restore, 0, temp,
786                     annotations[a].annotations.length, restore.length);
787           }
788           else
789           {
790             temp = annotations[a].annotations;
791           }
792         }
793       }
794       else
795       {
796         if (tSize != aSize || command.position < 2)
797         {
798           int copylen = Math.min(command.position,
799                   annotations[a].annotations.length);
800           if (copylen > 0)
801            {
802             System.arraycopy(annotations[a].annotations, 0, temp, 0,
803                     copylen); // command.position);
804           }
805
806           Annotation[] deleted = new Annotation[command.number];
807           if (copylen >= command.position)
808           {
809             copylen = Math.min(command.number,
810                     annotations[a].annotations.length - command.position);
811             if (copylen > 0)
812             {
813               System.arraycopy(annotations[a].annotations,
814                       command.position, deleted, 0, copylen); // command.number);
815             }
816           }
817
818           command.deletedAnnotations.put(annotations[a].annotationId,
819                   deleted);
820           if (annotations[a].annotations.length > command.position
821                   + command.number)
822           {
823             System.arraycopy(annotations[a].annotations, command.position
824                     + command.number, temp, command.position,
825                     annotations[a].annotations.length - command.position
826                             - command.number); // aSize
827           }
828         }
829         else
830         {
831           int dSize = aSize - command.position;
832
833           if (dSize > 0)
834           {
835             Annotation[] deleted = new Annotation[command.number];
836             System.arraycopy(annotations[a].annotations, command.position,
837                     deleted, 0, dSize);
838
839             command.deletedAnnotations.put(annotations[a].annotationId,
840                     deleted);
841
842             tSize = Math.min(annotations[a].annotations.length,
843                     command.position);
844             temp = new Annotation[tSize];
845             System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
846           }
847           else
848           {
849             temp = annotations[a].annotations;
850           }
851         }
852       }
853
854       annotations[a].annotations = temp;
855     }
856   }
857
858   final void adjustFeatures(Edit command, int index, int i, int j,
859           boolean insert)
860   {
861     SequenceI seq = command.seqs[index];
862     SequenceI sequence = seq.getDatasetSequence();
863     if (sequence == null)
864     {
865       sequence = seq;
866     }
867
868     if (insert)
869     {
870       if (command.editedFeatures != null
871               && command.editedFeatures.containsKey(seq))
872       {
873         sequence.setSequenceFeatures((SequenceFeature[]) command.editedFeatures
874                 .get(seq));
875       }
876
877       return;
878     }
879
880     SequenceFeature[] sf = sequence.getSequenceFeatures();
881
882     if (sf == null)
883     {
884       return;
885     }
886
887     SequenceFeature[] oldsf = new SequenceFeature[sf.length];
888
889     int cSize = j - i;
890
891     for (int s = 0; s < sf.length; s++)
892     {
893       SequenceFeature copy = new SequenceFeature(sf[s]);
894
895       oldsf[s] = copy;
896
897       if (sf[s].getEnd() < i)
898       {
899         continue;
900       }
901
902       if (sf[s].getBegin() > j)
903       {
904         sf[s].setBegin(copy.getBegin() - cSize);
905         sf[s].setEnd(copy.getEnd() - cSize);
906         continue;
907       }
908
909       if (sf[s].getBegin() >= i)
910       {
911         sf[s].setBegin(i);
912       }
913
914       if (sf[s].getEnd() < j)
915       {
916         sf[s].setEnd(j - 1);
917       }
918
919       sf[s].setEnd(sf[s].getEnd() - (cSize));
920
921       if (sf[s].getBegin() > sf[s].getEnd())
922       {
923         sequence.deleteFeature(sf[s]);
924       }
925     }
926
927     if (command.editedFeatures == null)
928     {
929       command.editedFeatures = new Hashtable();
930     }
931
932     command.editedFeatures.put(seq, oldsf);
933
934   }
935
936   class Edit
937   {
938     public SequenceI[] oldds;
939
940     boolean fullAlignmentHeight = false;
941
942     Hashtable deletedAnnotationRows;
943
944     Hashtable deletedAnnotations;
945
946     Hashtable editedFeatures;
947
948     AlignmentI al;
949
950     int command;
951
952     char[][] string;
953
954     SequenceI[] seqs;
955
956     int[] alIndex;
957
958     int position, number;
959
960     char gapChar;
961
962     Edit(int command, SequenceI[] seqs, int position, int number,
963             char gapChar)
964     {
965       this.command = command;
966       this.seqs = seqs;
967       this.position = position;
968       this.number = number;
969       this.gapChar = gapChar;
970     }
971
972     Edit(int command, SequenceI[] seqs, int position, int number,
973             AlignmentI al)
974     {
975       this.gapChar = al.getGapCharacter();
976       this.command = command;
977       this.seqs = seqs;
978       this.position = position;
979       this.number = number;
980       this.al = al;
981
982       alIndex = new int[seqs.length];
983       for (int i = 0; i < seqs.length; i++)
984       {
985         alIndex[i] = al.findIndex(seqs[i]);
986       }
987
988       fullAlignmentHeight = (al.getHeight() == seqs.length);
989     }
990
991     Edit(int command, SequenceI[] seqs, int position, int number,
992             AlignmentI al, String replace)
993     {
994       this.command = command;
995       this.seqs = seqs;
996       this.position = position;
997       this.number = number;
998       this.al = al;
999       this.gapChar = al.getGapCharacter();
1000       string = new char[seqs.length][];
1001       for (int i = 0; i < seqs.length; i++)
1002       {
1003         string[i] = replace.toCharArray();
1004       }
1005
1006       fullAlignmentHeight = (al.getHeight() == seqs.length);
1007     }
1008   }
1009 }