import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.Annotation;
+import jalview.datamodel.Range;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeaturesI;
import jalview.util.Comparison;
import jalview.util.ReverseListIterator;
import jalview.util.StringUtils;
int position, int number, AlignmentI al, boolean performEdit,
AlignmentI[] views)
{
- Edit edit = new Edit(command, seqs, position, number,
- al.getGapCharacter());
- if (al.getHeight() == seqs.length)
- {
- edit.al = al;
- edit.fullAlignmentHeight = true;
- }
-
- addEdit(edit);
-
- if (performEdit)
- {
- performEdit(edit, views);
- }
+ Edit edit = new Edit(command, seqs, position, number, al);
+ appendEdit(edit, al, performEdit, views);
}
/**
// we are redoing an undone cut.
sequence.setDatasetSequence(null);
}
- sequence.deleteChars(command.position,
+ Range cutPositions = sequence.findPositions(command.position + 1,
command.position + command.number);
+ boolean cutIsInternal = cutPositions != null
+ && sequence.getStart() != cutPositions
+ .getBegin() && sequence.getEnd() != cutPositions.getEnd();
+ sequence.deleteChars(command.position, command.position
+ + command.number);
+
if (command.oldds != null && command.oldds[i] != null)
{
// oldds entry contains the cut dataset sequence.
command.oldds = new SequenceI[command.seqs.length];
}
command.oldds[i] = oldds;
- // FIXME JAL-2541 JAL-2526 get correct positions if on a gap
- List<SequenceFeature[]> amendedFeatures = sequence
- .adjustFeatures(command.position, command.position
- + command.number - 1);
- if (command.editedFeatures == null)
+
+ if (cutPositions != null)
{
- command.editedFeatures = new HashMap<>();
+ cutFeatures(command, sequence, cutPositions.getBegin(),
+ cutPositions.getEnd(), cutIsInternal);
}
- command.editedFeatures.put(sequence, amendedFeatures);
- //
- // adjustFeatures(
- // command,
- // i,
- // sequence.findPosition(command.position),
- // sequence.findPosition(command.position + command.number),
- // false);
}
}
}
tmp.insert(command.position, command.string[i]);
for (int s = 0; s < command.string[i].length; s++)
{
- // if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]]
- // != 23)
if (!Comparison.isGap(command.string[i][s]))
{
if (!newDSNeeded)
}
/*
- * insert == true for an Undo of a Cut; restore the original features
- * and delete the amended ones
+ * TODO: shift right features that lie to the right of the restored cut
+ * Currently not needed as all features restored with saved dataset sequence
+ * nor if no saved dataset sequence (as coordinates left unchanged by Cut)
*/
- if (true)
- {
- // TODO shift right features that lie to the right of the restored cut
- // (add a start position parameter to SequenceFeatures.shift)
- if (command.editedFeatures != null
- && command.editedFeatures.containsKey(seq))
+ /*
+ * restore any features that were deleted or truncated
+ */
+ if (command.deletedFeatures != null
+ && command.deletedFeatures.containsKey(seq))
+ {
+ for (SequenceFeature deleted : command.deletedFeatures.get(seq))
{
- for (SequenceFeature[] toRestore : command.editedFeatures.get(seq))
- {
- sequence.addSequenceFeature(toRestore[0]);
- if (toRestore[1] != null)
- {
- sequence.deleteFeature(toRestore[1]);
- }
- }
+ sequence.addSequenceFeature(deleted);
}
- return;
}
- // List<SequenceFeature> sf = sequence.getFeatures()
- // .getPositionalFeatures();
- //
- // if (sf.isEmpty())
- // {
- // return;
- // }
- //
- // List<SequenceFeature> oldsf = new ArrayList<SequenceFeature>();
- //
- // int cSize = j - i;
- //
- // for (SequenceFeature feature : sf)
- // {
- // SequenceFeature copy = new SequenceFeature(feature);
- //
- // oldsf.add(copy);
- //
- // if (feature.getEnd() < i)
- // {
- // continue;
- // }
- //
- // if (feature.getBegin() > j)
- // {
- // int newBegin = copy.getBegin() - cSize;
- // int newEnd = copy.getEnd() - cSize;
- // SequenceFeature newSf = new SequenceFeature(feature, newBegin,
- // newEnd, feature.getFeatureGroup(), feature.getScore());
- // sequence.deleteFeature(feature);
- // sequence.addSequenceFeature(newSf);
- // // feature.setBegin(newBegin);
- // // feature.setEnd(newEnd);
- // continue;
- // }
- //
- // int newBegin = feature.getBegin();
- // int newEnd = feature.getEnd();
- // if (newBegin >= i)
- // {
- // newBegin = i;
- // // feature.setBegin(i);
- // }
- //
- // if (newEnd < j)
- // {
- // newEnd = j - 1;
- // // feature.setEnd(j - 1);
- // }
- // newEnd = newEnd - cSize;
- // // feature.setEnd(feature.getEnd() - (cSize));
- //
- // sequence.deleteFeature(feature);
- // if (newEnd >= newBegin)
- // {
- // sequence.addSequenceFeature(new SequenceFeature(feature, newBegin,
- // newEnd, feature.getFeatureGroup(), feature.getScore()));
- // }
- // // if (feature.getBegin() > feature.getEnd())
- // // {
- // // sequence.deleteFeature(feature);
- // // }
- // }
- //
- // if (command.editedFeatures == null)
- // {
- // command.editedFeatures = new Hashtable<SequenceI,
- // List<SequenceFeature>>();
- // }
- //
- // command.editedFeatures.put(seq, oldsf);
-
+ /*
+ * delete any truncated features
+ */
+ if (command.truncatedFeatures != null
+ && command.truncatedFeatures.containsKey(seq))
+ {
+ for (SequenceFeature amended : command.truncatedFeatures.get(seq))
+ {
+ sequence.deleteFeature(amended);
+ }
+ }
}
/**
Map<String, Annotation[]> deletedAnnotations;
- Map<SequenceI, List<SequenceFeature[]>> editedFeatures;
+ /*
+ * features deleted by the cut (re-add on Undo)
+ */
+ Map<SequenceI, List<SequenceFeature>> deletedFeatures;
+
+ /*
+ * features shortened by the cut (delete on Undo)
+ */
+ Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
AlignmentI al;
Edit(Action cmd, SequenceI[] sqs, int pos, int count,
AlignmentI align)
{
- this.gapChar = align.getGapCharacter();
- this.command = cmd;
- this.seqs = sqs;
- this.position = pos;
- this.number = count;
+ this(cmd, sqs, pos, count, align.getGapCharacter());
+
this.al = align;
alIndex = new int[sqs.length];
Edit(Action cmd, SequenceI[] sqs, int pos, int count,
AlignmentI align, String replace)
{
- this.command = cmd;
- this.seqs = sqs;
- this.position = pos;
- this.number = count;
- this.al = align;
- this.gapChar = align.getGapCharacter();
+ this(cmd, sqs, pos, count, align);
+
string = new char[sqs.length][];
for (int i = 0; i < sqs.length; i++)
{
string[i] = replace.toCharArray();
}
-
- fullAlignmentHeight = (align.getHeight() == sqs.length);
}
public SequenceI[] getSequences()
return new ReverseListIterator<Edit>(getEdits());
}
}
+
+ /**
+ * Adjusts features for Cut, and saves details of changes made to allow Undo
+ * <ul>
+ * <li>features left of the cut are unchanged</li>
+ * <li>features right of the cut are shifted left</li>
+ * <li>features internal to the cut region are deleted</li>
+ * <li>features that overlap or span the cut are shortened</li>
+ * <li>the originals of any deleted or shorted features are saved, to re-add
+ * on Undo</li>
+ * <li>any added (shortened) features are saved, to delete on Undo</li>
+ * </ul>
+ *
+ * @param command
+ * @param seq
+ * @param fromPosition
+ * @param toPosition
+ * @param cutIsInternal
+ */
+ protected static void cutFeatures(Edit command, SequenceI seq,
+ int fromPosition, int toPosition, boolean cutIsInternal)
+ {
+ List<SequenceFeature> added = new ArrayList<>();
+ List<SequenceFeature> removed = new ArrayList<>();
+
+ SequenceFeaturesI featureStore = seq.getFeatures();
+ if (toPosition < fromPosition || featureStore == null)
+ {
+ return;
+ }
+
+ int cutStartPos = fromPosition;
+ int cutEndPos = toPosition;
+ int cutWidth = cutEndPos - cutStartPos + 1;
+
+ synchronized (featureStore)
+ {
+ /*
+ * get features that overlap the cut region
+ */
+ List<SequenceFeature> toAmend = featureStore.findFeatures(
+ cutStartPos, cutEndPos);
+
+ /*
+ * adjust start-end of overlapping features;
+ * delete features enclosed by the cut;
+ * delete partially overlapping contact features
+ */
+ for (SequenceFeature sf : toAmend)
+ {
+ int sfBegin = sf.getBegin();
+ int sfEnd = sf.getEnd();
+ int newBegin = sfBegin;
+ int newEnd = sfEnd;
+ boolean toDelete = false;
+ boolean follows = false;
+
+ if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
+ {
+ /*
+ * feature lies within cut region - delete it
+ */
+ toDelete = true;
+ }
+ else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
+ {
+ /*
+ * feature spans cut region - left-shift the end
+ */
+ newEnd -= cutWidth;
+ }
+ else if (sfEnd <= cutEndPos)
+ {
+ /*
+ * feature overlaps left of cut region - truncate right
+ */
+ newEnd = cutStartPos - 1;
+ if (sf.isContactFeature())
+ {
+ toDelete = true;
+ }
+ }
+ else if (sfBegin >= cutStartPos)
+ {
+ /*
+ * remaining case - feature overlaps right
+ * truncate left, adjust end of feature
+ */
+ newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
+ // newEnd = newBegin + (sfEnd - sfBegin) - overlapsBy;
+ newEnd = newBegin + sfEnd - cutEndPos - 1;
+ if (sf.isContactFeature())
+ {
+ toDelete = true;
+ }
+ }
+
+ seq.deleteFeature(sf);
+ if (!follows)
+ {
+ removed.add(sf);
+ }
+ if (!toDelete)
+ {
+ SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
+ sf.getFeatureGroup(), sf.getScore());
+ seq.addSequenceFeature(copy);
+ if (!follows)
+ {
+ added.add(copy);
+ }
+ }
+ }
+
+ /*
+ * and left shift any features lying to the right of the cut region
+ * (but not if the cut is at start or end of sequence)
+ */
+ if (cutIsInternal)
+ {
+ featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
+ }
+ }
+
+ /*
+ * save deleted and amended features, so that Undo can
+ * re-add or delete them respectively
+ */
+ if (command.deletedFeatures == null)
+ {
+ command.deletedFeatures = new HashMap<>();
+ }
+ if (command.truncatedFeatures == null)
+ {
+ command.truncatedFeatures = new HashMap<>();
+ }
+ command.deletedFeatures.put(seq, removed);
+ command.truncatedFeatures.put(seq, added);
+ }
}