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>,
231 * <li>a new Delete <n> gaps which is <n> positions to the left of the last
235 boolean contiguous = (action == Action.INSERT_GAP
236 && e.position == lastEdit.position + lastEdit.number)
237 || (action == Action.DELETE_GAP
238 && e.position + e.number == lastEdit.position);
242 * We are just expanding the range of the last edit. For delete gap, also
243 * moving the start position left.
245 lastEdit.number += e.number;
246 lastEdit.seqs = e.seqs;
247 if (action == Action.DELETE_GAP)
257 * Clear the list of stored edit commands.
260 protected void clearEdits()
266 * Returns the i'th stored Edit command.
271 protected Edit getEdit(int i)
273 if (i >= 0 && i < edits.size())
281 final public String getDescription()
293 * Return the alignment for the first edit (or null if no edit).
297 final public AlignmentI getAlignment()
299 return (edits.isEmpty() ? null : edits.get(0).al);
303 * append a new editCommand Note. this shouldn't be called if the edit is an
304 * operation affects more alignment objects than the one referenced in al (for
305 * example, cut or pasting whole sequences). Use the form with an additional
306 * AlignmentI[] views parameter.
315 final public void appendEdit(Action command, SequenceI[] seqs,
316 int position, int number, AlignmentI al, boolean performEdit)
318 appendEdit(command, seqs, position, number, al, performEdit, null);
322 * append a new edit command with a set of alignment views that may be
333 final public void appendEdit(Action command, SequenceI[] seqs,
334 int position, int number, AlignmentI al, boolean performEdit,
337 Edit edit = new Edit(command, seqs, position, number, al);
338 appendEdit(edit, al, performEdit, views);
342 * Overloaded method that accepts an Edit object with additional parameters.
349 final public void appendEdit(Edit edit, AlignmentI al,
350 boolean performEdit, AlignmentI[] views)
352 if (al.getHeight() == edit.seqs.length)
355 edit.fullAlignmentHeight = true;
362 performEdit(edit, views);
367 * Execute all the edit commands, starting at the given commandIndex
369 * @param commandIndex
372 public final void performEdit(int commandIndex, AlignmentI[] views)
374 ListIterator<Edit> iterator = edits.listIterator(commandIndex);
375 while (iterator.hasNext())
377 Edit edit = iterator.next();
378 performEdit(edit, views);
383 * Execute one edit command in all the specified alignment views
388 protected static void performEdit(Edit edit, AlignmentI[] views)
390 switch (edit.command)
408 // TODO:add deleteNuc for UNDO
410 // insertNuc(edits[e]);
418 final public void doCommand(AlignmentI[] views)
420 performEdit(0, views);
424 * Undo the stored list of commands, in reverse order.
427 final public void undoCommand(AlignmentI[] views)
429 ListIterator<Edit> iterator = edits.listIterator(edits.size());
430 while (iterator.hasPrevious())
432 Edit e = iterator.previous();
460 * Insert gap(s) in sequences as specified by the command, and adjust
465 final private static void insertGap(Edit command)
468 for (int s = 0; s < command.seqs.length; s++)
470 command.seqs[s].insertCharAt(command.position, command.number,
472 // System.out.println("pos: "+command.position+" number:
473 // "+command.number);
476 adjustAnnotations(command, true, false, null);
480 // final void insertNuc(Edit command)
483 // for (int s = 0; s < command.seqs.length; s++)
485 // System.out.println("pos: "+command.position+" number: "+command.number);
486 // command.seqs[s].insertCharAt(command.position, command.number,'A');
489 // adjustAnnotations(command, true, false, null);
493 * Delete gap(s) in sequences as specified by the command, and adjust
498 final static private void deleteGap(Edit command)
500 for (int s = 0; s < command.seqs.length; s++)
502 command.seqs[s].deleteChars(command.position,
503 command.position + command.number);
506 adjustAnnotations(command, false, false, null);
510 * Carry out a Cut action. The cut characters are saved in case Undo is
516 static void cut(Edit command, AlignmentI[] views)
518 boolean seqDeleted = false;
519 command.string = new char[command.seqs.length][];
521 for (int i = 0; i < command.seqs.length; i++)
523 final SequenceI sequence = command.seqs[i];
524 if (sequence.getLength() > command.position)
526 command.string[i] = sequence.getSequence(command.position,
527 command.position + command.number);
528 SequenceI oldds = sequence.getDatasetSequence();
529 if (command.oldds != null && command.oldds[i] != null)
531 // we are redoing an undone cut.
532 sequence.setDatasetSequence(null);
534 Range cutPositions = sequence.findPositions(command.position + 1,
535 command.position + command.number);
536 boolean cutIsInternal = cutPositions != null
537 && sequence.getStart() != cutPositions
538 .getBegin() && sequence.getEnd() != cutPositions.getEnd();
539 sequence.deleteChars(command.position, command.position
542 if (command.oldds != null && command.oldds[i] != null)
544 // oldds entry contains the cut dataset sequence.
545 sequence.setDatasetSequence(command.oldds[i]);
546 command.oldds[i] = oldds;
550 // modify the oldds if necessary
551 if (oldds != sequence.getDatasetSequence()
552 || sequence.getFeatures().hasFeatures())
554 if (command.oldds == null)
556 command.oldds = new SequenceI[command.seqs.length];
558 command.oldds[i] = oldds;
560 if (cutPositions != null)
562 cutFeatures(command, sequence, cutPositions.getBegin(),
563 cutPositions.getEnd(), cutIsInternal);
569 if (sequence.getLength() < 1)
571 command.al.deleteSequence(sequence);
576 adjustAnnotations(command, false, seqDeleted, views);
580 * Perform the given Paste command. This may be to add cut or copied sequences
581 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
586 static void paste(Edit command, AlignmentI[] views)
590 boolean newDSWasNeeded;
591 int newstart, newend;
592 boolean seqWasDeleted = false;
593 int start = 0, end = 0;
595 for (int i = 0; i < command.seqs.length; i++)
598 newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
599 SequenceI sequence = command.seqs[i];
600 if (sequence.getLength() < 1)
602 // ie this sequence was deleted, we need to
603 // readd it to the alignment
604 if (command.alIndex[i] < command.al.getHeight())
606 List<SequenceI> sequences;
607 synchronized (sequences = command.al.getSequences())
609 if (!(command.alIndex[i] < 0))
611 sequences.add(command.alIndex[i], sequence);
617 command.al.addSequence(sequence);
619 seqWasDeleted = true;
621 newstart = sequence.getStart();
622 newend = sequence.getEnd();
624 tmp = new StringBuffer();
625 tmp.append(sequence.getSequence());
626 // Undo of a delete does not replace original dataset sequence on to
627 // alignment sequence.
629 if (command.string != null && command.string[i] != null)
631 if (command.position >= tmp.length())
633 // This occurs if padding is on, and residues
634 // are removed from end of alignment
635 int length = command.position - tmp.length();
638 tmp.append(command.gapChar);
642 tmp.insert(command.position, command.string[i]);
643 for (int s = 0; s < command.string[i].length; s++)
645 if (!Comparison.isGap(command.string[i][s]))
650 start = sequence.findPosition(command.position);
651 end = sequence.findPosition(command.position
654 if (sequence.getStart() == start)
664 command.string[i] = null;
667 sequence.setSequence(tmp.toString());
668 sequence.setStart(newstart);
669 sequence.setEnd(newend);
672 if (sequence.getDatasetSequence() != null)
677 ds = command.oldds[i];
681 // make a new DS sequence
682 // use new ds mechanism here
683 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
684 sequence.getSequenceAsString());
685 ds = new Sequence(sequence.getName(), ungapped,
686 sequence.getStart(), sequence.getEnd());
687 ds.setDescription(sequence.getDescription());
689 if (command.oldds == null)
691 command.oldds = new SequenceI[command.seqs.length];
693 command.oldds[i] = sequence.getDatasetSequence();
694 sequence.setDatasetSequence(ds);
696 undoCutFeatures(command, i, start, end);
699 adjustAnnotations(command, true, seqWasDeleted, views);
701 command.string = null;
704 static void replace(Edit command)
708 int start = command.position;
709 int end = command.number;
710 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
712 // TODO Jalview 2.4 bugfix change to an aggregate command - original
713 // sequence string is cut, new string is pasted in.
714 command.number = start + command.string[0].length;
715 for (int i = 0; i < command.seqs.length; i++)
717 boolean newDSWasNeeded = command.oldds != null
718 && command.oldds[i] != null;
721 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
722 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
723 * viewport.alignment));
727 * then addHistoryItem(new EditCommand( "Add sequences",
728 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
731 oldstring = command.seqs[i].getSequenceAsString();
732 tmp = new StringBuffer(oldstring.substring(0, start));
733 tmp.append(command.string[i]);
734 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
735 jalview.util.Comparison.GapChars,
736 new String(command.string[i]));
737 int ipos = command.seqs[i].findPosition(start)
738 - command.seqs[i].getStart();
739 tmp.append(oldstring.substring(end));
740 command.seqs[i].setSequence(tmp.toString());
741 command.string[i] = oldstring.substring(start, end).toCharArray();
742 String nogapold = jalview.analysis.AlignSeq.extractGaps(
743 jalview.util.Comparison.GapChars,
744 new String(command.string[i]));
745 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
749 SequenceI oldds = command.seqs[i].getDatasetSequence();
750 command.seqs[i].setDatasetSequence(command.oldds[i]);
751 command.oldds[i] = oldds;
755 if (command.oldds == null)
757 command.oldds = new SequenceI[command.seqs.length];
759 command.oldds[i] = command.seqs[i].getDatasetSequence();
760 SequenceI newds = new Sequence(
761 command.seqs[i].getDatasetSequence());
762 String fullseq, osp = newds.getSequenceAsString();
763 fullseq = osp.substring(0, ipos) + nogaprep
764 + osp.substring(ipos + nogaprep.length());
765 newds.setSequence(fullseq.toUpperCase());
766 // TODO: JAL-1131 ensure newly created dataset sequence is added to
768 // dataset sequences associated with the alignment.
769 // TODO: JAL-1131 fix up any annotation associated with new dataset
770 // sequence to ensure that original sequence/annotation relationships
772 command.seqs[i].setDatasetSequence(newds);
781 final static void adjustAnnotations(Edit command, boolean insert,
782 boolean modifyVisibility, AlignmentI[] views)
784 AlignmentAnnotation[] annotations = null;
786 if (modifyVisibility && !insert)
788 // only occurs if a sequence was added or deleted.
789 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
791 if (command.fullAlignmentHeight)
793 annotations = command.al.getAlignmentAnnotation();
798 AlignmentAnnotation[] tmp;
799 for (int s = 0; s < command.seqs.length; s++)
801 command.seqs[s].sequenceChanged();
803 if (modifyVisibility)
805 // Rows are only removed or added to sequence object.
809 tmp = command.seqs[s].getAnnotation();
812 int alen = tmp.length;
813 for (int aa = 0; aa < tmp.length; aa++)
815 if (!command.al.deleteAnnotation(tmp[aa]))
817 // strip out annotation not in the current al (will be put
818 // back on insert in all views)
823 command.seqs[s].setAlignmentAnnotation(null);
824 if (alen != tmp.length)
826 // save the non-null annotation references only
827 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
828 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
832 saved[aapos++] = tmp[aa];
837 command.deletedAnnotationRows.put(command.seqs[s], saved);
838 // and then remove any annotation in the other views
839 for (int alview = 0; views != null
840 && alview < views.length; alview++)
842 if (views[alview] != command.al)
844 AlignmentAnnotation[] toremove = views[alview]
845 .getAlignmentAnnotation();
846 if (toremove == null || toremove.length == 0)
850 // remove any alignment annotation on this sequence that's
851 // on that alignment view.
852 for (int aa = 0; aa < toremove.length; aa++)
854 if (toremove[aa].sequenceRef == command.seqs[s])
856 views[alview].deleteAnnotation(toremove[aa]);
864 // save all the annotation
865 command.deletedAnnotationRows.put(command.seqs[s], tmp);
872 if (command.deletedAnnotationRows != null
873 && command.deletedAnnotationRows
874 .containsKey(command.seqs[s]))
876 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
877 .get(command.seqs[s]);
878 command.seqs[s].setAlignmentAnnotation(revealed);
879 if (revealed != null)
881 for (int aa = 0; aa < revealed.length; aa++)
883 // iterate through al adding original annotation
884 command.al.addAnnotation(revealed[aa]);
886 for (int aa = 0; aa < revealed.length; aa++)
888 command.al.setAnnotationIndex(revealed[aa], aa);
890 // and then duplicate added annotation on every other alignment
892 for (int vnum = 0; views != null
893 && vnum < views.length; vnum++)
895 if (views[vnum] != command.al)
897 int avwidth = views[vnum].getWidth() + 1;
898 // duplicate in this view
899 for (int a = 0; a < revealed.length; a++)
901 AlignmentAnnotation newann = new AlignmentAnnotation(
903 command.seqs[s].addAlignmentAnnotation(newann);
904 newann.padAnnotation(avwidth);
905 views[vnum].addAnnotation(newann);
906 views[vnum].setAnnotationIndex(newann, a);
916 if (command.seqs[s].getAnnotation() == null)
923 annotations = command.seqs[s].getAnnotation();
927 tmp = new AlignmentAnnotation[aSize
928 + command.seqs[s].getAnnotation().length];
930 System.arraycopy(annotations, 0, tmp, 0, aSize);
932 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
933 command.seqs[s].getAnnotation().length);
937 aSize = annotations.length;
941 if (annotations == null)
948 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
953 for (int a = 0; a < annotations.length; a++)
955 if (annotations[a].autoCalculated
956 || annotations[a].annotations == null)
963 aSize = annotations[a].annotations.length;
966 temp = new Annotation[aSize + command.number];
967 if (annotations[a].padGaps)
969 for (int aa = 0; aa < temp.length; aa++)
971 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
977 if (command.position < aSize)
979 if (command.position + command.number >= aSize)
985 tSize = aSize - command.number;
997 temp = new Annotation[tSize];
1002 if (command.position < annotations[a].annotations.length)
1004 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1007 if (command.deletedAnnotations != null
1008 && command.deletedAnnotations
1009 .containsKey(annotations[a].annotationId))
1011 Annotation[] restore = command.deletedAnnotations
1012 .get(annotations[a].annotationId);
1014 System.arraycopy(restore, 0, temp, command.position,
1019 System.arraycopy(annotations[a].annotations, command.position,
1020 temp, command.position + command.number,
1021 aSize - command.position);
1025 if (command.deletedAnnotations != null
1026 && command.deletedAnnotations
1027 .containsKey(annotations[a].annotationId))
1029 Annotation[] restore = command.deletedAnnotations
1030 .get(annotations[a].annotationId);
1032 temp = new Annotation[annotations[a].annotations.length
1034 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1035 annotations[a].annotations.length);
1036 System.arraycopy(restore, 0, temp,
1037 annotations[a].annotations.length, restore.length);
1041 temp = annotations[a].annotations;
1047 if (tSize != aSize || command.position < 2)
1049 int copylen = Math.min(command.position,
1050 annotations[a].annotations.length);
1053 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1054 copylen); // command.position);
1057 Annotation[] deleted = new Annotation[command.number];
1058 if (copylen >= command.position)
1060 copylen = Math.min(command.number,
1061 annotations[a].annotations.length - command.position);
1064 System.arraycopy(annotations[a].annotations, command.position,
1065 deleted, 0, copylen); // command.number);
1069 command.deletedAnnotations.put(annotations[a].annotationId,
1071 if (annotations[a].annotations.length > command.position
1074 System.arraycopy(annotations[a].annotations,
1075 command.position + command.number, temp,
1076 command.position, annotations[a].annotations.length
1077 - command.position - command.number); // aSize
1082 int dSize = aSize - command.position;
1086 Annotation[] deleted = new Annotation[command.number];
1087 System.arraycopy(annotations[a].annotations, command.position,
1090 command.deletedAnnotations.put(annotations[a].annotationId,
1093 tSize = Math.min(annotations[a].annotations.length,
1095 temp = new Annotation[tSize];
1096 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1100 temp = annotations[a].annotations;
1105 annotations[a].annotations = temp;
1109 final static void undoCutFeatures(Edit command, int index, final int i,
1112 SequenceI seq = command.seqs[index];
1113 SequenceI sequence = seq.getDatasetSequence();
1114 if (sequence == null)
1120 * TODO: shift right features that lie to the right of the restored cut
1121 * Currently not needed as all features restored with saved dataset sequence
1122 * nor if no saved dataset sequence (as coordinates left unchanged by Cut)
1126 * restore any features that were deleted or truncated
1128 if (command.deletedFeatures != null
1129 && command.deletedFeatures.containsKey(seq))
1131 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1133 sequence.addSequenceFeature(deleted);
1138 * delete any truncated features
1140 if (command.truncatedFeatures != null
1141 && command.truncatedFeatures.containsKey(seq))
1143 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1145 sequence.deleteFeature(amended);
1151 * Returns the list of edit commands wrapped by this object.
1155 public List<Edit> getEdits()
1161 * Returns a map whose keys are the dataset sequences, and values their
1162 * aligned sequences before the command edit list was applied. The aligned
1163 * sequences are copies, which may be updated without affecting the originals.
1165 * The command holds references to the aligned sequences (after editing). If
1166 * the command is an 'undo',then the prior state is simply the aligned state.
1167 * Otherwise, we have to derive the prior state by working backwards through
1168 * the edit list to infer the aligned sequences before editing.
1170 * Note: an alternative solution would be to cache the 'before' state of each
1171 * edit, but this would be expensive in space in the common case that the
1172 * original is never needed (edits are not mirrored).
1175 * @throws IllegalStateException
1176 * on detecting an edit command of a type that can't be unwound
1178 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1180 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1181 if (getEdits() == null)
1187 for (Edit e : getEdits())
1189 for (SequenceI seq : e.getSequences())
1191 SequenceI ds = seq.getDatasetSequence();
1192 // SequenceI preEdit = result.get(ds);
1193 if (!result.containsKey(ds))
1196 * copy sequence including start/end (but don't use copy constructor
1197 * as we don't need annotations)
1199 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1200 seq.getStart(), seq.getEnd());
1201 preEdit.setDatasetSequence(ds);
1202 result.put(ds, preEdit);
1210 * Work backwards through the edit list, deriving the sequences before each
1211 * was applied. The final result is the sequence set before any edits.
1213 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1214 while (editList.hasNext())
1216 Edit oldEdit = editList.next();
1217 Action action = oldEdit.getAction();
1218 int position = oldEdit.getPosition();
1219 int number = oldEdit.getNumber();
1220 final char gap = oldEdit.getGapCharacter();
1221 for (SequenceI seq : oldEdit.getSequences())
1223 SequenceI ds = seq.getDatasetSequence();
1224 SequenceI preEdit = result.get(ds);
1225 if (preEdit == null)
1227 preEdit = new Sequence("", seq.getSequenceAsString(),
1228 seq.getStart(), seq.getEnd());
1229 preEdit.setDatasetSequence(ds);
1230 result.put(ds, preEdit);
1233 * 'Undo' this edit action on the sequence (updating the value in the
1238 if (action == Action.DELETE_GAP)
1240 preEdit.setSequence(new String(StringUtils.insertCharAt(
1241 preEdit.getSequence(), position, number, gap)));
1243 else if (action == Action.INSERT_GAP)
1245 preEdit.setSequence(new String(StringUtils.deleteChars(
1246 preEdit.getSequence(), position, position + number)));
1250 System.err.println("Can't undo edit action " + action);
1251 // throw new IllegalStateException("Can't undo edit action " +
1262 public SequenceI[] oldds;
1264 boolean fullAlignmentHeight = false;
1266 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1268 Map<String, Annotation[]> deletedAnnotations;
1271 * features deleted by the cut (re-add on Undo)
1272 * (including the original of any shortened features)
1274 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1277 * shortened features added by the cut (delete on Undo)
1279 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1291 int position, number;
1295 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1300 this.position = pos;
1301 this.number = count;
1305 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1308 this(cmd, sqs, pos, count, align.getGapCharacter());
1312 alIndex = new int[sqs.length];
1313 for (int i = 0; i < sqs.length; i++)
1315 alIndex[i] = align.findIndex(sqs[i]);
1318 fullAlignmentHeight = (align.getHeight() == sqs.length);
1321 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1322 AlignmentI align, String replace)
1324 this(cmd, sqs, pos, count, align);
1326 string = new char[sqs.length][];
1327 for (int i = 0; i < sqs.length; i++)
1329 string[i] = replace.toCharArray();
1333 public SequenceI[] getSequences()
1338 public int getPosition()
1343 public Action getAction()
1348 public int getNumber()
1353 public char getGapCharacter()
1360 * Returns an iterator over the list of edit commands which traverses the list
1361 * either forwards or backwards.
1366 public Iterator<Edit> getEditIterator(boolean forwards)
1370 return getEdits().iterator();
1374 return new ReverseListIterator<Edit>(getEdits());
1379 * Adjusts features for Cut, and saves details of changes made to allow Undo
1381 * <li>features left of the cut are unchanged</li>
1382 * <li>features right of the cut are shifted left</li>
1383 * <li>features internal to the cut region are deleted</li>
1384 * <li>features that overlap or span the cut are shortened</li>
1385 * <li>the originals of any deleted or shorted features are saved, to re-add
1387 * <li>any added (shortened) features are saved, to delete on Undo</li>
1392 * @param fromPosition
1394 * @param cutIsInternal
1396 protected static void cutFeatures(Edit command, SequenceI seq,
1397 int fromPosition, int toPosition, boolean cutIsInternal)
1399 List<SequenceFeature> added = new ArrayList<>();
1400 List<SequenceFeature> removed = new ArrayList<>();
1402 SequenceFeaturesI featureStore = seq.getFeatures();
1403 if (toPosition < fromPosition || featureStore == null)
1408 int cutStartPos = fromPosition;
1409 int cutEndPos = toPosition;
1410 int cutWidth = cutEndPos - cutStartPos + 1;
1412 synchronized (featureStore)
1415 * get features that overlap the cut region
1417 List<SequenceFeature> toAmend = featureStore.findFeatures(
1418 cutStartPos, cutEndPos);
1421 * add any contact features that span the cut region
1422 * (not returned by findFeatures)
1424 for (SequenceFeature contact : featureStore.getContactFeatures())
1426 if (contact.getBegin() < cutStartPos
1427 && contact.getEnd() > cutEndPos)
1429 toAmend.add(contact);
1434 * adjust start-end of overlapping features;
1435 * delete features enclosed by the cut;
1436 * delete partially overlapping contact features
1438 for (SequenceFeature sf : toAmend)
1440 int sfBegin = sf.getBegin();
1441 int sfEnd = sf.getEnd();
1442 int newBegin = sfBegin;
1444 boolean toDelete = false;
1445 boolean follows = false;
1447 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1450 * feature lies within cut region - delete it
1454 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1457 * feature spans cut region - left-shift the end
1461 else if (sfEnd <= cutEndPos)
1464 * feature overlaps left of cut region - truncate right
1466 newEnd = cutStartPos - 1;
1467 if (sf.isContactFeature())
1472 else if (sfBegin >= cutStartPos)
1475 * remaining case - feature overlaps right
1476 * truncate left, adjust end of feature
1478 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1479 // newEnd = newBegin + (sfEnd - sfBegin) - overlapsBy;
1480 newEnd = newBegin + sfEnd - cutEndPos - 1;
1481 if (sf.isContactFeature())
1487 seq.deleteFeature(sf);
1494 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1495 sf.getFeatureGroup(), sf.getScore());
1496 seq.addSequenceFeature(copy);
1505 * and left shift any features lying to the right of the cut region
1506 * (but not if the cut is at start or end of sequence)
1510 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1515 * save deleted and amended features, so that Undo can
1516 * re-add or delete them respectively
1518 if (command.deletedFeatures == null)
1520 command.deletedFeatures = new HashMap<>();
1522 if (command.truncatedFeatures == null)
1524 command.truncatedFeatures = new HashMap<>();
1526 command.deletedFeatures.put(seq, removed);
1527 command.truncatedFeatures.put(seq, added);