2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.commands;
23 import jalview.analysis.AlignSeq;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.Annotation;
27 import jalview.datamodel.Range;
28 import jalview.datamodel.Sequence;
29 import jalview.datamodel.SequenceFeature;
30 import jalview.datamodel.SequenceI;
31 import jalview.datamodel.features.SequenceFeaturesI;
32 import jalview.util.Comparison;
33 import jalview.util.ReverseListIterator;
34 import jalview.util.StringUtils;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.Hashtable;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.ListIterator;
51 * Description: Essential information for performing undo and redo for cut/paste
52 * insert/delete gap which can be stored in the HistoryList
56 * Copyright: Copyright (c) 2006
60 * Company: Dundee University
63 * @author not attributable
66 public class EditCommand implements CommandI
73 public Action getUndoAction()
81 public Action getUndoAction()
89 public Action getUndoAction()
97 public Action getUndoAction()
105 public Action getUndoAction()
113 public Action getUndoAction()
118 public abstract Action getUndoAction();
121 private List<Edit> edits = new ArrayList<Edit>();
129 public EditCommand(String desc)
131 this.description = desc;
134 public EditCommand(String desc, Action command, SequenceI[] seqs,
135 int position, int number, AlignmentI al)
137 this.description = desc;
138 if (command == Action.CUT || command == Action.PASTE)
140 setEdit(new Edit(command, seqs, position, number, al));
143 performEdit(0, null);
146 public EditCommand(String desc, Action command, String replace,
147 SequenceI[] seqs, int position, int number, AlignmentI al)
149 this.description = desc;
150 if (command == Action.REPLACE)
152 setEdit(new Edit(command, seqs, position, number, al, replace));
155 performEdit(0, null);
159 * Set the list of edits to the specified item (only).
163 protected void setEdit(Edit e)
170 * Add the given edit command to the stored list of commands. If simply
171 * expanding the range of the last command added, then modify it instead of
172 * adding a new command.
176 public void addEdit(Edit e)
178 if (!expandEdit(edits, e))
185 * Returns true if the new edit is incorporated by updating (expanding the
186 * range of) the last edit on the list, else false. We can 'expand' the last
187 * edit if the new one is the same action, on the same sequences, and acts on
188 * a contiguous range. This is the case where a mouse drag generates a series
189 * of contiguous gap insertions or deletions.
195 protected static boolean expandEdit(List<Edit> edits, Edit e)
197 if (edits == null || edits.isEmpty())
201 Edit lastEdit = edits.get(edits.size() - 1);
202 Action action = e.command;
203 if (lastEdit.command != action)
209 * Both commands must act on the same sequences - compare the underlying
210 * dataset sequences, rather than the aligned sequences, which change as
213 if (lastEdit.seqs.length != e.seqs.length)
217 for (int i = 0; i < e.seqs.length; i++)
219 if (lastEdit.seqs[i].getDatasetSequence() != e.seqs[i]
220 .getDatasetSequence())
227 * Check a contiguous edit; either
229 * <li>a new Insert <n> positions to the right of the last <insert n>, or</li>
230 * <li>a new Delete <n> gaps which is <n> positions to the left of the last
234 boolean contiguous = (action == Action.INSERT_GAP && e.position == lastEdit.position
236 || (action == Action.DELETE_GAP && e.position + e.number == lastEdit.position);
240 * We are just expanding the range of the last edit. For delete gap, also
241 * moving the start position left.
243 lastEdit.number += e.number;
244 lastEdit.seqs = e.seqs;
245 if (action == Action.DELETE_GAP)
255 * Clear the list of stored edit commands.
258 protected void clearEdits()
264 * Returns the i'th stored Edit command.
269 protected Edit getEdit(int i)
271 if (i >= 0 && i < edits.size())
279 final public String getDescription()
291 * Return the alignment for the first edit (or null if no edit).
295 final public AlignmentI getAlignment()
297 return (edits.isEmpty() ? null : edits.get(0).al);
301 * append a new editCommand Note. this shouldn't be called if the edit is an
302 * operation affects more alignment objects than the one referenced in al (for
303 * example, cut or pasting whole sequences). Use the form with an additional
304 * AlignmentI[] views parameter.
313 final public void appendEdit(Action command, SequenceI[] seqs,
314 int position, int number, AlignmentI al, boolean performEdit)
316 appendEdit(command, seqs, position, number, al, performEdit, null);
320 * append a new edit command with a set of alignment views that may be
331 final public void appendEdit(Action command, SequenceI[] seqs,
332 int position, int number, AlignmentI al, boolean performEdit,
335 Edit edit = new Edit(command, seqs, position, number, al);
336 appendEdit(edit, al, performEdit, views);
340 * Overloaded method that accepts an Edit object with additional parameters.
347 final public void appendEdit(Edit edit, AlignmentI al,
348 boolean performEdit, AlignmentI[] views)
350 if (al.getHeight() == edit.seqs.length)
353 edit.fullAlignmentHeight = true;
360 performEdit(edit, views);
365 * Execute all the edit commands, starting at the given commandIndex
367 * @param commandIndex
370 public final void performEdit(int commandIndex, AlignmentI[] views)
372 ListIterator<Edit> iterator = edits.listIterator(commandIndex);
373 while (iterator.hasNext())
375 Edit edit = iterator.next();
376 performEdit(edit, views);
381 * Execute one edit command in all the specified alignment views
386 protected static void performEdit(Edit edit, AlignmentI[] views)
388 switch (edit.command)
406 // TODO:add deleteNuc for UNDO
408 // insertNuc(edits[e]);
416 final public void doCommand(AlignmentI[] views)
418 performEdit(0, views);
422 * Undo the stored list of commands, in reverse order.
425 final public void undoCommand(AlignmentI[] views)
427 ListIterator<Edit> iterator = edits.listIterator(edits.size());
428 while (iterator.hasPrevious())
430 Edit e = iterator.previous();
458 * Insert gap(s) in sequences as specified by the command, and adjust
463 final private static void insertGap(Edit command)
466 for (int s = 0; s < command.seqs.length; s++)
468 command.seqs[s].insertCharAt(command.position, command.number,
470 // System.out.println("pos: "+command.position+" number: "+command.number);
473 adjustAnnotations(command, true, false, null);
477 // final void insertNuc(Edit command)
480 // for (int s = 0; s < command.seqs.length; s++)
482 // System.out.println("pos: "+command.position+" number: "+command.number);
483 // command.seqs[s].insertCharAt(command.position, command.number,'A');
486 // adjustAnnotations(command, true, false, null);
490 * Delete gap(s) in sequences as specified by the command, and adjust
495 final static private void deleteGap(Edit command)
497 for (int s = 0; s < command.seqs.length; s++)
499 command.seqs[s].deleteChars(command.position, command.position
503 adjustAnnotations(command, false, false, null);
507 * Carry out a Cut action. The cut characters are saved in case Undo is
513 static void cut(Edit command, AlignmentI[] views)
515 boolean seqDeleted = false;
516 command.string = new char[command.seqs.length][];
518 for (int i = 0; i < command.seqs.length; i++)
520 final SequenceI sequence = command.seqs[i];
521 if (sequence.getLength() > command.position)
523 command.string[i] = sequence.getSequence(command.position,
524 command.position + command.number);
525 SequenceI oldds = sequence.getDatasetSequence();
526 if (command.oldds != null && command.oldds[i] != null)
528 // we are redoing an undone cut.
529 sequence.setDatasetSequence(null);
531 Range cutPositions = sequence.findPositions(command.position + 1,
532 command.position + command.number);
533 boolean cutIsInternal = cutPositions != null
534 && sequence.getStart() != cutPositions
535 .getBegin() && sequence.getEnd() != cutPositions.getEnd();
536 sequence.deleteChars(command.position, command.position
538 if (command.oldds != null && command.oldds[i] != null)
540 // oldds entry contains the cut dataset sequence.
541 sequence.setDatasetSequence(command.oldds[i]);
542 command.oldds[i] = oldds;
546 // modify the oldds if necessary
547 if (oldds != sequence.getDatasetSequence()
548 || sequence.getFeatures().hasFeatures())
550 if (command.oldds == null)
552 command.oldds = new SequenceI[command.seqs.length];
554 command.oldds[i] = oldds;
556 if (cutPositions != null)
558 cutFeatures(command, sequence, cutPositions.getBegin(),
559 cutPositions.getEnd(), cutIsInternal);
565 if (sequence.getLength() < 1)
567 command.al.deleteSequence(sequence);
572 adjustAnnotations(command, false, seqDeleted, views);
576 * Perform the given Paste command. This may be to add cut or copied sequences
577 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
582 static void paste(Edit command, AlignmentI[] views)
586 boolean newDSWasNeeded;
587 int newstart, newend;
588 boolean seqWasDeleted = false;
589 int start = 0, end = 0;
591 for (int i = 0; i < command.seqs.length; i++)
594 newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
595 SequenceI sequence = command.seqs[i];
596 if (sequence.getLength() < 1)
598 // ie this sequence was deleted, we need to
599 // readd it to the alignment
600 if (command.alIndex[i] < command.al.getHeight())
602 List<SequenceI> sequences;
603 synchronized (sequences = command.al.getSequences())
605 if (!(command.alIndex[i] < 0))
607 sequences.add(command.alIndex[i], sequence);
613 command.al.addSequence(sequence);
615 seqWasDeleted = true;
617 newstart = sequence.getStart();
618 newend = sequence.getEnd();
620 tmp = new StringBuffer();
621 tmp.append(sequence.getSequence());
622 // Undo of a delete does not replace original dataset sequence on to
623 // alignment sequence.
625 if (command.string != null && command.string[i] != null)
627 if (command.position >= tmp.length())
629 // This occurs if padding is on, and residues
630 // are removed from end of alignment
631 int length = command.position - tmp.length();
634 tmp.append(command.gapChar);
638 tmp.insert(command.position, command.string[i]);
639 for (int s = 0; s < command.string[i].length; s++)
641 if (!Comparison.isGap(command.string[i][s]))
646 start = sequence.findPosition(command.position);
647 end = sequence.findPosition(command.position
650 if (sequence.getStart() == start)
660 command.string[i] = null;
663 sequence.setSequence(tmp.toString());
664 sequence.setStart(newstart);
665 sequence.setEnd(newend);
668 if (sequence.getDatasetSequence() != null)
673 ds = command.oldds[i];
677 // make a new DS sequence
678 // use new ds mechanism here
679 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
680 sequence.getSequenceAsString());
681 ds = new Sequence(sequence.getName(), ungapped,
682 sequence.getStart(), sequence.getEnd());
683 ds.setDescription(sequence.getDescription());
685 if (command.oldds == null)
687 command.oldds = new SequenceI[command.seqs.length];
689 command.oldds[i] = sequence.getDatasetSequence();
690 sequence.setDatasetSequence(ds);
692 undoCutFeatures(command, i, start, end);
695 adjustAnnotations(command, true, seqWasDeleted, views);
697 command.string = null;
700 static void replace(Edit command)
704 int start = command.position;
705 int end = command.number;
706 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
708 // TODO Jalview 2.4 bugfix change to an aggregate command - original
709 // sequence string is cut, new string is pasted in.
710 command.number = start + command.string[0].length;
711 for (int i = 0; i < command.seqs.length; i++)
713 boolean newDSWasNeeded = command.oldds != null
714 && command.oldds[i] != null;
717 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
718 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
719 * viewport.alignment));
723 * then addHistoryItem(new EditCommand( "Add sequences",
724 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
727 oldstring = command.seqs[i].getSequenceAsString();
728 tmp = new StringBuffer(oldstring.substring(0, start));
729 tmp.append(command.string[i]);
730 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
731 jalview.util.Comparison.GapChars, new String(
733 int ipos = command.seqs[i].findPosition(start)
734 - command.seqs[i].getStart();
735 tmp.append(oldstring.substring(end));
736 command.seqs[i].setSequence(tmp.toString());
737 command.string[i] = oldstring.substring(start, end).toCharArray();
738 String nogapold = jalview.analysis.AlignSeq.extractGaps(
739 jalview.util.Comparison.GapChars, new String(
741 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
745 SequenceI oldds = command.seqs[i].getDatasetSequence();
746 command.seqs[i].setDatasetSequence(command.oldds[i]);
747 command.oldds[i] = oldds;
751 if (command.oldds == null)
753 command.oldds = new SequenceI[command.seqs.length];
755 command.oldds[i] = command.seqs[i].getDatasetSequence();
756 SequenceI newds = new Sequence(
757 command.seqs[i].getDatasetSequence());
758 String fullseq, osp = newds.getSequenceAsString();
759 fullseq = osp.substring(0, ipos) + nogaprep
760 + osp.substring(ipos + nogaprep.length());
761 newds.setSequence(fullseq.toUpperCase());
762 // TODO: JAL-1131 ensure newly created dataset sequence is added to
764 // dataset sequences associated with the alignment.
765 // TODO: JAL-1131 fix up any annotation associated with new dataset
766 // sequence to ensure that original sequence/annotation relationships
768 command.seqs[i].setDatasetSequence(newds);
777 final static void adjustAnnotations(Edit command, boolean insert,
778 boolean modifyVisibility, AlignmentI[] views)
780 AlignmentAnnotation[] annotations = null;
782 if (modifyVisibility && !insert)
784 // only occurs if a sequence was added or deleted.
785 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
787 if (command.fullAlignmentHeight)
789 annotations = command.al.getAlignmentAnnotation();
794 AlignmentAnnotation[] tmp;
795 for (int s = 0; s < command.seqs.length; s++)
797 command.seqs[s].sequenceChanged();
799 if (modifyVisibility)
801 // Rows are only removed or added to sequence object.
805 tmp = command.seqs[s].getAnnotation();
808 int alen = tmp.length;
809 for (int aa = 0; aa < tmp.length; aa++)
811 if (!command.al.deleteAnnotation(tmp[aa]))
813 // strip out annotation not in the current al (will be put
814 // back on insert in all views)
819 command.seqs[s].setAlignmentAnnotation(null);
820 if (alen != tmp.length)
822 // save the non-null annotation references only
823 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
824 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
828 saved[aapos++] = tmp[aa];
833 command.deletedAnnotationRows.put(command.seqs[s], saved);
834 // and then remove any annotation in the other views
835 for (int alview = 0; views != null && alview < views.length; alview++)
837 if (views[alview] != command.al)
839 AlignmentAnnotation[] toremove = views[alview]
840 .getAlignmentAnnotation();
841 if (toremove == null || toremove.length == 0)
845 // remove any alignment annotation on this sequence that's
846 // on that alignment view.
847 for (int aa = 0; aa < toremove.length; aa++)
849 if (toremove[aa].sequenceRef == command.seqs[s])
851 views[alview].deleteAnnotation(toremove[aa]);
859 // save all the annotation
860 command.deletedAnnotationRows.put(command.seqs[s], tmp);
867 if (command.deletedAnnotationRows != null
868 && command.deletedAnnotationRows
869 .containsKey(command.seqs[s]))
871 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
872 .get(command.seqs[s]);
873 command.seqs[s].setAlignmentAnnotation(revealed);
874 if (revealed != null)
876 for (int aa = 0; aa < revealed.length; aa++)
878 // iterate through al adding original annotation
879 command.al.addAnnotation(revealed[aa]);
881 for (int aa = 0; aa < revealed.length; aa++)
883 command.al.setAnnotationIndex(revealed[aa], aa);
885 // and then duplicate added annotation on every other alignment
887 for (int vnum = 0; views != null && vnum < views.length; vnum++)
889 if (views[vnum] != command.al)
891 int avwidth = views[vnum].getWidth() + 1;
892 // duplicate in this view
893 for (int a = 0; a < revealed.length; a++)
895 AlignmentAnnotation newann = new AlignmentAnnotation(
897 command.seqs[s].addAlignmentAnnotation(newann);
898 newann.padAnnotation(avwidth);
899 views[vnum].addAnnotation(newann);
900 views[vnum].setAnnotationIndex(newann, a);
910 if (command.seqs[s].getAnnotation() == null)
917 annotations = command.seqs[s].getAnnotation();
921 tmp = new AlignmentAnnotation[aSize
922 + command.seqs[s].getAnnotation().length];
924 System.arraycopy(annotations, 0, tmp, 0, aSize);
926 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
927 command.seqs[s].getAnnotation().length);
931 aSize = annotations.length;
935 if (annotations == null)
942 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
947 for (int a = 0; a < annotations.length; a++)
949 if (annotations[a].autoCalculated
950 || annotations[a].annotations == null)
957 aSize = annotations[a].annotations.length;
960 temp = new Annotation[aSize + command.number];
961 if (annotations[a].padGaps)
963 for (int aa = 0; aa < temp.length; aa++)
965 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
971 if (command.position < aSize)
973 if (command.position + command.number >= aSize)
979 tSize = aSize - command.number;
991 temp = new Annotation[tSize];
996 if (command.position < annotations[a].annotations.length)
998 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1001 if (command.deletedAnnotations != null
1002 && command.deletedAnnotations
1003 .containsKey(annotations[a].annotationId))
1005 Annotation[] restore = command.deletedAnnotations
1006 .get(annotations[a].annotationId);
1008 System.arraycopy(restore, 0, temp, command.position,
1013 System.arraycopy(annotations[a].annotations, command.position,
1014 temp, command.position + command.number, aSize
1015 - command.position);
1019 if (command.deletedAnnotations != null
1020 && command.deletedAnnotations
1021 .containsKey(annotations[a].annotationId))
1023 Annotation[] restore = command.deletedAnnotations
1024 .get(annotations[a].annotationId);
1026 temp = new Annotation[annotations[a].annotations.length
1028 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1029 annotations[a].annotations.length);
1030 System.arraycopy(restore, 0, temp,
1031 annotations[a].annotations.length, restore.length);
1035 temp = annotations[a].annotations;
1041 if (tSize != aSize || command.position < 2)
1043 int copylen = Math.min(command.position,
1044 annotations[a].annotations.length);
1047 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1048 copylen); // command.position);
1051 Annotation[] deleted = new Annotation[command.number];
1052 if (copylen >= command.position)
1054 copylen = Math.min(command.number,
1055 annotations[a].annotations.length - command.position);
1058 System.arraycopy(annotations[a].annotations,
1059 command.position, deleted, 0, copylen); // command.number);
1063 command.deletedAnnotations.put(annotations[a].annotationId,
1065 if (annotations[a].annotations.length > command.position
1068 System.arraycopy(annotations[a].annotations, command.position
1069 + command.number, temp, command.position,
1070 annotations[a].annotations.length - command.position
1071 - command.number); // aSize
1076 int dSize = aSize - command.position;
1080 Annotation[] deleted = new Annotation[command.number];
1081 System.arraycopy(annotations[a].annotations, command.position,
1084 command.deletedAnnotations.put(annotations[a].annotationId,
1087 tSize = Math.min(annotations[a].annotations.length,
1089 temp = new Annotation[tSize];
1090 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1094 temp = annotations[a].annotations;
1099 annotations[a].annotations = temp;
1103 final static void undoCutFeatures(Edit command, int index, final int i,
1106 SequenceI seq = command.seqs[index];
1107 SequenceI sequence = seq.getDatasetSequence();
1108 if (sequence == null)
1114 * TODO: shift right features that lie to the right of the restored cut
1115 * Currently not needed as all features restored with saved dataset sequence
1116 * nor if no saved dataset sequence (as coordinates left unchanged by Cut)
1120 * restore any features that were deleted or truncated
1122 if (command.deletedFeatures != null
1123 && command.deletedFeatures.containsKey(seq))
1125 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1127 sequence.addSequenceFeature(deleted);
1132 * delete any truncated features
1134 if (command.truncatedFeatures != null
1135 && command.truncatedFeatures.containsKey(seq))
1137 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1139 sequence.deleteFeature(amended);
1145 * Returns the list of edit commands wrapped by this object.
1149 public List<Edit> getEdits()
1155 * Returns a map whose keys are the dataset sequences, and values their
1156 * aligned sequences before the command edit list was applied. The aligned
1157 * sequences are copies, which may be updated without affecting the originals.
1159 * The command holds references to the aligned sequences (after editing). If
1160 * the command is an 'undo',then the prior state is simply the aligned state.
1161 * Otherwise, we have to derive the prior state by working backwards through
1162 * the edit list to infer the aligned sequences before editing.
1164 * Note: an alternative solution would be to cache the 'before' state of each
1165 * edit, but this would be expensive in space in the common case that the
1166 * original is never needed (edits are not mirrored).
1169 * @throws IllegalStateException
1170 * on detecting an edit command of a type that can't be unwound
1172 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1174 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1175 if (getEdits() == null)
1181 for (Edit e : getEdits())
1183 for (SequenceI seq : e.getSequences())
1185 SequenceI ds = seq.getDatasetSequence();
1186 // SequenceI preEdit = result.get(ds);
1187 if (!result.containsKey(ds))
1190 * copy sequence including start/end (but don't use copy constructor
1191 * as we don't need annotations)
1193 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1194 seq.getStart(), seq.getEnd());
1195 preEdit.setDatasetSequence(ds);
1196 result.put(ds, preEdit);
1204 * Work backwards through the edit list, deriving the sequences before each
1205 * was applied. The final result is the sequence set before any edits.
1207 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1208 while (editList.hasNext())
1210 Edit oldEdit = editList.next();
1211 Action action = oldEdit.getAction();
1212 int position = oldEdit.getPosition();
1213 int number = oldEdit.getNumber();
1214 final char gap = oldEdit.getGapCharacter();
1215 for (SequenceI seq : oldEdit.getSequences())
1217 SequenceI ds = seq.getDatasetSequence();
1218 SequenceI preEdit = result.get(ds);
1219 if (preEdit == null)
1221 preEdit = new Sequence("", seq.getSequenceAsString(),
1222 seq.getStart(), seq.getEnd());
1223 preEdit.setDatasetSequence(ds);
1224 result.put(ds, preEdit);
1227 * 'Undo' this edit action on the sequence (updating the value in the
1232 if (action == Action.DELETE_GAP)
1234 preEdit.setSequence(new String(StringUtils.insertCharAt(
1235 preEdit.getSequence(), position, number, gap)));
1237 else if (action == Action.INSERT_GAP)
1239 preEdit.setSequence(new String(StringUtils.deleteChars(
1240 preEdit.getSequence(), position, position + number)));
1244 System.err.println("Can't undo edit action " + action);
1245 // throw new IllegalStateException("Can't undo edit action " +
1256 public SequenceI[] oldds;
1258 boolean fullAlignmentHeight = false;
1260 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1262 Map<String, Annotation[]> deletedAnnotations;
1265 * features deleted by the cut (re-add on Undo)
1266 * (including the original of any shortened features)
1268 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1271 * shortened features added by the cut (delete on Undo)
1273 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1285 int position, number;
1289 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1294 this.position = pos;
1295 this.number = count;
1299 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1302 this(cmd, sqs, pos, count, align.getGapCharacter());
1306 alIndex = new int[sqs.length];
1307 for (int i = 0; i < sqs.length; i++)
1309 alIndex[i] = align.findIndex(sqs[i]);
1312 fullAlignmentHeight = (align.getHeight() == sqs.length);
1315 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1316 AlignmentI align, String replace)
1318 this(cmd, sqs, pos, count, align);
1320 string = new char[sqs.length][];
1321 for (int i = 0; i < sqs.length; i++)
1323 string[i] = replace.toCharArray();
1327 public SequenceI[] getSequences()
1332 public int getPosition()
1337 public Action getAction()
1342 public int getNumber()
1347 public char getGapCharacter()
1354 * Returns an iterator over the list of edit commands which traverses the list
1355 * either forwards or backwards.
1360 public Iterator<Edit> getEditIterator(boolean forwards)
1364 return getEdits().iterator();
1368 return new ReverseListIterator<Edit>(getEdits());
1373 * Adjusts features for Cut, and saves details of changes made to allow Undo
1375 * <li>features left of the cut are unchanged</li>
1376 * <li>features right of the cut are shifted left</li>
1377 * <li>features internal to the cut region are deleted</li>
1378 * <li>features that overlap or span the cut are shortened</li>
1379 * <li>the originals of any deleted or shorted features are saved, to re-add
1381 * <li>any added (shortened) features are saved, to delete on Undo</li>
1386 * @param fromPosition
1388 * @param cutIsInternal
1390 protected static void cutFeatures(Edit command, SequenceI seq,
1391 int fromPosition, int toPosition, boolean cutIsInternal)
1393 List<SequenceFeature> added = new ArrayList<>();
1394 List<SequenceFeature> removed = new ArrayList<>();
1396 SequenceFeaturesI featureStore = seq.getFeatures();
1397 if (toPosition < fromPosition || featureStore == null)
1402 int cutStartPos = fromPosition;
1403 int cutEndPos = toPosition;
1404 int cutWidth = cutEndPos - cutStartPos + 1;
1406 synchronized (featureStore)
1409 * get features that overlap the cut region
1411 List<SequenceFeature> toAmend = featureStore.findFeatures(
1412 cutStartPos, cutEndPos);
1415 * add any contact features that span the cut region
1416 * (not returned by findFeatures)
1418 for (SequenceFeature contact : featureStore.getContactFeatures())
1420 if (contact.getBegin() < cutStartPos
1421 && contact.getEnd() > cutEndPos)
1423 toAmend.add(contact);
1428 * adjust start-end of overlapping features;
1429 * delete features enclosed by the cut;
1430 * delete partially overlapping contact features
1432 for (SequenceFeature sf : toAmend)
1434 int sfBegin = sf.getBegin();
1435 int sfEnd = sf.getEnd();
1436 int newBegin = sfBegin;
1438 boolean toDelete = false;
1440 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1443 * feature lies within cut region - delete it
1447 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1450 * feature spans cut region - left-shift the end
1454 else if (sfEnd <= cutEndPos)
1457 * feature overlaps left of cut region - truncate right
1459 newEnd = cutStartPos - 1;
1460 if (sf.isContactFeature())
1465 else if (sfBegin >= cutStartPos)
1468 * remaining case - feature overlaps right
1469 * truncate left, adjust end of feature
1471 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1472 // newEnd = newBegin + (sfEnd - sfBegin) - overlapsBy;
1473 newEnd = newBegin + sfEnd - cutEndPos - 1;
1474 if (sf.isContactFeature())
1480 seq.deleteFeature(sf);
1484 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1485 sf.getFeatureGroup(), sf.getScore());
1486 seq.addSequenceFeature(copy);
1492 * and left shift any features lying to the right of the cut region
1493 * (but not if the cut is at start or end of sequence)
1497 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1502 * save deleted and amended features, so that Undo can
1503 * re-add or delete them respectively
1505 if (command.deletedFeatures == null)
1507 command.deletedFeatures = new HashMap<>();
1509 if (command.truncatedFeatures == null)
1511 command.truncatedFeatures = new HashMap<>();
1513 command.deletedFeatures.put(seq, removed);
1514 command.truncatedFeatures.put(seq, added);