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;
555 if (oldds != sequence.getDatasetSequence())
557 oldds.getFeatures().deleteAll();
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)
588 boolean seqWasDeleted = false;
590 for (int i = 0; i < command.seqs.length; i++)
594 boolean newDSNeeded = false;
595 boolean newDSWasNeeded = command.oldds != null
596 && command.oldds[i] != null;
597 SequenceI sequence = command.seqs[i];
598 if (sequence.getLength() < 1)
601 * sequence was deleted; re-add it to the alignment
603 if (command.alIndex[i] < command.al.getHeight())
605 List<SequenceI> sequences;
606 synchronized (sequences = command.al.getSequences())
608 if (!(command.alIndex[i] < 0))
610 sequences.add(command.alIndex[i], sequence);
616 command.al.addSequence(sequence);
618 seqWasDeleted = true;
620 int newStart = sequence.getStart();
621 int newEnd = sequence.getEnd();
623 StringBuilder tmp = new StringBuilder();
624 tmp.append(sequence.getSequence());
625 // Undo of a delete does not replace original dataset sequence on to
626 // alignment sequence.
628 if (command.string != null && command.string[i] != null)
630 if (command.position >= tmp.length())
632 // This occurs if padding is on, and residues
633 // are removed from end of alignment
634 int length = command.position - tmp.length();
637 tmp.append(command.gapChar);
641 tmp.insert(command.position, command.string[i]);
642 for (int s = 0; s < command.string[i].length; s++)
644 if (!Comparison.isGap(command.string[i][s]))
649 start = sequence.findPosition(command.position);
650 end = sequence.findPosition(command.position
653 if (sequence.getStart() == start)
663 command.string[i] = null;
666 sequence.setSequence(tmp.toString());
667 sequence.setStart(newStart);
668 sequence.setEnd(newEnd);
671 * command and Undo share the same dataset sequence if cut was
672 * at start or end of sequence
674 boolean sameDatasetSequence = false;
677 if (sequence.getDatasetSequence() != null)
682 ds = command.oldds[i];
686 // make a new DS sequence
687 // use new ds mechanism here
688 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
689 sequence.getSequenceAsString());
690 ds = new Sequence(sequence.getName(), ungapped,
691 sequence.getStart(), sequence.getEnd());
692 ds.setDescription(sequence.getDescription());
694 if (command.oldds == null)
696 command.oldds = new SequenceI[command.seqs.length];
698 command.oldds[i] = sequence.getDatasetSequence();
699 sameDatasetSequence = ds == sequence.getDatasetSequence();
700 ds.setSequenceFeatures(sequence.getSequenceFeatures());
701 sequence.setDatasetSequence(ds);
703 undoCutFeatures(command, i, start, end, sameDatasetSequence);
706 adjustAnnotations(command, true, seqWasDeleted, views);
708 command.string = null;
711 static void replace(Edit command)
715 int start = command.position;
716 int end = command.number;
717 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
719 // TODO Jalview 2.4 bugfix change to an aggregate command - original
720 // sequence string is cut, new string is pasted in.
721 command.number = start + command.string[0].length;
722 for (int i = 0; i < command.seqs.length; i++)
724 boolean newDSWasNeeded = command.oldds != null
725 && command.oldds[i] != null;
728 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
729 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
730 * viewport.alignment));
734 * then addHistoryItem(new EditCommand( "Add sequences",
735 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
738 oldstring = command.seqs[i].getSequenceAsString();
739 tmp = new StringBuffer(oldstring.substring(0, start));
740 tmp.append(command.string[i]);
741 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
742 jalview.util.Comparison.GapChars, new String(
744 int ipos = command.seqs[i].findPosition(start)
745 - command.seqs[i].getStart();
746 tmp.append(oldstring.substring(end));
747 command.seqs[i].setSequence(tmp.toString());
748 command.string[i] = oldstring.substring(start, end).toCharArray();
749 String nogapold = jalview.analysis.AlignSeq.extractGaps(
750 jalview.util.Comparison.GapChars, new String(
752 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
756 SequenceI oldds = command.seqs[i].getDatasetSequence();
757 command.seqs[i].setDatasetSequence(command.oldds[i]);
758 command.oldds[i] = oldds;
762 if (command.oldds == null)
764 command.oldds = new SequenceI[command.seqs.length];
766 command.oldds[i] = command.seqs[i].getDatasetSequence();
767 SequenceI newds = new Sequence(
768 command.seqs[i].getDatasetSequence());
769 String fullseq, osp = newds.getSequenceAsString();
770 fullseq = osp.substring(0, ipos) + nogaprep
771 + osp.substring(ipos + nogaprep.length());
772 newds.setSequence(fullseq.toUpperCase());
773 // TODO: JAL-1131 ensure newly created dataset sequence is added to
775 // dataset sequences associated with the alignment.
776 // TODO: JAL-1131 fix up any annotation associated with new dataset
777 // sequence to ensure that original sequence/annotation relationships
779 command.seqs[i].setDatasetSequence(newds);
788 final static void adjustAnnotations(Edit command, boolean insert,
789 boolean modifyVisibility, AlignmentI[] views)
791 AlignmentAnnotation[] annotations = null;
793 if (modifyVisibility && !insert)
795 // only occurs if a sequence was added or deleted.
796 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
798 if (command.fullAlignmentHeight)
800 annotations = command.al.getAlignmentAnnotation();
805 AlignmentAnnotation[] tmp;
806 for (int s = 0; s < command.seqs.length; s++)
808 command.seqs[s].sequenceChanged();
810 if (modifyVisibility)
812 // Rows are only removed or added to sequence object.
816 tmp = command.seqs[s].getAnnotation();
819 int alen = tmp.length;
820 for (int aa = 0; aa < tmp.length; aa++)
822 if (!command.al.deleteAnnotation(tmp[aa]))
824 // strip out annotation not in the current al (will be put
825 // back on insert in all views)
830 command.seqs[s].setAlignmentAnnotation(null);
831 if (alen != tmp.length)
833 // save the non-null annotation references only
834 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
835 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
839 saved[aapos++] = tmp[aa];
844 command.deletedAnnotationRows.put(command.seqs[s], saved);
845 // and then remove any annotation in the other views
846 for (int alview = 0; views != null && alview < views.length; alview++)
848 if (views[alview] != command.al)
850 AlignmentAnnotation[] toremove = views[alview]
851 .getAlignmentAnnotation();
852 if (toremove == null || toremove.length == 0)
856 // remove any alignment annotation on this sequence that's
857 // on that alignment view.
858 for (int aa = 0; aa < toremove.length; aa++)
860 if (toremove[aa].sequenceRef == command.seqs[s])
862 views[alview].deleteAnnotation(toremove[aa]);
870 // save all the annotation
871 command.deletedAnnotationRows.put(command.seqs[s], tmp);
878 if (command.deletedAnnotationRows != null
879 && command.deletedAnnotationRows
880 .containsKey(command.seqs[s]))
882 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
883 .get(command.seqs[s]);
884 command.seqs[s].setAlignmentAnnotation(revealed);
885 if (revealed != null)
887 for (int aa = 0; aa < revealed.length; aa++)
889 // iterate through al adding original annotation
890 command.al.addAnnotation(revealed[aa]);
892 for (int aa = 0; aa < revealed.length; aa++)
894 command.al.setAnnotationIndex(revealed[aa], aa);
896 // and then duplicate added annotation on every other alignment
898 for (int vnum = 0; views != null && vnum < views.length; vnum++)
900 if (views[vnum] != command.al)
902 int avwidth = views[vnum].getWidth() + 1;
903 // duplicate in this view
904 for (int a = 0; a < revealed.length; a++)
906 AlignmentAnnotation newann = new AlignmentAnnotation(
908 command.seqs[s].addAlignmentAnnotation(newann);
909 newann.padAnnotation(avwidth);
910 views[vnum].addAnnotation(newann);
911 views[vnum].setAnnotationIndex(newann, a);
921 if (command.seqs[s].getAnnotation() == null)
928 annotations = command.seqs[s].getAnnotation();
932 tmp = new AlignmentAnnotation[aSize
933 + command.seqs[s].getAnnotation().length];
935 System.arraycopy(annotations, 0, tmp, 0, aSize);
937 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
938 command.seqs[s].getAnnotation().length);
942 aSize = annotations.length;
946 if (annotations == null)
953 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
958 for (int a = 0; a < annotations.length; a++)
960 if (annotations[a].autoCalculated
961 || annotations[a].annotations == null)
968 aSize = annotations[a].annotations.length;
971 temp = new Annotation[aSize + command.number];
972 if (annotations[a].padGaps)
974 for (int aa = 0; aa < temp.length; aa++)
976 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
982 if (command.position < aSize)
984 if (command.position + command.number >= aSize)
990 tSize = aSize - command.number;
1002 temp = new Annotation[tSize];
1007 if (command.position < annotations[a].annotations.length)
1009 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1012 if (command.deletedAnnotations != null
1013 && command.deletedAnnotations
1014 .containsKey(annotations[a].annotationId))
1016 Annotation[] restore = command.deletedAnnotations
1017 .get(annotations[a].annotationId);
1019 System.arraycopy(restore, 0, temp, command.position,
1024 System.arraycopy(annotations[a].annotations, command.position,
1025 temp, command.position + command.number, aSize
1026 - command.position);
1030 if (command.deletedAnnotations != null
1031 && command.deletedAnnotations
1032 .containsKey(annotations[a].annotationId))
1034 Annotation[] restore = command.deletedAnnotations
1035 .get(annotations[a].annotationId);
1037 temp = new Annotation[annotations[a].annotations.length
1039 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1040 annotations[a].annotations.length);
1041 System.arraycopy(restore, 0, temp,
1042 annotations[a].annotations.length, restore.length);
1046 temp = annotations[a].annotations;
1052 if (tSize != aSize || command.position < 2)
1054 int copylen = Math.min(command.position,
1055 annotations[a].annotations.length);
1058 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1059 copylen); // command.position);
1062 Annotation[] deleted = new Annotation[command.number];
1063 if (copylen >= command.position)
1065 copylen = Math.min(command.number,
1066 annotations[a].annotations.length - command.position);
1069 System.arraycopy(annotations[a].annotations,
1070 command.position, deleted, 0, copylen); // command.number);
1074 command.deletedAnnotations.put(annotations[a].annotationId,
1076 if (annotations[a].annotations.length > command.position
1079 System.arraycopy(annotations[a].annotations, command.position
1080 + command.number, temp, command.position,
1081 annotations[a].annotations.length - command.position
1082 - command.number); // aSize
1087 int dSize = aSize - command.position;
1091 Annotation[] deleted = new Annotation[command.number];
1092 System.arraycopy(annotations[a].annotations, command.position,
1095 command.deletedAnnotations.put(annotations[a].annotationId,
1098 tSize = Math.min(annotations[a].annotations.length,
1100 temp = new Annotation[tSize];
1101 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1105 temp = annotations[a].annotations;
1110 annotations[a].annotations = temp;
1114 final static void undoCutFeatures(Edit command, int index, final int i,
1115 final int j, boolean sameDatasetSequence)
1117 SequenceI seq = command.seqs[index];
1118 SequenceI sequence = seq.getDatasetSequence();
1119 if (sequence == null)
1125 * shift right features that lie to the right of the restored cut
1126 * (but not if dataset sequence unchanged - coordinates left unchanged by Cut)
1128 if (!sameDatasetSequence)
1130 seq.getFeatures().shiftFeatures(i + 1, j - i);
1134 * restore any features that were deleted or truncated
1136 if (command.deletedFeatures != null
1137 && command.deletedFeatures.containsKey(seq))
1139 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1141 sequence.addSequenceFeature(deleted);
1146 * delete any truncated features
1148 if (command.truncatedFeatures != null
1149 && command.truncatedFeatures.containsKey(seq))
1151 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1153 sequence.deleteFeature(amended);
1159 * Returns the list of edit commands wrapped by this object.
1163 public List<Edit> getEdits()
1169 * Returns a map whose keys are the dataset sequences, and values their
1170 * aligned sequences before the command edit list was applied. The aligned
1171 * sequences are copies, which may be updated without affecting the originals.
1173 * The command holds references to the aligned sequences (after editing). If
1174 * the command is an 'undo',then the prior state is simply the aligned state.
1175 * Otherwise, we have to derive the prior state by working backwards through
1176 * the edit list to infer the aligned sequences before editing.
1178 * Note: an alternative solution would be to cache the 'before' state of each
1179 * edit, but this would be expensive in space in the common case that the
1180 * original is never needed (edits are not mirrored).
1183 * @throws IllegalStateException
1184 * on detecting an edit command of a type that can't be unwound
1186 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1188 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1189 if (getEdits() == null)
1195 for (Edit e : getEdits())
1197 for (SequenceI seq : e.getSequences())
1199 SequenceI ds = seq.getDatasetSequence();
1200 // SequenceI preEdit = result.get(ds);
1201 if (!result.containsKey(ds))
1204 * copy sequence including start/end (but don't use copy constructor
1205 * as we don't need annotations)
1207 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1208 seq.getStart(), seq.getEnd());
1209 preEdit.setDatasetSequence(ds);
1210 result.put(ds, preEdit);
1218 * Work backwards through the edit list, deriving the sequences before each
1219 * was applied. The final result is the sequence set before any edits.
1221 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1222 while (editList.hasNext())
1224 Edit oldEdit = editList.next();
1225 Action action = oldEdit.getAction();
1226 int position = oldEdit.getPosition();
1227 int number = oldEdit.getNumber();
1228 final char gap = oldEdit.getGapCharacter();
1229 for (SequenceI seq : oldEdit.getSequences())
1231 SequenceI ds = seq.getDatasetSequence();
1232 SequenceI preEdit = result.get(ds);
1233 if (preEdit == null)
1235 preEdit = new Sequence("", seq.getSequenceAsString(),
1236 seq.getStart(), seq.getEnd());
1237 preEdit.setDatasetSequence(ds);
1238 result.put(ds, preEdit);
1241 * 'Undo' this edit action on the sequence (updating the value in the
1246 if (action == Action.DELETE_GAP)
1248 preEdit.setSequence(new String(StringUtils.insertCharAt(
1249 preEdit.getSequence(), position, number, gap)));
1251 else if (action == Action.INSERT_GAP)
1253 preEdit.setSequence(new String(StringUtils.deleteChars(
1254 preEdit.getSequence(), position, position + number)));
1258 System.err.println("Can't undo edit action " + action);
1259 // throw new IllegalStateException("Can't undo edit action " +
1270 public SequenceI[] oldds;
1272 boolean fullAlignmentHeight = false;
1274 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1276 Map<String, Annotation[]> deletedAnnotations;
1279 * features deleted by the cut (re-add on Undo)
1280 * (including the original of any shortened features)
1282 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1285 * shortened features added by the cut (delete on Undo)
1287 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1299 int position, number;
1303 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1308 this.position = pos;
1309 this.number = count;
1313 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1316 this(cmd, sqs, pos, count, align.getGapCharacter());
1320 alIndex = new int[sqs.length];
1321 for (int i = 0; i < sqs.length; i++)
1323 alIndex[i] = align.findIndex(sqs[i]);
1326 fullAlignmentHeight = (align.getHeight() == sqs.length);
1329 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1330 AlignmentI align, String replace)
1332 this(cmd, sqs, pos, count, align);
1334 string = new char[sqs.length][];
1335 for (int i = 0; i < sqs.length; i++)
1337 string[i] = replace.toCharArray();
1341 public SequenceI[] getSequences()
1346 public int getPosition()
1351 public Action getAction()
1356 public int getNumber()
1361 public char getGapCharacter()
1368 * Returns an iterator over the list of edit commands which traverses the list
1369 * either forwards or backwards.
1374 public Iterator<Edit> getEditIterator(boolean forwards)
1378 return getEdits().iterator();
1382 return new ReverseListIterator<Edit>(getEdits());
1387 * Adjusts features for Cut, and saves details of changes made to allow Undo
1389 * <li>features left of the cut are unchanged</li>
1390 * <li>features right of the cut are shifted left</li>
1391 * <li>features internal to the cut region are deleted</li>
1392 * <li>features that overlap or span the cut are shortened</li>
1393 * <li>the originals of any deleted or shorted features are saved, to re-add
1395 * <li>any added (shortened) features are saved, to delete on Undo</li>
1400 * @param fromPosition
1402 * @param cutIsInternal
1404 protected static void cutFeatures(Edit command, SequenceI seq,
1405 int fromPosition, int toPosition, boolean cutIsInternal)
1407 List<SequenceFeature> added = new ArrayList<>();
1408 List<SequenceFeature> removed = new ArrayList<>();
1410 SequenceFeaturesI featureStore = seq.getFeatures();
1411 if (toPosition < fromPosition || featureStore == null)
1416 int cutStartPos = fromPosition;
1417 int cutEndPos = toPosition;
1418 int cutWidth = cutEndPos - cutStartPos + 1;
1420 synchronized (featureStore)
1423 * get features that overlap the cut region
1425 List<SequenceFeature> toAmend = featureStore.findFeatures(
1426 cutStartPos, cutEndPos);
1429 * add any contact features that span the cut region
1430 * (not returned by findFeatures)
1432 for (SequenceFeature contact : featureStore.getContactFeatures())
1434 if (contact.getBegin() < cutStartPos
1435 && contact.getEnd() > cutEndPos)
1437 toAmend.add(contact);
1442 * adjust start-end of overlapping features;
1443 * delete features enclosed by the cut;
1444 * delete partially overlapping contact features
1446 for (SequenceFeature sf : toAmend)
1448 int sfBegin = sf.getBegin();
1449 int sfEnd = sf.getEnd();
1450 int newBegin = sfBegin;
1452 boolean toDelete = false;
1454 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1457 * feature lies within cut region - delete it
1461 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1464 * feature spans cut region - left-shift the end
1468 else if (sfEnd <= cutEndPos)
1471 * feature overlaps left of cut region - truncate right
1473 newEnd = cutStartPos - 1;
1474 if (sf.isContactFeature())
1479 else if (sfBegin >= cutStartPos)
1482 * remaining case - feature overlaps right
1483 * truncate left, adjust end of feature
1485 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1486 newEnd = newBegin + sfEnd - cutEndPos - 1;
1487 if (sf.isContactFeature())
1493 seq.deleteFeature(sf);
1497 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1498 sf.getFeatureGroup(), sf.getScore());
1499 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);