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;
559 if (oldds != sequence.getDatasetSequence())
561 oldds.getFeatures().deleteAll();
564 if (cutPositions != null)
566 cutFeatures(command, sequence, cutPositions.getBegin(),
567 cutPositions.getEnd(), cutIsInternal);
573 if (sequence.getLength() < 1)
575 command.al.deleteSequence(sequence);
580 adjustAnnotations(command, false, seqDeleted, views);
584 * Perform the given Paste command. This may be to add cut or copied sequences
585 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
590 static void paste(Edit command, AlignmentI[] views)
592 boolean seqWasDeleted = false;
594 for (int i = 0; i < command.seqs.length; i++)
598 boolean newDSNeeded = false;
599 boolean newDSWasNeeded = command.oldds != null
600 && command.oldds[i] != null;
601 SequenceI sequence = command.seqs[i];
602 if (sequence.getLength() < 1)
605 * sequence was deleted; re-add it to the alignment
607 if (command.alIndex[i] < command.al.getHeight())
609 List<SequenceI> sequences;
610 synchronized (sequences = command.al.getSequences())
612 if (!(command.alIndex[i] < 0))
614 sequences.add(command.alIndex[i], sequence);
620 command.al.addSequence(sequence);
622 seqWasDeleted = true;
624 int newStart = sequence.getStart();
625 int newEnd = sequence.getEnd();
627 StringBuilder tmp = new StringBuilder();
628 tmp.append(sequence.getSequence());
629 // Undo of a delete does not replace original dataset sequence on to
630 // alignment sequence.
632 if (command.string != null && command.string[i] != null)
634 if (command.position >= tmp.length())
636 // This occurs if padding is on, and residues
637 // are removed from end of alignment
638 int length = command.position - tmp.length();
641 tmp.append(command.gapChar);
645 tmp.insert(command.position, command.string[i]);
646 for (int s = 0; s < command.string[i].length; s++)
648 if (!Comparison.isGap(command.string[i][s]))
653 start = sequence.findPosition(command.position);
654 end = sequence.findPosition(command.position
657 if (sequence.getStart() == start)
667 command.string[i] = null;
670 sequence.setSequence(tmp.toString());
671 sequence.setStart(newStart);
672 sequence.setEnd(newEnd);
675 * command and Undo share the same dataset sequence if cut was
676 * at start or end of sequence
678 boolean sameDatasetSequence = false;
681 if (sequence.getDatasetSequence() != null)
686 ds = command.oldds[i];
690 // make a new DS sequence
691 // use new ds mechanism here
692 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
693 sequence.getSequenceAsString());
694 ds = new Sequence(sequence.getName(), ungapped,
695 sequence.getStart(), sequence.getEnd());
696 ds.setDescription(sequence.getDescription());
698 if (command.oldds == null)
700 command.oldds = new SequenceI[command.seqs.length];
702 command.oldds[i] = sequence.getDatasetSequence();
703 sameDatasetSequence = ds == sequence.getDatasetSequence();
704 ds.setSequenceFeatures(sequence.getSequenceFeatures());
705 sequence.setDatasetSequence(ds);
707 undoCutFeatures(command, i, start, end, sameDatasetSequence);
710 adjustAnnotations(command, true, seqWasDeleted, views);
712 command.string = null;
715 static void replace(Edit command)
719 int start = command.position;
720 int end = command.number;
721 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
723 // TODO Jalview 2.4 bugfix change to an aggregate command - original
724 // sequence string is cut, new string is pasted in.
725 command.number = start + command.string[0].length;
726 for (int i = 0; i < command.seqs.length; i++)
728 boolean newDSWasNeeded = command.oldds != null
729 && command.oldds[i] != null;
732 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
733 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
734 * viewport.alignment));
738 * then addHistoryItem(new EditCommand( "Add sequences",
739 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
742 oldstring = command.seqs[i].getSequenceAsString();
743 tmp = new StringBuffer(oldstring.substring(0, start));
744 tmp.append(command.string[i]);
745 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
746 jalview.util.Comparison.GapChars,
747 new String(command.string[i]));
748 int ipos = command.seqs[i].findPosition(start)
749 - command.seqs[i].getStart();
750 tmp.append(oldstring.substring(end));
751 command.seqs[i].setSequence(tmp.toString());
752 command.string[i] = oldstring.substring(start, end).toCharArray();
753 String nogapold = jalview.analysis.AlignSeq.extractGaps(
754 jalview.util.Comparison.GapChars,
755 new String(command.string[i]));
756 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
760 SequenceI oldds = command.seqs[i].getDatasetSequence();
761 command.seqs[i].setDatasetSequence(command.oldds[i]);
762 command.oldds[i] = oldds;
766 if (command.oldds == null)
768 command.oldds = new SequenceI[command.seqs.length];
770 command.oldds[i] = command.seqs[i].getDatasetSequence();
771 SequenceI newds = new Sequence(
772 command.seqs[i].getDatasetSequence());
773 String fullseq, osp = newds.getSequenceAsString();
774 fullseq = osp.substring(0, ipos) + nogaprep
775 + osp.substring(ipos + nogaprep.length());
776 newds.setSequence(fullseq.toUpperCase());
777 // TODO: JAL-1131 ensure newly created dataset sequence is added to
779 // dataset sequences associated with the alignment.
780 // TODO: JAL-1131 fix up any annotation associated with new dataset
781 // sequence to ensure that original sequence/annotation relationships
783 command.seqs[i].setDatasetSequence(newds);
792 final static void adjustAnnotations(Edit command, boolean insert,
793 boolean modifyVisibility, AlignmentI[] views)
795 AlignmentAnnotation[] annotations = null;
797 if (modifyVisibility && !insert)
799 // only occurs if a sequence was added or deleted.
800 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
802 if (command.fullAlignmentHeight)
804 annotations = command.al.getAlignmentAnnotation();
809 AlignmentAnnotation[] tmp;
810 for (int s = 0; s < command.seqs.length; s++)
812 command.seqs[s].sequenceChanged();
814 if (modifyVisibility)
816 // Rows are only removed or added to sequence object.
820 tmp = command.seqs[s].getAnnotation();
823 int alen = tmp.length;
824 for (int aa = 0; aa < tmp.length; aa++)
826 if (!command.al.deleteAnnotation(tmp[aa]))
828 // strip out annotation not in the current al (will be put
829 // back on insert in all views)
834 command.seqs[s].setAlignmentAnnotation(null);
835 if (alen != tmp.length)
837 // save the non-null annotation references only
838 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
839 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
843 saved[aapos++] = tmp[aa];
848 command.deletedAnnotationRows.put(command.seqs[s], saved);
849 // and then remove any annotation in the other views
850 for (int alview = 0; views != null
851 && alview < views.length; alview++)
853 if (views[alview] != command.al)
855 AlignmentAnnotation[] toremove = views[alview]
856 .getAlignmentAnnotation();
857 if (toremove == null || toremove.length == 0)
861 // remove any alignment annotation on this sequence that's
862 // on that alignment view.
863 for (int aa = 0; aa < toremove.length; aa++)
865 if (toremove[aa].sequenceRef == command.seqs[s])
867 views[alview].deleteAnnotation(toremove[aa]);
875 // save all the annotation
876 command.deletedAnnotationRows.put(command.seqs[s], tmp);
883 if (command.deletedAnnotationRows != null
884 && command.deletedAnnotationRows
885 .containsKey(command.seqs[s]))
887 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
888 .get(command.seqs[s]);
889 command.seqs[s].setAlignmentAnnotation(revealed);
890 if (revealed != null)
892 for (int aa = 0; aa < revealed.length; aa++)
894 // iterate through al adding original annotation
895 command.al.addAnnotation(revealed[aa]);
897 for (int aa = 0; aa < revealed.length; aa++)
899 command.al.setAnnotationIndex(revealed[aa], aa);
901 // and then duplicate added annotation on every other alignment
903 for (int vnum = 0; views != null
904 && vnum < views.length; vnum++)
906 if (views[vnum] != command.al)
908 int avwidth = views[vnum].getWidth() + 1;
909 // duplicate in this view
910 for (int a = 0; a < revealed.length; a++)
912 AlignmentAnnotation newann = new AlignmentAnnotation(
914 command.seqs[s].addAlignmentAnnotation(newann);
915 newann.padAnnotation(avwidth);
916 views[vnum].addAnnotation(newann);
917 views[vnum].setAnnotationIndex(newann, a);
927 if (command.seqs[s].getAnnotation() == null)
934 annotations = command.seqs[s].getAnnotation();
938 tmp = new AlignmentAnnotation[aSize
939 + command.seqs[s].getAnnotation().length];
941 System.arraycopy(annotations, 0, tmp, 0, aSize);
943 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
944 command.seqs[s].getAnnotation().length);
948 aSize = annotations.length;
952 if (annotations == null)
959 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
964 for (int a = 0; a < annotations.length; a++)
966 if (annotations[a].autoCalculated
967 || annotations[a].annotations == null)
974 aSize = annotations[a].annotations.length;
977 temp = new Annotation[aSize + command.number];
978 if (annotations[a].padGaps)
980 for (int aa = 0; aa < temp.length; aa++)
982 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
988 if (command.position < aSize)
990 if (command.position + command.number >= aSize)
996 tSize = aSize - command.number;
1008 temp = new Annotation[tSize];
1013 if (command.position < annotations[a].annotations.length)
1015 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1018 if (command.deletedAnnotations != null
1019 && command.deletedAnnotations
1020 .containsKey(annotations[a].annotationId))
1022 Annotation[] restore = command.deletedAnnotations
1023 .get(annotations[a].annotationId);
1025 System.arraycopy(restore, 0, temp, command.position,
1030 System.arraycopy(annotations[a].annotations, command.position,
1031 temp, command.position + command.number,
1032 aSize - command.position);
1036 if (command.deletedAnnotations != null
1037 && command.deletedAnnotations
1038 .containsKey(annotations[a].annotationId))
1040 Annotation[] restore = command.deletedAnnotations
1041 .get(annotations[a].annotationId);
1043 temp = new Annotation[annotations[a].annotations.length
1045 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1046 annotations[a].annotations.length);
1047 System.arraycopy(restore, 0, temp,
1048 annotations[a].annotations.length, restore.length);
1052 temp = annotations[a].annotations;
1058 if (tSize != aSize || command.position < 2)
1060 int copylen = Math.min(command.position,
1061 annotations[a].annotations.length);
1064 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1065 copylen); // command.position);
1068 Annotation[] deleted = new Annotation[command.number];
1069 if (copylen >= command.position)
1071 copylen = Math.min(command.number,
1072 annotations[a].annotations.length - command.position);
1075 System.arraycopy(annotations[a].annotations, command.position,
1076 deleted, 0, copylen); // command.number);
1080 command.deletedAnnotations.put(annotations[a].annotationId,
1082 if (annotations[a].annotations.length > command.position
1085 System.arraycopy(annotations[a].annotations,
1086 command.position + command.number, temp,
1087 command.position, annotations[a].annotations.length
1088 - command.position - command.number); // aSize
1093 int dSize = aSize - command.position;
1097 Annotation[] deleted = new Annotation[command.number];
1098 System.arraycopy(annotations[a].annotations, command.position,
1101 command.deletedAnnotations.put(annotations[a].annotationId,
1104 tSize = Math.min(annotations[a].annotations.length,
1106 temp = new Annotation[tSize];
1107 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1111 temp = annotations[a].annotations;
1116 annotations[a].annotations = temp;
1120 final static void undoCutFeatures(Edit command, int index, final int i,
1121 final int j, boolean sameDatasetSequence)
1123 SequenceI seq = command.seqs[index];
1124 SequenceI sequence = seq.getDatasetSequence();
1125 if (sequence == null)
1131 * shift right features that lie to the right of the restored cut
1132 * (but not if dataset sequence unchanged - coordinates left unchanged by Cut)
1134 if (!sameDatasetSequence)
1136 seq.getFeatures().shiftFeatures(i + 1, j - i);
1140 * restore any features that were deleted or truncated
1142 if (command.deletedFeatures != null
1143 && command.deletedFeatures.containsKey(seq))
1145 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1147 sequence.addSequenceFeature(deleted);
1152 * delete any truncated features
1154 if (command.truncatedFeatures != null
1155 && command.truncatedFeatures.containsKey(seq))
1157 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1159 sequence.deleteFeature(amended);
1165 * Returns the list of edit commands wrapped by this object.
1169 public List<Edit> getEdits()
1175 * Returns a map whose keys are the dataset sequences, and values their
1176 * aligned sequences before the command edit list was applied. The aligned
1177 * sequences are copies, which may be updated without affecting the originals.
1179 * The command holds references to the aligned sequences (after editing). If
1180 * the command is an 'undo',then the prior state is simply the aligned state.
1181 * Otherwise, we have to derive the prior state by working backwards through
1182 * the edit list to infer the aligned sequences before editing.
1184 * Note: an alternative solution would be to cache the 'before' state of each
1185 * edit, but this would be expensive in space in the common case that the
1186 * original is never needed (edits are not mirrored).
1189 * @throws IllegalStateException
1190 * on detecting an edit command of a type that can't be unwound
1192 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1194 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1195 if (getEdits() == null)
1201 for (Edit e : getEdits())
1203 for (SequenceI seq : e.getSequences())
1205 SequenceI ds = seq.getDatasetSequence();
1206 // SequenceI preEdit = result.get(ds);
1207 if (!result.containsKey(ds))
1210 * copy sequence including start/end (but don't use copy constructor
1211 * as we don't need annotations)
1213 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1214 seq.getStart(), seq.getEnd());
1215 preEdit.setDatasetSequence(ds);
1216 result.put(ds, preEdit);
1224 * Work backwards through the edit list, deriving the sequences before each
1225 * was applied. The final result is the sequence set before any edits.
1227 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1228 while (editList.hasNext())
1230 Edit oldEdit = editList.next();
1231 Action action = oldEdit.getAction();
1232 int position = oldEdit.getPosition();
1233 int number = oldEdit.getNumber();
1234 final char gap = oldEdit.getGapCharacter();
1235 for (SequenceI seq : oldEdit.getSequences())
1237 SequenceI ds = seq.getDatasetSequence();
1238 SequenceI preEdit = result.get(ds);
1239 if (preEdit == null)
1241 preEdit = new Sequence("", seq.getSequenceAsString(),
1242 seq.getStart(), seq.getEnd());
1243 preEdit.setDatasetSequence(ds);
1244 result.put(ds, preEdit);
1247 * 'Undo' this edit action on the sequence (updating the value in the
1252 if (action == Action.DELETE_GAP)
1254 preEdit.setSequence(new String(StringUtils.insertCharAt(
1255 preEdit.getSequence(), position, number, gap)));
1257 else if (action == Action.INSERT_GAP)
1259 preEdit.setSequence(new String(StringUtils.deleteChars(
1260 preEdit.getSequence(), position, position + number)));
1264 System.err.println("Can't undo edit action " + action);
1265 // throw new IllegalStateException("Can't undo edit action " +
1276 public SequenceI[] oldds;
1278 boolean fullAlignmentHeight = false;
1280 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1282 Map<String, Annotation[]> deletedAnnotations;
1285 * features deleted by the cut (re-add on Undo)
1286 * (including the original of any shortened features)
1288 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1291 * shortened features added by the cut (delete on Undo)
1293 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1305 int position, number;
1309 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1314 this.position = pos;
1315 this.number = count;
1319 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1322 this(cmd, sqs, pos, count, align.getGapCharacter());
1326 alIndex = new int[sqs.length];
1327 for (int i = 0; i < sqs.length; i++)
1329 alIndex[i] = align.findIndex(sqs[i]);
1332 fullAlignmentHeight = (align.getHeight() == sqs.length);
1335 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1336 AlignmentI align, String replace)
1338 this(cmd, sqs, pos, count, align);
1340 string = new char[sqs.length][];
1341 for (int i = 0; i < sqs.length; i++)
1343 string[i] = replace.toCharArray();
1347 public SequenceI[] getSequences()
1352 public int getPosition()
1357 public Action getAction()
1362 public int getNumber()
1367 public char getGapCharacter()
1374 * Returns an iterator over the list of edit commands which traverses the list
1375 * either forwards or backwards.
1380 public Iterator<Edit> getEditIterator(boolean forwards)
1384 return getEdits().iterator();
1388 return new ReverseListIterator<Edit>(getEdits());
1393 * Adjusts features for Cut, and saves details of changes made to allow Undo
1395 * <li>features left of the cut are unchanged</li>
1396 * <li>features right of the cut are shifted left</li>
1397 * <li>features internal to the cut region are deleted</li>
1398 * <li>features that overlap or span the cut are shortened</li>
1399 * <li>the originals of any deleted or shorted features are saved, to re-add
1401 * <li>any added (shortened) features are saved, to delete on Undo</li>
1406 * @param fromPosition
1408 * @param cutIsInternal
1410 protected static void cutFeatures(Edit command, SequenceI seq,
1411 int fromPosition, int toPosition, boolean cutIsInternal)
1413 List<SequenceFeature> added = new ArrayList<>();
1414 List<SequenceFeature> removed = new ArrayList<>();
1416 SequenceFeaturesI featureStore = seq.getFeatures();
1417 if (toPosition < fromPosition || featureStore == null)
1422 int cutStartPos = fromPosition;
1423 int cutEndPos = toPosition;
1424 int cutWidth = cutEndPos - cutStartPos + 1;
1426 synchronized (featureStore)
1429 * get features that overlap the cut region
1431 List<SequenceFeature> toAmend = featureStore.findFeatures(
1432 cutStartPos, cutEndPos);
1435 * add any contact features that span the cut region
1436 * (not returned by findFeatures)
1438 for (SequenceFeature contact : featureStore.getContactFeatures())
1440 if (contact.getBegin() < cutStartPos
1441 && contact.getEnd() > cutEndPos)
1443 toAmend.add(contact);
1448 * adjust start-end of overlapping features;
1449 * delete features enclosed by the cut;
1450 * delete partially overlapping contact features
1452 for (SequenceFeature sf : toAmend)
1454 int sfBegin = sf.getBegin();
1455 int sfEnd = sf.getEnd();
1456 int newBegin = sfBegin;
1458 boolean toDelete = false;
1459 boolean follows = false;
1461 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1464 * feature lies within cut region - delete it
1468 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1471 * feature spans cut region - left-shift the end
1475 else if (sfEnd <= cutEndPos)
1478 * feature overlaps left of cut region - truncate right
1480 newEnd = cutStartPos - 1;
1481 if (sf.isContactFeature())
1486 else if (sfBegin >= cutStartPos)
1489 * remaining case - feature overlaps right
1490 * truncate left, adjust end of feature
1492 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1493 newEnd = newBegin + sfEnd - cutEndPos - 1;
1494 if (sf.isContactFeature())
1500 seq.deleteFeature(sf);
1507 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1508 sf.getFeatureGroup(), sf.getScore());
1509 seq.addSequenceFeature(copy);
1518 * and left shift any features lying to the right of the cut region
1519 * (but not if the cut is at start or end of sequence)
1523 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1528 * save deleted and amended features, so that Undo can
1529 * re-add or delete them respectively
1531 if (command.deletedFeatures == null)
1533 command.deletedFeatures = new HashMap<>();
1535 if (command.truncatedFeatures == null)
1537 command.truncatedFeatures = new HashMap<>();
1539 command.deletedFeatures.put(seq, removed);
1540 command.truncatedFeatures.put(seq, added);